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>
29 #include <syncevo/SafeConfigNode.h>
31 #include <syncevo/SynthesisEngine.h>
32 #include <synthesis/SDK_util.h>
33 #include <synthesis/sync_dbapidef.h>
35 #include <boost/bind.hpp>
36 #include <boost/algorithm/string/join.hpp>
37 #include <boost/algorithm/string/split.hpp>
38 #include <boost/algorithm/string/classification.hpp>
39 #include <boost/algorithm/string/predicate.hpp>
48 #include <syncevo/declarations.h>
51 void SyncSourceBase::throwError(const string &action, int error)
53 throwError(action + ": " + strerror(error));
56 void SyncSourceBase::throwError(const string &failure)
58 SyncContext::throwError(string(getName()) + ": " + failure);
61 SyncMLStatus SyncSourceBase::handleException()
63 SyncMLStatus res = Exception::handle(this);
64 return res == STATUS_FATAL ?
65 STATUS_DATASTORE_FAILURE :
69 void SyncSourceBase::messagev(Level level,
77 string newprefix = getName();
82 LoggerBase::instance().messagev(level, newprefix.c_str(),
87 void SyncSourceBase::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
89 stringstream xmlstream;
92 getSynthesisInfo(info, fragments);
95 " <plugin_module>SyncEvolution</plugin_module>\n"
96 " <plugin_datastoreadmin>" <<
97 (serverModeEnabled() ? "yes" : "no") <<
98 "</plugin_datastoreadmin>\n"
99 " <fromremoteonlysupport> yes </fromremoteonlysupport>\n"
101 " <!-- General datastore settings for all DB types -->\n"
103 " <!-- if this is set to 'yes', SyncML clients can only read\n"
104 " from the database, but make no modifications -->\n"
105 " <readonly>no</readonly>\n"
107 " <!-- conflict strategy: Newer item wins\n"
108 " You can set 'server-wins' or 'client-wins' as well\n"
109 " if you want to give one side precedence\n"
111 " <conflictstrategy>newer-wins</conflictstrategy>\n"
113 " <!-- on slowsync: duplicate items that are not fully equal\n"
114 " You can set this to 'newer-wins' as well to avoid\n"
115 " duplicates as much as possible\n"
117 " <slowsyncstrategy>duplicate</slowsyncstrategy>\n"
119 " <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->\n"
120 " <datacharset>UTF-8</datacharset>\n"
121 " <!-- use C-language (unix style) linefeeds (\n, 0x0A) -->\n"
122 " <datalineends>unix</datalineends>\n"
124 " <!-- set this to 'UTC' if time values should be stored in UTC into the database\n"
125 " rather than local time. 'SYSTEM' denotes local server time zone. -->\n"
126 " <datatimezone>SYSTEM</datatimezone>\n"
128 " <!-- plugin DB may have its own identifiers to determine the point in time of changes, so\n"
129 " we must make sure this identifier is stored (and not only the sync time) -->\n"
130 " <storesyncidentifiers>yes</storesyncidentifiers>\n"
134 " <!-- Mapping of the fields to the fieldlist -->\n"
135 " <fieldmap fieldlist='" << info.m_fieldlist << "'>\n";
136 if (!info.m_profile.empty()) {
138 " <initscript><![CDATA[\n"
139 " string itemdata;\n"
140 " ]]></initscript>\n"
141 " <beforewritescript><![CDATA[\n";
142 if(!info.m_beforeWriteScript.empty()) {
144 " " << info.m_beforeWriteScript << "\n";
147 " itemdata = MAKETEXTWITHPROFILE(" << info.m_profile << ", \"" << info.m_backendRule << "\");\n"
148 " ]]></beforewritescript>\n"
149 " <afterreadscript><![CDATA[\n"
150 " PARSETEXTWITHPROFILE(itemdata, " << info.m_profile << ", \"" << info.m_backendRule << "\");\n";
151 if(!info.m_afterReadScript.empty()) {
153 " " << info.m_afterReadScript<< "\n";
156 " ]]></afterreadscript>\n"
157 " <map name='data' references='itemdata' type='string'/>\n";
165 " <!-- datatypes supported by this datastore -->\n"
166 " <typesupport>\n" <<
170 xml = xmlstream.str();
173 string SyncSourceBase::getNativeDatatypeName()
176 XMLConfigFragments fragments;
177 getSynthesisInfo(info, fragments);
178 return info.m_native;
181 SDKInterface *SyncSource::getSynthesisAPI() const
183 return m_synthesisAPI.empty() ?
185 static_cast<SDKInterface *>(m_synthesisAPI[m_synthesisAPI.size() - 1]);
188 void SyncSource::pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI)
190 m_synthesisAPI.push_back(synthesisAPI);
193 void SyncSource::popSynthesisAPI() {
194 m_synthesisAPI.pop_back();
197 SourceRegistry &SyncSource::getSourceRegistry()
199 static SourceRegistry sourceRegistry;
200 return sourceRegistry;
203 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
206 const string &typeDescr,
207 const Values &typeValues) :
208 m_shortDescr(shortDescr),
211 m_typeDescr(typeDescr),
212 m_typeValues(typeValues)
214 SourceRegistry ®istry(SyncSource::getSourceRegistry());
216 // insert sorted by description to have deterministic ordering
217 for(SourceRegistry::iterator it = registry.begin();
218 it != registry.end();
220 if ((*it)->m_shortDescr > shortDescr) {
221 registry.insert(it, this);
225 registry.push_back(this);
228 SyncSource *const RegisterSyncSource::InactiveSource = (SyncSource *)1;
230 TestRegistry &SyncSource::getTestRegistry()
232 static TestRegistry testRegistry;
236 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
237 m_configName(configName),
238 m_testCaseName(testCaseName)
240 SyncSource::getTestRegistry().push_back(this);
243 static class ScannedModules {
246 #ifdef ENABLE_MODULES
247 list<pair <string, boost::shared_ptr<ReadDir> > > dirs;
248 /* If enviroment variable SYNCEVOLUTION_BACKEND_DIR is set, will search
249 backends from this path instead. */
250 string backend_dir (SYNCEVO_BACKEND);
251 char *backend_env = getenv("SYNCEVOLUTION_BACKEND_DIR");
252 if (backend_env && strlen(backend_env)){
253 backend_dir = backend_env;
255 boost::shared_ptr<ReadDir> dir (new ReadDir (backend_dir, false));
256 string dirpath (backend_dir);
257 // scan directories for matching module names
259 debug<<"Scanning backend libraries in " <<dirpath <<endl;
260 BOOST_FOREACH (const string &entry, *dir) {
262 if (isDir (dirpath + '/' + entry)) {
263 /* This is a 2-level dir, this corresponds to loading
264 * backends from current building directory. The library
265 * should reside in .libs sub directory.*/
266 string path = dirpath + '/' + entry +"/.libs";
268 boost::shared_ptr<ReadDir> subdir (new ReadDir (path, false));
269 dirs.push_back (make_pair(path, subdir));
273 if (entry.rfind(".so") == entry.length()-3){
275 // Open the shared object so that backend can register
276 // itself. We keep that pointer, so never close the
278 string fullpath = dirpath + '/' + entry;
279 fullpath = normalizePath(fullpath);
280 dlhandle = dlopen(fullpath.c_str(), RTLD_NOW|RTLD_GLOBAL);
281 // remember which modules were found and which were not
283 debug<<"Loading backend library "<<entry<<endl;
284 info<<"Loading backend library "<<fullpath<<endl;
285 m_available.push_back(entry);
287 debug<<"Loading backend library "<<entry<<"failed "<< dlerror()<<endl;
292 dirpath = dirs.front().first;
293 dir = dirs.front().second;
301 list<string> m_available;
302 std::ostringstream debug, info;
305 string SyncSource::backendsInfo() {
306 return scannedModules.info.str();
308 string SyncSource::backendsDebug() {
309 return scannedModules.debug.str();
312 SyncSource *SyncSource::createSource(const SyncSourceParams ¶ms, bool error, SyncConfig *config)
314 string sourceTypeString = getSourceTypeString(params.m_nodes);
315 SourceType sourceType = getSourceType(params.m_nodes);
317 if (sourceType.m_backend == "virtual") {
318 SyncSource *source = NULL;
319 source = new VirtualSyncSource(params, config);
320 if (error && !source) {
321 SyncContext::throwError(params.m_name + ": virtual source cannot be instantiated");
326 const SourceRegistry ®istry(getSourceRegistry());
327 BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
328 SyncSource *source = sourceInfos->m_create(params);
330 if (source == RegisterSyncSource::InactiveSource) {
331 SyncContext::throwError(params.m_name + ": access to " + sourceInfos->m_shortDescr +
332 " not enabled, therefore type = " + sourceTypeString + " not supported");
339 string problem = params.m_name + ": type '" + sourceTypeString + "' not supported";
340 if (scannedModules.m_available.size()) {
341 problem += " by any of the backends (";
342 problem += boost::join(scannedModules.m_available, ", ");
345 SyncContext::throwError(problem);
351 SyncSource *SyncSource::createTestingSource(const string &name, const string &type, bool error,
354 SyncConfig config("testing@client-test");
355 SyncSourceNodes nodes = config.getSyncSourceNodes(name);
356 SyncSourceParams params(name, nodes);
357 PersistentSyncSourceConfig sourceconfig(name, nodes);
358 sourceconfig.setSourceType(type);
360 sourceconfig.setDatabaseID(string(prefix) + name + "_1");
362 return createSource(params, error);
365 VirtualSyncSource::VirtualSyncSource(const SyncSourceParams ¶ms, SyncConfig *config) :
366 DummySyncSource(params)
369 BOOST_FOREACH(std::string name, getMappedSources()) {
370 SyncSourceNodes source = config->getSyncSourceNodes(name);
371 SyncSourceParams params(name, source);
372 boost::shared_ptr<SyncSource> syncSource(createSource(params, true, config));
373 m_sources.push_back(syncSource);
378 void VirtualSyncSource::open()
380 getDataTypeSupport();
381 BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
386 void VirtualSyncSource::close()
388 BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
393 std::vector<std::string> VirtualSyncSource::getMappedSources()
395 std::string evoSyncSource = getDatabaseID();
396 std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
397 return mappedSources;
400 std::string VirtualSyncSource::getDataTypeSupport()
403 SourceType sourceType = getSourceType();
404 string type = sourceType.m_format;
406 datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
410 SyncSource::Databases VirtualSyncSource::getDatabases()
412 SyncSource::Databases dbs;
413 BOOST_FOREACH (boost::shared_ptr<SyncSource> &source, m_sources) {
414 SyncSource::Databases sub = source->getDatabases();
419 Database db ("calendar+todo", "");
424 void SyncSourceSession::init(SyncSource::Operations &ops)
426 ops.m_startDataRead = boost::bind(&SyncSourceSession::startDataRead, this, _1, _2);
427 ops.m_endDataWrite = boost::bind(&SyncSourceSession::endDataWrite, this, _1, _2);
430 sysync::TSyError SyncSourceSession::startDataRead(const char *lastToken, const char *resumeToken)
432 beginSync(lastToken ? lastToken : "",
433 resumeToken ? resumeToken : "");
434 return sysync::LOCERR_OK;
437 sysync::TSyError SyncSourceSession::endDataWrite(bool success, char **newToken)
439 std::string token = endSync(success);
440 *newToken = StrAlloc(token.c_str());
441 return sysync::LOCERR_OK;
444 void SyncSourceChanges::init(SyncSource::Operations &ops)
446 ops.m_readNextItem = boost::bind(&SyncSourceChanges::iterate, this, _1, _2, _3);
449 SyncSourceChanges::SyncSourceChanges() :
454 bool SyncSourceChanges::addItem(const string &luid, State state)
456 pair<Items_t::iterator, bool> res = m_items[state].insert(luid);
460 sysync::TSyError SyncSourceChanges::iterate(sysync::ItemID aID,
461 sysync::sInt32 *aStatus,
464 if (m_first || aFirst) {
465 m_it = m_items[ANY].begin();
469 if (m_it == m_items[ANY].end()) {
470 *aStatus = sysync::ReadNextItem_EOF;
472 const string &luid = *m_it;
474 if (m_items[NEW].find(luid) != m_items[NEW].end() ||
475 m_items[UPDATED].find(luid) != m_items[UPDATED].end()) {
476 *aStatus = sysync::ReadNextItem_Changed;
478 *aStatus = sysync::ReadNextItem_Unchanged;
480 aID->item = StrAlloc(luid.c_str());
484 return sysync::LOCERR_OK;
487 void SyncSourceDelete::init(SyncSource::Operations &ops)
489 ops.m_deleteItem = boost::bind(&SyncSourceDelete::deleteItemSynthesis, this, _1);
492 sysync::TSyError SyncSourceDelete::deleteItemSynthesis(sysync::cItemID aID)
494 deleteItem(aID->item);
495 incrementNumDeleted();
496 return sysync::LOCERR_OK;
500 void SyncSourceSerialize::getSynthesisInfo(SynthesisInfo &info,
501 XMLConfigFragments &fragments)
503 string type = getMimeType();
505 if (type == "text/x-vcard") {
506 info.m_native = "vCard21";
507 info.m_fieldlist = "contacts";
508 info.m_profile = "\"vCard\", 1";
510 " <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
511 " <use datatype='vCard30' mode='rw'/>\n";
512 } else if (type == "text/vcard") {
513 info.m_native = "vCard30";
514 info.m_fieldlist = "contacts";
515 info.m_profile = "\"vCard\", 2";
517 " <use datatype='vCard21' mode='rw'/>\n"
518 " <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
519 } else if (type == "text/x-calendar" || type == "text/x-vcalendar") {
520 info.m_native = "vCalendar10";
521 info.m_fieldlist = "calendar";
522 info.m_profile = "\"vCalendar\", 1";
524 " <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
525 " <use datatype='iCalendar20' mode='rw'/>\n";
527 * here are two default implementations. If user wants to reset it,
528 * just implement its own getSynthesisInfo. If user wants to use this default
529 * implementations and its new scripts, it is possible to append its implementations
530 * to info.m_afterReadScript and info.m_beforeWriteScript.
532 info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n";
533 info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n";
534 } else if (type == "text/calendar") {
535 info.m_native = "iCalendar20";
536 info.m_fieldlist = "calendar";
537 info.m_profile = "\"vCalendar\", 2";
539 " <use datatype='vCalendar10' mode='rw'/>\n"
540 " <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
541 } else if (type == "text/plain") {
542 info.m_fieldlist = "Note";
543 info.m_profile = "\"Note\", 2";
545 throwError(string("default MIME type not supported: ") + type);
548 SourceType sourceType = getSourceType();
549 if (!sourceType.m_format.empty()) {
550 type = sourceType.m_format;
552 info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
555 std::string SyncSourceBase::getDataTypeSupport(const std::string &type,
558 std::string datatypes;
560 if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
562 " <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
565 " <use datatype='vCard30' mode='rw'/>\n";
567 } else if (type == "text/vcard:3.0" || type == "text/vcard") {
569 " <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
572 " <use datatype='vCard21' mode='rw'/>\n";
574 } else if (type == "text/x-vcalendar:1.0" || type == "text/x-vcalendar"
575 || type == "text/x-calendar:1.0" || type == "text/x-calendar") {
577 " <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
580 " <use datatype='icalendar20' mode='rw'/>\n";
582 } else if (type == "text/calendar:2.0" || type == "text/calendar") {
584 " <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
587 " <use datatype='vcalendar10' mode='rw'/>\n";
589 } else if (type == "text/plain:1.0" || type == "text/plain") {
590 // note10 are the same as note11, so ignore force format
592 " <use datatype='note10' mode='rw' preferred='yes'/>\n"
593 " <use datatype='note11' mode='rw'/>\n";
594 } else if (type.empty()) {
595 throwError("no MIME type configured");
597 throwError(string("configured MIME type not supported: ") + type);
603 sysync::TSyError SyncSourceSerialize::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey)
607 readItem(aID->item, item);
608 TSyError res = getSynthesisAPI()->setValue(aItemKey, "data", item.c_str(), item.size());
612 sysync::TSyError SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
615 TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
618 InsertItemResult inserted =
619 insertItem(!aID ? "" : aID->item, data.get());
620 newID->item = StrAlloc(inserted.m_luid.c_str());
626 void SyncSourceSerialize::init(SyncSource::Operations &ops)
628 ops.m_readItemAsKey = boost::bind(&SyncSourceSerialize::readItemAsKey,
630 ops.m_insertItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
631 this, _1, (sysync::cItemID)NULL, _2);
632 ops.m_updateItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
637 * Mapping from Hash() value to file.
643 typedef std::string Hash_t;
644 Hash_t hashFunc(const std::string &data) { return SHA_256(data); }
646 typedef unsigned long Hash_t;
647 Hash_t hashFunc(const std::string &data) { return Hash(data); }
649 typedef unsigned long Counter_t;
651 /** mark the algorithm used for the hash via different suffices */
652 static const char *m_hashSuffix;
655 * Collect information about stored hashes. Provides
656 * access to file name via hash.
658 * If no hashes were written (as in an old SyncEvoltion
659 * version), we could read the files to recreate the
660 * hashes. This is not done because it won't occur
663 * Hashes are also not verified. Users should better
664 * not edit them or file contents...
666 * @param oldBackup existing backup to read; may be empty
668 void init(const SyncSource::Operations::ConstBackupInfo &oldBackup)
670 m_hash2counter.clear();
671 m_dirname = oldBackup.m_dirname;
672 if (m_dirname.empty() || !oldBackup.m_node) {
677 if (!oldBackup.m_node->getProperty("numitems", numitems)) {
680 for (long counter = 1; counter <= numitems; counter++) {
682 key << counter << m_hashSuffix;
684 if (oldBackup.m_node->getProperty(key.str(), hash)) {
685 m_hash2counter[hash] = counter;
691 * create file name for a specific hash, empty if no such hash
693 string getFilename(Hash_t hash)
695 Map_t::const_iterator it = m_hash2counter.find(hash);
696 if (it != m_hash2counter.end()) {
697 stringstream dirname;
698 dirname << m_dirname << "/" << it->second;
699 return dirname.str();
706 typedef std::map<Hash_t, Counter_t> Map_t;
707 Map_t m_hash2counter;
711 const char *ItemCache::m_hashSuffix =
719 void SyncSourceRevisions::initRevisions()
721 if (!m_revisionsSet) {
722 listAllItems(m_revisions);
723 m_revisionsSet = true;
728 void SyncSourceRevisions::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
729 const SyncSource::Operations::BackupInfo &newBackup,
730 BackupReport &report)
733 cache.init(oldBackup);
735 bool startOfSync = newBackup.m_mode == SyncSource::Operations::BackupInfo::BACKUP_BEFORE;
736 RevisionMap_t buffer;
737 RevisionMap_t *revisions;
740 revisions = &m_revisions;
742 listAllItems(buffer);
746 unsigned long counter = 1;
749 BOOST_FOREACH(const StringPair &mapping, *revisions) {
750 const string &uid = mapping.first;
751 const string &rev = mapping.second;
752 m_raw->readItemRaw(uid, item);
754 stringstream filename;
755 filename << newBackup.m_dirname << "/" << counter;
757 ItemCache::Hash_t hash = cache.hashFunc(item);
758 string oldfilename = cache.getFilename(hash);
759 if (!oldfilename.empty()) {
760 // found old file with same content, reuse it via hardlink
761 if (link(oldfilename.c_str(), filename.str().c_str())) {
762 // Hard linking failed. Record this, then continue
763 // by ignoring the old file.
764 SE_LOG_DEBUG(NULL, NULL, "hard linking old %s new %s: %s",
766 filename.str().c_str(),
772 if (oldfilename.empty()) {
773 // write new file instead of reusing old one
774 ofstream out(filename.str().c_str());
775 out.write(item.c_str(), item.size());
778 throwError(string("error writing ") + filename.str() + ": " + strerror(errno));
783 key << counter << "-uid";
784 newBackup.m_node->setProperty(key.str(), uid);
785 // clear() does not remove the existing content, which was
786 // intended here. This should have been key.str(""). As a
787 // result, keys for -rev are longer than intended because they
788 // start with the -uid part. We cannot change it now, because
789 // that would break compatibility with nodes that use the
790 // older, longer keys for -rev.
792 key << counter << "-rev";
793 newBackup.m_node->setProperty(key.str(), rev);
795 key << counter << ItemCache::m_hashSuffix;
796 newBackup.m_node->setProperty(key.str(), hash);
802 value << counter - 1;
803 newBackup.m_node->setProperty("numitems", value.str());
804 newBackup.m_node->flush();
806 report.setNumItems(counter - 1);
809 void SyncSourceRevisions::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
811 SyncSourceReport &report)
813 RevisionMap_t revisions;
814 listAllItems(revisions);
818 strval = oldBackup.m_node->readProperty("numitems");
819 stringstream stream(strval);
822 for (long counter = 1; counter <= numitems; counter++) {
824 key << counter << "-uid";
825 string uid = oldBackup.m_node->readProperty(key.str());
827 key << counter << "-rev";
828 string rev = oldBackup.m_node->readProperty(key.str());
829 RevisionMap_t::iterator it = revisions.find(uid);
830 report.incrementItemStat(report.ITEM_LOCAL,
833 if (it != revisions.end() &&
835 // item exists in backup and database with same revision:
838 // add or update, so need item
839 stringstream filename;
840 filename << oldBackup.m_dirname << "/" << counter;
842 if (!ReadFile(filename.str(), data)) {
843 throwError(StringPrintf("restoring %s from %s failed: could not read file",
845 filename.str().c_str()));
847 // TODO: it would be nicer to recreate the item
848 // with the original revision. If multiple peers
849 // synchronize against us, then some of them
850 // might still be in sync with that revision. By
851 // updating the revision here we force them to
852 // needlessly receive an update.
854 // For the current peer for which we restore this is
855 // avoided by the revision check above: unchanged
856 // items aren't touched.
857 SyncSourceReport::ItemState state =
858 it == revisions.end() ?
859 SyncSourceReport::ITEM_ADDED : // not found in database, create anew
860 SyncSourceReport::ITEM_UPDATED; // found, update existing item
862 report.incrementItemStat(report.ITEM_LOCAL,
866 m_raw->insertItemRaw(it == revisions.end() ? "" : uid,
870 report.incrementItemStat(report.ITEM_LOCAL,
877 // remove handled item from revision list so
878 // that when we are done, the only remaining
879 // items listed there are the ones which did
880 // no exist in the backup
881 if (it != revisions.end()) {
886 // now remove items that were not in the backup
887 BOOST_FOREACH(const StringPair &mapping, revisions) {
889 report.incrementItemStat(report.ITEM_LOCAL,
893 m_del->deleteItem(mapping.first);
896 report.incrementItemStat(report.ITEM_LOCAL,
904 void SyncSourceRevisions::detectChanges(ConfigNode &trackingNode)
908 BOOST_FOREACH(const StringPair &mapping, m_revisions) {
909 const string &uid = mapping.first;
910 const string &revision = mapping.second;
912 // always remember the item, need full list
915 string serverRevision(trackingNode.readProperty(uid));
916 if (!serverRevision.size()) {
918 trackingNode.setProperty(uid, revision);
920 if (revision != serverRevision) {
921 addItem(uid, UPDATED);
922 trackingNode.setProperty(uid, revision);
927 // clear information about all items that we recognized as deleted
929 trackingNode.readProperties(props);
931 BOOST_FOREACH(const StringPair &mapping, props) {
932 const string &uid(mapping.first);
933 if (getAllItems().find(uid) == getAllItems().end()) {
934 addItem(uid, DELETED);
935 trackingNode.removeProperty(uid);
940 void SyncSourceRevisions::updateRevision(ConfigNode &trackingNode,
941 const std::string &old_luid,
942 const std::string &new_luid,
943 const std::string &revision)
946 if (old_luid != new_luid) {
947 trackingNode.removeProperty(old_luid);
949 if (new_luid.empty() || revision.empty()) {
950 throwError("need non-empty LUID and revision string");
952 trackingNode.setProperty(new_luid, revision);
955 void SyncSourceRevisions::deleteRevision(ConfigNode &trackingNode,
956 const std::string &luid)
959 trackingNode.removeProperty(luid);
962 void SyncSourceRevisions::sleepSinceModification()
964 time_t current = time(NULL);
965 while (current - m_modTimeStamp < m_revisionAccuracySeconds) {
966 sleep(m_revisionAccuracySeconds - (current - m_modTimeStamp));
967 current = time(NULL);
971 void SyncSourceRevisions::databaseModified()
973 m_modTimeStamp = time(NULL);
976 void SyncSourceRevisions::init(SyncSourceRaw *raw,
977 SyncSourceDelete *del,
979 SyncSource::Operations &ops)
984 m_revisionAccuracySeconds = granularity;
985 m_revisionsSet = false;
987 ops.m_backupData = boost::bind(&SyncSourceRevisions::backupData,
991 ops.m_restoreData = boost::bind(&SyncSourceRevisions::restoreData,
994 ops.m_endSession.push_back(boost::bind(&SyncSourceRevisions::sleepSinceModification,
998 std::string SyncSourceLogging::getDescription(sysync::KeyH aItemKey)
1001 std::list<std::string> values;
1003 BOOST_FOREACH(const std::string &field, m_fields) {
1005 if (!getSynthesisAPI()->getValue(aItemKey, field, value) &&
1007 values.push_back(std::string(value.get()));
1011 std::string description = boost::join(values, m_sep);
1014 // Instead of failing we log the error and ask
1015 // the caller to log the UID. That way transient
1016 // errors or errors in the logging code don't
1023 std::string SyncSourceLogging::getDescription(const string &luid)
1028 sysync::TSyError SyncSourceLogging::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID, const boost::function<SyncSource::Operations::InsertItemAsKey_t> &parent)
1030 std::string description = getDescription(aItemKey);
1031 SE_LOG_INFO(this, NULL,
1032 description.empty() ? "%s <%s>" : "%s \"%s\"",
1034 !description.empty() ? description.c_str() : "???");
1036 return parent(aItemKey, newID);
1038 return sysync::LOCERR_NOTIMP;
1042 sysync::TSyError SyncSourceLogging::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID, const boost::function<SyncSource::Operations::UpdateItemAsKey_t> &parent)
1044 std::string description = getDescription(aItemKey);
1045 SE_LOG_INFO(this, NULL,
1046 description.empty() ? "%s <%s>" : "%s \"%s\"",
1048 !description.empty() ? description.c_str() : aID ? aID->item : "???");
1050 return parent(aItemKey, aID, newID);
1052 return sysync::LOCERR_NOTIMP;
1056 sysync::TSyError SyncSourceLogging::deleteItem(sysync::cItemID aID, const boost::function<SyncSource::Operations::DeleteItem_t> &parent)
1058 std::string description = getDescription(aID->item);
1059 SE_LOG_INFO(this, NULL,
1060 description.empty() ? "%s <%s>" : "%s \"%s\"",
1062 !description.empty() ? description.c_str() : aID->item);
1066 return sysync::LOCERR_NOTIMP;
1070 void SyncSourceLogging::init(const std::list<std::string> &fields,
1071 const std::string &sep,
1072 SyncSource::Operations &ops)
1077 ops.m_insertItemAsKey = boost::bind(&SyncSourceLogging::insertItemAsKey,
1078 this, _1, _2, ops.m_insertItemAsKey);
1079 ops.m_updateItemAsKey = boost::bind(&SyncSourceLogging::updateItemAsKey,
1080 this, _1, _2, _3, ops.m_updateItemAsKey);
1081 ops.m_deleteItem = boost::bind(&SyncSourceLogging::deleteItem,
1082 this, _1, ops.m_deleteItem);
1085 sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
1089 std::string data = m_configNode->readProperty(m_adminPropertyName);
1090 *adminData = StrAlloc(SafeConfigNode::unescape(data).c_str());
1092 return sysync::LOCERR_OK;
1095 sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData)
1097 m_configNode->setProperty(m_adminPropertyName,
1098 SafeConfigNode::escape(adminData, false, false));
1100 // Flush here, because some calls to saveAdminData() happend
1101 // after SyncSourceAdmin::flush() (= session end).
1102 m_configNode->flush();
1103 return sysync::LOCERR_OK;
1106 bool SyncSourceAdmin::readNextMapItem(sysync::MapID mID, bool aFirst)
1111 if (m_mappingIterator != m_mapping.end()) {
1112 entry2mapid(m_mappingIterator->first, m_mappingIterator->second, mID);
1113 ++m_mappingIterator;
1120 sysync::TSyError SyncSourceAdmin::insertMapItem(sysync::cMapID mID)
1123 mapid2entry(mID, key, value);
1126 StringMap::iterator it = m_mapping.find(key);
1127 if (it != m_mapping.end()) {
1128 // error, exists already
1129 return sysync::DB_Forbidden;
1131 m_mapping[key] = value;
1132 return sysync::LOCERR_OK;
1135 m_mapping[key] = value;
1136 m_mappingNode->clear();
1137 m_mappingNode->writeProperties(m_mapping);
1138 m_mappingNode->flush();
1139 return sysync::LOCERR_OK;
1143 sysync::TSyError SyncSourceAdmin::updateMapItem(sysync::cMapID mID)
1146 mapid2entry(mID, key, value);
1148 StringMap::iterator it = m_mapping.find(key);
1149 if (it == m_mapping.end()) {
1150 // error, does not exist
1151 return sysync::DB_Forbidden;
1153 m_mapping[key] = value;
1154 m_mappingNode->clear();
1155 m_mappingNode->writeProperties(m_mapping);
1156 m_mappingNode->flush();
1157 return sysync::LOCERR_OK;
1161 sysync::TSyError SyncSourceAdmin::deleteMapItem(sysync::cMapID mID)
1164 mapid2entry(mID, key, value);
1166 StringMap::iterator it = m_mapping.find(key);
1167 if (it == m_mapping.end()) {
1168 // error, does not exist
1169 return sysync::DB_Forbidden;
1171 m_mapping.erase(it);
1172 m_mappingNode->clear();
1173 m_mappingNode->writeProperties(m_mapping);
1174 m_mappingNode->flush();
1175 return sysync::LOCERR_OK;
1179 void SyncSourceAdmin::flush()
1181 m_configNode->flush();
1182 if (m_mappingLoaded) {
1183 m_mappingNode->clear();
1184 m_mappingNode->writeProperties(m_mapping);
1185 m_mappingNode->flush();
1189 void SyncSourceAdmin::resetMap()
1192 m_mappingNode->readProperties(m_mapping);
1193 m_mappingIterator = m_mapping.begin();
1194 m_mappingLoaded = true;
1198 void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value)
1200 key = StringPrintf ("%s-%x",
1201 SafeConfigNode::escape(mID->localID ? mID->localID : "", true, false).c_str(),
1203 if (mID->remoteID && mID->remoteID[0]) {
1204 value = StringPrintf("%s %x",
1205 SafeConfigNode::escape(mID->remoteID ? mID->remoteID : "", true, false).c_str(),
1208 value = StringPrintf("%x", mID->flags);
1212 void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID)
1214 size_t found = key.rfind('-');
1215 mID->localID = StrAlloc(SafeConfigNode::unescape(key.substr(0,found)).c_str());
1216 if (found != key.npos) {
1217 mID->ident = strtol(key.substr(found+1).c_str(), NULL, 16);
1221 std::vector< std::string > tokens;
1222 boost::split(tokens, value, boost::is_from_range(' ', ' '));
1223 if (tokens.size() >= 2) {
1224 // if branch from mapid2entry above
1225 mID->remoteID = StrAlloc(SafeConfigNode::unescape(tokens[0]).c_str());
1226 mID->flags = strtol(tokens[1].c_str(), NULL, 16);
1228 // else branch from above
1229 mID->remoteID = NULL;
1230 mID->flags = strtol(tokens[0].c_str(), NULL, 16);
1234 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1235 const boost::shared_ptr<ConfigNode> &config,
1236 const std::string adminPropertyName,
1237 const boost::shared_ptr<ConfigNode> &mapping)
1239 m_configNode = config;
1240 m_adminPropertyName = adminPropertyName;
1241 m_mappingNode = mapping;
1242 m_mappingLoaded = false;
1244 ops.m_loadAdminData = boost::bind(&SyncSourceAdmin::loadAdminData,
1246 ops.m_saveAdminData = boost::bind(&SyncSourceAdmin::saveAdminData,
1248 ops.m_readNextMapItem = boost::bind(&SyncSourceAdmin::readNextMapItem,
1250 ops.m_insertMapItem = boost::bind(&SyncSourceAdmin::insertMapItem,
1252 ops.m_updateMapItem = boost::bind(&SyncSourceAdmin::updateMapItem,
1254 ops.m_deleteMapItem = boost::bind(&SyncSourceAdmin::deleteMapItem,
1256 ops.m_endSession.push_back(boost::bind(&SyncSourceAdmin::flush,
1260 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1264 source->getProperties(true),
1265 SourceAdminDataName,
1266 source->getServerNode());
1269 void SyncSourceBlob::init(SyncSource::Operations &ops,
1270 const std::string &dir)
1272 m_blob.Init(getSynthesisAPI(),
1275 ops.m_readBlob = boost::bind(&SyncSourceBlob::readBlob, this,
1276 _1, _2, _3, _4, _5, _6, _7);
1277 ops.m_writeBlob = boost::bind(&SyncSourceBlob::writeBlob, this,
1278 _1, _2, _3, _4, _5, _6, _7);
1279 ops.m_deleteBlob = boost::bind(&SyncSourceBlob::deleteBlob, this,