Imported Upstream version 1.3.99.5_20131030_SE_05e5911_SYSYNC_69de386
[platform/upstream/syncevolution.git] / src / backends / evolution / EvolutionContactSource.cpp
1 /*
2  * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
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.
9  *
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.
14  *
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
18  * 02110-1301  USA
19  */
20
21 #include <memory>
22 #include <map>
23 #include <sstream>
24 #include <list>
25 using namespace std;
26
27 #include "config.h"
28 #include "EvolutionSyncSource.h"
29 #include <syncevo/IdentityProvider.h>
30
31 #ifdef ENABLE_EBOOK
32
33 #include <syncevo/SyncContext.h>
34 #include "EvolutionContactSource.h"
35 #include <syncevo/util.h>
36
37 #include <syncevo/Logging.h>
38
39 #ifdef USE_EDS_CLIENT
40 #include <boost/range/algorithm/find.hpp>
41 #endif
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>
47
48 #include <syncevo/declarations.h>
49
50 SE_GLIB_TYPE(EBookQuery, e_book_query)
51
52 SE_BEGIN_CXX
53
54 inline bool IsContactNotFound(const GError *gerror) {
55     return gerror &&
56 #ifdef USE_EDS_CLIENT
57         gerror->domain == E_BOOK_CLIENT_ERROR &&
58         gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND
59 #else
60         gerror->domain == E_BOOK_ERROR &&
61         gerror->code == E_BOOK_ERROR_CONTACT_NOT_FOUND
62 #endif
63         ;
64 }
65
66
67 const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions;
68 const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties;
69
70 EvolutionContactSource::EvolutionContactSource(const SyncSourceParams &params,
71                                                EVCardFormat vcardFormat) :
72     EvolutionSyncSource(params),
73     m_vcardFormat(vcardFormat)
74 {
75 #ifdef USE_EDS_CLIENT
76     m_cacheMisses =
77         m_cacheStalls =
78         m_contactReads =
79         m_contactsFromDB =
80         m_contactQueries = 0;
81     m_readAheadOrder = READ_NONE;
82 #endif
83     SyncSourceLogging::init(InitList<std::string>("N_FIRST") + "N_MIDDLE" + "N_LAST",
84                             " ",
85                             m_operations);
86 }
87
88 EvolutionContactSource::~EvolutionContactSource()
89 {
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.
95     //
96     // TODO: cancel the operations().
97     finishItemChanges();
98     close();
99
100 #ifdef USE_EDS_CLIENT
101     // logCacheStats(Logger::DEBUG);
102 #endif
103 }
104
105 #ifdef USE_EDS_CLIENT
106 static EClient *newEBookClient(ESource *source,
107                                GError **gerror)
108 {
109     return E_CLIENT(e_book_client_new(source, gerror));
110 }
111 #endif
112
113 EvolutionSyncSource::Databases EvolutionContactSource::getDatabases()
114 {
115     Databases result;
116 #ifdef USE_EDS_CLIENT
117     getDatabasesFromRegistry(result,
118                              E_SOURCE_EXTENSION_ADDRESS_BOOK,
119                              e_source_registry_ref_default_address_book);
120 #else
121     ESourceList *sources = NULL;
122     if (!e_book_get_addressbooks(&sources, NULL)) {
123         SyncContext::throwError("unable to access address books");
124     }
125
126     Databases secondary;
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));
132             string uristr;
133             if (uri) {
134                 uristr = uri.get();
135             }
136             Database entry(e_source_peek_name(source),
137                            uristr,
138                            false);
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.
142                 //
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.
147                 //
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);
154             } else {
155                 result.push_back(entry);
156             }
157         }
158     }
159     result.insert(result.end(), secondary.begin(), secondary.end());
160
161     // No results? Try system address book (workaround for embedded Evolution Dataserver).
162     if (!result.size()) {
163         eptr<EBook, GObject> book;
164         GErrorCXX gerror;
165         const char *name;
166
167         name = "<<system>>";
168         book = e_book_new_system_addressbook (gerror);
169         gerror.clear();
170         if (!book) {
171             name = "<<default>>";
172             book = e_book_new_default_addressbook (gerror);
173         }
174
175         if (book) {
176             const char *uri = e_book_get_uri (book);
177             result.push_back(Database(name, uri, true));
178         }
179     } else {
180         //  the first DB found is the default
181         result[0].m_isDefault = true;
182     }
183 #endif
184
185     return result;
186 }
187
188 void EvolutionContactSource::open()
189 {
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 :
197         DEFAULT;
198 #else
199     GErrorCXX gerror;
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
206     ESourceList *tmp;
207     if (!e_book_get_addressbooks(&tmp, gerror)) {
208         throwError("unable to access address books", gerror);
209     }
210     ESourceListCXX sources(tmp, TRANSFER_REF);
211
212     string id = getDatabaseID();
213     ESource *source = findSource(sources, id);
214     if (!source) {
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");
223         } else {
224             throwError(string(getName()) + ": no such address book: '" + id + "'");
225         }
226         created = true;
227     } else {
228         m_addressbook.set( e_book_new( source, gerror ), "address book" );
229     }
230  
231     if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) {
232         if (created) {
233             // opening newly created address books often fails, try again once more
234             sleep(5);
235             if (!e_book_open(m_addressbook, onlyIfExists, gerror)) {
236                 throwError("opening address book", gerror);
237             }
238         } else {
239             throwError("opening address book", gerror);
240         }
241     }
242
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()) {
249         GList *authmethod;
250         if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) {
251             throwError("getting authentication methods", gerror);
252         }
253         while (authmethod) {
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",
258                          method,
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(),
264                                          method,
265                                          gerror)) {
266                 SE_LOG_DEBUG(getDisplayName(), "authentication succeeded");
267                 break;
268             } else {
269                 SE_LOG_ERROR(getDisplayName(), "authentication failed: %s", gerror->message);
270             }
271             authmethod = authmethod->next;
272         }
273     }
274
275     g_signal_connect_after(m_addressbook,
276                            "backend-died",
277                            G_CALLBACK(SyncContext::fatalError),
278                            (void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
279 #endif
280 }
281
282 bool EvolutionContactSource::isEmpty()
283 {
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();
289 }
290
291 #ifdef USE_EDS_CLIENT
292 class EBookClientViewSyncHandler {
293     public:
294         typedef boost::function<void (const GSList *list)> Process_t;
295
296         EBookClientViewSyncHandler(const EBookClientViewCXX &view,
297                                    const Process_t &process) :
298             m_process(process),
299             m_view(view)
300         {}
301
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));
310
311             // Start the view
312             e_book_client_view_start (m_view, m_error);
313             if (m_error) {
314                 std::swap(gerror, m_error);
315                 return false;
316             }
317
318             // Async -> Sync
319             m_loop.run();
320             e_book_client_view_stop (m_view, NULL); 
321
322             if (m_error) {
323                 std::swap(gerror, m_error);
324                 return false;
325             } else {
326                 return true;
327             }
328         }
329  
330         void completed(const GError *error) {
331             m_error = error;
332             m_loop.quit();
333         }
334
335     public:
336         // Event loop for Async -> Sync
337         EvolutionAsync m_loop;
338
339     private:
340         // Process list callback
341         boost::function<void (const GSList *list)> m_process;
342         // View watched
343         EBookClientViewCXX m_view;
344         // Possible error while watching the view
345         GErrorCXX m_error;
346 };
347
348 static void list_revisions(const GSList *contacts, EvolutionContactSource::RevisionMap_t *revisions)
349 {
350     const GSList *l;
351
352     for (l = contacts; l; l = l->next) {
353         EContact *contact = E_CONTACT(l->data);
354         if (!contact) {
355             SE_THROW("contact entry without data");
356         }
357         pair<string, string> revmapping;
358         const char *uid = (const char *)e_contact_get_const(contact,
359                                                             E_CONTACT_UID);
360         if (!uid || !uid[0]) {
361             SE_THROW("contact entry without UID");
362         }
363         revmapping.first = uid;
364         const char *rev = (const char *)e_contact_get_const(contact,
365                                                             E_CONTACT_REV);
366         if (!rev || !rev[0]) {
367             SE_THROW(string("contact entry without REV: ") + revmapping.first);
368         }
369         revmapping.second = rev;
370         revisions->insert(revmapping);
371     }
372 }
373
374 #endif
375
376 void EvolutionContactSource::listAllItems(RevisionMap_t &revisions)
377 {
378 #ifdef USE_EDS_CLIENT
379     GErrorCXX gerror;
380     EBookClientView *view;
381
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");
385     if (sexp) {
386         SE_LOG_INFO(NULL, "restricting item set to items matching %s", sexp);
387     } else {
388         sexp = buffer;
389     }
390
391     if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) {
392         throwError( "getting the view" , gerror);
393     }
394     EBookClientViewCXX viewPtr = EBookClientViewCXX::steal(view);
395
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);
401     if (gerror) {
402         SE_LOG_ERROR(getDisplayName(), "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror);
403         gerror.clear();
404     }
405
406     EBookClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions));
407     if (!handler.process(gerror)) {
408         throwError("watching view", gerror);
409     }
410 #else
411     GErrorCXX gerror;
412     eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query");
413     GList *nextItem;
414     if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) {
415         throwError( "reading all items", gerror );
416     }
417     eptr<GList> listptr(nextItem);
418     while (nextItem) {
419         EContact *contact = E_CONTACT(nextItem->data);
420         if (!contact) {
421             throwError("contact entry without data");
422         }
423         pair<string, string> revmapping;
424         const char *uid = (const char *)e_contact_get_const(contact,
425                                                             E_CONTACT_UID);
426         if (!uid || !uid[0]) {
427             throwError("contact entry without UID");
428         }
429         revmapping.first = uid;
430         const char *rev = (const char *)e_contact_get_const(contact,
431                                                             E_CONTACT_REV);
432         if (!rev || !rev[0]) {
433             throwError(string("contact entry without REV: ") + revmapping.first);
434         }
435         revmapping.second = rev;
436         revisions.insert(revmapping);
437         nextItem = nextItem->next;
438     }
439 #endif
440 }
441
442 void EvolutionContactSource::close()
443 {
444     m_addressbook.reset();
445 }
446
447 string EvolutionContactSource::getRevision(const string &luid)
448 {
449     if (!needChanges()) {
450         return "";
451     }
452
453     EContact *contact;
454     GErrorCXX gerror;
455     if (
456 #ifdef USE_EDS_CLIENT
457         !e_book_client_get_contact_sync(m_addressbook,
458                                         luid.c_str(),
459                                         &contact,
460                                         NULL,
461                                         gerror)
462 #else
463         !e_book_get_contact(m_addressbook,
464                             luid.c_str(),
465                             &contact,
466                             gerror)
467 #endif
468         ) {
469         if (IsContactNotFound(gerror)) {
470             throwError(STATUS_NOT_FOUND, string("retrieving item: ") + luid);
471         } else {
472             throwError(string("reading contact ") + luid,
473                        gerror);
474         }
475     }
476     eptr<EContact, GObject> contactptr(contact, "contact");
477     const char *rev = (const char *)e_contact_get_const(contact,
478                                                         E_CONTACT_REV);
479     if (!rev || !rev[0]) {
480         throwError(string("contact entry without REV: ") + luid);
481     }
482     return rev;
483 }
484
485 #ifdef USE_EDS_CLIENT
486 class ContactCache : public std::map<std::string, EContactCXX>
487 {
488 public:
489     /** Asynchronous method call still pending. */
490     bool m_running;
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. */
494     GErrorCXX m_gerror;
495     /** A debug logging name for this query. */
496     std::string m_name;
497 };
498
499 void EvolutionContactSource::setReadAheadOrder(ReadAheadOrder order,
500                                                const ReadAheadItems &luids)
501 {
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" :
507                  "???",
508                  (long)luids.size());
509     m_readAheadOrder = order;
510     m_nextLUIDs = luids;
511
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
516     // misses.
517     //
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();
523 }
524
525 void EvolutionContactSource::getReadAheadOrder(ReadAheadOrder &order,
526                                                ReadAheadItems &luids)
527 {
528     order = m_readAheadOrder;
529     luids = m_nextLUIDs;
530 }
531
532 void EvolutionContactSource::checkCacheForError(boost::shared_ptr<ContactCache> &cache)
533 {
534     if (cache->m_gerror) {
535         GErrorCXX gerror;
536         std::swap(gerror, cache->m_gerror);
537         cache.reset();
538         throwError(StringPrintf("reading contacts %s", cache->m_name.c_str()), gerror);
539     }
540 }
541
542 void EvolutionContactSource::invalidateCachedContact(const std::string &luid)
543 {
544     invalidateCachedContact(m_contactCache, luid);
545     invalidateCachedContact(m_contactCacheNext, luid);
546 }
547
548 void EvolutionContactSource::invalidateCachedContact(boost::shared_ptr<ContactCache> &cache, const std::string &luid)
549 {
550     if (cache) {
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.
559             cache->erase(it);
560         }
561     }
562 }
563
564 bool EvolutionContactSource::getContact(const string &luid, EContact **contact, GErrorCXX &gerror)
565 {
566     SE_LOG_DEBUG(getDisplayName(), "reading: getting contact %s", luid.c_str());
567     ReadAheadOrder order = m_readAheadOrder;
568
569     // Use switch and let compiler tell us when we don't cover a case.
570     switch (m_accessMode) {
571     case SYNCHRONOUS:
572         order = READ_NONE;
573         break;
574     case BATCHED:
575     case DEFAULT:
576         order = m_readAheadOrder;
577         break;
578     };
579
580     m_contactReads++;
581     if (order == READ_NONE) {
582         m_contactsFromDB++;
583         m_contactQueries++;
584         return e_book_client_get_contact_sync(m_addressbook,
585                                               luid.c_str(),
586                                               contact,
587                                               NULL,
588                                               gerror);
589     } else {
590         return getContactFromCache(luid, contact, gerror);
591     }
592 }
593
594 bool EvolutionContactSource::getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror)
595 {
596     *contact = NULL;
597
598     // Use ContactCache.
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);
603
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);
615             } else {
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();
619             }
620         } else {
621             SE_LOG_DEBUG(getDisplayName(), "reading: in %s cache", m_contactCache->m_running ? "running" : "loaded");
622             if (m_contactCache->m_running) {
623                 m_cacheStalls++;
624                 GRunWhile(boost::lambda::var(m_contactCache->m_running));
625             }
626             // Problem?
627             checkCacheForError(m_contactCache);
628
629             SE_LOG_DEBUG(getDisplayName(), "reading: in cache, %s", it->second ? "available" : "not found");
630             if (it->second) {
631                 // Got it.
632                 *contact = it->second.ref();
633             } else {
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()));
637             }
638         }
639     }
640
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);
646     }
647
648     // Can we read ahead?
649     if (!m_contactCacheNext && !m_contactCache->m_running) {
650         m_contactCacheNext = startReading(m_contactCache->m_lastLUID, CONTINUE);
651     }
652
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);
657     return !gerror;
658 }
659
660 static int MaxBatchSize()
661 {
662     int maxBatchSize = atoi(getEnv("SYNCEVOLUTION_EDS_BATCH_SIZE", "50"));
663     if (maxBatchSize < 1) {
664         maxBatchSize = 1;
665     }
666     return maxBatchSize;
667 }
668
669 boost::shared_ptr<ContactCache> EvolutionContactSource::startReading(const std::string &luid, ReadingMode mode)
670 {
671     SE_LOG_DEBUG(getDisplayName(), "reading: %s contact %s",
672                  mode == START ? "must read" :
673                  mode == CONTINUE ? "continue after" :
674                  "???",
675                  luid.c_str());
676
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);
682     int size = 0;
683     bool found = false;
684
685     switch (m_readAheadOrder) {
686     case READ_ALL_ITEMS:
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);
692
693         // Always read the requested item, even if not found in item list?
694         if (mode == START) {
695             uids[0] = &luid;
696             size++;
697         }
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()) {
705                 found = true;
706             }
707             ++it;
708         }
709         while (size < maxBatchSize &&
710                it != items.end()) {
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()) {
715                 uids[size] = &luid;
716                 ++size;
717             }
718             ++it;
719         }
720         break;
721     }
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?
725         if (mode == START) {
726             uids[0] = &luid;
727             size++;
728         }
729         // luid is dealt with, either way.
730         if (it != m_nextLUIDs.end()) {
731             found = true;
732             ++it;
733         }
734         while (size < maxBatchSize &&
735                it != m_nextLUIDs.end()) {
736             uids[size] = &*it;
737             ++size;
738             ++it;
739         }
740         break;
741     }
742     case READ_NONE:
743         // May be reached when read-ahead was turned off while
744         // preparing for it.
745         if (mode == START) {
746             uids[0] = &luid;
747             size++;
748         }
749         break;
750     }
751
752     if (m_readAheadOrder != READ_NONE &&
753         mode == START &&
754         !found) {
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.
758         m_cacheMisses++;
759         SE_LOG_DEBUG(getDisplayName(), "reading: disable read-ahead due to cache miss");
760         m_readAheadOrder = READ_NONE;
761     }
762
763     boost::shared_ptr<ContactCache> cache;
764     if (size) {
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()));
771             //
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();
775         }
776         EBookQueryCXX query(e_book_query_or(size, queries.get(), false), TRANSFER_REF);
777         PlainGStr sexp(e_book_query_to_string(query.get()));
778
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();
785         }
786         m_contactsFromDB += size;
787         m_contactQueries++;
788         SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts,
789                                 boost::bind(&EvolutionContactSource::completedRead,
790                                             this,
791                                             boost::weak_ptr<ContactCache>(cache),
792                                             _1, _2, _3),
793                                 m_addressbook, sexp, NULL);
794         SE_LOG_DEBUG(getDisplayName(), "reading: started contact read %s", cache->m_name.c_str());
795     }
796     return cache;
797 }
798
799 typedef GListCXX< EContact, GSList, GObjectDestructor<EContact> > ContactListCXX;
800
801 void EvolutionContactSource::completedRead(const boost::weak_ptr<ContactCache> &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw()
802 {
803     try {
804         ContactListCXX contacts(contactsPtr); // transfers ownership
805         boost::shared_ptr<ContactCache> cache = cachePtr.lock();
806         if (!cache) {
807             SE_LOG_DEBUG(getDisplayName(), "reading: contact read finished, results no longer needed: %s", gerror ? gerror->message : "<<successful>>");
808             return;
809         }
810
811         SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s finished: %s",
812                      cache->m_name.c_str(),
813                      gerror ? gerror->message : "<<successful>>");
814         if (success) {
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);
819             }
820         } else {
821             cache->m_gerror = gerror;
822         }
823         cache->m_running = false;
824     } catch (...) {
825         Exception::handle(HANDLE_EXCEPTION_FATAL);
826     }
827 }
828
829 void EvolutionContactSource::logCacheStats(Logger::Level level)
830 {
831     SE_LOG(getDisplayName(), level,
832            "requested %d, retrieved %d from DB in %d queries, misses %d/%d (%d%%), stalls %d",
833            m_contactReads,
834            m_contactsFromDB,
835            m_contactQueries,
836            m_cacheMisses, m_contactReads, m_contactReads ? m_cacheMisses * 100 / m_contactReads : 0,
837            m_cacheStalls);
838 }
839
840 #endif
841
842 void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw)
843 {
844     EContact *contact;
845     GErrorCXX gerror;
846     if (
847 #ifdef USE_EDS_CLIENT
848         !getContact(luid, &contact, gerror)
849 #else
850         !e_book_get_contact(m_addressbook,
851                             luid.c_str(),
852                             &contact,
853                             gerror)
854 #endif
855         ) {
856         if (IsContactNotFound(gerror)) {
857             throwError(STATUS_NOT_FOUND, string("reading contact: ") + luid);
858         } else {
859             throwError(string("reading contact ") + luid,
860                        gerror);
861         }
862     }
863
864     eptr<EContact, GObject> contactptr(contact, "contact");
865
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.
871     if (raw
872 #ifdef EVOLUTION_COMPATIBILITY
873         && e_contact_inline_local_photos
874 #endif
875         ) {
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);
879         }
880 #endif
881     }
882
883     eptr<char> vcardstr(e_vcard_to_string(&contactptr->parent,
884                                           EVC_FORMAT_VCARD_30));
885     if (!vcardstr) {
886         throwError(string("failure extracting contact from Evolution " ) + luid);
887     }
888
889     item = vcardstr.get();
890 }
891
892 #ifdef USE_EDS_CLIENT
893 TrackingSyncSource::InsertItemResult EvolutionContactSource::checkBatchedInsert(const boost::shared_ptr<Pending> &pending)
894 {
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));
898     }
899     if (pending->m_gerror) {
900         pending->m_gerror.throwError(pending->m_name);
901     }
902     string newrev = getRevision(pending->m_uid);
903     return TrackingSyncSource::InsertItemResult(pending->m_uid, newrev, ITEM_OKAY);
904 }
905
906 void EvolutionContactSource::completedAdd(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, GSList *uids, const GError *gerror) throw()
907 {
908     try {
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();
915         GSList *uid = uids;
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>>");
921             if (success) {
922                 (*it)->m_uid = static_cast<gchar *>(uid->data);
923                 // Get revision when engine checks the item.
924                 (*it)->m_status = REVISION;
925             } else {
926                 (*it)->m_status = DONE;
927                 (*it)->m_gerror = gerror;
928             }
929             ++it;
930             uid = uid->next;
931         }
932
933         while (it != (*batched).end()) {
934             // Should never happen.
935             SE_LOG_DEBUG((*it)->m_name, "completed: missing uid?!");
936             (*it)->m_status = DONE;
937             ++it;
938         }
939
940         g_slist_free_full(uids, g_free);
941     } catch (...) {
942         Exception::handle(HANDLE_EXCEPTION_FATAL);
943     }
944 }
945
946 void EvolutionContactSource::completedUpdate(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, const GError *gerror) throw()
947 {
948     try {
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>>");
957             if (success) {
958                 (*it)->m_status = REVISION;
959             } else {
960                 (*it)->m_status = DONE;
961                 (*it)->m_gerror = gerror;
962             }
963             ++it;
964         }
965     } catch (...) {
966         Exception::handle(HANDLE_EXCEPTION_FATAL);
967     }
968 }
969
970 void EvolutionContactSource::flushItemChanges()
971 {
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());
979         }
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,
985                                             this,
986                                             batched,
987                                             _1, _2, _3),
988                                 m_addressbook, contacts, NULL);
989     }
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());
996         }
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,
1001                                             this,
1002                                             batched,
1003                                             _1, _2),
1004                                 m_addressbook, contacts, NULL);
1005     }
1006 }
1007
1008 void EvolutionContactSource::finishItemChanges()
1009 {
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);
1014         }
1015         SE_LOG_DEBUG(getDisplayName(), "pending operations completed");
1016     }
1017 }
1018
1019 #endif
1020
1021 TrackingSyncSource::InsertItemResult
1022 EvolutionContactSource::insertItem(const string &uid, const std::string &item, bool raw)
1023 {
1024     EContactCXX contact(e_contact_new_from_vcard(item.c_str()), TRANSFER_REF);
1025     if (contact) {
1026         e_contact_set(contact, E_CONTACT_UID,
1027                       uid.empty() ?
1028                       NULL :
1029                       const_cast<char *>(uid.c_str()));
1030         GErrorCXX gerror;
1031 #ifdef USE_EDS_CLIENT
1032         invalidateCachedContact(uid);
1033         switch (m_accessMode) {
1034         case SYNCHRONOUS:
1035             if (uid.empty()) {
1036                 gchar* newuid;
1037                 if (!e_book_client_add_contact_sync(m_addressbook, contact, &newuid, NULL, gerror)) {
1038                     throwError("add new contact", gerror);
1039                 }
1040                 PlainGStr newuidPtr(newuid);
1041                 string newrev = getRevision(newuid);
1042                 return InsertItemResult(newuid, newrev, ITEM_OKAY);
1043             } else {
1044                 if (!e_book_client_modify_contact_sync(m_addressbook, contact, NULL, gerror)) {
1045                     throwError("updating contact "+ uid, gerror);
1046                 }
1047                 string newrev = getRevision(uid);
1048                 return InsertItemResult(uid, newrev, ITEM_OKAY);
1049             }
1050             break;
1051         case BATCHED:
1052         case DEFAULT:
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;
1062             if (uid.empty()) {
1063                 m_batchedAdd.push_back(pending);
1064             } else {
1065                 m_batchedUpdate.push_back(pending);
1066             }
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));
1070             break;
1071         }
1072 #else
1073         if (uid.empty() ?
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);
1077             if (!newuid) {
1078                 throwError("no UID for contact");
1079             }
1080             string newrev = getRevision(newuid);
1081             return InsertItemResult(newuid, newrev, ITEM_OKAY);
1082         } else {
1083             throwError(uid.empty() ?
1084                        "storing new contact" :
1085                        string("updating contact ") + uid,
1086                        gerror);
1087         }
1088 #endif
1089     } else {
1090         throwError(string("failure parsing vcard " ) + item);
1091     }
1092     // not reached!
1093     return InsertItemResult("", "", ITEM_OKAY);
1094 }
1095
1096 void EvolutionContactSource::removeItem(const string &uid)
1097 {
1098     GErrorCXX gerror;
1099     if (
1100 #ifdef USE_EDS_CLIENT
1101         (invalidateCachedContact(uid),
1102          !e_book_client_remove_contact_by_uid_sync(m_addressbook, uid.c_str(), NULL, gerror))
1103 #else
1104         !e_book_remove_contact(m_addressbook, uid.c_str(), gerror)
1105 #endif
1106         ) {
1107         if (IsContactNotFound(gerror)) {
1108             throwError(STATUS_NOT_FOUND, string("deleting contact: ") + uid);
1109         } else {
1110             throwError( string( "deleting contact " ) + uid,
1111                         gerror);
1112         }
1113     }
1114 }
1115
1116 std::string EvolutionContactSource::getDescription(const string &luid)
1117 {
1118     try {
1119         EContact *contact;
1120         GErrorCXX gerror;
1121         if (
1122 #ifdef USE_EDS_CLIENT
1123             !getContact(luid,
1124                         &contact,
1125                         gerror)
1126 #else
1127             !e_book_get_contact(m_addressbook,
1128                                 luid.c_str(),
1129                                 &contact,
1130                                 gerror)
1131 #endif
1132             ) {
1133             throwError(string("reading contact ") + luid,
1134                        gerror);
1135         }
1136         eptr<EContact, GObject> contactptr(contact, "contact");
1137         const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME);
1138         if (name) {
1139             return name;
1140         }
1141         const char *fileas = (const char *)e_contact_get_const(contact, E_CONTACT_FILE_AS);
1142         if (fileas) {
1143             return fileas;
1144         }
1145         EContactName *names =
1146             (EContactName *)e_contact_get(contact, E_CONTACT_NAME);
1147         std::list<std::string> buffer;
1148         if (names) {
1149             try {
1150                 if (names->given && names->given[0]) {
1151                     buffer.push_back(names->given);
1152                 }
1153                 if (names->additional && names->additional[0]) {
1154                     buffer.push_back(names->additional);
1155                 }
1156                 if (names->family && names->family[0]) {
1157                     buffer.push_back(names->family);
1158                 }
1159             } catch (...) {
1160             }
1161             e_contact_name_free(names);
1162         }
1163         return boost::join(buffer, " ");
1164     } catch (...) {
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
1168         // prevent syncs.
1169         handleException();
1170         return "";
1171     }
1172 }
1173
1174 std::string EvolutionContactSource::getMimeType() const
1175 {
1176     switch( m_vcardFormat ) {
1177      case EVC_FORMAT_VCARD_21:
1178         return "text/x-vcard";
1179         break;
1180      case EVC_FORMAT_VCARD_30:
1181      default:
1182         return "text/vcard";
1183         break;
1184     }
1185 }
1186
1187 std::string EvolutionContactSource::getMimeVersion() const
1188 {
1189     switch( m_vcardFormat ) {
1190      case EVC_FORMAT_VCARD_21:
1191         return "2.1";
1192         break;
1193      case EVC_FORMAT_VCARD_30:
1194      default:
1195         return "3.0";
1196         break;
1197     }
1198 }
1199
1200 SE_END_CXX
1201
1202 #endif /* ENABLE_EBOOK */
1203
1204 #ifdef ENABLE_MODULES
1205 # include "EvolutionContactSourceRegister.cpp"
1206 #endif