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
21 #include "EvolutionSyncSource.h"
22 #include "EvolutionSyncClient.h"
23 #include "SyncEvolutionUtil.h"
26 #include <boost/algorithm/string.hpp>
27 #include <boost/algorithm/string/join.hpp>
28 #include <boost/foreach.hpp>
36 ESource *EvolutionSyncSource::findSource( ESourceList *list, const string &id )
38 for (GSList *g = e_source_list_peek_groups (list); g; g = g->next) {
39 ESourceGroup *group = E_SOURCE_GROUP (g->data);
41 for (s = e_source_group_peek_sources (group); s; s = s->next) {
42 ESource *source = E_SOURCE (s->data);
43 char *uri = e_source_get_uri(source);
44 bool found = id.empty() ||
45 !id.compare(e_source_peek_name(source)) ||
46 (uri && !id.compare(uri));
58 void EvolutionSyncSource::throwError(const string &action, GError *gerror)
63 gerrorstr += gerror->message;
64 g_clear_error(&gerror);
66 gerrorstr = ": failure";
69 throwError(action + gerrorstr);
73 void EvolutionSyncSource::throwError(const string &action, int error)
75 throwError(action + ": " + strerror(error));
78 void EvolutionSyncSource::throwError(const string &failure)
81 EvolutionSyncClient::throwError(string(getName()) + ": " + failure);
84 void EvolutionSyncSource::resetItems()
88 m_updatedItems.clear();
89 m_deletedItems.clear();
92 void EvolutionSyncSource::handleException()
94 SyncEvolutionException::handle();
98 SourceRegistry &EvolutionSyncSource::getSourceRegistry()
100 static SourceRegistry sourceRegistry;
101 return sourceRegistry;
104 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
107 const string &typeDescr,
108 const Values &typeValues) :
109 m_shortDescr(shortDescr),
112 m_typeDescr(typeDescr),
113 m_typeValues(typeValues)
115 SourceRegistry ®istry(EvolutionSyncSource::getSourceRegistry());
117 // insert sorted by description to have deterministic ordering
118 for(SourceRegistry::iterator it = registry.begin();
119 it != registry.end();
121 if ((*it)->m_shortDescr > shortDescr) {
122 registry.insert(it, this);
126 registry.push_back(this);
130 static ostream & operator << (ostream &out, const RegisterSyncSource &rhs)
132 out << rhs.m_shortDescr << (rhs.m_enabled ? " (enabled)" : " (disabled)");
136 EvolutionSyncSource *const RegisterSyncSource::InactiveSource = (EvolutionSyncSource *)1;
138 TestRegistry &EvolutionSyncSource::getTestRegistry()
140 static TestRegistry testRegistry;
144 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
145 m_configName(configName),
146 m_testCaseName(testCaseName)
148 EvolutionSyncSource::getTestRegistry().push_back(this);
151 static class ScannedModules {
154 #ifdef ENABLE_MODULES
157 // possible extension: scan directories for matching module names instead of hard-coding known names
158 const char *modules[] = {
167 for (int i = 0; modules[i]; i++) {
170 // Open the shared object so that backend can register
171 // itself. We keep that pointer, so never close the
173 dlhandle = dlopen(modules[i], RTLD_NOW|RTLD_GLOBAL);
175 string fullpath = LIBDIR "/syncevolution/";
176 fullpath += modules[i];
177 dlhandle = dlopen(fullpath.c_str(), RTLD_NOW|RTLD_GLOBAL);
179 // remember which modules were found and which were not
180 state = dlhandle ? &m_available : &m_missing;
181 state->push_back(modules[i]);
185 list<string> m_available;
186 list<string> m_missing;
190 EvolutionSyncSource *EvolutionSyncSource::createSource(const EvolutionSyncSourceParams ¶ms, bool error)
192 string sourceTypeString = getSourceTypeString(params.m_nodes);
193 SourceType sourceType = EvolutionSyncSource::getSourceType(params.m_nodes);
195 const SourceRegistry ®istry(getSourceRegistry());
196 BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
197 EvolutionSyncSource *source = sourceInfos->m_create(params);
199 if (source == RegisterSyncSource::InactiveSource) {
200 EvolutionSyncClient::throwError(params.m_name + ": access to " + sourceInfos->m_shortDescr +
201 " not enabled, therefore type = " + sourceTypeString + " not supported");
208 string problem = params.m_name + ": type '" + sourceTypeString + "' not supported";
209 if (scannedModules.m_available.size()) {
210 problem += " by any of the backends (";
211 problem += boost::join(scannedModules.m_available, ", ");
214 if (scannedModules.m_missing.size()) {
215 problem += ". The following backend(s) were not found: ";
216 problem += boost::join(scannedModules.m_missing, ", ");
218 EvolutionSyncClient::throwError(problem);
224 EvolutionSyncSource *EvolutionSyncSource::createTestingSource(const string &name, const string &type, bool error,
227 EvolutionSyncConfig config("testing");
228 SyncSourceNodes nodes = config.getSyncSourceNodes(name);
229 EvolutionSyncSourceParams params(name, nodes, "");
230 PersistentEvolutionSyncSourceConfig sourceconfig(name, nodes);
231 sourceconfig.setSourceType(type);
233 sourceconfig.setDatabaseID(string(prefix) + name + "_1");
235 return createSource(params, error);
238 void EvolutionSyncSource::getSynthesisInfo(string &profile,
241 XMLConfigFragments &fragments)
243 string type = getMimeType();
245 if (type == "text/x-vcard") {
247 profile = "\"vCard\", 1";
249 " <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
250 " <use datatype='vCard30' mode='rw'/>\n";
251 } else if (type == "text/vcard") {
253 profile = "\"vCard\", 2";
255 " <use datatype='vCard21' mode='rw'/>\n"
256 " <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
257 } else if (type == "text/x-calendar") {
258 native = "vCalendar10";
259 profile = "\"vCalendar\", 1";
261 " <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
262 " <use datatype='iCalendar20' mode='rw'/>\n";
263 } else if (type == "text/calendar") {
264 native = "iCalendar20";
265 profile = "\"vCalendar\", 2";
267 " <use datatype='vCalendar10' mode='rw'/>\n"
268 " <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
269 } else if (type == "text/plain") {
270 profile = "\"Note\", 2";
272 throwError(string("default MIME type not supported: ") + type);
275 SourceType sourceType = getSourceType();
276 if (!sourceType.m_format.empty()) {
277 type = sourceType.m_format;
280 if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
282 " <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
283 if (!sourceType.m_forceFormat) {
285 " <use datatype='vCard30' mode='rw'/>\n";
287 } else if (type == "text/vcard:3.0" || type == "text/vcard") {
289 " <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
290 if (!sourceType.m_forceFormat) {
292 " <use datatype='vCard21' mode='rw'/>\n";
294 } else if (type == "text/x-vcalendar:2.0" || type == "text/x-vcalendar") {
296 " <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
297 if (!sourceType.m_forceFormat) {
299 " <use datatype='icalendar20' mode='rw'/>\n";
301 } else if (type == "text/calendar:2.0" || type == "text/calendar") {
303 " <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
304 if (!sourceType.m_forceFormat) {
306 " <use datatype='vcalendar10' mode='rw'/>\n";
308 } else if (type == "text/plain:1.0" || type == "text/plain") {
309 // note10 are the same as note11, so ignore force format
311 " <use datatype='note10' mode='rw' preferred='yes'/>\n"
312 " <use datatype='note11' mode='rw'/>\n";
314 throwError(string("configured MIME type not supported: ") + type);
318 void EvolutionSyncSource::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
320 stringstream xmlstream;
325 getSynthesisInfo(profile, datatypes, native, fragments);
328 " <plugin_module>SyncEvolution</plugin_module>\n"
329 " <plugin_datastoreadmin>no</plugin_datastoreadmin>\n"
331 " <!-- General datastore settings for all DB types -->\n"
333 " <!-- if this is set to 'yes', SyncML clients can only read\n"
334 " from the database, but make no modifications -->\n"
335 " <readonly>no</readonly>\n"
337 " <!-- conflict strategy: Newer item wins\n"
338 " You can set 'server-wins' or 'client-wins' as well\n"
339 " if you want to give one side precedence\n"
341 " <conflictstrategy>newer-wins</conflictstrategy>\n"
343 " <!-- on slowsync: duplicate items that are not fully equal\n"
344 " You can set this to 'newer-wins' as well to avoid\n"
345 " duplicates as much as possible\n"
347 " <slowsyncstrategy>duplicate</slowsyncstrategy>\n"
349 " <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->\n"
350 " <datacharset>UTF-8</datacharset>\n"
351 " <!-- use C-language (unix style) linefeeds (\n, 0x0A) -->\n"
352 " <datalineends>unix</datalineends>\n"
354 " <!-- set this to 'UTC' if time values should be stored in UTC into the database\n"
355 " rather than local time. 'SYSTEM' denotes local server time zone. -->\n"
356 " <datatimezone>SYSTEM</datatimezone>\n"
358 " <!-- plugin DB may have its own identifiers to determine the point in time of changes, so\n"
359 " we must make sure this identifier is stored (and not only the sync time) -->\n"
360 " <storesyncidentifiers>yes</storesyncidentifiers>\n"
362 " <!-- Mapping of the fields to the fieldlist 'contacts' -->\n"
363 " <fieldmap fieldlist='contacts'>\n"
364 " <initscript><![CDATA[\n"
365 " string itemdata;\n"
366 " ]]></initscript>\n"
367 " <beforewritescript><![CDATA[\n"
368 " itemdata = MAKETEXTWITHPROFILE(" << profile << ", \"EVOLUTION\");\n"
369 " ]]></beforewritescript>\n"
370 " <afterreadscript><![CDATA[\n"
371 " PARSETEXTWITHPROFILE(itemdata, " << profile << ", \"EVOLUTION\");\n"
372 " ]]></afterreadscript>\n"
373 " <map name='data' references='itemdata' type='string'/>\n"
376 " <!-- datatypes supported by this datastore -->\n"
377 " <typesupport>\n" <<
381 xml = xmlstream.str();
384 SyncMLStatus EvolutionSyncSource::beginSync(SyncMode mode) throw()
386 // start background thread if not running yet:
387 // necessary to catch problems with Evolution backend
388 EvolutionSyncClient::startLoopThread();
391 // @TODO: force slow sync if something goes wrong
393 // reset anchors now, once we proceed there is no going back
394 // (because the change marker is about to be moved)
395 // and the sync must either complete or result in a slow sync
398 const char *error = getenv("SYNCEVOLUTION_BEGIN_SYNC_ERROR");
399 if (error && strstr(error, getName())) {
400 EvolutionSyncClient::throwError("artificial error in beginSync()");
404 m_isModified = false;
407 m_updatedItems.clear();
408 m_deletedItems.clear();
410 // determine what to do
411 bool needAll = false;
412 bool needPartial = false;
413 bool deleteLocal = false;
419 case SYNC_ONE_WAY_FROM_CLIENT:
423 case SYNC_REFRESH_FROM_SERVER:
427 case SYNC_REFRESH_FROM_CLIENT:
432 // special mode for testing: prepare both all and partial lists
433 needAll = needPartial = true;
435 case SYNC_ONE_WAY_FROM_SERVER:
436 // nothing to do, just wait for server's changes
439 EvolutionSyncClient::throwError("unsupported sync mode, valid are only: slow, two-way, refresh");
443 beginSyncThrow(needAll, needPartial, deleteLocal);
445 // This code here puts iterators in a state
446 // where iterating with nextItem() is possible.
455 void EvolutionSyncSource::rewindItems() throw()
460 SyncItem::State EvolutionSyncSource::nextItem(string *data, string &luid) throw()
462 cxxptr<SyncItem> item(m_allItems.iterate(data ? false : true));
463 SyncItem::State state = SyncItem::NO_MORE_ITEMS;
466 if (m_newItems.find(item->getKey()) != m_newItems.end()) {
467 state = SyncItem::NEW;
468 } else if (m_updatedItems.find(item->getKey()) != m_updatedItems.end()) {
469 state = SyncItem::UPDATED;
471 state = SyncItem::UNCHANGED;
474 data->assign((const char *)item->getData(), item->getDataSize());
476 luid = item->getKey();
481 SyncMLStatus EvolutionSyncSource::endSync() throw()
489 // @TODO: Do _not_ tell the caller (SyncManager) if an error occurred
490 // because that causes Sync4jClient to abort processing for all
491 // sync sources. Instead deal with failed sync sources in
492 // EvolutionSyncClient::sync().
496 SyncMLStatus EvolutionSyncSource::addItem(SyncItem& item) throw()
498 return processItem("add", &EvolutionSyncSource::addItemThrow, item, true);
501 SyncMLStatus EvolutionSyncSource::updateItem(SyncItem& item) throw()
503 return processItem("update", &EvolutionSyncSource::updateItemThrow, item, true);
506 SyncMLStatus EvolutionSyncSource::deleteItem(SyncItem& item) throw()
508 return processItem("delete", &EvolutionSyncSource::deleteItemThrow, item, false);
511 SyncMLStatus EvolutionSyncSource::removeAllItems() throw()
513 SyncMLStatus status = STATUS_OK;
516 BOOST_FOREACH(const string &key, m_allItems) {
518 item.setKey(key.c_str());
519 logItem(item, "delete all items");
520 deleteItemThrow(item);
525 status = STATUS_FATAL;
530 SyncMLStatus EvolutionSyncSource::processItem(const char *action,
531 SyncMLStatus (EvolutionSyncSource::*func)(SyncItem& item),
533 bool needData) throw()
535 SyncMLStatus status = STATUS_FATAL;
538 logItem(item, action);
539 if (needData && (item.getDataSize() < 0 || !item.getData())) {
540 // Something went wrong in the server: update or add without data.
541 // Shouldn't happen, but it did with one server and thus this
542 // security check was added to prevent segfaults.
543 logItem(item, "ignored due to missing data");
546 status = (this->*func)(item);
556 void EvolutionSyncSource::sleepSinceModification(int seconds)
558 time_t current = time(NULL);
559 while (current - m_modTimeStamp < seconds) {
560 sleep(seconds - (current - m_modTimeStamp));
561 current = time(NULL);
565 void EvolutionSyncSource::databaseModified()
567 m_modTimeStamp = time(NULL);
570 void EvolutionSyncSource::logItemUtil(const string data, const string &mimeType, const string &mimeVersion,
571 const string &uid, const string &info, bool debug)
573 if (getLevel() >= (debug ? Logger::DEBUG : Logger::INFO)) {
576 if (mimeType == "text/plain") {
577 size_t eol = data.find('\n');
578 if (eol != data.npos) {
579 name.assign(data, 0, eol);
584 // Avoid pulling in a full vCard/iCalendar parser by just
585 // searching for a specific property. This is rather crude
586 // and does not handle encoding correctly at the moment, too.
589 if (mimeType == "text/vcard" ||
590 mimeType == "text/x-vcard") {
592 } else if (mimeType == "text/calendar" ||
593 mimeType == "text/x-calendar") {
600 while (start < data.size()) {
601 start = data.find(prop, start);
602 if (start == data.npos) {
605 // must follow line break and continue with
607 if (start > 0 && data[start - 1] == '\n' &&
608 start + prop.size() < data.size() &&
609 (data[start + prop.size()] == ';' ||
610 data[start + prop.size()] == ':')) {
611 start = data.find(':', start);
612 if (start != data.npos) {
614 size_t end = data.find_first_of("\n\r", start);
617 end == data.npos ? data.npos : (end - start));
621 start += prop.size();
628 SE_LOG(debug ? Logger::DEBUG : Logger::INFO, this, NULL,
633 SE_LOG(debug ? Logger::DEBUG : Logger::INFO, this, NULL,
641 void EvolutionSyncSource::setLevel(Level level)
643 LoggerBase::instance().setLevel(level);
646 Logger::Level EvolutionSyncSource::getLevel()
648 return LoggerBase::instance().getLevel();
651 void EvolutionSyncSource::messagev(Level level,
655 const char *function,
659 string newprefix = getName();
664 LoggerBase::instance().messagev(level, newprefix.c_str(),
665 file, line, function,
669 SyncItem *EvolutionSyncSource::Items::start()
672 SE_LOG_DEBUG(&m_source, NULL, "start scanning %s items", m_type.c_str());
676 SyncItem *EvolutionSyncSource::Items::iterate(bool idOnly)
679 const string &uid( *m_it );
680 SE_LOG_DEBUG(&m_source, NULL, "next %s item: %s", m_type.c_str(), uid.c_str());
682 if (&m_source.m_deletedItems == this || idOnly) {
683 // just tell caller the uid of the (possibly deleted) item
684 cxxptr<SyncItem> item(new SyncItem());
686 return item.release();
688 // retrieve item with all its data
690 cxxptr<SyncItem> item(m_source.createItem(uid));
691 return item.release();
693 m_source.handleException();
702 bool EvolutionSyncSource::Items::addItem(const string &uid) {
703 pair<iterator, bool> res = insert(uid);
705 m_source.logItem(uid, m_type, true);