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 <syncevo/SyncContext.h>
34 #include "EvolutionContactSource.h"
35 #include <syncevo/util.h>
37 #include <syncevo/Logging.h>
40 #include <boost/range/algorithm/find.hpp>
42 #include <boost/algorithm/string.hpp>
43 #include <boost/algorithm/string/join.hpp>
44 #include <boost/algorithm/string/predicate.hpp>
45 #include <boost/foreach.hpp>
46 #include <boost/lambda/lambda.hpp>
48 #include <syncevo/declarations.h>
50 SE_GLIB_TYPE(EBookQuery, e_book_query)
54 inline bool IsContactNotFound(const GError *gerror) {
57 gerror->domain == E_BOOK_CLIENT_ERROR &&
58 gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND
60 gerror->domain == E_BOOK_ERROR &&
61 gerror->code == E_BOOK_ERROR_CONTACT_NOT_FOUND
67 const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions;
68 const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties;
70 EvolutionContactSource::EvolutionContactSource(const SyncSourceParams ¶ms,
71 EVCardFormat vcardFormat) :
72 EvolutionSyncSource(params),
73 m_vcardFormat(vcardFormat)
81 m_readAheadOrder = READ_NONE;
83 SyncSourceLogging::init(InitList<std::string>("N_FIRST") + "N_MIDDLE" + "N_LAST",
88 EvolutionContactSource::~EvolutionContactSource()
90 // Don't close while we have pending operations. They might
91 // complete after we got destroyed, causing them to use an invalid
92 // "this" pointer. We also don't know how well EDS copes with
93 // closing the address book while it has pending operations - EDS
94 // maintainer mcrha wasn't sure.
96 // TODO: cancel the operations().
100 #ifdef USE_EDS_CLIENT
101 // logCacheStats(Logger::DEBUG);
105 #ifdef USE_EDS_CLIENT
106 static EClient *newEBookClient(ESource *source,
109 return E_CLIENT(e_book_client_new(source, gerror));
113 EvolutionSyncSource::Databases EvolutionContactSource::getDatabases()
116 #ifdef USE_EDS_CLIENT
117 getDatabasesFromRegistry(result,
118 E_SOURCE_EXTENSION_ADDRESS_BOOK,
119 e_source_registry_ref_default_address_book);
121 ESourceList *sources = NULL;
122 if (!e_book_get_addressbooks(&sources, NULL)) {
123 SyncContext::throwError("unable to access address books");
127 for (GSList *g = e_source_list_peek_groups (sources); g; g = g->next) {
128 ESourceGroup *group = E_SOURCE_GROUP (g->data);
129 for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) {
130 ESource *source = E_SOURCE (s->data);
131 eptr<char> uri(e_source_get_uri(source));
136 Database entry(e_source_peek_name(source),
139 if (boost::starts_with(uristr, "couchdb://")) {
140 // Append CouchDB address books at the end of the list,
141 // otherwise preserving the order of address books.
143 // The reason is Moblin Bugzilla #7877 (aka CouchDB
144 // feature request #479110): the initial release of
145 // evolution-couchdb in Ubuntu 9.10 is unusable because
146 // it does not support the REV property.
148 // Reordering the entries ensures that the CouchDB
149 // address book is not used as the default database by
150 // SyncEvolution, as it happened in Ubuntu 9.10.
151 // Users can still pick it intentionally via
152 // "evolutionsource".
153 secondary.push_back(entry);
155 result.push_back(entry);
159 result.insert(result.end(), secondary.begin(), secondary.end());
161 // No results? Try system address book (workaround for embedded Evolution Dataserver).
162 if (!result.size()) {
163 eptr<EBook, GObject> book;
168 book = e_book_new_system_addressbook (gerror);
171 name = "<<default>>";
172 book = e_book_new_default_addressbook (gerror);
176 const char *uri = e_book_get_uri (book);
177 result.push_back(Database(name, uri, true));
180 // the first DB found is the default
181 result[0].m_isDefault = true;
188 void EvolutionContactSource::open()
190 #ifdef USE_EDS_CLIENT
191 m_addressbook.reset(E_BOOK_CLIENT(openESource(E_SOURCE_EXTENSION_ADDRESS_BOOK,
192 e_source_registry_ref_builtin_address_book,
193 newEBookClient).get()));
194 const char *mode = getEnv("SYNCEVOLUTION_EDS_ACCESS_MODE", "");
195 m_accessMode = boost::iequals(mode, "synchronous") ? SYNCHRONOUS :
196 boost::iequals(mode, "batched") ? BATCHED :
200 bool created = false;
201 bool onlyIfExists = false; // always try to create address book, because even if there is
202 // a source there's no guarantee that the actual database was
203 // created already; the original logic below for only setting
204 // this when explicitly requesting a new address book
205 // therefore failed in some cases
207 if (!e_book_get_addressbooks(&tmp, gerror)) {
208 throwError("unable to access address books", gerror);
210 ESourceListCXX sources(tmp, TRANSFER_REF);
212 string id = getDatabaseID();
213 ESource *source = findSource(sources, id);
215 // might have been special "<<system>>" or "<<default>>", try that and
216 // creating address book from file:// URI before giving up
217 if (id.empty() || id == "<<system>>") {
218 m_addressbook.set( e_book_new_system_addressbook (gerror), "system address book" );
219 } else if (id.empty() || id == "<<default>>") {
220 m_addressbook.set( e_book_new_default_addressbook (gerror), "default address book" );
221 } else if (boost::starts_with(id, "file://")) {
222 m_addressbook.set(e_book_new_from_uri(id.c_str(), gerror), "creating address book");
224 throwError(string(getName()) + ": no such address book: '" + id + "'");
228 m_addressbook.set( e_book_new( source, gerror ), "address book" );
231 if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) {
233 // opening newly created address books often fails, try again once more
235 if (!e_book_open(m_addressbook, onlyIfExists, gerror)) {
236 throwError("opening address book", gerror);
239 throwError("opening address book", gerror);
243 // users are not expected to configure an authentication method,
244 // so pick one automatically if the user indicated that he wants authentication
245 // by setting user or password
246 UserIdentity identity = getUser();
247 InitStateString passwd = getPassword();
248 if (identity.wasSet() || passwd.wasSet()) {
250 if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) {
251 throwError("getting authentication methods", gerror);
254 // map identity + password to plain username/password credentials
255 Credentials cred = IdentityProviderCredentials(identity, passwd);
256 const char *method = (const char *)authmethod->data;
257 SE_LOG_DEBUG(getDisplayName(), "trying authentication method \"%s\", user %s, password %s",
259 identity.wasSet() ? "configured" : "not configured",
260 passwd.wasSet() ? "configured" : "not configured");
261 if (e_book_authenticate_user(m_addressbook,
262 cred.m_username.c_str(),
263 cred.m_password.c_str(),
266 SE_LOG_DEBUG(getDisplayName(), "authentication succeeded");
269 SE_LOG_ERROR(getDisplayName(), "authentication failed: %s", gerror->message);
271 authmethod = authmethod->next;
275 g_signal_connect_after(m_addressbook,
277 G_CALLBACK(SyncContext::fatalError),
278 (void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
282 bool EvolutionContactSource::isEmpty()
284 // TODO: add more efficient implementation which does not
285 // depend on actually pulling all items from EDS
286 RevisionMap_t revisions;
287 listAllItems(revisions);
288 return revisions.empty();
291 #ifdef USE_EDS_CLIENT
292 class EBookClientViewSyncHandler {
294 typedef boost::function<void (const GSList *list)> Process_t;
296 EBookClientViewSyncHandler(const EBookClientViewCXX &view,
297 const Process_t &process) :
302 bool process(GErrorCXX &gerror) {
303 // Listen for view signals
304 m_view.connectSignal<void (EBookClientView *ebookview,
305 const GSList *contacts)>("objects-added",
306 boost::bind(m_process, _2));
307 m_view.connectSignal<void (EBookClientView *ebookview,
308 const GError *error)>("complete",
309 boost::bind(&EBookClientViewSyncHandler::completed, this, _2));
312 e_book_client_view_start (m_view, m_error);
314 std::swap(gerror, m_error);
320 e_book_client_view_stop (m_view, NULL);
323 std::swap(gerror, m_error);
330 void completed(const GError *error) {
336 // Event loop for Async -> Sync
337 EvolutionAsync m_loop;
340 // Process list callback
341 boost::function<void (const GSList *list)> m_process;
343 EBookClientViewCXX m_view;
344 // Possible error while watching the view
348 static void list_revisions(const GSList *contacts, EvolutionContactSource::RevisionMap_t *revisions)
352 for (l = contacts; l; l = l->next) {
353 EContact *contact = E_CONTACT(l->data);
355 SE_THROW("contact entry without data");
357 pair<string, string> revmapping;
358 const char *uid = (const char *)e_contact_get_const(contact,
360 if (!uid || !uid[0]) {
361 SE_THROW("contact entry without UID");
363 revmapping.first = uid;
364 const char *rev = (const char *)e_contact_get_const(contact,
366 if (!rev || !rev[0]) {
367 SE_THROW(string("contact entry without REV: ") + revmapping.first);
369 revmapping.second = rev;
370 revisions->insert(revmapping);
376 void EvolutionContactSource::listAllItems(RevisionMap_t &revisions)
378 #ifdef USE_EDS_CLIENT
380 EBookClientView *view;
382 EBookQueryCXX allItemsQuery(e_book_query_any_field_contains(""), TRANSFER_REF);
383 PlainGStr buffer(e_book_query_to_string (allItemsQuery.get()));
384 const char *sexp = getenv("SYNCEVOLUTION_EBOOK_QUERY");
386 SE_LOG_INFO(NULL, "restricting item set to items matching %s", sexp);
391 if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) {
392 throwError( "getting the view" , gerror);
394 EBookClientViewCXX viewPtr = EBookClientViewCXX::steal(view);
396 // Optimization: set fields_of_interest (UID / REV)
397 GListCXX<const char, GSList> interesting_field_list;
398 interesting_field_list.push_back(e_contact_field_name (E_CONTACT_UID));
399 interesting_field_list.push_back(e_contact_field_name (E_CONTACT_REV));
400 e_book_client_view_set_fields_of_interest (viewPtr, interesting_field_list, gerror);
402 SE_LOG_ERROR(getDisplayName(), "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror);
406 EBookClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions));
407 if (!handler.process(gerror)) {
408 throwError("watching view", gerror);
412 eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query");
414 if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) {
415 throwError( "reading all items", gerror );
417 eptr<GList> listptr(nextItem);
419 EContact *contact = E_CONTACT(nextItem->data);
421 throwError("contact entry without data");
423 pair<string, string> revmapping;
424 const char *uid = (const char *)e_contact_get_const(contact,
426 if (!uid || !uid[0]) {
427 throwError("contact entry without UID");
429 revmapping.first = uid;
430 const char *rev = (const char *)e_contact_get_const(contact,
432 if (!rev || !rev[0]) {
433 throwError(string("contact entry without REV: ") + revmapping.first);
435 revmapping.second = rev;
436 revisions.insert(revmapping);
437 nextItem = nextItem->next;
442 void EvolutionContactSource::close()
444 m_addressbook.reset();
447 string EvolutionContactSource::getRevision(const string &luid)
449 if (!needChanges()) {
456 #ifdef USE_EDS_CLIENT
457 !e_book_client_get_contact_sync(m_addressbook,
463 !e_book_get_contact(m_addressbook,
469 if (IsContactNotFound(gerror)) {
470 throwError(STATUS_NOT_FOUND, string("retrieving item: ") + luid);
472 throwError(string("reading contact ") + luid,
476 eptr<EContact, GObject> contactptr(contact, "contact");
477 const char *rev = (const char *)e_contact_get_const(contact,
479 if (!rev || !rev[0]) {
480 throwError(string("contact entry without REV: ") + luid);
485 #ifdef USE_EDS_CLIENT
486 class ContactCache : public std::map<std::string, EContactCXX>
489 /** Asynchronous method call still pending. */
491 /** The last luid requested in this query. Needed to start with the next contact after it. */
492 std::string m_lastLUID;
493 /** Result of batch read. Any error here means that the call failed completely. */
495 /** A debug logging name for this query. */
499 void EvolutionContactSource::setReadAheadOrder(ReadAheadOrder order,
500 const ReadAheadItems &luids)
502 SE_LOG_DEBUG(getDisplayName(), "reading: set order '%s', %ld luids",
503 order == READ_NONE ? "none" :
504 order == READ_ALL_ITEMS ? "all" :
505 order == READ_CHANGED_ITEMS ? "changed" :
506 order == READ_SELECTED_ITEMS ? "selected" :
509 m_readAheadOrder = order;
512 // Be conservative and throw away all cached data. Not doing so
513 // can confuse our "cache miss" counting, for example when it uses
514 // a cache where some entries have been removed in
515 // invalidateCachedContact() and then mistakes the gaps for cache
518 // Another reason is that we want to use fairly recent data (in
519 // case of concurrent changes in the DB, which currently is not
520 // detected by the cache).
521 m_contactCache.reset();
522 m_contactCacheNext.reset();
525 void EvolutionContactSource::getReadAheadOrder(ReadAheadOrder &order,
526 ReadAheadItems &luids)
528 order = m_readAheadOrder;
532 void EvolutionContactSource::checkCacheForError(boost::shared_ptr<ContactCache> &cache)
534 if (cache->m_gerror) {
536 std::swap(gerror, cache->m_gerror);
538 throwError(StringPrintf("reading contacts %s", cache->m_name.c_str()), gerror);
542 void EvolutionContactSource::invalidateCachedContact(const std::string &luid)
544 invalidateCachedContact(m_contactCache, luid);
545 invalidateCachedContact(m_contactCacheNext, luid);
548 void EvolutionContactSource::invalidateCachedContact(boost::shared_ptr<ContactCache> &cache, const std::string &luid)
551 ContactCache::iterator it = cache->find(luid);
552 if (it != cache->end()) {
553 SE_LOG_DEBUG(getDisplayName(), "reading: remove contact %s from cache because of remove or update", luid.c_str());
554 // If we happen to read that contact (unlikely), it'll be
555 // considered a cache miss. That's okay. Together with
556 // counting cache misses it'll help us avoid using
557 // read-ahead when the Synthesis engine is randomly
558 // accessing contacts.
564 bool EvolutionContactSource::getContact(const string &luid, EContact **contact, GErrorCXX &gerror)
566 SE_LOG_DEBUG(getDisplayName(), "reading: getting contact %s", luid.c_str());
567 ReadAheadOrder order = m_readAheadOrder;
569 // Use switch and let compiler tell us when we don't cover a case.
570 switch (m_accessMode) {
576 order = m_readAheadOrder;
581 if (order == READ_NONE) {
584 return e_book_client_get_contact_sync(m_addressbook,
590 return getContactFromCache(luid, contact, gerror);
594 bool EvolutionContactSource::getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror)
599 if (m_contactCache) {
600 SE_LOG_DEBUG(getDisplayName(), "reading: active cache %s", m_contactCache->m_name.c_str());
601 // Ran into a problem?
602 checkCacheForError(m_contactCache);
604 // Does the cache cover our item?
605 ContactCache::const_iterator it = m_contactCache->find(luid);
606 if (it == m_contactCache->end()) {
607 if (m_contactCacheNext) {
608 SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, try cache %s",
609 m_contactCacheNext->m_name.c_str());
610 // Throw away old cache, try with next one. This is not
611 // a cache miss (yet).
612 m_contactCache = m_contactCacheNext;
613 m_contactCacheNext.reset();
614 return getContactFromCache(luid, contact, gerror);
616 SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, nothing pending -> start reading");
617 // Throw away cache, start new read below.
618 m_contactCache.reset();
621 SE_LOG_DEBUG(getDisplayName(), "reading: in %s cache", m_contactCache->m_running ? "running" : "loaded");
622 if (m_contactCache->m_running) {
624 GRunWhile(boost::lambda::var(m_contactCache->m_running));
627 checkCacheForError(m_contactCache);
629 SE_LOG_DEBUG(getDisplayName(), "reading: in cache, %s", it->second ? "available" : "not found");
632 *contact = it->second.ref();
634 // Delay throwing error. We need to go through the read-ahead code below.
635 gerror.take(g_error_new(E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
636 "uid %s not found in batch read", luid.c_str()));
641 // No current cache? In that case we must read and block.
642 if (!m_contactCache) {
643 m_contactCache = startReading(luid, START);
644 // Call code above recursively, which will block.
645 return getContactFromCache(luid, contact, gerror);
648 // Can we read ahead?
649 if (!m_contactCacheNext && !m_contactCache->m_running) {
650 m_contactCacheNext = startReading(m_contactCache->m_lastLUID, CONTINUE);
653 // Everything is okay when we get here. Either we have the contact or
654 // it wasn't in the database.
655 SE_LOG_DEBUG(getDisplayName(), "reading: read %s: %s", luid.c_str(), gerror ? gerror->message : "<<okay>>");
656 logCacheStats(Logger::DEBUG);
660 static int MaxBatchSize()
662 int maxBatchSize = atoi(getEnv("SYNCEVOLUTION_EDS_BATCH_SIZE", "50"));
663 if (maxBatchSize < 1) {
669 boost::shared_ptr<ContactCache> EvolutionContactSource::startReading(const std::string &luid, ReadingMode mode)
671 SE_LOG_DEBUG(getDisplayName(), "reading: %s contact %s",
672 mode == START ? "must read" :
673 mode == CONTINUE ? "continue after" :
677 static int maxBatchSize = MaxBatchSize();
678 std::vector<EBookQueryCXX> uidQueries;
679 uidQueries.resize(maxBatchSize);
680 std::vector<const std::string *> uids;
681 uids.resize(maxBatchSize);
685 switch (m_readAheadOrder) {
687 case READ_CHANGED_ITEMS: {
688 const Items_t &items = getAllItems();
689 const Items_t &newItems = getNewItems();
690 const Items_t &updatedItems = getUpdatedItems();
691 Items_t::const_iterator it = items.find(luid);
693 // Always read the requested item, even if not found in item list?
698 // luid is dealt with, either way.
699 if (it != items.end()) {
700 // Check that it is a valid candidate for caching, else
701 // we have a cache miss prediction.
702 if (m_readAheadOrder == READ_ALL_ITEMS ||
703 newItems.find(luid) != newItems.end() ||
704 updatedItems.find(luid) != updatedItems.end()) {
709 while (size < maxBatchSize &&
711 const std::string &luid = *it;
712 if (m_readAheadOrder == READ_ALL_ITEMS ||
713 newItems.find(luid) != newItems.end() ||
714 updatedItems.find(luid) != updatedItems.end()) {
722 case READ_SELECTED_ITEMS: {
723 ReadAheadItems::const_iterator it = boost::find(std::make_pair(m_nextLUIDs.begin(), m_nextLUIDs.end()), luid);
724 // Always read the requested item, even if not found in item list?
729 // luid is dealt with, either way.
730 if (it != m_nextLUIDs.end()) {
734 while (size < maxBatchSize &&
735 it != m_nextLUIDs.end()) {
743 // May be reached when read-ahead was turned off while
752 if (m_readAheadOrder != READ_NONE &&
755 // The requested contact was not on our list. Consider this
756 // a cache miss (or rather, cache prediction failure) and turn
757 // of the read-ahead.
759 SE_LOG_DEBUG(getDisplayName(), "reading: disable read-ahead due to cache miss");
760 m_readAheadOrder = READ_NONE;
763 boost::shared_ptr<ContactCache> cache;
765 // Prepare parameter for EDS C call. Ownership of query instances is in uidQueries array.
766 boost::scoped_array<EBookQuery *> queries(new EBookQuery *[size]);
767 for (int i = 0; i < size; i++) {
768 // This shouldn't compile because we don't specify how ownership is handled.
769 // The reset() method always bumps the ref count, which is not what we want here!
770 // uidQueries[i].reset(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, it->c_str()));
772 // Take over ownership.
773 uidQueries[i] = EBookQueryCXX::steal(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, uids[i]->c_str()));
774 queries[i] = uidQueries[i].get();
776 EBookQueryCXX query(e_book_query_or(size, queries.get(), false), TRANSFER_REF);
777 PlainGStr sexp(e_book_query_to_string(query.get()));
779 cache.reset(new ContactCache);
780 cache->m_running = true;
781 cache->m_name = StringPrintf("%s-%s (%d)", uids[0]->c_str(), uids[size - 1]->c_str(), size);
782 cache->m_lastLUID = *uids[size - 1];
783 BOOST_FOREACH (const std::string *uid, std::make_pair(uids.begin(), uids.begin() + size)) {
784 (*cache)[*uid] = EContactCXX();
786 m_contactsFromDB += size;
788 SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts,
789 boost::bind(&EvolutionContactSource::completedRead,
791 boost::weak_ptr<ContactCache>(cache),
793 m_addressbook, sexp, NULL);
794 SE_LOG_DEBUG(getDisplayName(), "reading: started contact read %s", cache->m_name.c_str());
799 typedef GListCXX< EContact, GSList, GObjectDestructor<EContact> > ContactListCXX;
801 void EvolutionContactSource::completedRead(const boost::weak_ptr<ContactCache> &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw()
804 ContactListCXX contacts(contactsPtr); // transfers ownership
805 boost::shared_ptr<ContactCache> cache = cachePtr.lock();
807 SE_LOG_DEBUG(getDisplayName(), "reading: contact read finished, results no longer needed: %s", gerror ? gerror->message : "<<successful>>");
811 SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s finished: %s",
812 cache->m_name.c_str(),
813 gerror ? gerror->message : "<<successful>>");
815 BOOST_FOREACH (EContact *contact, contacts) {
816 const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
817 SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s got %s", cache->m_name.c_str(), uid);
818 (*cache)[uid] = EContactCXX(contact, ADD_REF);
821 cache->m_gerror = gerror;
823 cache->m_running = false;
825 Exception::handle(HANDLE_EXCEPTION_FATAL);
829 void EvolutionContactSource::logCacheStats(Logger::Level level)
831 SE_LOG(getDisplayName(), level,
832 "requested %d, retrieved %d from DB in %d queries, misses %d/%d (%d%%), stalls %d",
836 m_cacheMisses, m_contactReads, m_contactReads ? m_cacheMisses * 100 / m_contactReads : 0,
842 void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw)
847 #ifdef USE_EDS_CLIENT
848 !getContact(luid, &contact, gerror)
850 !e_book_get_contact(m_addressbook,
856 if (IsContactNotFound(gerror)) {
857 throwError(STATUS_NOT_FOUND, string("reading contact: ") + luid);
859 throwError(string("reading contact ") + luid,
864 eptr<EContact, GObject> contactptr(contact, "contact");
866 // Inline PHOTO data if exporting, leave VALUE=uri references unchanged
867 // when processing inside engine (will be inlined by engine as needed).
868 // The function for doing the inlining was added in EDS 3.4.
869 // In compatibility mode, we must check the function pointer for non-NULL.
870 // In direct call mode, the existence check is done by configure.
872 #ifdef EVOLUTION_COMPATIBILITY
873 && e_contact_inline_local_photos
876 #if defined(EVOLUTION_COMPATIBILITY) || defined(HAVE_E_CONTACT_INLINE_LOCAL_PHOTOS)
877 if (!e_contact_inline_local_photos(contactptr, gerror)) {
878 throwError(string("inlining PHOTO file data in ") + luid, gerror);
883 eptr<char> vcardstr(e_vcard_to_string(&contactptr->parent,
884 EVC_FORMAT_VCARD_30));
886 throwError(string("failure extracting contact from Evolution " ) + luid);
889 item = vcardstr.get();
892 #ifdef USE_EDS_CLIENT
893 TrackingSyncSource::InsertItemResult EvolutionContactSource::checkBatchedInsert(const boost::shared_ptr<Pending> &pending)
895 SE_LOG_DEBUG(pending->m_name, "checking operation: %s", pending->m_status == MODIFYING ? "waiting" : "inserted");
896 if (pending->m_status == MODIFYING) {
897 return TrackingSyncSource::InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending));
899 if (pending->m_gerror) {
900 pending->m_gerror.throwError(pending->m_name);
902 string newrev = getRevision(pending->m_uid);
903 return TrackingSyncSource::InsertItemResult(pending->m_uid, newrev, ITEM_OKAY);
906 void EvolutionContactSource::completedAdd(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, GSList *uids, const GError *gerror) throw()
909 // The destructor ensures that the pending operations complete
910 // before destructing the instance, so our "this" pointer is
911 // always valid here.
912 SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts completed", (int)batched->size());
913 m_numRunningOperations--;
914 PendingContainer_t::iterator it = (*batched).begin();
916 while (it != (*batched).end() && uid) {
917 SE_LOG_DEBUG((*it)->m_name, "completed: %s",
918 success ? "<<successfully>>" :
919 gerror ? gerror->message :
920 "<<unknown failure>>");
922 (*it)->m_uid = static_cast<gchar *>(uid->data);
923 // Get revision when engine checks the item.
924 (*it)->m_status = REVISION;
926 (*it)->m_status = DONE;
927 (*it)->m_gerror = gerror;
933 while (it != (*batched).end()) {
934 // Should never happen.
935 SE_LOG_DEBUG((*it)->m_name, "completed: missing uid?!");
936 (*it)->m_status = DONE;
940 g_slist_free_full(uids, g_free);
942 Exception::handle(HANDLE_EXCEPTION_FATAL);
946 void EvolutionContactSource::completedUpdate(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, const GError *gerror) throw()
949 SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts completed", (int)batched->size());
950 m_numRunningOperations--;
951 PendingContainer_t::iterator it = (*batched).begin();
952 while (it != (*batched).end()) {
953 SE_LOG_DEBUG((*it)->m_name, "completed: %s",
954 success ? "<<successfully>>" :
955 gerror ? gerror->message :
956 "<<unknown failure>>");
958 (*it)->m_status = REVISION;
960 (*it)->m_status = DONE;
961 (*it)->m_gerror = gerror;
966 Exception::handle(HANDLE_EXCEPTION_FATAL);
970 void EvolutionContactSource::flushItemChanges()
972 if (!m_batchedAdd.empty()) {
973 SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts starting", (int)m_batchedAdd.size());
974 m_numRunningOperations++;
975 GListCXX<EContact, GSList> contacts;
976 // Iterate backwards, push to front (cheaper for single-linked list) -> same order in the end.
977 BOOST_REVERSE_FOREACH (const boost::shared_ptr<Pending> &pending, m_batchedAdd) {
978 contacts.push_front(pending->m_contact.get());
980 // Transfer content without copying and then copy only the shared pointer.
981 boost::shared_ptr<PendingContainer_t> batched(new PendingContainer_t);
982 std::swap(*batched, m_batchedAdd);
983 SYNCEVO_GLIB_CALL_ASYNC(e_book_client_add_contacts,
984 boost::bind(&EvolutionContactSource::completedAdd,
988 m_addressbook, contacts, NULL);
990 if (!m_batchedUpdate.empty()) {
991 SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts starting", (int)m_batchedUpdate.size());
992 m_numRunningOperations++;
993 GListCXX<EContact, GSList> contacts;
994 BOOST_REVERSE_FOREACH (const boost::shared_ptr<Pending> &pending, m_batchedUpdate) {
995 contacts.push_front(pending->m_contact.get());
997 boost::shared_ptr<PendingContainer_t> batched(new PendingContainer_t);
998 std::swap(*batched, m_batchedUpdate);
999 SYNCEVO_GLIB_CALL_ASYNC(e_book_client_modify_contacts,
1000 boost::bind(&EvolutionContactSource::completedUpdate,
1004 m_addressbook, contacts, NULL);
1008 void EvolutionContactSource::finishItemChanges()
1010 if (m_numRunningOperations) {
1011 SE_LOG_DEBUG(getDisplayName(), "waiting for %d pending operations to complete", m_numRunningOperations.get());
1012 while (m_numRunningOperations) {
1013 g_main_context_iteration(NULL, true);
1015 SE_LOG_DEBUG(getDisplayName(), "pending operations completed");
1021 TrackingSyncSource::InsertItemResult
1022 EvolutionContactSource::insertItem(const string &uid, const std::string &item, bool raw)
1024 EContactCXX contact(e_contact_new_from_vcard(item.c_str()), TRANSFER_REF);
1026 e_contact_set(contact, E_CONTACT_UID,
1029 const_cast<char *>(uid.c_str()));
1031 #ifdef USE_EDS_CLIENT
1032 invalidateCachedContact(uid);
1033 switch (m_accessMode) {
1037 if (!e_book_client_add_contact_sync(m_addressbook, contact, &newuid, NULL, gerror)) {
1038 throwError("add new contact", gerror);
1040 PlainGStr newuidPtr(newuid);
1041 string newrev = getRevision(newuid);
1042 return InsertItemResult(newuid, newrev, ITEM_OKAY);
1044 if (!e_book_client_modify_contact_sync(m_addressbook, contact, NULL, gerror)) {
1045 throwError("updating contact "+ uid, gerror);
1047 string newrev = getRevision(uid);
1048 return InsertItemResult(uid, newrev, ITEM_OKAY);
1053 std::string name = StringPrintf("%s: %s operation #%d",
1054 getDisplayName().c_str(),
1055 uid.empty() ? "add" : ("insert " + uid).c_str(),
1056 m_asyncOpCounter++);
1057 SE_LOG_DEBUG(name, "queueing for batched %s", uid.empty() ? "add" : "update");
1058 boost::shared_ptr<Pending> pending(new Pending);
1059 pending->m_name = name;
1060 pending->m_contact = contact;
1061 pending->m_uid = uid;
1063 m_batchedAdd.push_back(pending);
1065 m_batchedUpdate.push_back(pending);
1067 // SyncSource is going to live longer than Synthesis
1068 // engine, so using "this" is safe here.
1069 return InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending));
1074 e_book_add_contact(m_addressbook, contact, gerror) :
1075 e_book_commit_contact(m_addressbook, contact, gerror)) {
1076 const char *newuid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
1078 throwError("no UID for contact");
1080 string newrev = getRevision(newuid);
1081 return InsertItemResult(newuid, newrev, ITEM_OKAY);
1083 throwError(uid.empty() ?
1084 "storing new contact" :
1085 string("updating contact ") + uid,
1090 throwError(string("failure parsing vcard " ) + item);
1093 return InsertItemResult("", "", ITEM_OKAY);
1096 void EvolutionContactSource::removeItem(const string &uid)
1100 #ifdef USE_EDS_CLIENT
1101 (invalidateCachedContact(uid),
1102 !e_book_client_remove_contact_by_uid_sync(m_addressbook, uid.c_str(), NULL, gerror))
1104 !e_book_remove_contact(m_addressbook, uid.c_str(), gerror)
1107 if (IsContactNotFound(gerror)) {
1108 throwError(STATUS_NOT_FOUND, string("deleting contact: ") + uid);
1110 throwError( string( "deleting contact " ) + uid,
1116 std::string EvolutionContactSource::getDescription(const string &luid)
1122 #ifdef USE_EDS_CLIENT
1127 !e_book_get_contact(m_addressbook,
1133 throwError(string("reading contact ") + luid,
1136 eptr<EContact, GObject> contactptr(contact, "contact");
1137 const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME);
1141 const char *fileas = (const char *)e_contact_get_const(contact, E_CONTACT_FILE_AS);
1145 EContactName *names =
1146 (EContactName *)e_contact_get(contact, E_CONTACT_NAME);
1147 std::list<std::string> buffer;
1150 if (names->given && names->given[0]) {
1151 buffer.push_back(names->given);
1153 if (names->additional && names->additional[0]) {
1154 buffer.push_back(names->additional);
1156 if (names->family && names->family[0]) {
1157 buffer.push_back(names->family);
1161 e_contact_name_free(names);
1163 return boost::join(buffer, " ");
1165 // Instead of failing we log the error and ask
1166 // the caller to log the UID. That way transient
1167 // errors or errors in the logging code don't
1174 std::string EvolutionContactSource::getMimeType() const
1176 switch( m_vcardFormat ) {
1177 case EVC_FORMAT_VCARD_21:
1178 return "text/x-vcard";
1180 case EVC_FORMAT_VCARD_30:
1182 return "text/vcard";
1187 std::string EvolutionContactSource::getMimeVersion() const
1189 switch( m_vcardFormat ) {
1190 case EVC_FORMAT_VCARD_21:
1193 case EVC_FORMAT_VCARD_30:
1202 #endif /* ENABLE_EBOOK */
1204 #ifdef ENABLE_MODULES
1205 # include "EvolutionContactSourceRegister.cpp"