2 * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) version 3.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 # define _GNU_SOURCE 1
26 #include <syncevo/SyncSource.h>
27 #include <syncevo/SyncContext.h>
28 #include <syncevo/util.h>
30 #include <syncevo/SynthesisEngine.h>
31 #include <synthesis/SDK_util.h>
32 #include <synthesis/sync_dbapidef.h>
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>
48 #ifdef ENABLE_UNIT_TESTS
52 #include <syncevo/declarations.h>
55 void SyncSourceBase::throwError(const SourceLocation &where, const string &action, int error)
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
61 if (error == ENOENT) {
62 throwError(where, STATUS_NOT_FOUND, what);
64 throwError(where, what);
68 void SyncSourceBase::throwError(const SourceLocation &where, const string &failure)
70 Exception::throwError(where, string(getDisplayName()) + ": " + failure);
73 void SyncSourceBase::throwError(const SourceLocation &where, SyncMLStatus status, const string &failure)
75 Exception::throwError(where, status, getDisplayName() + ": " + failure);
78 SyncMLStatus SyncSourceBase::handleException(HandleExceptionFlags flags)
80 SyncMLStatus res = Exception::handle(getDisplayName(), flags);
81 return res == STATUS_FATAL ?
82 STATUS_DATASTORE_FAILURE :
86 void SyncSourceBase::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
88 stringstream xmlstream;
91 getSynthesisInfo(info, fragments);
94 " <plugin_module>SyncEvolution</plugin_module>\n";
95 if (info.m_earlyStartDataRead) {
97 " <plugin_earlystartdataread>yes</plugin_earlystartdataread>\n";
99 if (info.m_readOnly) {
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";
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) {
113 " <syncmode>1122583000</syncmode>";
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"
121 " <conflictstrategy>newer-wins</conflictstrategy>\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"
127 " <slowsyncstrategy>newer-wins</slowsyncstrategy>\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"
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"
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"
144 " <!-- Mapping of the fields to the fieldlist -->\n"
145 " <fieldmap fieldlist='" << info.m_fieldlist << "'>\n";
146 if (!info.m_profile.empty()) {
148 " <initscript><![CDATA[\n"
149 " string itemdata;\n"
150 " ]]></initscript>\n"
151 " <beforewritescript><![CDATA[\n";
152 if(!info.m_beforeWriteScript.empty()) {
154 " " << info.m_beforeWriteScript << "\n";
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()) {
163 " " << info.m_afterReadScript<< "\n";
166 " ]]></afterreadscript>\n"
167 " <map name='data' references='itemdata' type='string'/>\n";
175 " <!-- datatypes supported by this datastore -->\n"
176 " <typesupport>\n" <<
180 // arbitrary configuration options, can override the ones above
181 xmlstream << info.m_datastoreOptions;
183 xml = xmlstream.str();
186 string SyncSourceBase::getNativeDatatypeName()
189 XMLConfigFragments fragments;
190 getSynthesisInfo(info, fragments);
191 return info.m_native;
194 SyncSourceBase::Operations::Operations(SyncSourceName &source) :
195 m_startDataRead(source),
196 m_endDataRead(source),
197 m_startDataWrite(source),
198 m_finalizeLocalID(source),
199 m_endDataWrite(source),
200 m_readNextItem(source),
201 m_readItemAsKey(source),
202 m_insertItemAsKey(source),
203 m_updateItemAsKey(source),
204 m_deleteItem(source),
205 m_loadAdminData(source),
206 m_saveAdminData(source),
207 m_insertMapItem(source),
208 m_updateMapItem(source),
209 m_deleteMapItem(source),
214 static void BumpCounter(int32_t &counter)
219 SyncSource::SyncSource(const SyncSourceParams ¶ms) :
220 SyncSourceConfig(params.m_name, params.m_nodes),
226 m_forceSlowSync(false),
228 m_name(params.getDisplayName()),
231 m_operations.m_insertItemAsKey.getPreSignal().connect(boost::bind(BumpCounter, boost::ref(m_added)));
232 m_operations.m_updateItemAsKey.getPreSignal().connect(boost::bind(BumpCounter, boost::ref(m_updated)));
233 m_operations.m_deleteItem.getPreSignal().connect(boost::bind(BumpCounter, boost::ref(m_deleted)));
236 SDKInterface *SyncSource::getSynthesisAPI() const
238 return m_synthesisAPI.empty() ?
240 static_cast<SDKInterface *>(m_synthesisAPI[m_synthesisAPI.size() - 1]);
243 void SyncSource::pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI)
245 m_synthesisAPI.push_back(synthesisAPI);
248 void SyncSource::popSynthesisAPI() {
249 m_synthesisAPI.pop_back();
252 SourceRegistry &SyncSource::getSourceRegistry()
254 static SourceRegistry sourceRegistry;
255 return sourceRegistry;
258 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
261 const string &typeDescr,
262 const Values &typeValues) :
263 m_shortDescr(shortDescr),
266 m_typeDescr(typeDescr),
267 m_typeValues(typeValues)
269 SourceRegistry ®istry(SyncSource::getSourceRegistry());
271 // insert sorted by description to have deterministic ordering
272 for(SourceRegistry::iterator it = registry.begin();
273 it != registry.end();
275 if ((*it)->m_shortDescr > shortDescr) {
276 registry.insert(it, this);
280 registry.push_back(this);
283 class InactiveSyncSource : public SyncSource
286 InactiveSyncSource(const SyncSourceParams ¶ms) : SyncSource(params) {}
288 virtual bool isInactive() const { return true; }
289 virtual void enableServerMode() {}
290 virtual bool serverModeEnabled() const { return false; }
291 virtual void getSynthesisInfo(SyncEvo::SyncSourceBase::SynthesisInfo&, SyncEvo::XMLConfigFragments&) { throwError(SE_HERE, "inactive"); }
292 virtual Databases getDatabases() { throwError(SE_HERE, "inactive"); return Databases(); }
293 virtual void open() { throwError(SE_HERE, "inactive"); }
294 virtual void close() { throwError(SE_HERE, "inactive"); }
295 virtual std::string getPeerMimeType() const { return ""; }
298 SyncSource *RegisterSyncSource::InactiveSource(const SyncSourceParams ¶ms)
300 return new InactiveSyncSource(params);
303 TestRegistry &SyncSource::getTestRegistry()
305 static TestRegistry testRegistry;
309 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
310 m_configName(configName),
311 m_testCaseName(testCaseName)
313 SyncSource::getTestRegistry().push_back(this);
316 static class ScannedModules {
319 #ifdef ENABLE_MODULES
320 list<pair <string, boost::shared_ptr<ReadDir> > > dirs;
321 /* If enviroment variable SYNCEVOLUTION_BACKEND_DIR is set, will search
322 backends from this path instead. */
323 string backend_dir (SYNCEVO_BACKEND);
324 char *backend_env = getenv("SYNCEVOLUTION_BACKEND_DIR");
325 if (backend_env && strlen(backend_env)){
326 backend_dir = backend_env;
328 boost::shared_ptr<ReadDir> dir (new ReadDir (backend_dir, false));
329 string dirpath (backend_dir);
330 // Base name (= no dir, no .so suffix) mapping to full file
331 // name (including .so).
332 std::map<std::string, std::string> candidates;
333 // scan directories for matching module names
335 debug<<"Scanning backend libraries in " <<dirpath <<endl;
336 BOOST_FOREACH (const string &entry, *dir) {
337 if (isDir (dirpath + '/' + entry)) {
338 /* This is a 2-level dir, this corresponds to loading
339 * backends from current building directory. The library
340 * should reside in .libs sub directory.*/
341 string path = dirpath + '/' + entry +"/.libs";
343 boost::shared_ptr<ReadDir> subdir (new ReadDir (path, false));
344 dirs.push_back (make_pair(path, subdir));
348 if (boost::ends_with(entry, ".so")) {
349 string fullpath = dirpath + '/' + entry;
350 fullpath = normalizePath(fullpath);
351 candidates[entry.substr(0, entry.size() - 3)] = fullpath;
355 dirpath = dirs.front().first;
356 dir = dirs.front().second;
363 // Look at foo-<version> before foo. If there is more than
364 // one version and the version sorts lexically, the "highest"
365 // one will be checked first, too.
367 // The intention is to try loading syncebook-2 (with explicit
368 // library dependencies) first, then skip syncebook if loading
369 // of syncebook-2 succeeded. If loading of syncebook-2 fails
370 // due to missing libraries, we proceed to use syncebook.
371 BOOST_REVERSE_FOREACH (const StringPair &entry, candidates) {
372 const std::string &basename = entry.first;
373 const std::string &fullpath = entry.second;
374 std::string replacement;
376 size_t offset = basename.rfind('-');
377 if (offset != basename.npos) {
378 modname = basename.substr(0, offset);
382 BOOST_FOREACH (const std::string &l, m_available) {
383 if (boost::starts_with(l, modname)) {
388 if (!replacement.empty()) {
389 debug << "Skipping " << basename << " = " << fullpath << " because a more recent version of it was already loaded: " << replacement;
393 // Open the shared object so that backend can register
394 // itself. We keep that pointer, so never close the
396 // RTLD_LAZY is needed for the WebDAV backend, which
397 // needs to do an explicit dlopen() of libneon in compatibility
398 // mode before any of the neon functions can be resolved.
399 void *dlhandle = dlopen(fullpath.c_str(), RTLD_LAZY|RTLD_GLOBAL);
400 // remember which modules were found and which were not
402 debug<<"Loading backend library "<<basename<<endl;
403 info<<"Loading backend library "<<fullpath<<endl;
404 m_available.push_back(basename);
406 debug<<"Loading backend library "<<basename<<"failed "<< dlerror()<<endl;
411 list<string> m_available;
412 std::ostringstream debug, info;
415 string SyncSource::backendsInfo() {
416 return scannedModules.info.str();
418 string SyncSource::backendsDebug() {
419 return scannedModules.debug.str();
422 void SyncSource::requestAnotherSync()
424 // At the moment the per-source request to restart cannot be
425 // stored; instead only a per-session request is set. That's okay
426 // for now because restarting is limited to sessions with only
427 // one source active (intentional simplification).
428 SE_LOG_DEBUG(getDisplayName(), "requesting another sync");
429 SyncContext::requestAnotherSync();
433 SyncSource *SyncSource::createSource(const SyncSourceParams ¶ms, bool error, SyncConfig *config)
435 SourceType sourceType = getSourceType(params.m_nodes);
437 if (sourceType.m_backend == "virtual") {
438 SyncSource *source = NULL;
439 source = new VirtualSyncSource(params, config);
440 if (error && !source) {
441 Exception::throwError(SE_HERE, params.getDisplayName() + ": virtual source cannot be instantiated");
446 const SourceRegistry ®istry(getSourceRegistry());
447 auto_ptr<SyncSource> source;
448 BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
449 auto_ptr<SyncSource> nextSource(sourceInfos->m_create(params));
450 if (nextSource.get()) {
452 Exception::throwError(SE_HERE, params.getDisplayName() + ": backend " + sourceType.m_backend +
453 " is ambiguous, avoid the alias and pick a specific backend instead directly");
459 return source.release();
464 if (!scannedModules.m_available.empty()) {
465 backends += "by any of the backend modules (";
466 backends += boost::join(scannedModules.m_available, ", ");
470 StringPrintf("%s%sbackend not supported %sor not correctly configured (backend=%s databaseFormat=%s syncFormat=%s)",
471 params.m_name.c_str(),
472 params.m_name.empty() ? "" : ": ",
474 sourceType.m_backend.c_str(),
475 sourceType.m_localFormat.c_str(),
476 sourceType.m_format.c_str());
477 Exception::throwError(SE_HERE, SyncMLStatus(sysync::LOCERR_CFGPARSE), problem);
483 SyncSource *SyncSource::createTestingSource(const string &name, const string &type, bool error,
486 std::string config = "target-config@client-test";
487 const char *server = getenv("CLIENT_TEST_SERVER");
492 boost::shared_ptr<SyncConfig> context(new SyncConfig(config));
493 SyncSourceNodes nodes = context->getSyncSourceNodes(name);
494 SyncSourceParams params(name, nodes, context);
495 PersistentSyncSourceConfig sourceconfig(name, nodes);
496 sourceconfig.setSourceType(type);
498 sourceconfig.setDatabaseID(string(prefix) + name + "_1");
500 return createSource(params, error);
503 VirtualSyncSource::VirtualSyncSource(const SyncSourceParams ¶ms, SyncConfig *config) :
504 DummySyncSource(params)
507 std::string evoSyncSource = getDatabaseID();
508 BOOST_FOREACH(std::string name, getMappedSources()) {
510 throwError(SE_HERE, StringPrintf("configuration of underlying sources contains empty source name: database = '%s'",
511 evoSyncSource.c_str()));
513 SyncSourceNodes source = config->getSyncSourceNodes(name);
514 SyncSourceParams params(name, source, boost::shared_ptr<SyncConfig>(config, SyncConfigNOP()));
515 boost::shared_ptr<SyncSource> syncSource(createSource(params, true, config));
516 m_sources.push_back(syncSource);
518 if (m_sources.size() != 2) {
519 throwError(SE_HERE, StringPrintf("configuration of underlying sources must contain exactly one calendar and one todo source (like calendar+todo): database = '%s'",
520 evoSyncSource.c_str()));
525 void VirtualSyncSource::open()
527 getDataTypeSupport();
528 BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
533 void VirtualSyncSource::close()
535 BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
540 std::vector<std::string> VirtualSyncSource::getMappedSources()
542 std::string evoSyncSource = getDatabaseID();
543 std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
544 return mappedSources;
547 std::string VirtualSyncSource::getDataTypeSupport()
550 SourceType sourceType = getSourceType();
551 string type = sourceType.m_format;
553 datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
557 SyncSource::Databases VirtualSyncSource::getDatabases()
559 SyncSource::Databases dbs;
560 BOOST_FOREACH (boost::shared_ptr<SyncSource> &source, m_sources) {
561 SyncSource::Databases sub = source->getDatabases();
566 Database db ("calendar+todo", "");
571 void SyncSourceSession::init(SyncSource::Operations &ops)
573 ops.m_startDataRead = boost::bind(&SyncSourceSession::startDataRead, this, _1, _2);
574 ops.m_endDataRead = boost::lambda::constant(sysync::LOCERR_OK);
575 ops.m_startDataWrite = boost::lambda::constant(sysync::LOCERR_OK);
576 ops.m_endDataWrite = boost::bind(&SyncSourceSession::endDataWrite, this, _1, _2);
579 sysync::TSyError SyncSourceSession::startDataRead(const char *lastToken, const char *resumeToken)
582 beginSync(lastToken ? lastToken : "",
583 resumeToken ? resumeToken : "");
584 } catch (const StatusException &ex) {
585 SyncMLStatus status = ex.syncMLStatus();
586 if (status == STATUS_SLOW_SYNC_508) {
587 // Not an error. Return it normally, without ERROR logging
594 return sysync::LOCERR_OK;
597 sysync::TSyError SyncSourceSession::endDataWrite(bool success, char **newToken)
599 std::string token = endSync(success);
600 *newToken = StrAlloc(token.c_str());
601 return sysync::LOCERR_OK;
604 void SyncSourceChanges::init(SyncSource::Operations &ops)
606 ops.m_readNextItem = boost::bind(&SyncSourceChanges::iterate, this, _1, _2, _3);
609 SyncSourceChanges::SyncSourceChanges() :
614 bool SyncSourceChanges::addItem(const string &luid, State state)
616 pair<Items_t::iterator, bool> res = m_items[state].insert(luid);
620 bool SyncSourceChanges::reset()
622 bool removed = false;
623 for (int i = 0; i < MAX; i++) {
624 if (!m_items[i].empty()) {
633 sysync::TSyError SyncSourceChanges::iterate(sysync::ItemID aID,
634 sysync::sInt32 *aStatus,
640 if (m_first || aFirst) {
641 m_it = m_items[ANY].begin();
645 if (m_it == m_items[ANY].end()) {
646 *aStatus = sysync::ReadNextItem_EOF;
648 const string &luid = *m_it;
650 if (m_items[NEW].find(luid) != m_items[NEW].end() ||
651 m_items[UPDATED].find(luid) != m_items[UPDATED].end()) {
652 *aStatus = sysync::ReadNextItem_Changed;
654 *aStatus = sysync::ReadNextItem_Unchanged;
656 aID->item = StrAlloc(luid.c_str());
660 return sysync::LOCERR_OK;
663 void SyncSourceDelete::init(SyncSource::Operations &ops)
665 ops.m_deleteItem = boost::bind(&SyncSourceDelete::deleteItemSynthesis, this, _1);
668 sysync::TSyError SyncSourceDelete::deleteItemSynthesis(sysync::cItemID aID)
670 deleteItem(aID->item);
671 incrementNumDeleted();
672 return sysync::LOCERR_OK;
676 void SyncSourceSerialize::getSynthesisInfo(SynthesisInfo &info,
677 XMLConfigFragments &fragments)
679 string type = getMimeType();
681 // default remote rule (local-storage.xml): suppresses empty properties
682 info.m_backendRule = "LOCALSTORAGE";
684 // We store entire items locally and thus have to make sure that
685 // they are complete by having the engine merge incoming and local
687 info.m_datastoreOptions += " <updateallfields>true</updateallfields>\n";
689 if (type == "text/x-vcard") {
690 info.m_native = "vCard21";
691 info.m_fieldlist = "contacts";
692 info.m_profile = "\"vCard\", 1";
694 " <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
695 " <use datatype='vCard30' mode='rw'/>\n";
696 } else if (type == "text/vcard") {
697 info.m_native = "vCard30";
698 info.m_fieldlist = "contacts";
699 info.m_profile = "\"vCard\", 2";
701 " <use datatype='vCard21' mode='rw'/>\n"
702 " <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
703 // If a backend overwrites the m_beforeWriteScript, then it must
704 // include $VCARD_OUTGOING_PHOTO_VALUE_SCRIPT in its own script,
705 // otherwise it will be sent invalid, empty PHOTO;TYPE=unknown;VALUE=binary:
707 info.m_beforeWriteScript = "$VCARD_BEFOREWRITE_SCRIPT;\n";
708 // Likewise for reading. This is needed to ensure proper merging
710 info.m_afterReadScript = "$VCARD_AFTERREAD_SCRIPT;\n";
711 } else if (type == "text/x-calendar" || type == "text/x-vcalendar") {
712 info.m_native = "vCalendar10";
713 info.m_fieldlist = "calendar";
714 info.m_profile = "\"vCalendar\", 1";
716 " <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
717 " <use datatype='iCalendar20' mode='rw'/>\n";
719 * here are two default implementations. If user wants to reset it,
720 * just implement its own getSynthesisInfo. If user wants to use this default
721 * implementations and its new scripts, it is possible to append its implementations
722 * to info.m_afterReadScript and info.m_beforeWriteScript.
724 info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n";
725 info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n"
726 "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
727 } else if (type == "text/calendar" ||
728 boost::starts_with(type, "text/calendar+")) {
729 info.m_native = "iCalendar20";
730 info.m_fieldlist = "calendar";
731 info.m_profile = "\"vCalendar\", 2";
733 " <use datatype='vCalendar10' mode='rw'/>\n"
734 " <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
735 info.m_beforeWriteScript = "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
736 } else if (type == "text/plain") {
737 info.m_fieldlist = "Note";
738 info.m_profile = "\"Note\", 2";
740 throwError(SE_HERE, string("default MIME type not supported: ") + type);
743 SourceType sourceType = getSourceType();
744 if (!sourceType.m_format.empty()) {
745 type = sourceType.m_format;
747 info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
750 std::string SyncSourceBase::getDataTypeSupport(const std::string &type,
753 std::string datatypes;
755 if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
757 " <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
760 " <use datatype='vCard30' mode='rw'/>\n";
762 } else if (type == "text/vcard:3.0" || type == "text/vcard") {
764 " <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
767 " <use datatype='vCard21' mode='rw'/>\n";
769 } else if (type == "text/x-vcalendar:1.0" || type == "text/x-vcalendar"
770 || type == "text/x-calendar:1.0" || type == "text/x-calendar") {
772 " <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
775 " <use datatype='icalendar20' mode='rw'/>\n";
777 } else if (type == "text/calendar:2.0" || type == "text/calendar") {
779 " <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
782 " <use datatype='vcalendar10' mode='rw'/>\n";
784 } else if (type == "text/calendar+plain") {
786 " <use datatype='icalendar20' mode='rw' preferred='yes'/>\n"
787 " <use datatype='journaltext10' mode='rw'/>\n"
788 " <use datatype='journaltext11' mode='rw'/>\n";
789 } else if (type == "text/plain:1.0" || type == "text/plain") {
790 // note10 are the same as note11, so ignore force format
792 " <use datatype='note10' mode='rw' preferred='yes'/>\n"
793 " <use datatype='note11' mode='rw'/>\n";
794 } else if (type.empty()) {
795 throwError(SE_HERE, "no MIME type configured");
797 throwError(SE_HERE, string("configured MIME type not supported: ") + type);
803 sysync::TSyError SyncSourceSerialize::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey)
807 readItem(aID->item, item);
808 TSyError res = getSynthesisAPI()->setValue(aItemKey, "data", item.c_str(), item.size());
812 SyncSource::Operations::InsertItemAsKeyResult_t SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID)
815 TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
818 InsertItemResult inserted = insertItem("", data.get());
819 switch (inserted.m_state) {
823 // Skip setting the newID.
824 return Operations::InsertItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _2, inserted.m_continue));
827 res = sysync::DB_DataReplaced;
830 res = sysync::DB_DataMerged;
832 case ITEM_NEEDS_MERGE:
833 res = sysync::DB_Conflict;
836 newID->item = StrAlloc(inserted.m_luid.c_str());
842 SyncSource::Operations::UpdateItemAsKeyResult_t SyncSourceSerialize::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
845 TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
848 InsertItemResult inserted = insertItem(aID->item, data.get());
849 switch (inserted.m_state) {
853 // Skip setting the newID.
854 return Operations::UpdateItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _3, inserted.m_continue));
857 res = sysync::DB_DataReplaced;
860 res = sysync::DB_DataMerged;
862 case ITEM_NEEDS_MERGE:
863 res = sysync::DB_Conflict;
866 newID->item = StrAlloc(inserted.m_luid.c_str());
872 sysync::TSyError SyncSourceSerialize::insertContinue(sysync::ItemID newID, const InsertItemResult::Continue_t &cont)
874 // The engine cannot tell us when it needs results (for example,
875 // in the "final message received from peer" case in
876 // TSyncSession::EndMessage(), so assume that it does whenever it
877 // calls us again => flush and wait.
881 InsertItemResult inserted = cont();
882 TSyError res = sysync::LOCERR_OK;
883 switch (inserted.m_state) {
887 // Skip setting the newID.
888 return sysync::LOCERR_AGAIN;
891 res = sysync::DB_DataReplaced;
894 res = sysync::DB_DataMerged;
896 case ITEM_NEEDS_MERGE:
897 res = sysync::DB_Conflict;
900 newID->item = StrAlloc(inserted.m_luid.c_str());
904 SyncSourceSerialize::InsertItemResult SyncSourceSerialize::insertItemRaw(const std::string &luid, const std::string &item)
906 InsertItemResult result = insertItem(luid, item);
908 while (result.m_state == ITEM_AGAIN) {
909 // Flush and wait, because caller (command line, restore) is
910 // not prepared to deal with asynchronous execution.
913 result = result.m_continue();
919 void SyncSourceSerialize::readItemRaw(const std::string &luid, std::string &item)
921 return readItem(luid, item);
926 void SyncSourceSerialize::init(SyncSource::Operations &ops)
928 ops.m_readItemAsKey = boost::bind(&SyncSourceSerialize::readItemAsKey,
930 ops.m_insertItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
932 ops.m_updateItemAsKey = boost::bind(&SyncSourceSerialize::updateItemAsKey,
937 void ItemCache::init(const SyncSource::Operations::ConstBackupInfo &oldBackup,
938 const SyncSource::Operations::BackupInfo &newBackup,
943 m_backup = newBackup;
944 m_hash2counter.clear();
945 m_dirname = oldBackup.m_dirname;
946 if (m_dirname.empty() || !oldBackup.m_node) {
951 if (!oldBackup.m_node->getProperty("numitems", numitems)) {
954 for (long counter = 1; counter <= numitems; counter++) {
956 key << counter << m_hashSuffix;
958 if (oldBackup.m_node->getProperty(key.str(), hash)) {
959 m_hash2counter[hash] = counter;
964 void ItemCache::reset()
966 // clean directory and start counting at 1 again
968 rm_r(m_backup.m_dirname);
969 mkdir_p(m_backup.m_dirname);
970 m_backup.m_node->clear();
973 string ItemCache::getFilename(Hash_t hash)
975 Map_t::const_iterator it = m_hash2counter.find(hash);
976 if (it != m_hash2counter.end()) {
977 stringstream dirname;
978 dirname << m_dirname << "/" << it->second;
979 return dirname.str();
985 const char *ItemCache::m_hashSuffix =
993 void ItemCache::backupItem(const std::string &item,
994 const std::string &uid,
995 const std::string &rev)
997 stringstream filename;
998 filename << m_backup.m_dirname << "/" << m_counter;
1000 ItemCache::Hash_t hash = hashFunc(item);
1001 string oldfilename = getFilename(hash);
1002 if (!oldfilename.empty()) {
1003 // found old file with same content, reuse it via hardlink
1004 if (link(oldfilename.c_str(), filename.str().c_str())) {
1005 // Hard linking failed. Record this, then continue
1006 // by ignoring the old file.
1007 SE_LOG_DEBUG(NULL, "hard linking old %s new %s: %s",
1008 oldfilename.c_str(),
1009 filename.str().c_str(),
1011 oldfilename.clear();
1015 if (oldfilename.empty()) {
1016 // write new file instead of reusing old one
1017 ofstream out(filename.str().c_str());
1018 out.write(item.c_str(), item.size());
1021 SE_THROW(string("error writing ") + filename.str() + ": " + strerror(errno));
1026 key << m_counter << "-uid";
1027 m_backup.m_node->setProperty(key.str(), uid);
1029 // clear() does not remove the existing content, which was
1030 // intended here. This should have been key.str(""). As a
1031 // result, keys for -rev are longer than intended because they
1032 // start with the -uid part. We cannot change it now, because
1033 // that would break compatibility with nodes that use the
1034 // older, longer keys for -rev.
1039 key << m_counter << "-rev";
1040 m_backup.m_node->setProperty(key.str(), rev);
1042 key << m_counter << ItemCache::m_hashSuffix;
1043 m_backup.m_node->setProperty(key.str(), hash);
1048 void ItemCache::finalize(BackupReport &report)
1051 value << m_counter - 1;
1052 m_backup.m_node->setProperty("numitems", value.str());
1053 m_backup.m_node->flush();
1055 report.setNumItems(m_counter - 1);
1058 void SyncSourceRevisions::initRevisions()
1060 if (!m_revisionsSet) {
1061 // might still be filled with garbage from previous run
1062 m_revisions.clear();
1063 listAllItems(m_revisions);
1064 m_revisionsSet = true;
1069 void SyncSourceRevisions::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
1070 const SyncSource::Operations::BackupInfo &newBackup,
1071 BackupReport &report)
1074 cache.init(oldBackup, newBackup, true);
1076 bool startOfSync = newBackup.m_mode == SyncSource::Operations::BackupInfo::BACKUP_BEFORE;
1077 RevisionMap_t buffer;
1078 RevisionMap_t *revisions;
1081 revisions = &m_revisions;
1083 listAllItems(buffer);
1084 revisions = &buffer;
1087 // Ensure that source knows what we are going to read.
1088 std::vector<std::string> uids;
1089 uids.reserve(revisions->size());
1090 BOOST_FOREACH(const StringPair &mapping, *revisions) {
1091 uids.push_back(mapping.first);
1094 // We may dump after a hint was already set when starting the
1095 // sync. Remember that and restore it when done. If we fail, we
1096 // don't need to restore, because then syncing will abort or skip
1098 ReadAheadOrder oldOrder;
1099 ReadAheadItems oldLUIDs;
1100 getReadAheadOrder(oldOrder, oldLUIDs);
1102 setReadAheadOrder(READ_SELECTED_ITEMS, uids);
1106 BOOST_FOREACH(const StringPair &mapping, *revisions) {
1107 const string &uid = mapping.first;
1108 const string &rev = mapping.second;
1109 m_raw->readItemRaw(uid, item);
1110 cache.backupItem(item, uid, rev);
1113 setReadAheadOrder(oldOrder, oldLUIDs);
1114 cache.finalize(report);
1117 void SyncSourceRevisions::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
1119 SyncSourceReport &report)
1121 RevisionMap_t revisions;
1122 listAllItems(revisions);
1126 strval = oldBackup.m_node->readProperty("numitems");
1127 stringstream stream(strval);
1130 for (long counter = 1; counter <= numitems; counter++) {
1132 key << counter << "-uid";
1133 string uid = oldBackup.m_node->readProperty(key.str());
1135 key << counter << "-rev";
1136 string rev = oldBackup.m_node->readProperty(key.str());
1137 RevisionMap_t::iterator it = revisions.find(uid);
1138 report.incrementItemStat(report.ITEM_LOCAL,
1141 if (it != revisions.end() &&
1142 it->second == rev) {
1143 // item exists in backup and database with same revision:
1146 // add or update, so need item
1147 stringstream filename;
1148 filename << oldBackup.m_dirname << "/" << counter;
1150 if (!ReadFile(filename.str(), data)) {
1151 throwError(SE_HERE, StringPrintf("restoring %s from %s failed: could not read file",
1153 filename.str().c_str()));
1155 // TODO: it would be nicer to recreate the item
1156 // with the original revision. If multiple peers
1157 // synchronize against us, then some of them
1158 // might still be in sync with that revision. By
1159 // updating the revision here we force them to
1160 // needlessly receive an update.
1162 // For the current peer for which we restore this is
1163 // avoided by the revision check above: unchanged
1164 // items aren't touched.
1165 SyncSourceReport::ItemState state =
1166 it == revisions.end() ?
1167 SyncSourceReport::ITEM_ADDED : // not found in database, create anew
1168 SyncSourceReport::ITEM_UPDATED; // found, update existing item
1170 report.incrementItemStat(report.ITEM_LOCAL,
1174 m_raw->insertItemRaw(it == revisions.end() ? "" : uid,
1178 report.incrementItemStat(report.ITEM_LOCAL,
1180 report.ITEM_REJECT);
1185 // remove handled item from revision list so
1186 // that when we are done, the only remaining
1187 // items listed there are the ones which did
1188 // no exist in the backup
1189 if (it != revisions.end()) {
1190 revisions.erase(it);
1194 // now remove items that were not in the backup
1195 BOOST_FOREACH(const StringPair &mapping, revisions) {
1197 report.incrementItemStat(report.ITEM_LOCAL,
1198 report.ITEM_REMOVED,
1201 m_del->deleteItem(mapping.first);
1204 report.incrementItemStat(report.ITEM_LOCAL,
1205 report.ITEM_REMOVED,
1206 report.ITEM_REJECT);
1212 bool SyncSourceRevisions::detectChanges(ConfigNode &trackingNode, ChangeMode mode)
1214 bool forceSlowSync = false;
1216 // erase content which might have been set in a previous call
1218 if (!m_firstCycle) {
1219 // detectChanges() must have been called before;
1220 // don't trust our cached revisions in that case (not updated during sync!)
1221 // TODO: keep the revision map up-to-date as part of a sync and reuse it
1222 m_revisionsSet = false;
1224 m_firstCycle = false;
1227 if (mode == CHANGES_NONE) {
1228 // shortcut because nothing changed: just copy our known item list
1230 trackingNode.readProperties(props);
1232 RevisionMap_t revisions;
1233 BOOST_FOREACH(const StringPair &mapping, props) {
1234 const string &uid = mapping.first;
1235 const string &revision = mapping.second;
1237 revisions[uid] = revision;
1239 setAllItems(revisions);
1243 if (!m_revisionsSet &&
1244 mode == CHANGES_FULL) {
1246 trackingNode.readProperties(props);
1247 if (!props.empty()) {
1248 // We were not asked to throw away all old information and
1249 // there is some that may be worth salvaging, so let's give
1250 // our derived class a chance to update it instead of having
1251 // to reread everything.
1253 // The exact number of items at which the update method is
1254 // more efficient depends on the derived class; here we assume
1255 // that even a single item makes it worthwhile. The derived
1256 // class can always ignore the information if it has different
1259 // TODO (?): an API which only provides the information
1261 m_revisions.clear();
1262 m_revisions.insert(props.begin(), props.end());
1263 updateAllItems(m_revisions);
1264 // continue with m_revisions initialized below
1265 m_revisionsSet = true;
1269 // traditional, slow fallback follows...
1272 // Check whether we have valid revision information. If not, then
1273 // we need to do a slow sync. The assumption here is that an empty
1274 // revision string marks missing information. When we don't need
1275 // change information, not having a revision string is okay.
1276 if (needChanges() &&
1277 !m_revisions.empty() &&
1278 m_revisions.begin()->second.empty()) {
1279 forceSlowSync = true;
1280 mode = CHANGES_SLOW;
1283 // If we don't need changes, then override the mode so that
1284 // we don't compute them below.
1285 if (!needChanges()) {
1286 mode = CHANGES_SLOW;
1289 // Delay setProperty calls until after checking all uids.
1290 // Necessary for MapSyncSource, which shares the revision among
1291 // several uids. Another advantage is that we can do the "find
1292 // deleted items" check with less entries (new items not added
1294 StringMap revUpdates;
1296 if (mode == CHANGES_SLOW) {
1297 // Make tracking node identical to current set of items
1298 // by re-adding them below.
1299 trackingNode.clear();
1302 BOOST_FOREACH(const StringPair &mapping, m_revisions) {
1303 const string &uid = mapping.first;
1304 const string &revision = mapping.second;
1306 // always remember the item, need full list
1309 // avoid unnecessary work in CHANGES_SLOW mode
1310 if (mode == CHANGES_SLOW) {
1311 trackingNode.setProperty(uid, revision);
1314 string serverRevision(trackingNode.readProperty(uid));
1315 if (!serverRevision.size()) {
1317 revUpdates[uid] = revision;
1319 if (revision != serverRevision) {
1320 addItem(uid, UPDATED);
1321 revUpdates[uid] = revision;
1327 if (mode != CHANGES_SLOW) {
1328 // clear information about all items that we recognized as deleted
1330 trackingNode.readProperties(props);
1332 BOOST_FOREACH(const StringPair &mapping, props) {
1333 const string &uid(mapping.first);
1334 if (getAllItems().find(uid) == getAllItems().end()) {
1335 addItem(uid, DELETED);
1336 trackingNode.removeProperty(uid);
1340 // now update tracking node
1341 BOOST_FOREACH(const StringPair &update, revUpdates) {
1342 trackingNode.setProperty(update.first, update.second);
1346 return forceSlowSync;
1349 void SyncSourceRevisions::updateRevision(ConfigNode &trackingNode,
1350 const std::string &old_luid,
1351 const std::string &new_luid,
1352 const std::string &revision)
1354 if (!needChanges()) {
1359 if (old_luid != new_luid) {
1360 trackingNode.removeProperty(old_luid);
1362 if (new_luid.empty() || revision.empty()) {
1363 throwError(SE_HERE, "need non-empty LUID and revision string");
1365 trackingNode.setProperty(new_luid, revision);
1368 void SyncSourceRevisions::deleteRevision(ConfigNode &trackingNode,
1369 const std::string &luid)
1371 if (!needChanges()) {
1375 trackingNode.removeProperty(luid);
1378 void SyncSourceRevisions::sleepSinceModification()
1380 Timespec current = Timespec::monotonic();
1381 // Don't let this get interrupted by user abort.
1382 // It is needed for correct change tracking.
1383 while ((current - m_modTimeStamp).duration() < m_revisionAccuracySeconds) {
1384 Sleep(m_revisionAccuracySeconds - (current - m_modTimeStamp).duration());
1385 current = Timespec::monotonic();
1389 void SyncSourceRevisions::databaseModified()
1391 m_modTimeStamp = Timespec::monotonic();
1394 void SyncSourceRevisions::init(SyncSourceRaw *raw,
1395 SyncSourceDelete *del,
1397 SyncSource::Operations &ops)
1401 m_revisionAccuracySeconds = granularity;
1402 m_revisionsSet = false;
1403 m_firstCycle = false;
1405 ops.m_backupData = boost::bind(&SyncSourceRevisions::backupData,
1409 ops.m_restoreData = boost::bind(&SyncSourceRevisions::restoreData,
1412 ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceRevisions::sleepSinceModification,
1416 std::string SyncSourceLogging::getDescription(sysync::KeyH aItemKey)
1419 std::list<std::string> values;
1421 BOOST_FOREACH(const std::string &field, m_fields) {
1423 if (!getSynthesisAPI()->getValue(aItemKey, field, value) &&
1425 values.push_back(std::string(value.get()));
1429 std::string description = boost::join(values, m_sep);
1432 // Instead of failing we log the error and ask
1433 // the caller to log the UID. That way transient
1434 // errors or errors in the logging code don't
1441 std::string SyncSourceLogging::getDescription(const string &luid)
1446 void SyncSourceLogging::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID)
1448 std::string description = getDescription(aItemKey);
1449 SE_LOG_INFO(getDisplayName(),
1450 description.empty() ? "%s <%s>" : "%s \"%s\"",
1452 !description.empty() ? description.c_str() : "???");
1455 void SyncSourceLogging::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
1457 std::string description = getDescription(aItemKey);
1458 SE_LOG_INFO(getDisplayName(),
1459 description.empty() ? "%s <%s>" : "%s \"%s\"",
1461 !description.empty() ? description.c_str() : aID ? aID->item : "???");
1464 void SyncSourceLogging::deleteItem(sysync::cItemID aID)
1466 std::string description = getDescription(aID->item);
1467 SE_LOG_INFO(getDisplayName(),
1468 description.empty() ? "%s <%s>" : "%s \"%s\"",
1470 !description.empty() ? description.c_str() : aID->item);
1473 void SyncSourceLogging::init(const std::list<std::string> &fields,
1474 const std::string &sep,
1475 SyncSource::Operations &ops)
1480 ops.m_insertItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::insertItemAsKey,
1482 ops.m_updateItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::updateItemAsKey,
1484 ops.m_deleteItem.getPreSignal().connect(boost::bind(&SyncSourceLogging::deleteItem,
1488 sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
1492 std::string data = m_configNode->readProperty(m_adminPropertyName);
1493 *adminData = StrAlloc(StringEscape::unescape(data, '!').c_str());
1495 return sysync::LOCERR_OK;
1498 sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData)
1500 m_configNode->setProperty(m_adminPropertyName,
1501 StringEscape::escape(adminData, '!', StringEscape::INI_VALUE));
1503 // Flush here, because some calls to saveAdminData() happend
1504 // after SyncSourceAdmin::flush() (= session end).
1505 m_configNode->flush();
1506 return sysync::LOCERR_OK;
1509 bool SyncSourceAdmin::readNextMapItem(sysync::MapID mID, bool aFirst)
1514 if (m_mappingIterator != m_mapping.end()) {
1515 entry2mapid(m_mappingIterator->first, m_mappingIterator->second, mID);
1516 ++m_mappingIterator;
1523 sysync::TSyError SyncSourceAdmin::insertMapItem(sysync::cMapID mID)
1526 mapid2entry(mID, key, value);
1529 StringMap::iterator it = m_mapping.find(key);
1530 if (it != m_mapping.end()) {
1531 // error, exists already
1532 return sysync::DB_Forbidden;
1534 m_mapping[key] = value;
1535 return sysync::LOCERR_OK;
1538 m_mapping[key] = value;
1539 m_mappingNode->clear();
1540 m_mappingNode->writeProperties(m_mapping);
1541 m_mappingNode->flush();
1542 return sysync::LOCERR_OK;
1546 sysync::TSyError SyncSourceAdmin::updateMapItem(sysync::cMapID mID)
1549 mapid2entry(mID, key, value);
1551 ConfigProps::iterator it = m_mapping.find(key);
1552 if (it == m_mapping.end()) {
1553 // error, does not exist
1554 return sysync::DB_Forbidden;
1556 m_mapping[key] = value;
1557 m_mappingNode->clear();
1558 m_mappingNode->writeProperties(m_mapping);
1559 m_mappingNode->flush();
1560 return sysync::LOCERR_OK;
1564 sysync::TSyError SyncSourceAdmin::deleteMapItem(sysync::cMapID mID)
1567 mapid2entry(mID, key, value);
1569 ConfigProps::iterator it = m_mapping.find(key);
1570 if (it == m_mapping.end()) {
1571 // error, does not exist
1572 return sysync::DB_Forbidden;
1574 m_mapping.erase(it);
1575 m_mappingNode->clear();
1576 m_mappingNode->writeProperties(m_mapping);
1577 m_mappingNode->flush();
1578 return sysync::LOCERR_OK;
1582 void SyncSourceAdmin::flush()
1584 m_configNode->flush();
1585 if (m_mappingLoaded) {
1586 m_mappingNode->clear();
1587 m_mappingNode->writeProperties(m_mapping);
1588 m_mappingNode->flush();
1592 void SyncSourceAdmin::resetMap()
1595 m_mappingNode->readProperties(m_mapping);
1596 m_mappingIterator = m_mapping.begin();
1597 m_mappingLoaded = true;
1601 void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value)
1603 key = StringPrintf("%s-%x",
1604 StringEscape::escape(mID->localID ? mID->localID : "", '!', StringEscape::INI_WORD).c_str(),
1606 if (mID->remoteID && mID->remoteID[0]) {
1607 value = StringPrintf("%s %x",
1608 StringEscape::escape(mID->remoteID ? mID->remoteID : "", '!', StringEscape::INI_WORD).c_str(),
1611 value = StringPrintf("%x", mID->flags);
1615 void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID)
1617 size_t found = key.rfind('-');
1618 mID->localID = StrAlloc(StringEscape::unescape(key.substr(0,found), '!').c_str());
1619 if (found != key.npos) {
1620 mID->ident = strtol(key.substr(found+1).c_str(), NULL, 16);
1624 std::vector< std::string > tokens;
1625 boost::split(tokens, value, boost::is_from_range(' ', ' '));
1626 if (tokens.size() >= 2) {
1627 // if branch from mapid2entry above
1628 mID->remoteID = StrAlloc(StringEscape::unescape(tokens[0], '!').c_str());
1629 mID->flags = strtol(tokens[1].c_str(), NULL, 16);
1631 // else branch from above
1632 mID->remoteID = NULL;
1633 mID->flags = strtol(tokens[0].c_str(), NULL, 16);
1637 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1638 const boost::shared_ptr<ConfigNode> &config,
1639 const std::string adminPropertyName,
1640 const boost::shared_ptr<ConfigNode> &mapping)
1642 m_configNode = config;
1643 m_adminPropertyName = adminPropertyName;
1644 m_mappingNode = mapping;
1645 m_mappingLoaded = false;
1647 ops.m_loadAdminData = boost::bind(&SyncSourceAdmin::loadAdminData,
1649 ops.m_saveAdminData = boost::bind(&SyncSourceAdmin::saveAdminData,
1651 if (mapping->isVolatile()) {
1652 // Don't provide map item operations. SynthesisDBPlugin will
1653 // tell the Synthesis engine not to call these (normally needed
1654 // for suspend/resume, which we don't support in volatile mode
1655 // because we don't store any meta data persistently).
1657 // ops.m_readNextMapItem = boost::lambda::constant(false);
1658 // ops.m_insertMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1659 // ops.m_updateMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1660 // ops.m_deleteMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1662 ops.m_readNextMapItem = boost::bind(&SyncSourceAdmin::readNextMapItem,
1664 ops.m_insertMapItem = boost::bind(&SyncSourceAdmin::insertMapItem,
1666 ops.m_updateMapItem = boost::bind(&SyncSourceAdmin::updateMapItem,
1668 ops.m_deleteMapItem = boost::bind(&SyncSourceAdmin::deleteMapItem,
1671 ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceAdmin::flush, this));
1674 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1678 source->getProperties(true),
1679 SourceAdminDataName,
1680 source->getServerNode());
1683 void SyncSourceBlob::init(SyncSource::Operations &ops,
1684 const std::string &dir)
1686 m_blob.Init(getSynthesisAPI(),
1689 ops.m_readBlob = boost::bind(&SyncSourceBlob::readBlob, this,
1690 _1, _2, _3, _4, _5, _6, _7);
1691 ops.m_writeBlob = boost::bind(&SyncSourceBlob::writeBlob, this,
1692 _1, _2, _3, _4, _5, _6, _7);
1693 ops.m_deleteBlob = boost::bind(&SyncSourceBlob::deleteBlob, this,
1697 void TestingSyncSource::removeAllItems()
1699 // remove longest luids first:
1700 // for luid=UID[+RECURRENCE-ID] that will
1701 // remove children from a merged event first,
1702 // which is better supported by certain servers
1703 Items_t items = getAllItems();
1704 for (Items_t::reverse_iterator it = items.rbegin();
1713 #ifdef ENABLE_UNIT_TESTS
1715 class SyncSourceTest : public CppUnit::TestFixture {
1716 CPPUNIT_TEST_SUITE(SyncSourceTest);
1717 CPPUNIT_TEST(backendsAvailable);
1718 CPPUNIT_TEST_SUITE_END();
1720 void backendsAvailable()
1722 //We expect backendsInfo() to be empty if !ENABLE_MODULES
1723 //Otherwise, there should be at least some backends.
1724 #ifdef ENABLE_MODULES
1725 CPPUNIT_ASSERT( !SyncSource::backendsInfo().empty() );
1730 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncSourceTest);
1732 #endif // ENABLE_UNIT_TESTS