1210 lines
44 KiB
C++
1210 lines
44 KiB
C++
/*
|
|
* Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
|
|
* Copyright (C) 2009 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <memory>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <list>
|
|
using namespace std;
|
|
|
|
#include "config.h"
|
|
#include "EvolutionSyncSource.h"
|
|
#include <syncevo/IdentityProvider.h>
|
|
|
|
#ifdef ENABLE_EBOOK
|
|
|
|
#include "EvolutionContactSource.h"
|
|
#include <syncevo/util.h>
|
|
#include <syncevo/Exception.h>
|
|
#include <syncevo/Logging.h>
|
|
|
|
#ifdef USE_EDS_CLIENT
|
|
#include <boost/range/algorithm/find.hpp>
|
|
#endif
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/algorithm/string/join.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/lambda/lambda.hpp>
|
|
|
|
#include <syncevo/declarations.h>
|
|
|
|
SE_GLIB_TYPE(EBookQuery, e_book_query)
|
|
|
|
SE_BEGIN_CXX
|
|
|
|
inline bool IsContactNotFound(const GError *gerror) {
|
|
return gerror &&
|
|
#ifdef USE_EDS_CLIENT
|
|
gerror->domain == E_BOOK_CLIENT_ERROR &&
|
|
gerror->code == E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND
|
|
#else
|
|
gerror->domain == E_BOOK_ERROR &&
|
|
gerror->code == E_BOOK_ERROR_CONTACT_NOT_FOUND
|
|
#endif
|
|
;
|
|
}
|
|
|
|
|
|
const EvolutionContactSource::extensions EvolutionContactSource::m_vcardExtensions;
|
|
const EvolutionContactSource::unique EvolutionContactSource::m_uniqueProperties;
|
|
|
|
EvolutionContactSource::EvolutionContactSource(const SyncSourceParams ¶ms,
|
|
EVCardFormat vcardFormat) :
|
|
EvolutionSyncSource(params),
|
|
m_vcardFormat(vcardFormat)
|
|
{
|
|
#ifdef USE_EDS_CLIENT
|
|
m_cacheMisses =
|
|
m_cacheStalls =
|
|
m_contactReads =
|
|
m_contactsFromDB =
|
|
m_contactQueries = 0;
|
|
m_readAheadOrder = READ_NONE;
|
|
const char *mode = getEnv("SYNCEVOLUTION_EDS_ACCESS_MODE", "");
|
|
m_accessMode = boost::iequals(mode, "synchronous") ? SYNCHRONOUS :
|
|
boost::iequals(mode, "batched") ? BATCHED :
|
|
DEFAULT;
|
|
#endif
|
|
SyncSourceLogging::init(InitList<std::string>("N_FIRST") + "N_MIDDLE" + "N_LAST",
|
|
" ",
|
|
m_operations);
|
|
}
|
|
|
|
EvolutionContactSource::~EvolutionContactSource()
|
|
{
|
|
// Don't close while we have pending operations. They might
|
|
// complete after we got destroyed, causing them to use an invalid
|
|
// "this" pointer. We also don't know how well EDS copes with
|
|
// closing the address book while it has pending operations - EDS
|
|
// maintainer mcrha wasn't sure.
|
|
//
|
|
// TODO: cancel the operations().
|
|
finishItemChanges();
|
|
close();
|
|
|
|
#ifdef USE_EDS_CLIENT
|
|
// logCacheStats(Logger::DEBUG);
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_EDS_CLIENT
|
|
static EClient *newEBookClient(ESource *source,
|
|
GError **gerror)
|
|
{
|
|
return E_CLIENT(e_book_client_new(source, gerror));
|
|
}
|
|
#endif
|
|
|
|
EvolutionSyncSource::Databases EvolutionContactSource::getDatabases()
|
|
{
|
|
Databases result;
|
|
#ifdef USE_EDS_CLIENT
|
|
getDatabasesFromRegistry(result,
|
|
E_SOURCE_EXTENSION_ADDRESS_BOOK,
|
|
e_source_registry_ref_default_address_book);
|
|
#else
|
|
ESourceList *sources = NULL;
|
|
if (!e_book_get_addressbooks(&sources, NULL)) {
|
|
Exception::throwError(SE_HERE, "unable to access address books");
|
|
}
|
|
|
|
Databases secondary;
|
|
for (GSList *g = e_source_list_peek_groups (sources); g; g = g->next) {
|
|
ESourceGroup *group = E_SOURCE_GROUP (g->data);
|
|
for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) {
|
|
ESource *source = E_SOURCE (s->data);
|
|
eptr<char> uri(e_source_get_uri(source));
|
|
string uristr;
|
|
if (uri) {
|
|
uristr = uri.get();
|
|
}
|
|
Database entry(e_source_peek_name(source),
|
|
uristr,
|
|
false);
|
|
if (boost::starts_with(uristr, "couchdb://")) {
|
|
// Append CouchDB address books at the end of the list,
|
|
// otherwise preserving the order of address books.
|
|
//
|
|
// The reason is Moblin Bugzilla #7877 (aka CouchDB
|
|
// feature request #479110): the initial release of
|
|
// evolution-couchdb in Ubuntu 9.10 is unusable because
|
|
// it does not support the REV property.
|
|
//
|
|
// Reordering the entries ensures that the CouchDB
|
|
// address book is not used as the default database by
|
|
// SyncEvolution, as it happened in Ubuntu 9.10.
|
|
// Users can still pick it intentionally via
|
|
// "evolutionsource".
|
|
secondary.push_back(entry);
|
|
} else {
|
|
result.push_back(entry);
|
|
}
|
|
}
|
|
}
|
|
result.insert(result.end(), secondary.begin(), secondary.end());
|
|
|
|
// No results? Try system address book (workaround for embedded Evolution Dataserver).
|
|
if (!result.size()) {
|
|
eptr<EBook, GObject> book;
|
|
GErrorCXX gerror;
|
|
const char *name;
|
|
|
|
name = "<<system>>";
|
|
book = e_book_new_system_addressbook (gerror);
|
|
gerror.clear();
|
|
if (!book) {
|
|
name = "<<default>>";
|
|
book = e_book_new_default_addressbook (gerror);
|
|
}
|
|
|
|
if (book) {
|
|
const char *uri = e_book_get_uri (book);
|
|
result.push_back(Database(name, uri, true));
|
|
}
|
|
} else {
|
|
// the first DB found is the default
|
|
result[0].m_isDefault = true;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
void EvolutionContactSource::open()
|
|
{
|
|
#ifdef USE_EDS_CLIENT
|
|
m_addressbook.reset(E_BOOK_CLIENT(openESource(E_SOURCE_EXTENSION_ADDRESS_BOOK,
|
|
e_source_registry_ref_builtin_address_book,
|
|
newEBookClient).get()));
|
|
#else
|
|
GErrorCXX gerror;
|
|
bool created = false;
|
|
bool onlyIfExists = false; // always try to create address book, because even if there is
|
|
// a source there's no guarantee that the actual database was
|
|
// created already; the original logic below for only setting
|
|
// this when explicitly requesting a new address book
|
|
// therefore failed in some cases
|
|
ESourceList *tmp;
|
|
if (!e_book_get_addressbooks(&tmp, gerror)) {
|
|
throwError(SE_HERE, "unable to access address books", gerror);
|
|
}
|
|
ESourceListCXX sources(tmp, TRANSFER_REF);
|
|
|
|
string id = getDatabaseID();
|
|
ESource *source = findSource(sources, id);
|
|
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.set( e_book_new_system_addressbook (gerror), "system address book" );
|
|
} else if (id.empty() || id == "<<default>>") {
|
|
m_addressbook.set( e_book_new_default_addressbook (gerror), "default address book" );
|
|
} else if (boost::starts_with(id, "file://")) {
|
|
m_addressbook.set(e_book_new_from_uri(id.c_str(), gerror), "creating address book");
|
|
} else {
|
|
throwError(SE_HERE, string(getName()) + ": no such address book: '" + id + "'");
|
|
}
|
|
created = true;
|
|
} else {
|
|
m_addressbook.set( e_book_new( source, gerror ), "address book" );
|
|
}
|
|
|
|
if (!e_book_open( m_addressbook, onlyIfExists, gerror) ) {
|
|
if (created) {
|
|
// opening newly created address books often fails, try again once more
|
|
sleep(5);
|
|
if (!e_book_open(m_addressbook, onlyIfExists, gerror)) {
|
|
throwError(SE_HERE, "opening address book", gerror);
|
|
}
|
|
} else {
|
|
throwError(SE_HERE, "opening address book", gerror);
|
|
}
|
|
}
|
|
|
|
// users are not expected to configure an authentication method,
|
|
// so pick one automatically if the user indicated that he wants authentication
|
|
// by setting user or password
|
|
UserIdentity identity = getUser();
|
|
InitStateString passwd = getPassword();
|
|
if (identity.wasSet() || passwd.wasSet()) {
|
|
GList *authmethod;
|
|
if (!e_book_get_supported_auth_methods(m_addressbook, &authmethod, gerror)) {
|
|
throwError(SE_HERE, "getting authentication methods", gerror);
|
|
}
|
|
while (authmethod) {
|
|
// map identity + password to plain username/password credentials
|
|
Credentials cred = IdentityProviderCredentials(identity, passwd);
|
|
const char *method = (const char *)authmethod->data;
|
|
SE_LOG_DEBUG(getDisplayName(), "trying authentication method \"%s\", user %s, password %s",
|
|
method,
|
|
identity.wasSet() ? "configured" : "not configured",
|
|
passwd.wasSet() ? "configured" : "not configured");
|
|
if (e_book_authenticate_user(m_addressbook,
|
|
cred.m_username.c_str(),
|
|
cred.m_password.c_str(),
|
|
method,
|
|
gerror)) {
|
|
SE_LOG_DEBUG(getDisplayName(), "authentication succeeded");
|
|
break;
|
|
} else {
|
|
SE_LOG_ERROR(getDisplayName(), "authentication failed: %s", gerror->message);
|
|
}
|
|
authmethod = authmethod->next;
|
|
}
|
|
}
|
|
|
|
g_signal_connect_after(m_addressbook,
|
|
"backend-died",
|
|
G_CALLBACK(Exception::fatalError),
|
|
(void *)"Evolution Data Server has died unexpectedly, contacts no longer available.");
|
|
#endif
|
|
}
|
|
|
|
bool EvolutionContactSource::isEmpty()
|
|
{
|
|
// TODO: add more efficient implementation which does not
|
|
// depend on actually pulling all items from EDS
|
|
RevisionMap_t revisions;
|
|
listAllItems(revisions);
|
|
return revisions.empty();
|
|
}
|
|
|
|
#ifdef USE_EDS_CLIENT
|
|
class EBookClientViewSyncHandler {
|
|
public:
|
|
typedef boost::function<void (const GSList *list)> Process_t;
|
|
|
|
EBookClientViewSyncHandler(const EBookClientViewCXX &view,
|
|
const Process_t &process) :
|
|
m_process(process),
|
|
m_view(view)
|
|
{}
|
|
|
|
bool process(GErrorCXX &gerror) {
|
|
// Listen for view signals
|
|
m_view.connectSignal<void (EBookClientView *ebookview,
|
|
const GSList *contacts)>("objects-added",
|
|
boost::bind(m_process, _2));
|
|
m_view.connectSignal<void (EBookClientView *ebookview,
|
|
const GError *error)>("complete",
|
|
boost::bind(&EBookClientViewSyncHandler::completed, this, _2));
|
|
|
|
// Start the view
|
|
e_book_client_view_start (m_view, m_error);
|
|
if (m_error) {
|
|
std::swap(gerror, m_error);
|
|
return false;
|
|
}
|
|
|
|
// Async -> Sync
|
|
m_loop.run();
|
|
e_book_client_view_stop (m_view, NULL);
|
|
|
|
if (m_error) {
|
|
std::swap(gerror, m_error);
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void completed(const GError *error) {
|
|
m_error = error;
|
|
m_loop.quit();
|
|
}
|
|
|
|
public:
|
|
// Event loop for Async -> Sync
|
|
EvolutionAsync m_loop;
|
|
|
|
private:
|
|
// Process list callback
|
|
boost::function<void (const GSList *list)> m_process;
|
|
// View watched
|
|
EBookClientViewCXX m_view;
|
|
// Possible error while watching the view
|
|
GErrorCXX m_error;
|
|
};
|
|
|
|
static void list_revisions(const GSList *contacts, EvolutionContactSource::RevisionMap_t *revisions)
|
|
{
|
|
const GSList *l;
|
|
|
|
for (l = contacts; l; l = l->next) {
|
|
EContact *contact = E_CONTACT(l->data);
|
|
if (!contact) {
|
|
SE_THROW("contact entry without data");
|
|
}
|
|
pair<string, string> revmapping;
|
|
const char *uid = (const char *)e_contact_get_const(contact,
|
|
E_CONTACT_UID);
|
|
if (!uid || !uid[0]) {
|
|
SE_THROW("contact entry without UID");
|
|
}
|
|
revmapping.first = uid;
|
|
const char *rev = (const char *)e_contact_get_const(contact,
|
|
E_CONTACT_REV);
|
|
if (!rev || !rev[0]) {
|
|
SE_THROW(string("contact entry without REV: ") + revmapping.first);
|
|
}
|
|
revmapping.second = rev;
|
|
revisions->insert(revmapping);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void EvolutionContactSource::listAllItems(RevisionMap_t &revisions)
|
|
{
|
|
#ifdef USE_EDS_CLIENT
|
|
GErrorCXX gerror;
|
|
EBookClientView *view;
|
|
|
|
EBookQueryCXX allItemsQuery(e_book_query_any_field_contains(""), TRANSFER_REF);
|
|
PlainGStr buffer(e_book_query_to_string (allItemsQuery.get()));
|
|
const char *sexp = getenv("SYNCEVOLUTION_EBOOK_QUERY");
|
|
if (sexp) {
|
|
SE_LOG_INFO(NULL, "restricting item set to items matching %s", sexp);
|
|
} else {
|
|
sexp = buffer;
|
|
}
|
|
|
|
if (!e_book_client_get_view_sync(m_addressbook, sexp, &view, NULL, gerror)) {
|
|
throwError(SE_HERE, "getting the view" , gerror);
|
|
}
|
|
EBookClientViewCXX viewPtr = 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 (viewPtr, interesting_field_list, gerror);
|
|
if (gerror) {
|
|
SE_LOG_ERROR(getDisplayName(), "e_book_client_view_set_fields_of_interest: %s", (const char*)gerror);
|
|
gerror.clear();
|
|
}
|
|
|
|
EBookClientViewSyncHandler handler(viewPtr, boost::bind(list_revisions, _1, &revisions));
|
|
if (!handler.process(gerror)) {
|
|
throwError(SE_HERE, "watching view", gerror);
|
|
}
|
|
#else
|
|
GErrorCXX gerror;
|
|
eptr<EBookQuery> allItemsQuery(e_book_query_any_field_contains(""), "query");
|
|
GList *nextItem;
|
|
if (!e_book_get_contacts(m_addressbook, allItemsQuery, &nextItem, gerror)) {
|
|
throwError(SE_HERE, "reading all items", gerror );
|
|
}
|
|
eptr<GList> listptr(nextItem);
|
|
while (nextItem) {
|
|
EContact *contact = E_CONTACT(nextItem->data);
|
|
if (!contact) {
|
|
throwError(SE_HERE, "contact entry without data");
|
|
}
|
|
pair<string, string> revmapping;
|
|
const char *uid = (const char *)e_contact_get_const(contact,
|
|
E_CONTACT_UID);
|
|
if (!uid || !uid[0]) {
|
|
throwError(SE_HERE, "contact entry without UID");
|
|
}
|
|
revmapping.first = uid;
|
|
const char *rev = (const char *)e_contact_get_const(contact,
|
|
E_CONTACT_REV);
|
|
if (!rev || !rev[0]) {
|
|
throwError(SE_HERE, string("contact entry without REV: ") + revmapping.first);
|
|
}
|
|
revmapping.second = rev;
|
|
revisions.insert(revmapping);
|
|
nextItem = nextItem->next;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void EvolutionContactSource::close()
|
|
{
|
|
m_addressbook.reset();
|
|
}
|
|
|
|
string EvolutionContactSource::getRevision(const string &luid)
|
|
{
|
|
if (!needChanges()) {
|
|
return "";
|
|
}
|
|
|
|
EContact *contact;
|
|
GErrorCXX gerror;
|
|
if (
|
|
#ifdef USE_EDS_CLIENT
|
|
!e_book_client_get_contact_sync(m_addressbook,
|
|
luid.c_str(),
|
|
&contact,
|
|
NULL,
|
|
gerror)
|
|
#else
|
|
!e_book_get_contact(m_addressbook,
|
|
luid.c_str(),
|
|
&contact,
|
|
gerror)
|
|
#endif
|
|
) {
|
|
if (IsContactNotFound(gerror)) {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, string("retrieving item: ") + luid);
|
|
} else {
|
|
throwError(SE_HERE, string("reading contact ") + luid,
|
|
gerror);
|
|
}
|
|
}
|
|
eptr<EContact, GObject> contactptr(contact, "contact");
|
|
const char *rev = (const char *)e_contact_get_const(contact,
|
|
E_CONTACT_REV);
|
|
if (!rev || !rev[0]) {
|
|
throwError(SE_HERE, string("contact entry without REV: ") + luid);
|
|
}
|
|
return rev;
|
|
}
|
|
|
|
#ifdef USE_EDS_CLIENT
|
|
class ContactCache : public std::map<std::string, EContactCXX>
|
|
{
|
|
public:
|
|
/** Asynchronous method call still pending. */
|
|
bool m_running;
|
|
/** The last luid requested in this query. Needed to start with the next contact after it. */
|
|
std::string m_lastLUID;
|
|
/** Result of batch read. Any error here means that the call failed completely. */
|
|
GErrorCXX m_gerror;
|
|
/** A debug logging name for this query. */
|
|
std::string m_name;
|
|
};
|
|
|
|
void EvolutionContactSource::setReadAheadOrder(ReadAheadOrder order,
|
|
const ReadAheadItems &luids)
|
|
{
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: set order '%s', %ld luids",
|
|
order == READ_NONE ? "none" :
|
|
order == READ_ALL_ITEMS ? "all" :
|
|
order == READ_CHANGED_ITEMS ? "changed" :
|
|
order == READ_SELECTED_ITEMS ? "selected" :
|
|
"???",
|
|
(long)luids.size());
|
|
m_readAheadOrder = order;
|
|
m_nextLUIDs = luids;
|
|
|
|
// Be conservative and throw away all cached data. Not doing so
|
|
// can confuse our "cache miss" counting, for example when it uses
|
|
// a cache where some entries have been removed in
|
|
// invalidateCachedContact() and then mistakes the gaps for cache
|
|
// misses.
|
|
//
|
|
// Another reason is that we want to use fairly recent data (in
|
|
// case of concurrent changes in the DB, which currently is not
|
|
// detected by the cache).
|
|
m_contactCache.reset();
|
|
m_contactCacheNext.reset();
|
|
}
|
|
|
|
void EvolutionContactSource::getReadAheadOrder(ReadAheadOrder &order,
|
|
ReadAheadItems &luids)
|
|
{
|
|
order = m_readAheadOrder;
|
|
luids = m_nextLUIDs;
|
|
}
|
|
|
|
void EvolutionContactSource::checkCacheForError(boost::shared_ptr<ContactCache> &cache)
|
|
{
|
|
if (cache->m_gerror) {
|
|
GErrorCXX gerror;
|
|
std::swap(gerror, cache->m_gerror);
|
|
cache.reset();
|
|
throwError(SE_HERE, StringPrintf("reading contacts %s", cache->m_name.c_str()), gerror);
|
|
}
|
|
}
|
|
|
|
void EvolutionContactSource::invalidateCachedContact(const std::string &luid)
|
|
{
|
|
invalidateCachedContact(m_contactCache, luid);
|
|
invalidateCachedContact(m_contactCacheNext, luid);
|
|
}
|
|
|
|
void EvolutionContactSource::invalidateCachedContact(boost::shared_ptr<ContactCache> &cache, const std::string &luid)
|
|
{
|
|
if (cache) {
|
|
ContactCache::iterator it = cache->find(luid);
|
|
if (it != cache->end()) {
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: remove contact %s from cache because of remove or update", luid.c_str());
|
|
// If we happen to read that contact (unlikely), it'll be
|
|
// considered a cache miss. That's okay. Together with
|
|
// counting cache misses it'll help us avoid using
|
|
// read-ahead when the Synthesis engine is randomly
|
|
// accessing contacts.
|
|
cache->erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EvolutionContactSource::getContact(const string &luid, EContact **contact, GErrorCXX &gerror)
|
|
{
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: getting contact %s", luid.c_str());
|
|
|
|
// Use switch and let compiler tell us when we don't cover a case.
|
|
ReadAheadOrder order
|
|
#ifndef __clang_analyzer__
|
|
// scan-build would complain: value stored to 'order' during
|
|
// its initialization is never read. But we need to keep it
|
|
// otherwise, to avoid: 'order' may be used uninitialized in
|
|
// this function [-Werror=maybe-uninitialized]
|
|
= m_readAheadOrder
|
|
#endif
|
|
;
|
|
switch (m_accessMode) {
|
|
case SYNCHRONOUS:
|
|
order = READ_NONE;
|
|
break;
|
|
case BATCHED:
|
|
case DEFAULT:
|
|
order = m_readAheadOrder;
|
|
break;
|
|
};
|
|
|
|
m_contactReads++;
|
|
if (order == READ_NONE) {
|
|
m_contactsFromDB++;
|
|
m_contactQueries++;
|
|
return e_book_client_get_contact_sync(m_addressbook,
|
|
luid.c_str(),
|
|
contact,
|
|
NULL,
|
|
gerror);
|
|
} else {
|
|
return getContactFromCache(luid, contact, gerror);
|
|
}
|
|
}
|
|
|
|
bool EvolutionContactSource::getContactFromCache(const string &luid, EContact **contact, GErrorCXX &gerror)
|
|
{
|
|
*contact = NULL;
|
|
|
|
// Use ContactCache.
|
|
if (m_contactCache) {
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: active cache %s", m_contactCache->m_name.c_str());
|
|
// Ran into a problem?
|
|
checkCacheForError(m_contactCache);
|
|
|
|
// Does the cache cover our item?
|
|
ContactCache::const_iterator it = m_contactCache->find(luid);
|
|
if (it == m_contactCache->end()) {
|
|
if (m_contactCacheNext) {
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, try cache %s",
|
|
m_contactCacheNext->m_name.c_str());
|
|
// Throw away old cache, try with next one. This is not
|
|
// a cache miss (yet).
|
|
m_contactCache = m_contactCacheNext;
|
|
m_contactCacheNext.reset();
|
|
return getContactFromCache(luid, contact, gerror);
|
|
} else {
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: not in cache, nothing pending -> start reading");
|
|
// Throw away cache, start new read below.
|
|
m_contactCache.reset();
|
|
}
|
|
} else {
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: in %s cache", m_contactCache->m_running ? "running" : "loaded");
|
|
if (m_contactCache->m_running) {
|
|
m_cacheStalls++;
|
|
GRunWhile(boost::lambda::var(m_contactCache->m_running));
|
|
}
|
|
// Problem?
|
|
checkCacheForError(m_contactCache);
|
|
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: in cache, %s", it->second ? "available" : "not found");
|
|
if (it->second) {
|
|
// Got it.
|
|
*contact = it->second.ref();
|
|
} else {
|
|
// Delay throwing error. We need to go through the read-ahead code below.
|
|
gerror.take(g_error_new(E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND,
|
|
"uid %s not found in batch read", luid.c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// No current cache? In that case we must read and block.
|
|
if (!m_contactCache) {
|
|
m_contactCache = startReading(luid, START);
|
|
// Call code above recursively, which will block.
|
|
return getContactFromCache(luid, contact, gerror);
|
|
}
|
|
|
|
// Can we read ahead?
|
|
if (!m_contactCacheNext && !m_contactCache->m_running) {
|
|
m_contactCacheNext = startReading(m_contactCache->m_lastLUID, CONTINUE);
|
|
}
|
|
|
|
// Everything is okay when we get here. Either we have the contact or
|
|
// it wasn't in the database.
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: read %s: %s", luid.c_str(), gerror ? gerror->message : "<<okay>>");
|
|
logCacheStats(Logger::DEBUG);
|
|
return !gerror;
|
|
}
|
|
|
|
static int MaxBatchSize()
|
|
{
|
|
int maxBatchSize = atoi(getEnv("SYNCEVOLUTION_EDS_BATCH_SIZE", "50"));
|
|
if (maxBatchSize < 1) {
|
|
maxBatchSize = 1;
|
|
}
|
|
return maxBatchSize;
|
|
}
|
|
|
|
boost::shared_ptr<ContactCache> EvolutionContactSource::startReading(const std::string &luid, ReadingMode mode)
|
|
{
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: %s contact %s",
|
|
mode == START ? "must read" :
|
|
mode == CONTINUE ? "continue after" :
|
|
"???",
|
|
luid.c_str());
|
|
|
|
static int maxBatchSize = MaxBatchSize();
|
|
std::vector<EBookQueryCXX> uidQueries;
|
|
uidQueries.resize(maxBatchSize);
|
|
std::vector<const std::string *> uids;
|
|
uids.resize(maxBatchSize);
|
|
int size = 0;
|
|
bool found = false;
|
|
|
|
switch (m_readAheadOrder) {
|
|
case READ_ALL_ITEMS:
|
|
case READ_CHANGED_ITEMS: {
|
|
const Items_t &items = getAllItems();
|
|
const Items_t &newItems = getNewItems();
|
|
const Items_t &updatedItems = getUpdatedItems();
|
|
Items_t::const_iterator it = items.find(luid);
|
|
|
|
// Always read the requested item, even if not found in item list?
|
|
if (mode == START) {
|
|
uids[0] = &luid;
|
|
size++;
|
|
}
|
|
// luid is dealt with, either way.
|
|
if (it != items.end()) {
|
|
// Check that it is a valid candidate for caching, else
|
|
// we have a cache miss prediction.
|
|
if (m_readAheadOrder == READ_ALL_ITEMS ||
|
|
newItems.find(luid) != newItems.end() ||
|
|
updatedItems.find(luid) != updatedItems.end()) {
|
|
found = true;
|
|
}
|
|
++it;
|
|
}
|
|
while (size < maxBatchSize &&
|
|
it != items.end()) {
|
|
const std::string &luid = *it;
|
|
if (m_readAheadOrder == READ_ALL_ITEMS ||
|
|
newItems.find(luid) != newItems.end() ||
|
|
updatedItems.find(luid) != updatedItems.end()) {
|
|
uids[size] = &luid;
|
|
++size;
|
|
}
|
|
++it;
|
|
}
|
|
break;
|
|
}
|
|
case READ_SELECTED_ITEMS: {
|
|
ReadAheadItems::const_iterator it = boost::find(std::make_pair(m_nextLUIDs.begin(), m_nextLUIDs.end()), luid);
|
|
// Always read the requested item, even if not found in item list?
|
|
if (mode == START) {
|
|
uids[0] = &luid;
|
|
size++;
|
|
}
|
|
// luid is dealt with, either way.
|
|
if (it != m_nextLUIDs.end()) {
|
|
found = true;
|
|
++it;
|
|
}
|
|
while (size < maxBatchSize &&
|
|
it != m_nextLUIDs.end()) {
|
|
uids[size] = &*it;
|
|
++size;
|
|
++it;
|
|
}
|
|
break;
|
|
}
|
|
case READ_NONE:
|
|
// May be reached when read-ahead was turned off while
|
|
// preparing for it.
|
|
if (mode == START) {
|
|
uids[0] = &luid;
|
|
size++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (m_readAheadOrder != READ_NONE &&
|
|
mode == START &&
|
|
!found) {
|
|
// The requested contact was not on our list. Consider this
|
|
// a cache miss (or rather, cache prediction failure) and turn
|
|
// of the read-ahead.
|
|
m_cacheMisses++;
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: disable read-ahead due to cache miss");
|
|
m_readAheadOrder = READ_NONE;
|
|
}
|
|
|
|
boost::shared_ptr<ContactCache> cache;
|
|
if (size) {
|
|
// Prepare parameter for EDS C call. Ownership of query instances is in uidQueries array.
|
|
boost::scoped_array<EBookQuery *> queries(new EBookQuery *[size]);
|
|
for (int i = 0; i < size; i++) {
|
|
// This shouldn't compile because we don't specify how ownership is handled.
|
|
// The reset() method always bumps the ref count, which is not what we want here!
|
|
// uidQueries[i].reset(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, it->c_str()));
|
|
//
|
|
// Take over ownership.
|
|
uidQueries[i] = EBookQueryCXX::steal(e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, uids[i]->c_str()));
|
|
queries[i] = uidQueries[i].get();
|
|
}
|
|
EBookQueryCXX query(e_book_query_or(size, queries.get(), false), TRANSFER_REF);
|
|
PlainGStr sexp(e_book_query_to_string(query.get()));
|
|
|
|
cache.reset(new ContactCache);
|
|
cache->m_running = true;
|
|
cache->m_name = StringPrintf("%s-%s (%d)", uids[0]->c_str(), uids[size - 1]->c_str(), size);
|
|
cache->m_lastLUID = *uids[size - 1];
|
|
BOOST_FOREACH (const std::string *uid, std::make_pair(uids.begin(), uids.begin() + size)) {
|
|
(*cache)[*uid] = EContactCXX();
|
|
}
|
|
m_contactsFromDB += size;
|
|
m_contactQueries++;
|
|
SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts,
|
|
boost::bind(&EvolutionContactSource::completedRead,
|
|
this,
|
|
boost::weak_ptr<ContactCache>(cache),
|
|
_1, _2, _3),
|
|
m_addressbook, sexp, NULL);
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: started contact read %s", cache->m_name.c_str());
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
typedef GListCXX< EContact, GSList, GObjectDestructor<EContact> > ContactListCXX;
|
|
|
|
void EvolutionContactSource::completedRead(const boost::weak_ptr<ContactCache> &cachePtr, gboolean success, GSList *contactsPtr, const GError *gerror) throw()
|
|
{
|
|
try {
|
|
ContactListCXX contacts(contactsPtr); // transfers ownership
|
|
boost::shared_ptr<ContactCache> cache = cachePtr.lock();
|
|
if (!cache) {
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: contact read finished, results no longer needed: %s", gerror ? gerror->message : "<<successful>>");
|
|
return;
|
|
}
|
|
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s finished: %s",
|
|
cache->m_name.c_str(),
|
|
gerror ? gerror->message : "<<successful>>");
|
|
if (success) {
|
|
BOOST_FOREACH (EContact *contact, contacts) {
|
|
const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
|
|
SE_LOG_DEBUG(getDisplayName(), "reading: contact read %s got %s", cache->m_name.c_str(), uid);
|
|
(*cache)[uid] = EContactCXX(contact, ADD_REF);
|
|
}
|
|
} else {
|
|
cache->m_gerror = gerror;
|
|
}
|
|
cache->m_running = false;
|
|
} catch (...) {
|
|
Exception::handle(HANDLE_EXCEPTION_FATAL);
|
|
}
|
|
}
|
|
|
|
void EvolutionContactSource::logCacheStats(Logger::Level level)
|
|
{
|
|
SE_LOG(getDisplayName(), level,
|
|
"requested %d, retrieved %d from DB in %d queries, misses %d/%d (%d%%), stalls %d",
|
|
m_contactReads,
|
|
m_contactsFromDB,
|
|
m_contactQueries,
|
|
m_cacheMisses, m_contactReads, m_contactReads ? m_cacheMisses * 100 / m_contactReads : 0,
|
|
m_cacheStalls);
|
|
}
|
|
|
|
#endif
|
|
|
|
void EvolutionContactSource::readItem(const string &luid, std::string &item, bool raw)
|
|
{
|
|
EContact *contact;
|
|
GErrorCXX gerror;
|
|
if (
|
|
#ifdef USE_EDS_CLIENT
|
|
!getContact(luid, &contact, gerror)
|
|
#else
|
|
!e_book_get_contact(m_addressbook,
|
|
luid.c_str(),
|
|
&contact,
|
|
gerror)
|
|
#endif
|
|
) {
|
|
if (IsContactNotFound(gerror)) {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, string("reading contact: ") + luid);
|
|
} else {
|
|
throwError(SE_HERE, string("reading contact ") + luid,
|
|
gerror);
|
|
}
|
|
}
|
|
|
|
eptr<EContact, GObject> contactptr(contact, "contact");
|
|
|
|
// Inline PHOTO data if exporting, leave VALUE=uri references unchanged
|
|
// when processing inside engine (will be inlined by engine as needed).
|
|
// The function for doing the inlining was added in EDS 3.4.
|
|
// In compatibility mode, we must check the function pointer for non-NULL.
|
|
// In direct call mode, the existence check is done by configure.
|
|
if (raw) {
|
|
#if defined(HAVE_E_CONTACT_INLINE_LOCAL_PHOTOS)
|
|
if (!e_contact_inline_local_photos(contactptr, gerror)) {
|
|
throwError(SE_HERE, string("inlining PHOTO file data in ") + luid, gerror);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
eptr<char> vcardstr(e_vcard_to_string(&contactptr->parent,
|
|
EVC_FORMAT_VCARD_30));
|
|
if (!vcardstr) {
|
|
throwError(SE_HERE, string("failure extracting contact from Evolution " ) + luid);
|
|
}
|
|
|
|
item = vcardstr.get();
|
|
}
|
|
|
|
#ifdef USE_EDS_CLIENT
|
|
TrackingSyncSource::InsertItemResult EvolutionContactSource::checkBatchedInsert(const boost::shared_ptr<Pending> &pending)
|
|
{
|
|
SE_LOG_DEBUG(pending->m_name, "checking operation: %s", pending->m_status == MODIFYING ? "waiting" : "inserted");
|
|
if (pending->m_status == MODIFYING) {
|
|
return TrackingSyncSource::InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending));
|
|
}
|
|
if (pending->m_gerror) {
|
|
pending->m_gerror.throwError(SE_HERE, pending->m_name);
|
|
}
|
|
string newrev = getRevision(pending->m_uid);
|
|
return TrackingSyncSource::InsertItemResult(pending->m_uid, newrev, ITEM_OKAY);
|
|
}
|
|
|
|
void EvolutionContactSource::completedAdd(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, GSList *uids, const GError *gerror) throw()
|
|
{
|
|
try {
|
|
// The destructor ensures that the pending operations complete
|
|
// before destructing the instance, so our "this" pointer is
|
|
// always valid here.
|
|
SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts completed", (int)batched->size());
|
|
m_numRunningOperations--;
|
|
PendingContainer_t::iterator it = (*batched).begin();
|
|
GSList *uid = uids;
|
|
while (it != (*batched).end() && uid) {
|
|
SE_LOG_DEBUG((*it)->m_name, "completed: %s",
|
|
success ? "<<successfully>>" :
|
|
gerror ? gerror->message :
|
|
"<<unknown failure>>");
|
|
if (success) {
|
|
(*it)->m_uid = static_cast<gchar *>(uid->data);
|
|
// Get revision when engine checks the item.
|
|
(*it)->m_status = REVISION;
|
|
} else {
|
|
(*it)->m_status = DONE;
|
|
(*it)->m_gerror = gerror;
|
|
}
|
|
++it;
|
|
uid = uid->next;
|
|
}
|
|
|
|
while (it != (*batched).end()) {
|
|
// Should never happen.
|
|
SE_LOG_DEBUG((*it)->m_name, "completed: missing uid?!");
|
|
(*it)->m_status = DONE;
|
|
++it;
|
|
}
|
|
|
|
g_slist_free_full(uids, g_free);
|
|
} catch (...) {
|
|
Exception::handle(HANDLE_EXCEPTION_FATAL);
|
|
}
|
|
}
|
|
|
|
void EvolutionContactSource::completedUpdate(const boost::shared_ptr<PendingContainer_t> &batched, gboolean success, const GError *gerror) throw()
|
|
{
|
|
try {
|
|
SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts completed", (int)batched->size());
|
|
m_numRunningOperations--;
|
|
PendingContainer_t::iterator it = (*batched).begin();
|
|
while (it != (*batched).end()) {
|
|
SE_LOG_DEBUG((*it)->m_name, "completed: %s",
|
|
success ? "<<successfully>>" :
|
|
gerror ? gerror->message :
|
|
"<<unknown failure>>");
|
|
if (success) {
|
|
(*it)->m_status = REVISION;
|
|
} else {
|
|
(*it)->m_status = DONE;
|
|
(*it)->m_gerror = gerror;
|
|
}
|
|
++it;
|
|
}
|
|
} catch (...) {
|
|
Exception::handle(HANDLE_EXCEPTION_FATAL);
|
|
}
|
|
}
|
|
|
|
void EvolutionContactSource::flushItemChanges()
|
|
{
|
|
if (!m_batchedAdd.empty()) {
|
|
SE_LOG_DEBUG(getDisplayName(), "batch add of %d contacts starting", (int)m_batchedAdd.size());
|
|
m_numRunningOperations++;
|
|
GListCXX<EContact, GSList> contacts;
|
|
// Iterate backwards, push to front (cheaper for single-linked list) -> same order in the end.
|
|
BOOST_REVERSE_FOREACH (const boost::shared_ptr<Pending> &pending, m_batchedAdd) {
|
|
contacts.push_front(pending->m_contact.get());
|
|
}
|
|
// Transfer content without copying and then copy only the shared pointer.
|
|
boost::shared_ptr<PendingContainer_t> batched(new PendingContainer_t);
|
|
std::swap(*batched, m_batchedAdd);
|
|
SYNCEVO_GLIB_CALL_ASYNC(e_book_client_add_contacts,
|
|
boost::bind(&EvolutionContactSource::completedAdd,
|
|
this,
|
|
batched,
|
|
_1, _2, _3),
|
|
m_addressbook, contacts, NULL);
|
|
}
|
|
if (!m_batchedUpdate.empty()) {
|
|
SE_LOG_DEBUG(getDisplayName(), "batch update of %d contacts starting", (int)m_batchedUpdate.size());
|
|
m_numRunningOperations++;
|
|
GListCXX<EContact, GSList> contacts;
|
|
BOOST_REVERSE_FOREACH (const boost::shared_ptr<Pending> &pending, m_batchedUpdate) {
|
|
contacts.push_front(pending->m_contact.get());
|
|
}
|
|
boost::shared_ptr<PendingContainer_t> batched(new PendingContainer_t);
|
|
std::swap(*batched, m_batchedUpdate);
|
|
SYNCEVO_GLIB_CALL_ASYNC(e_book_client_modify_contacts,
|
|
boost::bind(&EvolutionContactSource::completedUpdate,
|
|
this,
|
|
batched,
|
|
_1, _2),
|
|
m_addressbook, contacts, NULL);
|
|
}
|
|
}
|
|
|
|
void EvolutionContactSource::finishItemChanges()
|
|
{
|
|
if (m_numRunningOperations) {
|
|
SE_LOG_DEBUG(getDisplayName(), "waiting for %d pending operations to complete", m_numRunningOperations.get());
|
|
while (m_numRunningOperations) {
|
|
g_main_context_iteration(NULL, true);
|
|
}
|
|
SE_LOG_DEBUG(getDisplayName(), "pending operations completed");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
TrackingSyncSource::InsertItemResult
|
|
EvolutionContactSource::insertItem(const string &uid, const std::string &item, bool raw)
|
|
{
|
|
EContactCXX contact(e_contact_new_from_vcard(item.c_str()), TRANSFER_REF);
|
|
if (contact) {
|
|
e_contact_set(contact, E_CONTACT_UID,
|
|
uid.empty() ?
|
|
NULL :
|
|
const_cast<char *>(uid.c_str()));
|
|
GErrorCXX gerror;
|
|
#ifdef USE_EDS_CLIENT
|
|
invalidateCachedContact(uid);
|
|
switch (m_accessMode) {
|
|
case SYNCHRONOUS:
|
|
if (uid.empty()) {
|
|
gchar* newuid;
|
|
if (!e_book_client_add_contact_sync(m_addressbook, contact, &newuid, NULL, gerror)) {
|
|
throwError(SE_HERE, "add new contact", gerror);
|
|
}
|
|
PlainGStr newuidPtr(newuid);
|
|
string newrev = getRevision(newuid);
|
|
return InsertItemResult(newuid, newrev, ITEM_OKAY);
|
|
} else {
|
|
if (!e_book_client_modify_contact_sync(m_addressbook, contact, NULL, gerror)) {
|
|
throwError(SE_HERE, "updating contact "+ uid, gerror);
|
|
}
|
|
string newrev = getRevision(uid);
|
|
return InsertItemResult(uid, newrev, ITEM_OKAY);
|
|
}
|
|
break;
|
|
case BATCHED:
|
|
case DEFAULT:
|
|
std::string name = StringPrintf("%s: %s operation #%d",
|
|
getDisplayName().c_str(),
|
|
uid.empty() ? "add" : ("insert " + uid).c_str(),
|
|
m_asyncOpCounter++);
|
|
SE_LOG_DEBUG(name, "queueing for batched %s", uid.empty() ? "add" : "update");
|
|
boost::shared_ptr<Pending> pending(new Pending);
|
|
pending->m_name = name;
|
|
pending->m_contact = contact;
|
|
pending->m_uid = uid;
|
|
if (uid.empty()) {
|
|
m_batchedAdd.push_back(pending);
|
|
} else {
|
|
m_batchedUpdate.push_back(pending);
|
|
}
|
|
// SyncSource is going to live longer than Synthesis
|
|
// engine, so using "this" is safe here.
|
|
return InsertItemResult(boost::bind(&EvolutionContactSource::checkBatchedInsert, this, pending));
|
|
break;
|
|
}
|
|
#else
|
|
if (uid.empty() ?
|
|
e_book_add_contact(m_addressbook, contact, gerror) :
|
|
e_book_commit_contact(m_addressbook, contact, gerror)) {
|
|
const char *newuid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
|
|
if (!newuid) {
|
|
throwError(SE_HERE, "no UID for contact");
|
|
}
|
|
string newrev = getRevision(newuid);
|
|
return InsertItemResult(newuid, newrev, ITEM_OKAY);
|
|
} else {
|
|
throwError(SE_HERE, uid.empty() ?
|
|
"storing new contact" :
|
|
string("updating contact ") + uid,
|
|
gerror);
|
|
}
|
|
#endif
|
|
} else {
|
|
throwError(SE_HERE, string("failure parsing vcard " ) + item);
|
|
}
|
|
// not reached!
|
|
return InsertItemResult("", "", ITEM_OKAY);
|
|
}
|
|
|
|
void EvolutionContactSource::removeItem(const string &uid)
|
|
{
|
|
GErrorCXX gerror;
|
|
if (
|
|
#ifdef USE_EDS_CLIENT
|
|
(invalidateCachedContact(uid),
|
|
!e_book_client_remove_contact_by_uid_sync(m_addressbook, uid.c_str(), NULL, gerror))
|
|
#else
|
|
!e_book_remove_contact(m_addressbook, uid.c_str(), gerror)
|
|
#endif
|
|
) {
|
|
if (IsContactNotFound(gerror)) {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, string("deleting contact: ") + uid);
|
|
} else {
|
|
throwError(SE_HERE, string( "deleting contact " ) + uid,
|
|
gerror);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string EvolutionContactSource::getDescription(const string &luid)
|
|
{
|
|
try {
|
|
EContact *contact;
|
|
GErrorCXX gerror;
|
|
if (
|
|
#ifdef USE_EDS_CLIENT
|
|
!getContact(luid,
|
|
&contact,
|
|
gerror)
|
|
#else
|
|
!e_book_get_contact(m_addressbook,
|
|
luid.c_str(),
|
|
&contact,
|
|
gerror)
|
|
#endif
|
|
) {
|
|
throwError(SE_HERE, string("reading contact ") + luid,
|
|
gerror);
|
|
}
|
|
eptr<EContact, GObject> contactptr(contact, "contact");
|
|
const char *name = (const char *)e_contact_get_const(contact, E_CONTACT_FULL_NAME);
|
|
if (name) {
|
|
return name;
|
|
}
|
|
const char *fileas = (const char *)e_contact_get_const(contact, E_CONTACT_FILE_AS);
|
|
if (fileas) {
|
|
return fileas;
|
|
}
|
|
EContactName *names =
|
|
(EContactName *)e_contact_get(contact, E_CONTACT_NAME);
|
|
std::list<std::string> buffer;
|
|
if (names) {
|
|
try {
|
|
if (names->given && names->given[0]) {
|
|
buffer.push_back(names->given);
|
|
}
|
|
if (names->additional && names->additional[0]) {
|
|
buffer.push_back(names->additional);
|
|
}
|
|
if (names->family && names->family[0]) {
|
|
buffer.push_back(names->family);
|
|
}
|
|
} catch (...) {
|
|
}
|
|
e_contact_name_free(names);
|
|
}
|
|
return boost::join(buffer, " ");
|
|
} catch (...) {
|
|
// Instead of failing we log the error and ask
|
|
// the caller to log the UID. That way transient
|
|
// errors or errors in the logging code don't
|
|
// prevent syncs.
|
|
handleException();
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::string EvolutionContactSource::getMimeType() const
|
|
{
|
|
switch( m_vcardFormat ) {
|
|
case EVC_FORMAT_VCARD_21:
|
|
return "text/x-vcard";
|
|
break;
|
|
case EVC_FORMAT_VCARD_30:
|
|
default:
|
|
return "text/vcard";
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string EvolutionContactSource::getMimeVersion() const
|
|
{
|
|
switch( m_vcardFormat ) {
|
|
case EVC_FORMAT_VCARD_21:
|
|
return "2.1";
|
|
break;
|
|
case EVC_FORMAT_VCARD_30:
|
|
default:
|
|
return "3.0";
|
|
break;
|
|
}
|
|
}
|
|
|
|
SE_END_CXX
|
|
|
|
#endif /* ENABLE_EBOOK */
|
|
|
|
#ifdef ENABLE_MODULES
|
|
# include "EvolutionContactSourceRegister.cpp"
|
|
#endif
|