Sending again due to wrong author email in the previous patch. On Fri, Aug 5, 2011 at 11:03 AM, Dumez, Christophe <[email protected]> wrote: > Hi, > > Please find attached a patch for SyncEvolution bringing support for > the new EBookClient API present in EDS 3.2. > > *** > Support for the new EBookClient API in EDS 3.2 > > SyncEvolution EDS backend currently uses the EBook API which is > deprecated as of EDS v3.2 and replaced by the new EBookClient API. > This patch brings support for this new EBookClient API (when EDS > 3.2 is detected at compile time). > > The new code based on the EBookClient API enables a few > optimizations, especially the partial fetching of contacts to > avoid useless DBus trafic. This results in better contact change > tracking by fetching only the UID/REV attributes for each contact. > > Note: The new code uses the new GLibSupport header instead of the > deprecated SmartPointer (eptr). > *** > > Note that this patch only alters the contacts code (I think the > calendar code should be handled in a separate patch). I also haven't > optimized the EvolutionContactSource::isEmpty() yet and I'm planning > to send another patch for this (the idea would be to use a view, ask > only for the UIDs and stop the view as soon as we get a objectsAdded > signal, this way we don't fetch the whole contacts and hopefully we > don't even fetch all the UIDs, just the first burst). > > I'm awaiting review for this patch and for the > EvolutionContactSource::isEmpty() change proposal before I move onto > the next tasks. > > Kr, > -- > Dr. Christophe Dumez > Linux Software Engineer > Intel Finland Oy - Open Source Technology Center >
-- Dr. Christophe Dumez Linux Software Engineer Intel Finland Oy - Open Source Technology Center
From e85e78a016af6ea9c8e3eaa80308663c03212cdc Mon Sep 17 00:00:00 2001 From: Christophe Dumez <[email protected]> Date: Fri, 5 Aug 2011 10:50:16 +0300 Subject: [PATCH] Support for the new EBookClient API in EDS 3.2 SyncEvolution EDS backend currently uses the EBook API which is deprecated as of EDS v3.2 and replaced by the new EBookClient API. This patch brings support for this new EBookClient API (when EDS 3.2 is detected at compile time). The new code based on the EBookClient API enables a few optimizations, especially the partial fetching of contacts to avoid useless DBus trafic. This results in better contact change tracking by fetching only the UID/REV attributes for each contact. Note: The new code uses the new GLibSupport header instead of the deprecated SmartPointer (eptr). --- src/backends/evolution/EvolutionContactSource.cpp | 260 +++++++++++++++++++-- src/backends/evolution/EvolutionContactSource.h | 12 + src/backends/evolution/EvolutionSyncSource.h | 28 +++ src/syncevo/GLibSupport.cpp | 3 + src/syncevo/GLibSupport.h | 38 +++- src/syncevo/eds_abi_wrapper.h | 9 + 6 files changed, 328 insertions(+), 22 deletions(-) diff --git a/src/backends/evolution/EvolutionContactSource.cpp b/src/backends/evolution/EvolutionContactSource.cpp index 28fd082..661377e 100644 --- a/src/backends/evolution/EvolutionContactSource.cpp +++ b/src/backends/evolution/EvolutionContactSource.cpp @@ -41,25 +41,8 @@ using namespace std; #include <boost/foreach.hpp> #include <syncevo/declarations.h> -SE_BEGIN_CXX - -class unrefEBookChanges { - public: - /** free list of EBookChange instances */ - static void unref(GList *pointer) { - if (pointer) { - GList *next = pointer; - do { - EBookChange *ebc = (EBookChange *)next->data; - g_object_unref(ebc->contact); - g_free(next->data); - next = next->next; - } while (next); - g_list_free(pointer); - } - } -}; +SE_BEGIN_CXX const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions; const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties; @@ -78,7 +61,11 @@ EvolutionSyncSource::Databases EvolutionContactSource::getDatabases() { ESourceList *sources = NULL; +#ifdef USE_EBOOK_CLIENT + if (!e_book_client_get_sources(&sources, NULL)) { +#else if (!e_book_get_addressbooks(&sources, NULL)) { +#endif SyncContext::throwError("unable to access address books"); } @@ -120,6 +107,22 @@ EvolutionSyncSource::Databases EvolutionContactSource::getDatabases() // No results? Try system address book (workaround for embedded Evolution Dataserver). if (!result.size()) { +#ifdef USE_EBOOK_CLIENT + EBookClientCXX book; + const char *name; + + name = "<<system>>"; + book = EBookClientCXX::steal(e_book_client_new_system (NULL)); + if (!book) { + name = "<<default>>"; + book = EBookClientCXX::steal(e_book_client_new_default (NULL)); + } + + if (book) { + const char *uri = e_client_get_uri (E_CLIENT (book.get())); + result.push_back(Database(name, uri, true)); + } +#else eptr<EBook, GObject> book; GError *gerror = NULL; const char *name; @@ -137,6 +140,7 @@ EvolutionSyncSource::Databases EvolutionContactSource::getDatabases() const char *uri = e_book_get_uri (book); result.push_back(Database(name, uri, true)); } +#endif } else { // the first DB found is the default result[0].m_isDefault = true; @@ -145,8 +149,88 @@ EvolutionSyncSource::Databases EvolutionContactSource::getDatabases() return result; } +#ifdef USE_EBOOK_CLIENT +static void +handle_error_cb (EClient */*client*/, const gchar *error_msg, gpointer user_data) +{ + EvolutionContactSource *that = static_cast<EvolutionContactSource *>(user_data); + SE_LOG_ERROR(that, NULL, error_msg); +} + +static gboolean +handle_authentication_cb (EClient */*client*/, ECredentials *credentials, gpointer user_data) +{ + EvolutionContactSource *that = static_cast<EvolutionContactSource *>(user_data); + std::string user = that->getUser(); + std::string passwd = that->getPassword(); + + if (!user.empty() || !passwd.empty()) { + e_credentials_set (credentials, E_CREDENTIALS_KEY_USERNAME, user.c_str()); + e_credentials_set (credentials, E_CREDENTIALS_KEY_PASSWORD, passwd.c_str()); + return true; + } else { + return false; + } +} +#endif + void EvolutionContactSource::open() { +#ifdef USE_EBOOK_CLIENT + ESourceList *sources; + + if (!e_book_client_get_sources(&sources, NULL)) { + throwError("unable to access address books"); + } + + string id = getDatabaseID(); + ESource *source = findSource(sources, id); + bool onlyIfExists = true; + bool created = false; + if (!source) { + // might have been special "<<system>>" or "<<default>>", try that and + // creating address book from file:// URI before giving up + if (id.empty() || id == "<<system>>") { + m_addressbook = EBookClientCXX::steal(e_book_client_new_system (NULL)); + } else if (id.empty() || id == "<<default>>") { + m_addressbook = EBookClientCXX::steal(e_book_client_new_default (NULL)); + } else if (boost::starts_with(id, "file://")) { + m_addressbook = EBookClientCXX::steal(e_book_client_new_from_uri(id.c_str(), NULL)); + } else { + throwError(string(getName()) + ": no such address book: '" + id + "'"); + } + created = true; + onlyIfExists = false; + } else { + m_addressbook = EBookClientCXX::steal(e_book_client_new( source, NULL )); + } + + // Listen for errors + g_signal_connect (m_addressbook.get(), "backend-error", G_CALLBACK (handle_error_cb), this); + + // Handle authentication requests from the backend + g_signal_connect (m_addressbook.get(), "authenticate", G_CALLBACK (handle_authentication_cb), this); + + // Open the address book + GErrorCXX gerror; + if (!e_client_open_sync( E_CLIENT (m_addressbook.get()), onlyIfExists, NULL, gerror) ) { + if (created) { + // opening newly created address books often fails, try again once more + gerror.clear(); + sleep(5); + if (!e_client_open_sync( E_CLIENT (m_addressbook.get()), onlyIfExists, NULL, gerror)) { + gerror.throwError("opening address book"); + } + } else { + gerror.throwError("opening address book"); + } + } + + g_signal_connect_after(m_addressbook.get(), + "backend-died", + G_CALLBACK(SyncContext::fatalError), + (void *)"Evolution Data Server has died unexpectedly, contacts no longer available."); +#else ESourceList *sources; if (!e_book_get_addressbooks(&sources, NULL)) { throwError("unable to access address books"); @@ -223,6 +307,7 @@ void EvolutionContactSource::open() "backend-died", G_CALLBACK(SyncContext::fatalError), (void *)"Evolution Data Server has died unexpectedly, contacts no longer available."); +#endif } bool EvolutionContactSource::isEmpty() @@ -234,8 +319,89 @@ bool EvolutionContactSource::isEmpty() return revisions.empty(); } +#ifdef USE_EBOOK_CLIENT +struct EvolutionContactAsyncList { + EvolutionContactAsyncList(EBookClientView *view): m_view(view) + { + // Listen for view signals + g_signal_connect(view, "objects-added", G_CALLBACK(contactsAdded), this); + g_signal_connect(view, "complete", G_CALLBACK(completed), this); + + // Start the view + e_book_client_view_start (view, m_error); + if (!m_error.isNull()) + return; + + // Async -> Sync + m_loop.run(); + e_book_client_view_stop (view, NULL); + } + + static void contactsAdded(EBookClientView *ebookview, + const GSList *contacts, + gpointer user_data) { + EvolutionContactAsyncList *that = (EvolutionContactAsyncList *)user_data; + const GSList *elem; + for (elem = contacts; elem; elem = elem->next) { + EContact *contact = E_CONTACT(elem->data); + g_object_ref (contact); + that->m_contacts.push_back(contact); + } + } + + static void completed(EBookClientView *ebookview, + const GError *error, + gpointer user_data) { + EvolutionContactAsyncList *that = (EvolutionContactAsyncList *)user_data; + that->m_error = error; + that->m_loop.quit(); + } + + // View watched + EBookClientView *m_view; + // List of contacts in the view + GListCXX<EContact, GSList, GObjectDestructor<EContact> > m_contacts; + // Possible error while watching the view + GErrorCXX m_error; + // Event loop for Async -> Sync + EvolutionAsync m_loop; +}; + +#endif + void EvolutionContactSource::listAllItems(RevisionMap_t &revisions) { +#ifdef USE_EBOOK_CLIENT + GErrorCXX gerror; + EBookClientView *view; + + EBookQueryCXX allItemsQuery = EBookQueryCXX::steal(e_book_query_any_field_contains("")); + PlainCStr sexp(e_book_query_to_string (allItemsQuery.get())); + + if (!e_book_client_get_view_sync(m_addressbook.get(), sexp, &view, NULL, gerror)) { + gerror.throwError( "getting the view" ); + } + EBookClientViewCXX viewWrapper = EBookClientViewCXX::steal(view); + + // Optimization: set fields_of_interest (UID / REV) + GListCXX<const char, GSList> interesting_field_list; + interesting_field_list.push_back(e_contact_field_name (E_CONTACT_UID)); + interesting_field_list.push_back(e_contact_field_name (E_CONTACT_REV)); + e_book_client_view_set_fields_of_interest (view, interesting_field_list, gerror); + if (!gerror.isNull()) { + SE_LOG_ERROR(this, NULL, "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror); + gerror.clear(); + } + + EvolutionContactAsyncList async_list(view); + + if (!async_list.m_error.isNull()) { + async_list.m_error.throwError("watching view"); + } + + GSList *nextItem = async_list.m_contacts; + GListCXX<EContact, GSList, GObjectDestructor<EContact> > listptr(nextItem); +#else GError *gerror = NULL; eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query"); GList *nextItem; @@ -243,6 +409,7 @@ void EvolutionContactSource::listAllItems(RevisionMap_t &revisions) throwError( "reading all items", gerror ); } eptr<GList> listptr(nextItem); +#endif while (nextItem) { EContact *contact = E_CONTACT(nextItem->data); if (!contact) { @@ -274,6 +441,16 @@ void EvolutionContactSource::close() string EvolutionContactSource::getRevision(const string &luid) { EContact *contact; +#ifdef USE_EBOOK_CLIENT + GErrorCXX gerror; + if (!e_book_client_get_contact_sync(m_addressbook.get(), + luid.c_str(), + &contact, + NULL, + gerror)) { + gerror.throwError(string("reading contact ") + luid); + } +#else GError *gerror = NULL; if (!e_book_get_contact(m_addressbook, luid.c_str(), @@ -282,6 +459,7 @@ string EvolutionContactSource::getRevision(const string &luid) throwError(string("reading contact ") + luid, gerror); } +#endif eptr<EContact, GObject> contactptr(contact, "contact"); const char *rev = (const char *)e_contact_get_const(contact, E_CONTACT_REV); @@ -294,6 +472,16 @@ string EvolutionContactSource::getRevision(const string &luid) void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw) { EContact *contact; +#ifdef USE_EBOOK_CLIENT + GErrorCXX gerror; + if (!e_book_client_get_contact_sync(m_addressbook.get(), + luid.c_str(), + &contact, + NULL, + gerror)) { + gerror.throwError(string("reading contact ") + luid); + } +#else GError *gerror = NULL; if (!e_book_get_contact(m_addressbook, luid.c_str(), @@ -302,6 +490,7 @@ void EvolutionContactSource::readItem(const string &luid, std::string &item, boo throwError(string("reading contact ") + luid, gerror); } +#endif eptr<EContact, GObject> contactptr(contact, "contact"); eptr<char> vcardstr(e_vcard_to_string(&contactptr->parent, EVC_FORMAT_VCARD_30)); @@ -322,6 +511,16 @@ EvolutionContactSource::insertItem(const string &uid, const std::string &item, b uid.empty() ? NULL : const_cast<char *>(uid.c_str())); +#ifdef USE_EBOOK_CLIENT + char* newuid; + if (uid.empty() ? + e_book_client_add_contact_sync(m_addressbook.get(), contact, &newuid, NULL, &gerror) : + e_book_client_modify_contact_sync(m_addressbook.get(), contact, NULL, &gerror)) { + if (!newuid) { + throwError("no UID for contact"); + } + PlainCStr newuidWrapper(newuid); +#else if (uid.empty() ? e_book_add_contact(m_addressbook, contact, &gerror) : e_book_commit_contact(m_addressbook, contact, &gerror)) { @@ -329,6 +528,7 @@ EvolutionContactSource::insertItem(const string &uid, const std::string &item, b if (!newuid) { throwError("no UID for contact"); } +#endif string newrev = getRevision(newuid); return InsertItemResult(newuid, newrev, false); } else { @@ -346,6 +546,18 @@ EvolutionContactSource::insertItem(const string &uid, const std::string &item, b void EvolutionContactSource::removeItem(const string &uid) { +#ifdef USE_EBOOK_CLIENT + GErrorCXX gerror; + if (!e_book_client_remove_contact_by_uid_sync(m_addressbook.get(), uid.c_str(), NULL, gerror)) { + if (gerror->domain == E_BOOK_CLIENT_ERROR && + gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND) { + SE_LOG_DEBUG(this, NULL, "%s: request to delete non-existant contact ignored", + uid.c_str()); + } else { + gerror.throwError( string( "deleting contact " ) + uid ); + } + } +#else GError *gerror = NULL; if (!e_book_remove_contact(m_addressbook, uid.c_str(), &gerror)) { if (gerror->domain == E_BOOK_ERROR && @@ -358,12 +570,23 @@ void EvolutionContactSource::removeItem(const string &uid) gerror ); } } +#endif } std::string EvolutionContactSource::getDescription(const string &luid) { try { EContact *contact; +#ifdef USE_EBOOK_CLIENT + GErrorCXX gerror; + if (!e_book_client_get_contact_sync(m_addressbook.get(), + luid.c_str(), + &contact, + NULL, + gerror)) { + gerror.throwError(string("reading contact ") + luid); + } +#else GError *gerror = NULL; if (!e_book_get_contact(m_addressbook, luid.c_str(), @@ -372,6 +595,7 @@ std::string EvolutionContactSource::getDescription(const string &luid) throwError(string("reading contact ") + luid, gerror); } +#endif eptr<EContact, GObject> contactptr(contact, "contact"); const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME); if (name) { diff --git a/src/backends/evolution/EvolutionContactSource.h b/src/backends/evolution/EvolutionContactSource.h index 7d7b5ac..2b659ef 100644 --- a/src/backends/evolution/EvolutionContactSource.h +++ b/src/backends/evolution/EvolutionContactSource.h @@ -24,6 +24,7 @@ #include "config.h" #include "EvolutionSyncSource.h" #include <syncevo/SmartPtr.h> +#include <syncevo/GLibSupport.h> #include <boost/noncopyable.hpp> @@ -32,6 +33,13 @@ #include <set> #include <syncevo/declarations.h> + +#ifdef USE_EBOOK_CLIENT +SE_GOBJECT_TYPE(EBookClient) +SE_GOBJECT_TYPE(EBookClientView) +SE_GOBJECT_TYPE(EBookQuery) +#endif + SE_BEGIN_CXX /** @@ -84,7 +92,11 @@ class EvolutionContactSource : public EvolutionSyncSource, std::string getRevision(const std::string &uid); /** valid after open(): the address book that this source references */ +#ifdef USE_EBOOK_CLIENT + EBookClientCXX m_addressbook; +#else eptr<EBook, GObject> m_addressbook; +#endif /** the format of vcards that new items are expected to have */ const EVCardFormat m_vcardFormat; diff --git a/src/backends/evolution/EvolutionSyncSource.h b/src/backends/evolution/EvolutionSyncSource.h index 64ed70c..475260f 100644 --- a/src/backends/evolution/EvolutionSyncSource.h +++ b/src/backends/evolution/EvolutionSyncSource.h @@ -25,6 +25,10 @@ #include <syncevo/eds_abi_wrapper.h> #include <syncevo/declarations.h> +#include <syncevo/GLibSupport.h> + +SE_GOBJECT_TYPE(GMainLoop) + SE_BEGIN_CXX @@ -85,6 +89,30 @@ class EvolutionSyncSource : public TrackingSyncSource #endif }; +/** + * Utility class which hides the mechanisms needed to handle events + * during asynchronous calls. + */ +class EvolutionAsync { + public: + EvolutionAsync() + { + m_loop = GMainLoopCXX::steal(g_main_loop_new(NULL, FALSE)); + } + + /** start processing events */ + void run() { + g_main_loop_run(m_loop.get()); + } + + /** stop processing events, to be called inside run() by callback */ + void quit() { + g_main_loop_quit(m_loop.get()); + } + + private: + GMainLoopCXX m_loop; +}; SE_END_CXX #endif // INCL_EVOLUTIONSYNCSOURCE diff --git a/src/syncevo/GLibSupport.cpp b/src/syncevo/GLibSupport.cpp index c75f3d0..92f8f5c 100644 --- a/src/syncevo/GLibSupport.cpp +++ b/src/syncevo/GLibSupport.cpp @@ -37,6 +37,9 @@ SE_BEGIN_CXX #ifdef HAVE_GLIB +template<class T> +void GObjectDestructor(T *obj) { g_object_unref(obj); } + class Select { GMainLoop *m_loop; GMainContext *m_context; diff --git a/src/syncevo/GLibSupport.h b/src/syncevo/GLibSupport.h index e050cb4..1f50852 100644 --- a/src/syncevo/GLibSupport.h +++ b/src/syncevo/GLibSupport.h @@ -33,6 +33,7 @@ typedef void *GMainLoop; #endif +#include <boost/shared_ptr.hpp> #include <boost/intrusive_ptr.hpp> #include <boost/utility.hpp> #include <boost/foreach.hpp> @@ -157,10 +158,23 @@ struct GErrorCXX { } return *this; } + GErrorCXX &operator =(const GError* err) { + if (m_gerror) { + g_clear_error(&m_gerror); + } + m_gerror = g_error_copy(err); + return *this; + } + + /** For convenient access to GError members (message, domain, ...) */ + GError* operator-> () const { return m_gerror; } /** error description, with fallback if not set (not expected, so not localized) */ operator const char * () { return m_gerror ? m_gerror->message : "<<no error>>"; } + /** Check if a gError was set */ + bool isNull() const { return m_gerror == NULL; } + /** clear error */ ~GErrorCXX() { g_clear_error(&m_gerror); } @@ -183,6 +197,7 @@ struct GErrorCXX { }; template<class T> void NoopDestructor(T *) {} +template<class T> void GObjectDestructor(T *obj); /** * Wraps a G[S]List of pointers to a specific type. @@ -202,11 +217,11 @@ template< class T, class L, void (*D)(T*) = NoopDestructor<T> > struct GListCXX static void listFree(GSList *l) { g_slist_free(l); } static void listFree(GList *l) { g_list_free(l); } - static GSList *listPrepend(GSList *list, T *entry) { return g_slist_prepend(list, static_cast<gpointer>(entry)); } - static GList *listPrepend(GList *list, T *entry) { return g_list_prepend(list, static_cast<gpointer>(entry)); } + static GSList *listPrepend(GSList *list, T *entry) { return g_slist_prepend(list, (gpointer)entry); } + static GList *listPrepend(GList *list, T *entry) { return g_list_prepend(list, (gpointer)entry); } - static GSList *listAppend(GSList *list, T *entry) { return g_slist_append(list, static_cast<gpointer>(entry)); } - static GList *listAppend(GList *list, T *entry) { return g_list_append(list, static_cast<gpointer>(entry)); } + static GSList *listAppend(GSList *list, T *entry) { return g_slist_append(list, (gpointer)entry); } + static GList *listAppend(GList *list, T *entry) { return g_list_append(list, (gpointer)entry); } public: typedef T * value_type; @@ -214,6 +229,8 @@ template< class T, class L, void (*D)(T*) = NoopDestructor<T> > struct GListCXX /** empty error, NULL pointer */ GListCXX() : m_list(NULL) {} + GListCXX(L* l) : m_list(l) {} + /** free list */ ~GListCXX() { clear(); } @@ -289,6 +306,19 @@ template< class T, class L, void (*D)(T*) = NoopDestructor<T> > struct GListCXX void push_front(T *entry) { m_list = listPrepend(m_list, entry); } }; +/** + * Wraps a C char array and takes care of freeing the memory. + */ +class PlainCStr : public boost::shared_ptr<char> +{ + public: + PlainCStr() {} + PlainCStr(char *str) : boost::shared_ptr<char>(str, free) {} + PlainCStr(const PlainCStr &other) : boost::shared_ptr<char>(other) {} + operator const char *() const { return &**this; } + const char *c_str() const { return &**this; } +}; + #endif diff --git a/src/syncevo/eds_abi_wrapper.h b/src/syncevo/eds_abi_wrapper.h index 69d56b7..451b0bf 100644 --- a/src/syncevo/eds_abi_wrapper.h +++ b/src/syncevo/eds_abi_wrapper.h @@ -52,11 +52,20 @@ #ifdef HAVE_EDS #include <glib-object.h> +#include <libedataserver/eds-version.h> #include <libedataserver/e-source.h> #include <libedataserver/e-source-list.h> +#if EDS_CHECK_VERSION(3,1,0) +#define USE_EBOOK_CLIENT 1 +#endif #ifdef ENABLE_EBOOK +#ifdef USE_EBOOK_CLIENT +#include <libebook/e-book-client.h> +#else #include <libebook/e-book.h> +#endif #include <libebook/e-vcard.h> +#include <libebook/e-book-query.h> #endif #ifdef ENABLE_ECAL # define HANDLE_LIBICAL_MEMORY 1 -- 1.7.6
_______________________________________________ SyncEvolution mailing list [email protected] http://lists.syncevolution.org/listinfo/syncevolution
