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
28 #include "EvolutionSyncSource.h"
29 #include <syncevo/IdentityProvider.h>
33 #include "EvolutionContactSource.h"
34 #include <syncevo/util.h>
35 #include <syncevo/Exception.h>
36 #include <syncevo/Logging.h>
39 #include <boost/range/algorithm/find.hpp>
41 #include <boost/algorithm/string.hpp>
42 #include <boost/algorithm/string/join.hpp>
43 #include <boost/algorithm/string/predicate.hpp>
44 #include <boost/foreach.hpp>
45 #include <boost/lambda/lambda.hpp>
47 #include <syncevo/declarations.h>
49 SE_GLIB_TYPE(EBookQuery, e_book_query)
53 inline bool IsContactNotFound(const GError *gerror) {
56 gerror->domain == E_BOOK_CLIENT_ERROR &&
57 gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND
59 gerror->domain == E_BOOK_ERROR &&
60 gerror->code == E_BOOK_ERROR_CONTACT_NOT_FOUND
66 const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions;
67 const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties;
69 EvolutionContactSource::EvolutionContactSource(const SyncSourceParams ¶ms,
70 EVCardFormat vcardFormat) :
71 EvolutionSyncSource(params),
72 m_vcardFormat(vcardFormat)
80 m_readAheadOrder = READ_NONE;
81 const char *mode = getEnv("SYNCEVOLUTION_EDS_ACCESS_MODE", "");
82 m_accessMode = boost::iequals(mode, "synchronous") ? SYNCHRONOUS :
83 boost::iequals(mode, "batched") ? BATCHED :
86 SyncSourceLogging::init(InitList<std::string>("N_FIRST") + "N_MIDDLE" + "N_LAST",
91 EvolutionContactSource::~EvolutionContactSource()
93 // Don't close while we have pending operations. They might
94 // complete after we got destroyed, causing them to use an invalid
95 // "this" pointer. We also don't know how well EDS copes with
96 // closing the address book while it has pending operations - EDS
97 // maintainer mcrha wasn't sure.
99 // TODO: cancel the operations().
103 #ifdef USE_EDS_CLIENT
104 // logCacheStats(Logger::DEBUG);
108 #ifdef USE_EDS_CLIENT
109 static EClient *newEBookClient(ESource *source,
112 return E_CLIENT(e_book_client_new(source, gerror));
116 EvolutionSyncSource::Databases EvolutionContactSource::getDatabases()
119 #ifdef USE_EDS_CLIENT
120 getDatabasesFromRegistry(result,
121 E_SOURCE_EXTENSION_ADDRESS_BOOK,
122 e_source_registry_ref_default_address_book);
124 ESourceList *sources = NULL;
125 if (!e_book_get_addressbooks(&sources, NULL)) {
126 Exception::throwError(SE_HERE, "unable to access address books");
130 for (GSList *g = e_source_list_peek_groups (sources); g; g = g->next) {
131 ESourceGroup *group = E_SOURCE_GROUP (g->data);
132 for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) {
133 ESource *source = E_SOURCE (s->data);
134 eptr<char> uri(e_source_get_uri(source));
139 Database entry(e_source_peek_name(source),
142 if (boost::starts_with(uristr, "couchdb://")) {
143 // Append CouchDB address books at the end of the list,
144 // otherwise preserving the order of address books.
146 // The reason is Moblin Bugzilla #7877 (aka CouchDB
147 // feature request #479110): the initial release of
148 // evolution-couchdb in Ubuntu 9.10 is unusable because
149 // it does not support the REV property.
151 // Reordering the entries ensures that the CouchDB
152 // address book is not used as the default database by
153 // SyncEvolution, as it happened in Ubuntu 9.10.
154 // Users can still pick it intentionally via
155 // "evolutionsource".
156 secondary.push_back(entry);
158 result.push_back(entry);
162 result.insert(result.end(), secondary.begin(), secondary.end());
164 // No results? Try system address book (workaround for embedded Evolution Dataserver).
165 if (!result.size()) {
166 eptr<EBook, GObject> book;
171 book = e_book_new_system_addressbook (gerror);
174 name = "<<default>>";
175 book = e_book_new_default_addressbook (gerror);
179 const char *uri = e_book_get_uri (book);
180 result.push_back(Database(name, uri, true));
183 // the first DB found is the default
184 result[0].m_isDefault = true;
191 void EvolutionContactSource::open()
193 #ifdef USE_EDS_CLIENT
194 m_addressbook.reset(E_BOOK_CLIENT(openESource(E_SOURCE_EXTENSION_ADDRESS_BOOK,
195 e_source_registry_ref_builtin_address_book,
196 newEBookClient).get()));
199 bool created = false;
200 bool onlyIfExists = false; // always try to create address book, because even if there is
201 // a source there's no guarantee that the actual database was
202 // created already; the original logic below for only setting
203 // this when explicitly requesting a new address book
204 // therefore failed in some cases
206 if (!e_book_get_addressbooks(&tmp, gerror)) {
207 throwError(SE_HERE, "unable to access address books", gerror);
209 ESourceListCXX sources(tmp, TRANSFER_REF);
211 string id = getDatabaseID();
212 ESource *source = findSource(sources, id);
214 // might have been special "<<system>>" or "<<default>>", try that and
215 // creating address book from file:// URI before giving up
216 if (id.empty() || id == "<<system>>") {
217 m_addressbook.set( e_book_new_system_addressbook (gerror), "system address book" );
218 } else if (id.empty() || id == "<<default>>") {
219 m_addressbook.set( e_book_new_default_addressbook (gerror), "default address book" );
220 } else if (boost::starts_with(id, "file://")) {
221 m_addressbook.set(e_book_new_from_uri(id.c_str(), gerror), "creating address book");
223 throwError(SE_HERE, string(getName()) + ": no such address book: '" + id + "'");
227 m_addressbook.set( e_book_new( source, gerror ), "address book" );
230 if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) {
232 // opening newly created address books often fails, try again once more
234 if (!e_book_open(m_addressbook, onlyIfExists, gerror)) {
235 throwError(SE_HERE, "opening address book", gerror);
238 throwError(SE_HERE, "opening address book", gerror);
242 // users are not expected to configure an authentication method,
243 // so pick one automatically if the user indicated that he wants authentication
244 // by setting user or password
245 UserIdentity identity = getUser();
246 InitStateString passwd = getPassword();
247 if (identity.wasSet() || passwd.wasSet()) {
249 if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) {
250 throwError(SE_HERE, "getting authentication methods", gerror);
253 // map identity + password to plain username/password credentials
254 Credentials cred = IdentityProviderCredentials(identity, passwd);
255 const char *method = (const char *)authmethod->data;
256 SE_LOG_DEBUG(getDisplayName(), "trying authentication method \"%s\", user %s, password %s",
258 identity.wasSet() ? "configured" : "not configured",
259 passwd.wasSet() ? "configured" : "not configured");
260 if (e_book_authenticate_user(m_addressbook,
261 cred.m_username.c_str(),
262 cred.m_password.c_str(),
265 SE_LOG_DEBUG(getDisplayName(), "authentication succeeded");
268 SE_LOG_ERROR(getDisplayName(), "authentication failed: %s", gerror->message);
270 authmethod = authmethod->next;
274 g_signal_connect_after(m_addressbook,
276 G_CALLBACK(Exception::fatalError),
277 (void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
281 bool EvolutionContactSource::isEmpty()
283 // TODO: add more efficient implementation which does not
284 // depend on actually pulling all items from EDS
285 RevisionMap_t revisions;
286 listAllItems(revisions);
287 return revisions.empty();
290 #ifdef USE_EDS_CLIENT
291 class EBookClientViewSyncHandler {
293 typedef boost::function<void (const GSList *list)> Process_t;
295 EBookClientViewSyncHandler(const EBookClientViewCXX &view,
296 const Process_t &process) :
301 bool process(GErrorCXX &gerror) {
302 // Listen for view signals
303 m_view.connectSignal<void (EBookClientView *ebookview,
304 const GSList *contacts)>("objects-added",
305 boost::bind(m_process, _2));
306 m_view.connectSignal<void (EBookClientView *ebookview,
307 const GError *error)>("complete",
308 boost::bind(&EBookClientViewSyncHandler::completed, this, _2));
311 e_book_client_view_start (m_view, m_error);
313 std::swap(gerror, m_error);
319 e_book_client_view_stop (m_view, NULL);
322 std::swap(gerror, m_error);
329 void completed(const GError *error) {
335 // Event loop for Async -> Sync
336 EvolutionAsync m_loop;
339 // Process list callback
340 boost::function<void (const GSList *list)> m_process;
342 EBookClientViewCXX m_view;
343 // Possible error while watching the view
347 static void list_revisions(const GSList *contacts, EvolutionContactSource::RevisionMap_t *revisions)
351 for (l = contacts; l; l = l->next) {
352 EContact *contact = E_CONTACT(l->data);
354 SE_THROW("contact entry without data");
356 pair<string, string> revmapping;
357 const char *uid = (const char *)e_contact_get_const(contact,
359 if (!uid || !uid[0]) {
360 SE_THROW("contact entry without UID");
362 revmapping.first = uid;
363 const char *rev = (const char *)e_contact_get_const(contact,
365 if (!rev || !rev[0]) {
366 SE_THROW(string("contact entry without REV: ") + revmapping.first);
368 revmapping.second = rev;
369 revisions->insert(revmapping);
375 void EvolutionContactSource::listAllItems(RevisionMap_t &revisions)
377 #ifdef USE_EDS_CLIENT
379 EBookClientView *view;
381 EBookQueryCXX allItemsQuery(e_book_query_any_field_contains(""), TRANSFER_REF);
382 PlainGStr buffer(e_book_query_to_string (allItemsQuery.get()));
383 const char *sexp = getenv("SYNCEVOLUTION_EBOOK_QUERY");
385 SE_LOG_INFO(NULL, "restricting item set to items matching %s", sexp);
390 if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) {
391 throwError(SE_HERE, "getting the view" , gerror);
393 EBookClientViewCXX viewPtr = EBookClientViewCXX::steal(view);
395 // Optimization: set fields_of_interest (UID / REV)
396 GListCXX<const char, GSList> interesting_field_list;
397 interesting_field_list.push_back(e_contact_field_name (E_CONTACT_UID));
398 interesting_field_list.push_back(e_contact_field_name (E_CONTACT_REV));
399 e_book_client_view_set_fields_of_interest (viewPtr, interesting_field_list, gerror);
401 SE_LOG_ERROR(getDisplayName(), "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror);
405 EBookClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions));
406 if (!handler.process(gerror)) {
407 throwError(SE_HERE, "watching view", gerror);
411 eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query");
413 if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) {
414 throwError(SE_HERE, "reading all items", gerror );
416 eptr<GList> listptr(nextItem);
418 EContact *contact = E_CONTACT(nextItem->data);
420 throwError(SE_HERE, "contact entry without data");
422 pair<string, string> revmapping;
423 const char *uid = (const char *)e_contact_get_const(contact,
425 if (!uid || !uid[0]) {
426 throwError(SE_HERE, "contact entry without UID");
428 revmapping.first = uid;
429 const char *rev = (const char *)e_contact_get_const(contact,
431 if (!rev || !rev[0]) {
432 throwError(SE_HERE, string("contact entry without REV: ") + revmapping.first);
434 revmapping.second = rev;
435 revisions.insert(revmapping);
436 nextItem = nextItem->next;
441 void EvolutionContactSource::close()
443 m_addressbook.reset();
446 string EvolutionContactSource::getRevision(const string &luid)
448 if (!needChanges()) {
455 #ifdef USE_EDS_CLIENT
456 !e_book_client_get_contact_sync(m_addressbook,
462 !e_book_get_contact(m_addressbook,
468 if (IsContactNotFound(gerror)) {
469 throwError(SE_HERE, STATUS_NOT_FOUND, string("retrieving item: ") + luid);
471 throwError(SE_HERE, string("reading contact ") + luid,
475 eptr<EContact, GObject> contactptr(contact, "contact");
476 const char *rev = (const char *)e_contact_get_const(contact,
478 if (!rev || !rev[0]) {
479 throwError(SE_HERE, string("contact entry without REV: ") + luid);
484 #ifdef USE_EDS_CLIENT
485 class ContactCache : public std::map<std::string, EContactCXX>
488 /** Asynchronous method call still pending. */
490 /** The last luid requested in this query. Needed to start with the next contact after it. */
491 std::string m_lastLUID;
492 /** Result of batch read. Any error here means that the call failed completely. */
494 /** A debug logging name for this query. */
498 void EvolutionContactSource::setReadAheadOrder(ReadAheadOrder order,
499 const ReadAheadItems &luids)
501 SE_LOG_DEBUG(getDisplayName(), "reading: set order '%s', %ld luids",
502 order == READ_NONE ? "none" :
503 order == READ_ALL_ITEMS ? "all" :
504 order == READ_CHANGED_ITEMS ? "changed" :
505 order == READ_SELECTED_ITEMS ? "selected" :
508 m_readAheadOrder = order;
511 // Be conservative and throw away all cached data. Not doing so
512 // can confuse our "cache miss" counting, for example when it uses
513 // a cache where some entries have been removed in
514 // invalidateCachedContact() and then mistakes the gaps for cache
517 // Another reason is that we want to use fairly recent data (in
518 // case of concurrent changes in the DB, which currently is not
519 // detected by the cache).
520 m_contactCache.reset();
521 m_contactCacheNext.reset();
524 void EvolutionContactSource::getReadAheadOrder(ReadAheadOrder &order,
525 ReadAheadItems &luids)
527 order = m_readAheadOrder;
531 void EvolutionContactSource::checkCacheForError(boost::shared_ptr<ContactCache> &cache)
533 if (cache->m_gerror) {
535 std::swap(gerror, cache->m_gerror);
537 throwError(SE_HERE, StringPrintf("reading contacts %s", cache->m_name.c_str()), gerror);
541 void EvolutionContactSource::invalidateCachedContact(const std::string &luid)
543 invalidateCachedContact(m_contactCache, luid);
544 invalidateCachedContact(m_contactCacheNext, luid);
547 void EvolutionContactSource::invalidateCachedContact(boost::shared_ptr<ContactCache> &cache, const std::string &luid)
550 ContactCache::iterator it = cache->find(luid);
551 if (it != cache->end()) {
552 SE_LOG_DEBUG(getDisplayName(), "reading: remove contact %s from cache because of remove or update", luid.c_str());
553 // If we happen to read that contact (unlikely), it'll be
554 // considered a cache miss. That's okay. Together with
555 // counting cache misses it'll help us avoid using
556 // read-ahead when the Synthesis engine is randomly
557 // accessing contacts.
563 bool EvolutionContactSource::getContact(const string &luid, EContact **contact, GErrorCXX &gerror)
565 SE_LOG_DEBUG(getDisplayName(), "reading: getting contact %s", luid.c_str());
567 // Use switch and let compiler tell us when we don't cover a case.
569 #ifndef __clang_analyzer__
570 // scan-build would complain: value stored to 'order' during
571 // its initialization is never read. But we need to keep it
572 // otherwise, to avoid: 'order' may be used uninitialized in
573 // this function [-Werror=maybe-uninitialized]
577 switch (m_accessMode) {
583 order = m_readAheadOrder;
588 if (order == READ_NONE) {
591 return e_book_client_get_contact_sync(m_addressbook,
597 return getContactFromCache(luid, contact, gerror);
601 bool EvolutionContactSource::getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror)
606 if (m_contactCache) {
607 SE_LOG_DEBUG(getDisplayName(), "reading: active cache %s", m_contactCache->m_name.c_str());
608 // Ran into a problem?
609 checkCacheForError(m_contactCache);
611 // Does the cache cover our item?
612 ContactCache::const_iterator it = m_contactCache->find(luid);
613 if (it == m_contactCache->end()) {
614 if (m_contactCacheNext) {
615 SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, try cache %s",
616 m_contactCacheNext->m_name.c_str());
617 // Throw away old cache, try with next one. This is not
618 // a cache miss (yet).
619 m_contactCache = m_contactCacheNext;
620 m_contactCacheNext.reset();
621 return getContactFromCache(luid, contact, gerror);
623 SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, nothing pending -> start reading");
624 // Throw away cache, start new read below.
625 m_contactCache.reset();
628 SE_LOG_DEBUG(getDisplayName(), "reading: in %s cache", m_contactCache->m_running ? "running" : "loaded");
629 if (m_contactCache->m_running) {
631 GRunWhile(boost::lambda::var(m_contactCache->m_running));
634 checkCacheForError(m_contactCache);
636 SE_LOG_DEBUG(getDisplayName(), "reading: in cache, %s", it->second ? "available" : "not found");
639 *contact = it->second.ref();
641 // Delay throwing error. We need to go through the read-ahead code below.
642 gerror.take(g_error_new(E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
643 "uid %s not found in batch read", luid.c_str()));
648 // No current cache? In that case we must read and block.
649 if (!m_contactCache) {
650 m_contactCache = startReading(luid, START);
651 // Call code above recursively, which will block.
652 return getContactFromCache(luid, contact, gerror);
655 // Can we read ahead?
656 if (!m_contactCacheNext && !m_contactCache->m_running) {
657 m_contactCacheNext = startReading(m_contactCache->m_lastLUID, CONTINUE);
660 // Everything is okay when we get here. Either we have the contact or
661 // it wasn't in the database.
662 SE_LOG_DEBUG(getDisplayName(), "reading: read %s: %s", luid.c_str(), gerror ? gerror->message : "<<okay>>");
663 logCacheStats(Logger::DEBUG);
667 static int MaxBatchSize()
669 int maxBatchSize = atoi(getEnv("SYNCEVOLUTION_EDS_BATCH_SIZE", "50"));
670 if (maxBatchSize < 1) {
676 boost::shared_ptr<ContactCache> EvolutionContactSource::startReading(const std::string &luid, ReadingMode mode)
678 SE_LOG_DEBUG(getDisplayName(), "reading: %s contact %s",
679 mode == START ? "must read" :
680 mode == CONTINUE ? "continue after" :
684 static int maxBatchSize = MaxBatchSize();
685 std::vector<EBookQueryCXX> uidQueries;
686 uidQueries.resize(maxBatchSize);
687 std::vector<const std::string *> uids;
688 uids.resize(maxBatchSize);
692 switch (m_readAheadOrder) {
694 case READ_CHANGED_ITEMS: {
695 const Items_t &items = getAllItems();
696 const Items_t &newItems = getNewItems();
697 const Items_t &updatedItems = getUpdatedItems();
698 Items_t::const_iterator it = items.find(luid);
700 // Always read the requested item, even if not found in item list?
705 // luid is dealt with, either way.
706 if (it != items.end()) {
707 // Check that it is a valid candidate for caching, else
708 // we have a cache miss prediction.
709 if (m_readAheadOrder == READ_ALL_ITEMS ||
710 newItems.find(luid) != newItems.end() ||
711 updatedItems.find(luid) != updatedItems.end()) {
716 while (size < maxBatchSize &&
718 const std::string &luid = *it;
719 if (m_readAheadOrder == READ_ALL_ITEMS ||
720 newItems.find(luid) != newItems.end() ||
721 updatedItems.find(luid) != updatedItems.end()) {
729 case READ_SELECTED_ITEMS: {
730 ReadAheadItems::const_iterator it = boost::find(std::make_pair(m_nextLUIDs.begin(), m_nextLUIDs.end()), luid);
731 // Always read the requested item, even if not found in item list?
736 // luid is dealt with, either way.
737 if (it != m_nextLUIDs.end()) {
741 while (size < maxBatchSize &&
742 it != m_nextLUIDs.end()) {
750 // May be reached when read-ahead was turned off while
759 if (m_readAheadOrder != READ_NONE &&
762 // The requested contact was not on our list. Consider this
763 // a cache miss (or rather, cache prediction failure) and turn
764 // of the read-ahead.
766 SE_LOG_DEBUG(getDisplayName(), "reading: disable read-ahead due to cache miss");
767 m_readAheadOrder = READ_NONE;
770 boost::shared_ptr<ContactCache> cache;
772 // Prepare parameter for EDS C call. Ownership of query instances is in uidQueries array.
773 boost::scoped_array<EBookQuery *> queries(new EBookQuery *[size]);
774 for (int i = 0; i < size; i++) {
775 // This shouldn't compile because we don't specify how ownership is handled.
776 // The reset() method always bumps the ref count, which is not what we want here!
777 // uidQueries[i].reset(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, it->c_str()));
779 // Take over ownership.
780 uidQueries[i] = EBookQueryCXX::steal(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, uids[i]->c_str()));
781 queries[i] = uidQueries[i].get();
783 EBookQueryCXX query(e_book_query_or(size, queries.get(), false), TRANSFER_REF);
784 PlainGStr sexp(e_book_query_to_string(query.get()));
786 cache.reset(new ContactCache);
787 cache->m_running = true;
788 cache->m_name = StringPrintf("%s-%s (%d)", uids[0]->c_str(), uids[size - 1]->c_str(), size);
789 cache->m_lastLUID = *uids[size - 1];
790 BOOST_FOREACH (const std::string *uid, std::make_pair(uids.begin(), uids.begin() + size)) {
791 (*cache)[*uid] = EContactCXX();
793 m_contactsFromDB += size;
795 SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts,
796 boost::bind(&EvolutionContactSource::completedRead,
798 boost::weak_ptr<ContactCache>(cache),
800 m_addressbook, sexp, NULL);
801 SE_LOG_DEBUG(getDisplayName(), "reading: started contact read %s", cache->m_name.c_str());
806 typedef GListCXX< EContact, GSList, GObjectDestructor<EContact> > ContactListCXX;
808 void EvolutionContactSource::completedRead(const boost::weak_ptr<ContactCache> &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw()
811 ContactListCXX contacts(contactsPtr); // transfers ownership
812 boost::shared_ptr<ContactCache> cache = cachePtr.lock();
814 SE_LOG_DEBUG(getDisplayName(), "reading: contact read finished, results no longer needed: %s", gerror ? gerror->message : "<<successful>>");
818 SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s finished: %s",
819 cache->m_name.c_str(),
820 gerror ? gerror->message : "<<successful>>");
822 BOOST_FOREACH (EContact *contact, contacts) {
823 const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
824 SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s got %s", cache->m_name.c_str(), uid);
825 (*cache)[uid] = EContactCXX(contact, ADD_REF);
828 cache->m_gerror = gerror;
830 cache->m_running = false;
832 Exception::handle(HANDLE_EXCEPTION_FATAL);
836 void EvolutionContactSource::logCacheStats(Logger::Level level)
838 SE_LOG(getDisplayName(), level,
839 "requested %d, retrieved %d from DB in %d queries, misses %d/%d (%d%%), stalls %d",
843 m_cacheMisses, m_contactReads, m_contactReads ? m_cacheMisses * 100 / m_contactReads : 0,
849 void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw)
854 #ifdef USE_EDS_CLIENT
855 !getContact(luid, &contact, gerror)
857 !e_book_get_contact(m_addressbook,
863 if (IsContactNotFound(gerror)) {
864 throwError(SE_HERE, STATUS_NOT_FOUND, string("reading contact: ") + luid);
866 throwError(SE_HERE, string("reading contact ") + luid,
871 eptr<EContact, GObject> contactptr(contact, "contact");
873 // Inline PHOTO data if exporting, leave VALUE=uri references unchanged
874 // when processing inside engine (will be inlined by engine as needed).
875 // The function for doing the inlining was added in EDS 3.4.
876 // In compatibility mode, we must check the function pointer for non-NULL.
877 // In direct call mode, the existence check is done by configure.
879 #ifdef EVOLUTION_COMPATIBILITY
880 && e_contact_inline_local_photos
883 #if defined(EVOLUTION_COMPATIBILITY) || defined(HAVE_E_CONTACT_INLINE_LOCAL_PHOTOS)
884 if (!e_contact_inline_local_photos(contactptr, gerror)) {
885 throwError(SE_HERE, string("inlining PHOTO file data in ") + luid, gerror);
890 eptr<char> vcardstr(e_vcard_to_string(&contactptr->parent,
891 EVC_FORMAT_VCARD_30));
893 throwError(SE_HERE, string("failure extracting contact from Evolution " ) + luid);
896 item = vcardstr.get();
899 #ifdef USE_EDS_CLIENT
900 TrackingSyncSource::InsertItemResult EvolutionContactSource::checkBatchedInsert(const boost::shared_ptr<Pending> &pending)
902 SE_LOG_DEBUG(pending->m_name, "checking operation: %s", pending->m_status == MODIFYING ? "waiting" : "inserted");
903 if (pending->m_status == MODIFYING) {
904 return TrackingSyncSource::InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending));
906 if (pending->m_gerror) {
907 pending->m_gerror.throwError(SE_HERE, pending->m_name);
909 string newrev = getRevision(pending->m_uid);
910 return TrackingSyncSource::InsertItemResult(pending->m_uid, newrev, ITEM_OKAY);
913 void EvolutionContactSource::completedAdd(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, GSList *uids, const GError *gerror) throw()
916 // The destructor ensures that the pending operations complete
917 // before destructing the instance, so our "this" pointer is
918 // always valid here.
919 SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts completed", (int)batched->size());
920 m_numRunningOperations--;
921 PendingContainer_t::iterator it = (*batched).begin();
923 while (it != (*batched).end() && uid) {
924 SE_LOG_DEBUG((*it)->m_name, "completed: %s",
925 success ? "<<successfully>>" :
926 gerror ? gerror->message :
927 "<<unknown failure>>");
929 (*it)->m_uid = static_cast<gchar *>(uid->data);
930 // Get revision when engine checks the item.
931 (*it)->m_status = REVISION;
933 (*it)->m_status = DONE;
934 (*it)->m_gerror = gerror;
940 while (it != (*batched).end()) {
941 // Should never happen.
942 SE_LOG_DEBUG((*it)->m_name, "completed: missing uid?!");
943 (*it)->m_status = DONE;
947 g_slist_free_full(uids, g_free);
949 Exception::handle(HANDLE_EXCEPTION_FATAL);
953 void EvolutionContactSource::completedUpdate(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, const GError *gerror) throw()
956 SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts completed", (int)batched->size());
957 m_numRunningOperations--;
958 PendingContainer_t::iterator it = (*batched).begin();
959 while (it != (*batched).end()) {
960 SE_LOG_DEBUG((*it)->m_name, "completed: %s",
961 success ? "<<successfully>>" :
962 gerror ? gerror->message :
963 "<<unknown failure>>");
965 (*it)->m_status = REVISION;
967 (*it)->m_status = DONE;
968 (*it)->m_gerror = gerror;
973 Exception::handle(HANDLE_EXCEPTION_FATAL);
977 void EvolutionContactSource::flushItemChanges()
979 if (!m_batchedAdd.empty()) {
980 SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts starting", (int)m_batchedAdd.size());
981 m_numRunningOperations++;
982 GListCXX<EContact, GSList> contacts;
983 // Iterate backwards, push to front (cheaper for single-linked list) -> same order in the end.
984 BOOST_REVERSE_FOREACH (const boost::shared_ptr<Pending> &pending, m_batchedAdd) {
985 contacts.push_front(pending->m_contact.get());
987 // Transfer content without copying and then copy only the shared pointer.
988 boost::shared_ptr<PendingContainer_t> batched(new PendingContainer_t);
989 std::swap(*batched, m_batchedAdd);
990 SYNCEVO_GLIB_CALL_ASYNC(e_book_client_add_contacts,
991 boost::bind(&EvolutionContactSource::completedAdd,
995 m_addressbook, contacts, NULL);
997 if (!m_batchedUpdate.empty()) {
998 SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts starting", (int)m_batchedUpdate.size());
999 m_numRunningOperations++;
1000 GListCXX<EContact, GSList> contacts;
1001 BOOST_REVERSE_FOREACH (const boost::shared_ptr<Pending> &pending, m_batchedUpdate) {
1002 contacts.push_front(pending->m_contact.get());
1004 boost::shared_ptr<PendingContainer_t> batched(new PendingContainer_t);
1005 std::swap(*batched, m_batchedUpdate);
1006 SYNCEVO_GLIB_CALL_ASYNC(e_book_client_modify_contacts,
1007 boost::bind(&EvolutionContactSource::completedUpdate,
1011 m_addressbook, contacts, NULL);
1015 void EvolutionContactSource::finishItemChanges()
1017 if (m_numRunningOperations) {
1018 SE_LOG_DEBUG(getDisplayName(), "waiting for %d pending operations to complete", m_numRunningOperations.get());
1019 while (m_numRunningOperations) {
1020 g_main_context_iteration(NULL, true);
1022 SE_LOG_DEBUG(getDisplayName(), "pending operations completed");
1028 TrackingSyncSource::InsertItemResult
1029 EvolutionContactSource::insertItem(const string &uid, const std::string &item, bool raw)
1031 EContactCXX contact(e_contact_new_from_vcard(item.c_str()), TRANSFER_REF);
1033 e_contact_set(contact, E_CONTACT_UID,
1036 const_cast<char *>(uid.c_str()));
1038 #ifdef USE_EDS_CLIENT
1039 invalidateCachedContact(uid);
1040 switch (m_accessMode) {
1044 if (!e_book_client_add_contact_sync(m_addressbook, contact, &newuid, NULL, gerror)) {
1045 throwError(SE_HERE, "add new contact", gerror);
1047 PlainGStr newuidPtr(newuid);
1048 string newrev = getRevision(newuid);
1049 return InsertItemResult(newuid, newrev, ITEM_OKAY);
1051 if (!e_book_client_modify_contact_sync(m_addressbook, contact, NULL, gerror)) {
1052 throwError(SE_HERE, "updating contact "+ uid, gerror);
1054 string newrev = getRevision(uid);
1055 return InsertItemResult(uid, newrev, ITEM_OKAY);
1060 std::string name = StringPrintf("%s: %s operation #%d",
1061 getDisplayName().c_str(),
1062 uid.empty() ? "add" : ("insert " + uid).c_str(),
1063 m_asyncOpCounter++);
1064 SE_LOG_DEBUG(name, "queueing for batched %s", uid.empty() ? "add" : "update");
1065 boost::shared_ptr<Pending> pending(new Pending);
1066 pending->m_name = name;
1067 pending->m_contact = contact;
1068 pending->m_uid = uid;
1070 m_batchedAdd.push_back(pending);
1072 m_batchedUpdate.push_back(pending);
1074 // SyncSource is going to live longer than Synthesis
1075 // engine, so using "this" is safe here.
1076 return InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending));
1081 e_book_add_contact(m_addressbook, contact, gerror) :
1082 e_book_commit_contact(m_addressbook, contact, gerror)) {
1083 const char *newuid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
1085 throwError(SE_HERE, "no UID for contact");
1087 string newrev = getRevision(newuid);
1088 return InsertItemResult(newuid, newrev, ITEM_OKAY);
1090 throwError(SE_HERE, uid.empty() ?
1091 "storing new contact" :
1092 string("updating contact ") + uid,
1097 throwError(SE_HERE, string("failure parsing vcard " ) + item);
1100 return InsertItemResult("", "", ITEM_OKAY);
1103 void EvolutionContactSource::removeItem(const string &uid)
1107 #ifdef USE_EDS_CLIENT
1108 (invalidateCachedContact(uid),
1109 !e_book_client_remove_contact_by_uid_sync(m_addressbook, uid.c_str(), NULL, gerror))
1111 !e_book_remove_contact(m_addressbook, uid.c_str(), gerror)
1114 if (IsContactNotFound(gerror)) {
1115 throwError(SE_HERE, STATUS_NOT_FOUND, string("deleting contact: ") + uid);
1117 throwError(SE_HERE, string( "deleting contact " ) + uid,
1123 std::string EvolutionContactSource::getDescription(const string &luid)
1129 #ifdef USE_EDS_CLIENT
1134 !e_book_get_contact(m_addressbook,
1140 throwError(SE_HERE, string("reading contact ") + luid,
1143 eptr<EContact, GObject> contactptr(contact, "contact");
1144 const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME);
1148 const char *fileas = (const char *)e_contact_get_const(contact, E_CONTACT_FILE_AS);
1152 EContactName *names =
1153 (EContactName *)e_contact_get(contact, E_CONTACT_NAME);
1154 std::list<std::string> buffer;
1157 if (names->given && names->given[0]) {
1158 buffer.push_back(names->given);
1160 if (names->additional && names->additional[0]) {
1161 buffer.push_back(names->additional);
1163 if (names->family && names->family[0]) {
1164 buffer.push_back(names->family);
1168 e_contact_name_free(names);
1170 return boost::join(buffer, " ");
1172 // Instead of failing we log the error and ask
1173 // the caller to log the UID. That way transient
1174 // errors or errors in the logging code don't
1181 std::string EvolutionContactSource::getMimeType() const
1183 switch( m_vcardFormat ) {
1184 case EVC_FORMAT_VCARD_21:
1185 return "text/x-vcard";
1187 case EVC_FORMAT_VCARD_30:
1189 return "text/vcard";
1194 std::string EvolutionContactSource::getMimeVersion() const
1196 switch( m_vcardFormat ) {
1197 case EVC_FORMAT_VCARD_21:
1200 case EVC_FORMAT_VCARD_30:
1209 #endif /* ENABLE_EBOOK */
1211 #ifdef ENABLE_MODULES
1212 # include "EvolutionContactSourceRegister.cpp"