add standard Smack manifest
[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 "EvolutionContactSource.h"
34 #include <syncevo/util.h>
35 #include <syncevo/Exception.h>
36 #include <syncevo/Logging.h>
37
38 #ifdef USE_EDS_CLIENT
39 #include <boost/range/algorithm/find.hpp>
40 #endif
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>
46
47 #include <syncevo/declarations.h>
48
49 SE_GLIB_TYPE(EBookQuery, e_book_query)
50
51 SE_BEGIN_CXX
52
53 inline bool IsContactNotFound(const GError *gerror) {
54     return gerror &&
55 #ifdef USE_EDS_CLIENT
56         gerror->domain == E_BOOK_CLIENT_ERROR &&
57         gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND
58 #else
59         gerror->domain == E_BOOK_ERROR &&
60         gerror->code == E_BOOK_ERROR_CONTACT_NOT_FOUND
61 #endif
62         ;
63 }
64
65
66 const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions;
67 const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties;
68
69 EvolutionContactSource::EvolutionContactSource(const SyncSourceParams &params,
70                                                EVCardFormat vcardFormat) :
71     EvolutionSyncSource(params),
72     m_vcardFormat(vcardFormat)
73 {
74 #ifdef USE_EDS_CLIENT
75     m_cacheMisses =
76         m_cacheStalls =
77         m_contactReads =
78         m_contactsFromDB =
79         m_contactQueries = 0;
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 :
84         DEFAULT;
85 #endif
86     SyncSourceLogging::init(InitList<std::string>("N_FIRST") + "N_MIDDLE" + "N_LAST",
87                             " ",
88                             m_operations);
89 }
90
91 EvolutionContactSource::~EvolutionContactSource()
92 {
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.
98     //
99     // TODO: cancel the operations().
100     finishItemChanges();
101     close();
102
103 #ifdef USE_EDS_CLIENT
104     // logCacheStats(Logger::DEBUG);
105 #endif
106 }
107
108 #ifdef USE_EDS_CLIENT
109 static EClient *newEBookClient(ESource *source,
110                                GError **gerror)
111 {
112     return E_CLIENT(e_book_client_new(source, gerror));
113 }
114 #endif
115
116 EvolutionSyncSource::Databases EvolutionContactSource::getDatabases()
117 {
118     Databases result;
119 #ifdef USE_EDS_CLIENT
120     getDatabasesFromRegistry(result,
121                              E_SOURCE_EXTENSION_ADDRESS_BOOK,
122                              e_source_registry_ref_default_address_book);
123 #else
124     ESourceList *sources = NULL;
125     if (!e_book_get_addressbooks(&sources, NULL)) {
126         Exception::throwError(SE_HERE, "unable to access address books");
127     }
128
129     Databases secondary;
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));
135             string uristr;
136             if (uri) {
137                 uristr = uri.get();
138             }
139             Database entry(e_source_peek_name(source),
140                            uristr,
141                            false);
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.
145                 //
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.
150                 //
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);
157             } else {
158                 result.push_back(entry);
159             }
160         }
161     }
162     result.insert(result.end(), secondary.begin(), secondary.end());
163
164     // No results? Try system address book (workaround for embedded Evolution Dataserver).
165     if (!result.size()) {
166         eptr<EBook, GObject> book;
167         GErrorCXX gerror;
168         const char *name;
169
170         name = "<<system>>";
171         book = e_book_new_system_addressbook (gerror);
172         gerror.clear();
173         if (!book) {
174             name = "<<default>>";
175             book = e_book_new_default_addressbook (gerror);
176         }
177
178         if (book) {
179             const char *uri = e_book_get_uri (book);
180             result.push_back(Database(name, uri, true));
181         }
182     } else {
183         //  the first DB found is the default
184         result[0].m_isDefault = true;
185     }
186 #endif
187
188     return result;
189 }
190
191 void EvolutionContactSource::open()
192 {
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()));
197 #else
198     GErrorCXX gerror;
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
205     ESourceList *tmp;
206     if (!e_book_get_addressbooks(&tmp, gerror)) {
207         throwError(SE_HERE, "unable to access address books", gerror);
208     }
209     ESourceListCXX sources(tmp, TRANSFER_REF);
210
211     string id = getDatabaseID();
212     ESource *source = findSource(sources, id);
213     if (!source) {
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");
222         } else {
223             throwError(SE_HERE, string(getName()) + ": no such address book: '" + id + "'");
224         }
225         created = true;
226     } else {
227         m_addressbook.set( e_book_new( source, gerror ), "address book" );
228     }
229  
230     if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) {
231         if (created) {
232             // opening newly created address books often fails, try again once more
233             sleep(5);
234             if (!e_book_open(m_addressbook, onlyIfExists, gerror)) {
235                 throwError(SE_HERE, "opening address book", gerror);
236             }
237         } else {
238             throwError(SE_HERE, "opening address book", gerror);
239         }
240     }
241
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()) {
248         GList *authmethod;
249         if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) {
250             throwError(SE_HERE, "getting authentication methods", gerror);
251         }
252         while (authmethod) {
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",
257                          method,
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(),
263                                          method,
264                                          gerror)) {
265                 SE_LOG_DEBUG(getDisplayName(), "authentication succeeded");
266                 break;
267             } else {
268                 SE_LOG_ERROR(getDisplayName(), "authentication failed: %s", gerror->message);
269             }
270             authmethod = authmethod->next;
271         }
272     }
273
274     g_signal_connect_after(m_addressbook,
275                            "backend-died",
276                            G_CALLBACK(Exception::fatalError),
277                            (void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
278 #endif
279 }
280
281 bool EvolutionContactSource::isEmpty()
282 {
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();
288 }
289
290 #ifdef USE_EDS_CLIENT
291 class EBookClientViewSyncHandler {
292     public:
293         typedef boost::function<void (const GSList *list)> Process_t;
294
295         EBookClientViewSyncHandler(const EBookClientViewCXX &view,
296                                    const Process_t &process) :
297             m_process(process),
298             m_view(view)
299         {}
300
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));
309
310             // Start the view
311             e_book_client_view_start (m_view, m_error);
312             if (m_error) {
313                 std::swap(gerror, m_error);
314                 return false;
315             }
316
317             // Async -> Sync
318             m_loop.run();
319             e_book_client_view_stop (m_view, NULL); 
320
321             if (m_error) {
322                 std::swap(gerror, m_error);
323                 return false;
324             } else {
325                 return true;
326             }
327         }
328  
329         void completed(const GError *error) {
330             m_error = error;
331             m_loop.quit();
332         }
333
334     public:
335         // Event loop for Async -> Sync
336         EvolutionAsync m_loop;
337
338     private:
339         // Process list callback
340         boost::function<void (const GSList *list)> m_process;
341         // View watched
342         EBookClientViewCXX m_view;
343         // Possible error while watching the view
344         GErrorCXX m_error;
345 };
346
347 static void list_revisions(const GSList *contacts, EvolutionContactSource::RevisionMap_t *revisions)
348 {
349     const GSList *l;
350
351     for (l = contacts; l; l = l->next) {
352         EContact *contact = E_CONTACT(l->data);
353         if (!contact) {
354             SE_THROW("contact entry without data");
355         }
356         pair<string, string> revmapping;
357         const char *uid = (const char *)e_contact_get_const(contact,
358                                                             E_CONTACT_UID);
359         if (!uid || !uid[0]) {
360             SE_THROW("contact entry without UID");
361         }
362         revmapping.first = uid;
363         const char *rev = (const char *)e_contact_get_const(contact,
364                                                             E_CONTACT_REV);
365         if (!rev || !rev[0]) {
366             SE_THROW(string("contact entry without REV: ") + revmapping.first);
367         }
368         revmapping.second = rev;
369         revisions->insert(revmapping);
370     }
371 }
372
373 #endif
374
375 void EvolutionContactSource::listAllItems(RevisionMap_t &revisions)
376 {
377 #ifdef USE_EDS_CLIENT
378     GErrorCXX gerror;
379     EBookClientView *view;
380
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");
384     if (sexp) {
385         SE_LOG_INFO(NULL, "restricting item set to items matching %s", sexp);
386     } else {
387         sexp = buffer;
388     }
389
390     if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) {
391         throwError(SE_HERE, "getting the view" , gerror);
392     }
393     EBookClientViewCXX viewPtr = EBookClientViewCXX::steal(view);
394
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);
400     if (gerror) {
401         SE_LOG_ERROR(getDisplayName(), "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror);
402         gerror.clear();
403     }
404
405     EBookClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions));
406     if (!handler.process(gerror)) {
407         throwError(SE_HERE, "watching view", gerror);
408     }
409 #else
410     GErrorCXX gerror;
411     eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query");
412     GList *nextItem;
413     if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) {
414         throwError(SE_HERE, "reading all items", gerror );
415     }
416     eptr<GList> listptr(nextItem);
417     while (nextItem) {
418         EContact *contact = E_CONTACT(nextItem->data);
419         if (!contact) {
420             throwError(SE_HERE, "contact entry without data");
421         }
422         pair<string, string> revmapping;
423         const char *uid = (const char *)e_contact_get_const(contact,
424                                                             E_CONTACT_UID);
425         if (!uid || !uid[0]) {
426             throwError(SE_HERE, "contact entry without UID");
427         }
428         revmapping.first = uid;
429         const char *rev = (const char *)e_contact_get_const(contact,
430                                                             E_CONTACT_REV);
431         if (!rev || !rev[0]) {
432             throwError(SE_HERE, string("contact entry without REV: ") + revmapping.first);
433         }
434         revmapping.second = rev;
435         revisions.insert(revmapping);
436         nextItem = nextItem->next;
437     }
438 #endif
439 }
440
441 void EvolutionContactSource::close()
442 {
443     m_addressbook.reset();
444 }
445
446 string EvolutionContactSource::getRevision(const string &luid)
447 {
448     if (!needChanges()) {
449         return "";
450     }
451
452     EContact *contact;
453     GErrorCXX gerror;
454     if (
455 #ifdef USE_EDS_CLIENT
456         !e_book_client_get_contact_sync(m_addressbook,
457                                         luid.c_str(),
458                                         &contact,
459                                         NULL,
460                                         gerror)
461 #else
462         !e_book_get_contact(m_addressbook,
463                             luid.c_str(),
464                             &contact,
465                             gerror)
466 #endif
467         ) {
468         if (IsContactNotFound(gerror)) {
469             throwError(SE_HERE, STATUS_NOT_FOUND, string("retrieving item: ") + luid);
470         } else {
471             throwError(SE_HERE, string("reading contact ") + luid,
472                        gerror);
473         }
474     }
475     eptr<EContact, GObject> contactptr(contact, "contact");
476     const char *rev = (const char *)e_contact_get_const(contact,
477                                                         E_CONTACT_REV);
478     if (!rev || !rev[0]) {
479         throwError(SE_HERE, string("contact entry without REV: ") + luid);
480     }
481     return rev;
482 }
483
484 #ifdef USE_EDS_CLIENT
485 class ContactCache : public std::map<std::string, EContactCXX>
486 {
487 public:
488     /** Asynchronous method call still pending. */
489     bool m_running;
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. */
493     GErrorCXX m_gerror;
494     /** A debug logging name for this query. */
495     std::string m_name;
496 };
497
498 void EvolutionContactSource::setReadAheadOrder(ReadAheadOrder order,
499                                                const ReadAheadItems &luids)
500 {
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" :
506                  "???",
507                  (long)luids.size());
508     m_readAheadOrder = order;
509     m_nextLUIDs = luids;
510
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
515     // misses.
516     //
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();
522 }
523
524 void EvolutionContactSource::getReadAheadOrder(ReadAheadOrder &order,
525                                                ReadAheadItems &luids)
526 {
527     order = m_readAheadOrder;
528     luids = m_nextLUIDs;
529 }
530
531 void EvolutionContactSource::checkCacheForError(boost::shared_ptr<ContactCache> &cache)
532 {
533     if (cache->m_gerror) {
534         GErrorCXX gerror;
535         std::swap(gerror, cache->m_gerror);
536         cache.reset();
537         throwError(SE_HERE, StringPrintf("reading contacts %s", cache->m_name.c_str()), gerror);
538     }
539 }
540
541 void EvolutionContactSource::invalidateCachedContact(const std::string &luid)
542 {
543     invalidateCachedContact(m_contactCache, luid);
544     invalidateCachedContact(m_contactCacheNext, luid);
545 }
546
547 void EvolutionContactSource::invalidateCachedContact(boost::shared_ptr<ContactCache> &cache, const std::string &luid)
548 {
549     if (cache) {
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.
558             cache->erase(it);
559         }
560     }
561 }
562
563 bool EvolutionContactSource::getContact(const string &luid, EContact **contact, GErrorCXX &gerror)
564 {
565     SE_LOG_DEBUG(getDisplayName(), "reading: getting contact %s", luid.c_str());
566
567     // Use switch and let compiler tell us when we don't cover a case.
568     ReadAheadOrder order
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]
574         = m_readAheadOrder
575 #endif
576         ;
577     switch (m_accessMode) {
578     case SYNCHRONOUS:
579         order = READ_NONE;
580         break;
581     case BATCHED:
582     case DEFAULT:
583         order = m_readAheadOrder;
584         break;
585     };
586
587     m_contactReads++;
588     if (order == READ_NONE) {
589         m_contactsFromDB++;
590         m_contactQueries++;
591         return e_book_client_get_contact_sync(m_addressbook,
592                                               luid.c_str(),
593                                               contact,
594                                               NULL,
595                                               gerror);
596     } else {
597         return getContactFromCache(luid, contact, gerror);
598     }
599 }
600
601 bool EvolutionContactSource::getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror)
602 {
603     *contact = NULL;
604
605     // Use ContactCache.
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);
610
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);
622             } else {
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();
626             }
627         } else {
628             SE_LOG_DEBUG(getDisplayName(), "reading: in %s cache", m_contactCache->m_running ? "running" : "loaded");
629             if (m_contactCache->m_running) {
630                 m_cacheStalls++;
631                 GRunWhile(boost::lambda::var(m_contactCache->m_running));
632             }
633             // Problem?
634             checkCacheForError(m_contactCache);
635
636             SE_LOG_DEBUG(getDisplayName(), "reading: in cache, %s", it->second ? "available" : "not found");
637             if (it->second) {
638                 // Got it.
639                 *contact = it->second.ref();
640             } else {
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()));
644             }
645         }
646     }
647
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);
653     }
654
655     // Can we read ahead?
656     if (!m_contactCacheNext && !m_contactCache->m_running) {
657         m_contactCacheNext = startReading(m_contactCache->m_lastLUID, CONTINUE);
658     }
659
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);
664     return !gerror;
665 }
666
667 static int MaxBatchSize()
668 {
669     int maxBatchSize = atoi(getEnv("SYNCEVOLUTION_EDS_BATCH_SIZE", "50"));
670     if (maxBatchSize < 1) {
671         maxBatchSize = 1;
672     }
673     return maxBatchSize;
674 }
675
676 boost::shared_ptr<ContactCache> EvolutionContactSource::startReading(const std::string &luid, ReadingMode mode)
677 {
678     SE_LOG_DEBUG(getDisplayName(), "reading: %s contact %s",
679                  mode == START ? "must read" :
680                  mode == CONTINUE ? "continue after" :
681                  "???",
682                  luid.c_str());
683
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);
689     int size = 0;
690     bool found = false;
691
692     switch (m_readAheadOrder) {
693     case READ_ALL_ITEMS:
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);
699
700         // Always read the requested item, even if not found in item list?
701         if (mode == START) {
702             uids[0] = &luid;
703             size++;
704         }
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()) {
712                 found = true;
713             }
714             ++it;
715         }
716         while (size < maxBatchSize &&
717                it != items.end()) {
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()) {
722                 uids[size] = &luid;
723                 ++size;
724             }
725             ++it;
726         }
727         break;
728     }
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?
732         if (mode == START) {
733             uids[0] = &luid;
734             size++;
735         }
736         // luid is dealt with, either way.
737         if (it != m_nextLUIDs.end()) {
738             found = true;
739             ++it;
740         }
741         while (size < maxBatchSize &&
742                it != m_nextLUIDs.end()) {
743             uids[size] = &*it;
744             ++size;
745             ++it;
746         }
747         break;
748     }
749     case READ_NONE:
750         // May be reached when read-ahead was turned off while
751         // preparing for it.
752         if (mode == START) {
753             uids[0] = &luid;
754             size++;
755         }
756         break;
757     }
758
759     if (m_readAheadOrder != READ_NONE &&
760         mode == START &&
761         !found) {
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.
765         m_cacheMisses++;
766         SE_LOG_DEBUG(getDisplayName(), "reading: disable read-ahead due to cache miss");
767         m_readAheadOrder = READ_NONE;
768     }
769
770     boost::shared_ptr<ContactCache> cache;
771     if (size) {
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()));
778             //
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();
782         }
783         EBookQueryCXX query(e_book_query_or(size, queries.get(), false), TRANSFER_REF);
784         PlainGStr sexp(e_book_query_to_string(query.get()));
785
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();
792         }
793         m_contactsFromDB += size;
794         m_contactQueries++;
795         SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts,
796                                 boost::bind(&EvolutionContactSource::completedRead,
797                                             this,
798                                             boost::weak_ptr<ContactCache>(cache),
799                                             _1, _2, _3),
800                                 m_addressbook, sexp, NULL);
801         SE_LOG_DEBUG(getDisplayName(), "reading: started contact read %s", cache->m_name.c_str());
802     }
803     return cache;
804 }
805
806 typedef GListCXX< EContact, GSList, GObjectDestructor<EContact> > ContactListCXX;
807
808 void EvolutionContactSource::completedRead(const boost::weak_ptr<ContactCache> &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw()
809 {
810     try {
811         ContactListCXX contacts(contactsPtr); // transfers ownership
812         boost::shared_ptr<ContactCache> cache = cachePtr.lock();
813         if (!cache) {
814             SE_LOG_DEBUG(getDisplayName(), "reading: contact read finished, results no longer needed: %s", gerror ? gerror->message : "<<successful>>");
815             return;
816         }
817
818         SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s finished: %s",
819                      cache->m_name.c_str(),
820                      gerror ? gerror->message : "<<successful>>");
821         if (success) {
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);
826             }
827         } else {
828             cache->m_gerror = gerror;
829         }
830         cache->m_running = false;
831     } catch (...) {
832         Exception::handle(HANDLE_EXCEPTION_FATAL);
833     }
834 }
835
836 void EvolutionContactSource::logCacheStats(Logger::Level level)
837 {
838     SE_LOG(getDisplayName(), level,
839            "requested %d, retrieved %d from DB in %d queries, misses %d/%d (%d%%), stalls %d",
840            m_contactReads,
841            m_contactsFromDB,
842            m_contactQueries,
843            m_cacheMisses, m_contactReads, m_contactReads ? m_cacheMisses * 100 / m_contactReads : 0,
844            m_cacheStalls);
845 }
846
847 #endif
848
849 void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw)
850 {
851     EContact *contact;
852     GErrorCXX gerror;
853     if (
854 #ifdef USE_EDS_CLIENT
855         !getContact(luid, &contact, gerror)
856 #else
857         !e_book_get_contact(m_addressbook,
858                             luid.c_str(),
859                             &contact,
860                             gerror)
861 #endif
862         ) {
863         if (IsContactNotFound(gerror)) {
864             throwError(SE_HERE, STATUS_NOT_FOUND, string("reading contact: ") + luid);
865         } else {
866             throwError(SE_HERE, string("reading contact ") + luid,
867                        gerror);
868         }
869     }
870
871     eptr<EContact, GObject> contactptr(contact, "contact");
872
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.
878     if (raw
879 #ifdef EVOLUTION_COMPATIBILITY
880         && e_contact_inline_local_photos
881 #endif
882         ) {
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);
886         }
887 #endif
888     }
889
890     eptr<char> vcardstr(e_vcard_to_string(&contactptr->parent,
891                                           EVC_FORMAT_VCARD_30));
892     if (!vcardstr) {
893         throwError(SE_HERE, string("failure extracting contact from Evolution " ) + luid);
894     }
895
896     item = vcardstr.get();
897 }
898
899 #ifdef USE_EDS_CLIENT
900 TrackingSyncSource::InsertItemResult EvolutionContactSource::checkBatchedInsert(const boost::shared_ptr<Pending> &pending)
901 {
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));
905     }
906     if (pending->m_gerror) {
907         pending->m_gerror.throwError(SE_HERE, pending->m_name);
908     }
909     string newrev = getRevision(pending->m_uid);
910     return TrackingSyncSource::InsertItemResult(pending->m_uid, newrev, ITEM_OKAY);
911 }
912
913 void EvolutionContactSource::completedAdd(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, GSList *uids, const GError *gerror) throw()
914 {
915     try {
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();
922         GSList *uid = uids;
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>>");
928             if (success) {
929                 (*it)->m_uid = static_cast<gchar *>(uid->data);
930                 // Get revision when engine checks the item.
931                 (*it)->m_status = REVISION;
932             } else {
933                 (*it)->m_status = DONE;
934                 (*it)->m_gerror = gerror;
935             }
936             ++it;
937             uid = uid->next;
938         }
939
940         while (it != (*batched).end()) {
941             // Should never happen.
942             SE_LOG_DEBUG((*it)->m_name, "completed: missing uid?!");
943             (*it)->m_status = DONE;
944             ++it;
945         }
946
947         g_slist_free_full(uids, g_free);
948     } catch (...) {
949         Exception::handle(HANDLE_EXCEPTION_FATAL);
950     }
951 }
952
953 void EvolutionContactSource::completedUpdate(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, const GError *gerror) throw()
954 {
955     try {
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>>");
964             if (success) {
965                 (*it)->m_status = REVISION;
966             } else {
967                 (*it)->m_status = DONE;
968                 (*it)->m_gerror = gerror;
969             }
970             ++it;
971         }
972     } catch (...) {
973         Exception::handle(HANDLE_EXCEPTION_FATAL);
974     }
975 }
976
977 void EvolutionContactSource::flushItemChanges()
978 {
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());
986         }
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,
992                                             this,
993                                             batched,
994                                             _1, _2, _3),
995                                 m_addressbook, contacts, NULL);
996     }
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());
1003         }
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,
1008                                             this,
1009                                             batched,
1010                                             _1, _2),
1011                                 m_addressbook, contacts, NULL);
1012     }
1013 }
1014
1015 void EvolutionContactSource::finishItemChanges()
1016 {
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);
1021         }
1022         SE_LOG_DEBUG(getDisplayName(), "pending operations completed");
1023     }
1024 }
1025
1026 #endif
1027
1028 TrackingSyncSource::InsertItemResult
1029 EvolutionContactSource::insertItem(const string &uid, const std::string &item, bool raw)
1030 {
1031     EContactCXX contact(e_contact_new_from_vcard(item.c_str()), TRANSFER_REF);
1032     if (contact) {
1033         e_contact_set(contact, E_CONTACT_UID,
1034                       uid.empty() ?
1035                       NULL :
1036                       const_cast<char *>(uid.c_str()));
1037         GErrorCXX gerror;
1038 #ifdef USE_EDS_CLIENT
1039         invalidateCachedContact(uid);
1040         switch (m_accessMode) {
1041         case SYNCHRONOUS:
1042             if (uid.empty()) {
1043                 gchar* newuid;
1044                 if (!e_book_client_add_contact_sync(m_addressbook, contact, &newuid, NULL, gerror)) {
1045                     throwError(SE_HERE, "add new contact", gerror);
1046                 }
1047                 PlainGStr newuidPtr(newuid);
1048                 string newrev = getRevision(newuid);
1049                 return InsertItemResult(newuid, newrev, ITEM_OKAY);
1050             } else {
1051                 if (!e_book_client_modify_contact_sync(m_addressbook, contact, NULL, gerror)) {
1052                     throwError(SE_HERE, "updating contact "+ uid, gerror);
1053                 }
1054                 string newrev = getRevision(uid);
1055                 return InsertItemResult(uid, newrev, ITEM_OKAY);
1056             }
1057             break;
1058         case BATCHED:
1059         case DEFAULT:
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;
1069             if (uid.empty()) {
1070                 m_batchedAdd.push_back(pending);
1071             } else {
1072                 m_batchedUpdate.push_back(pending);
1073             }
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));
1077             break;
1078         }
1079 #else
1080         if (uid.empty() ?
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);
1084             if (!newuid) {
1085                 throwError(SE_HERE, "no UID for contact");
1086             }
1087             string newrev = getRevision(newuid);
1088             return InsertItemResult(newuid, newrev, ITEM_OKAY);
1089         } else {
1090             throwError(SE_HERE, uid.empty() ?
1091                        "storing new contact" :
1092                        string("updating contact ") + uid,
1093                        gerror);
1094         }
1095 #endif
1096     } else {
1097         throwError(SE_HERE, string("failure parsing vcard " ) + item);
1098     }
1099     // not reached!
1100     return InsertItemResult("", "", ITEM_OKAY);
1101 }
1102
1103 void EvolutionContactSource::removeItem(const string &uid)
1104 {
1105     GErrorCXX gerror;
1106     if (
1107 #ifdef USE_EDS_CLIENT
1108         (invalidateCachedContact(uid),
1109          !e_book_client_remove_contact_by_uid_sync(m_addressbook, uid.c_str(), NULL, gerror))
1110 #else
1111         !e_book_remove_contact(m_addressbook, uid.c_str(), gerror)
1112 #endif
1113         ) {
1114         if (IsContactNotFound(gerror)) {
1115             throwError(SE_HERE, STATUS_NOT_FOUND, string("deleting contact: ") + uid);
1116         } else {
1117             throwError(SE_HERE, string( "deleting contact " ) + uid,
1118                         gerror);
1119         }
1120     }
1121 }
1122
1123 std::string EvolutionContactSource::getDescription(const string &luid)
1124 {
1125     try {
1126         EContact *contact;
1127         GErrorCXX gerror;
1128         if (
1129 #ifdef USE_EDS_CLIENT
1130             !getContact(luid,
1131                         &contact,
1132                         gerror)
1133 #else
1134             !e_book_get_contact(m_addressbook,
1135                                 luid.c_str(),
1136                                 &contact,
1137                                 gerror)
1138 #endif
1139             ) {
1140             throwError(SE_HERE, string("reading contact ") + luid,
1141                        gerror);
1142         }
1143         eptr<EContact, GObject> contactptr(contact, "contact");
1144         const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME);
1145         if (name) {
1146             return name;
1147         }
1148         const char *fileas = (const char *)e_contact_get_const(contact, E_CONTACT_FILE_AS);
1149         if (fileas) {
1150             return fileas;
1151         }
1152         EContactName *names =
1153             (EContactName *)e_contact_get(contact, E_CONTACT_NAME);
1154         std::list<std::string> buffer;
1155         if (names) {
1156             try {
1157                 if (names->given && names->given[0]) {
1158                     buffer.push_back(names->given);
1159                 }
1160                 if (names->additional && names->additional[0]) {
1161                     buffer.push_back(names->additional);
1162                 }
1163                 if (names->family && names->family[0]) {
1164                     buffer.push_back(names->family);
1165                 }
1166             } catch (...) {
1167             }
1168             e_contact_name_free(names);
1169         }
1170         return boost::join(buffer, " ");
1171     } catch (...) {
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
1175         // prevent syncs.
1176         handleException();
1177         return "";
1178     }
1179 }
1180
1181 std::string EvolutionContactSource::getMimeType() const
1182 {
1183     switch( m_vcardFormat ) {
1184      case EVC_FORMAT_VCARD_21:
1185         return "text/x-vcard";
1186         break;
1187      case EVC_FORMAT_VCARD_30:
1188      default:
1189         return "text/vcard";
1190         break;
1191     }
1192 }
1193
1194 std::string EvolutionContactSource::getMimeVersion() const
1195 {
1196     switch( m_vcardFormat ) {
1197      case EVC_FORMAT_VCARD_21:
1198         return "2.1";
1199         break;
1200      case EVC_FORMAT_VCARD_30:
1201      default:
1202         return "3.0";
1203         break;
1204     }
1205 }
1206
1207 SE_END_CXX
1208
1209 #endif /* ENABLE_EBOOK */
1210
1211 #ifdef ENABLE_MODULES
1212 # include "EvolutionContactSourceRegister.cpp"
1213 #endif