--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qtmobility/plugins/contacts/maemo5/qcontactabook.cpp Mon May 03 13:18:40 2010 +0300
@@ -0,0 +1,1930 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qcontactmaemo5backend_p.h"
+#include "qcontactabook_p.h"
+
+#include <QEventLoop>
+#include <libebook/e-book-util.h>
+
+
+/* Error handling Macros */
+#define FATAL_IF_ERROR(x) if(x) { \
+ QString message(x->message); \
+ g_error_free(x); \
+ qFatal(qPrintable(message)); \
+ }
+#define WARNING_IF_ERROR(x) if(x) { \
+ QString message(x->message); \
+ g_error_free(x); \
+ qWarning(qPrintable(message)); \
+ }
+
+/* Casting Macros */
+#define A_CONTACT(x) reinterpret_cast<OssoABookContact*>(x)
+#define A_ROSTER(x) reinterpret_cast<OssoABookRoster*>(x)
+#define CONST_CHAR(x) static_cast<const char*>(x)
+#define FREE(x) free((void*)x)
+
+struct cbSharedData{
+ QContactIDsHash* hash;
+ QContactABook *that;
+};
+
+struct jobSharedData{
+ QContactABook* that;
+ bool *result;
+ char *uid;
+};
+
+/* QContactABook */
+QContactABook::QContactABook(QObject* parent) :QObject(parent), m_cbSD(0), m_deleteJobSD(0), m_saveJobSD(0)
+{
+ //Initialize QContactDetail context list
+ initAddressBook();
+}
+
+QContactABook::~QContactABook()
+{
+ OssoABookAggregator *roster = reinterpret_cast<OssoABookAggregator*>(m_abookAgregator);
+ if (g_signal_handler_is_connected(roster, m_contactAddedHandlerId))
+ g_signal_handler_disconnect(roster, m_contactAddedHandlerId);
+ if (g_signal_handler_is_connected(roster, m_contactChangedHandlerId))
+ g_signal_handler_disconnect(roster, m_contactChangedHandlerId);
+ if (g_signal_handler_is_connected(roster, m_contactRemovedHandlerId))
+ g_signal_handler_disconnect(roster, m_contactRemovedHandlerId);
+
+ // XXX FIXME: memory leak?
+ //g_object_unref(m_abookAgregator);
+ delete m_cbSD;
+ m_cbSD = 0;
+ delete m_deleteJobSD;
+ m_deleteJobSD = 0;
+ delete m_saveJobSD;
+ m_saveJobSD = 0;
+}
+
+static void contactsAddedCB(OssoABookRoster *roster, OssoABookContact **contacts, gpointer data)
+{
+ QCM5_DEBUG << "CONTACT ADDED";
+ Q_UNUSED(roster)
+
+ cbSharedData* d = static_cast<cbSharedData*>(data);
+ if (!d){
+ qWarning() << "d has been deleted";
+ return;
+ }
+
+ OssoABookContact **p;
+ QList<QContactLocalId> contactIds;
+
+ for (p = contacts; *p; ++p) {
+ if (osso_abook_contact_is_roster_contact(*p))
+ continue;
+
+ // Add a new localID to the local ID hash
+ const char* uid = CONST_CHAR(e_contact_get_const(E_CONTACT(*p), E_CONTACT_UID));
+ QContactLocalId id = d->hash->append(uid);
+
+ if (id)
+ contactIds << id;
+ }
+ d->that->_contactsAdded(contactIds);
+}
+
+static void contactsChangedCB(OssoABookRoster *roster, OssoABookContact **contacts, gpointer data)
+{
+ QCM5_DEBUG << "CONTACT CHANGED";
+ Q_UNUSED(roster)
+
+ cbSharedData* d = static_cast<cbSharedData*>(data);
+ if (!d){
+ qWarning() << "d has been deleted";
+ return;
+ }
+
+ OssoABookContact **p;
+ QList<QContactLocalId> contactIds;
+
+ for (p = contacts; *p; ++p) {
+ if (osso_abook_contact_is_roster_contact(*p))
+ continue;
+
+ const char* uid = CONST_CHAR(e_contact_get_const(E_CONTACT(*p), E_CONTACT_UID));
+ QContactLocalId id = d->hash->find(uid);
+ //FREE(uid);
+ if (id)
+ contactIds << id;
+ }
+ d->that->_contactsChanged(contactIds);
+}
+
+static void contactsRemovedCB(OssoABookRoster *roster, const char **ids, gpointer data)
+{
+ QCM5_DEBUG << "CONTACT REMOVED";
+ Q_UNUSED(roster)
+
+ cbSharedData* d = static_cast<cbSharedData*>(data);
+ if (!d){
+ qWarning() << "d has been deleted";
+ return;
+ }
+
+ const char **p;
+ QList<QContactLocalId> contactIds;
+
+ for (p = ids; *p; ++p) {
+ QContactLocalId id = d->hash->take(*p);
+ QCM5_DEBUG << "Contact" << id << "has been removed";
+ if (id)
+ contactIds << id;
+ }
+
+ d->that->_contactsRemoved(contactIds);
+}
+
+void QContactABook::initAddressBook(){
+ /* Open AddressBook */
+ GError *gError = NULL;
+ OssoABookRoster* roster = NULL;
+
+ roster = osso_abook_aggregator_get_default(&gError);
+ FATAL_IF_ERROR(gError)
+
+ osso_abook_waitable_run((OssoABookWaitable *) roster, g_main_context_default(), &gError);
+ FATAL_IF_ERROR(gError)
+
+ if (!osso_abook_waitable_is_ready ((OssoABookWaitable *) roster, &gError))
+ FATAL_IF_ERROR(gError)
+
+ m_abookAgregator = reinterpret_cast<OssoABookAggregator*>(roster);
+
+ /* Initialize local Id Hash */
+ initLocalIdHash();
+
+ /* Initialize callbacks shared data */
+ m_cbSD = new cbSharedData;
+ m_cbSD->hash = &m_localIds;
+ m_cbSD->that = this;
+
+ /* Setup signals */
+ m_contactAddedHandlerId = g_signal_connect(roster, "contacts-added",
+ G_CALLBACK (contactsAddedCB), m_cbSD);
+ m_contactChangedHandlerId = g_signal_connect(roster, "contacts-changed",
+ G_CALLBACK (contactsChangedCB), m_cbSD);
+ m_contactRemovedHandlerId = g_signal_connect(roster, "contacts-removed",
+ G_CALLBACK (contactsRemovedCB), m_cbSD);
+
+#if 0
+ //TEST List supported fields
+ EBook *book = NULL;
+ GList *l;
+ book = osso_abook_roster_get_book(roster);
+ e_book_get_supported_fields (book, &l, NULL);
+ while (l) {
+ qDebug() << "SUPPORTED FIELD" << (const char*)l->data;
+ l = l->next;
+ }
+ g_list_free(l);
+#endif
+}
+
+/*! Fill LocalId Hash associating an internal QContactLocalId to any
+ * master contact ID.
+ * NOTE: master contact IDs are string like "1" or "osso-abook-tmc1".
+ */
+void QContactABook::initLocalIdHash()
+{
+ GList *contactList = NULL;
+ GList *node;
+
+ contactList = osso_abook_aggregator_list_master_contacts(m_abookAgregator);
+
+ if (!contactList) {
+ qWarning() << "There are no Master contacts. LocalId hash is empty.";
+ return;
+ }
+
+ for (node = contactList; node != NULL; node = g_list_next (node)) {
+ EContact *contact = E_CONTACT(node->data);
+ const char* data = CONST_CHAR(e_contact_get_const(contact, E_CONTACT_UID));
+ QByteArray eContactUID(data);
+ //FREE(data);
+ m_localIds << eContactUID; //FIXME MemLeak
+ QCM5_DEBUG << "eContactID " << eContactUID << "has been stored in m_localIDs with key" << m_localIds[eContactUID];
+
+ // Useful for debugging.
+ if (QCM5_DEBUG_ENABLED) e_vcard_dump_structure((EVCard*)contact);
+ }
+
+ g_list_free(contactList);
+}
+
+QList<QContactLocalId> QContactABook::contactIds(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, QContactManager::Error* error) const
+{
+ QList<QContactLocalId> rtn;
+
+ /* Sorting */
+ //NOTE Native sorting is possible thanks to g_list_sort.
+ // It's limited just to one filter.
+ // Multi filters support need non native sorting.
+ // Native filtering needs a lot of coding since we need
+ // detailDefinitionName * detailFieldName functions
+ // to compare couple of contacts
+ if (sortOrders.count()){
+ QCM5_DEBUG << "Sorting...";
+ // We don't need
+ // Fill Ids
+ QList<QContactLocalId> Ids;
+ {
+ QList<QContactSortOrder> so;
+ QContactManager::Error e;
+ Ids = contactIds(filter, so, &e);
+ }
+
+ // Fill Contact List
+ QList<QContact> contacts;
+ foreach(QContactLocalId id, Ids){
+ QContact *c;
+ QContactManager::Error e;
+ c = getQContact(id, &e);
+ if (e == QContactManager::NoError)
+ contacts << *c;
+ else
+ *error = e;
+ }
+
+ // Non native sorting
+ return QContactManagerEngine::sortContacts(contacts, sortOrders);
+ }
+
+ /* Matching action filter */
+ //NOTE The code was not really tested */
+ if(filter.type() == QContactFilter::ActionFilter){
+ QContactActionFilter af(filter);
+ /* This looks a bit strange for me */
+ QList<QContactActionDescriptor> descriptors = QContactAction::actionDescriptors(af.actionName(), af.vendorName(), af.implementationVersion());
+
+ GList *masterContacts = osso_abook_aggregator_list_master_contacts(m_abookAgregator);
+ for(; masterContacts; ){
+ OssoABookContact *masterContact = A_CONTACT(masterContacts->data);
+ bool match = contactActionsMatch(masterContact, descriptors);
+ if(!match) {
+ GList *rosterContacts = osso_abook_contact_get_roster_contacts(masterContact);
+ for(; rosterContacts && !match; ){
+ OssoABookContact *rosterContact = A_CONTACT(rosterContacts->data);
+ match = contactActionsMatch(rosterContact, descriptors);
+ rosterContacts = g_list_delete_link(rosterContacts, rosterContacts);
+ }
+ g_list_free(rosterContacts);
+ }
+ if(match){
+ EContact *contact = E_CONTACT(masterContact);
+ const char* data = CONST_CHAR(e_contact_get_const(contact, E_CONTACT_UID));
+ QByteArray localId(data);
+ m_localIds << localId;
+ rtn.append(m_localIds[localId]);
+ QCM5_DEBUG << "eContactID " << localId << "has been stored in m_localIDs with key" << m_localIds[localId];
+ }
+ masterContacts = g_list_delete_link(masterContacts, masterContacts);
+ }
+ *error = QContactManager::NoError;
+ return rtn;
+ }
+
+ EBookQuery* query = convert(filter);
+
+ GList* l = osso_abook_aggregator_find_contacts(m_abookAgregator, query);
+ if (query)
+ e_book_query_unref(query);
+
+ while (l){
+ EContact *contact = E_CONTACT(l->data);
+ const char* data = CONST_CHAR(e_contact_get_const(contact, E_CONTACT_UID));
+ QByteArray localId(data);
+ m_localIds << localId;
+ rtn.append(m_localIds[localId]);
+ QCM5_DEBUG << "eContactID " << localId << "has been stored in m_localIDs with key" << m_localIds[localId];
+ l = g_list_delete_link(l, l);
+ }
+
+ *error = QContactManager::NoError;
+ return rtn;
+}
+
+QContact* QContactABook::getQContact(const QContactLocalId& contactId, QContactManager::Error* error) const
+{
+ QContact *rtn;
+ OssoABookContact* aContact = getAContact(contactId);
+ if (!aContact) {
+ qWarning() << "Unable to get a valid AContact";
+ *error = QContactManager::DoesNotExistError;
+ return new QContact;
+ }
+
+ //Convert aContact => qContact
+ rtn = convert(E_CONTACT(aContact));
+ return rtn;
+}
+
+static void delContactCB(EBook *book, EBookStatus status, gpointer closure)
+{
+ Q_UNUSED(book);
+ QCM5_DEBUG << "Contact Removed";
+
+ jobSharedData *sd = static_cast<jobSharedData*>(closure);
+ if (!sd)
+ return;
+
+ *sd->result = (status != E_BOOK_ERROR_OK &&
+ status != E_BOOK_ERROR_CONTACT_NOT_FOUND) ? false : true;
+ sd->that->_jobRemovingCompleted();
+}
+
+//### FIXME error is not managed
+bool QContactABook::removeContact(const QContactLocalId& contactId, QContactManager::Error* error)
+{
+ Q_UNUSED(error);
+ QMutexLocker locker(&m_delContactMutex);
+
+ bool ok = false;
+
+ OssoABookRoster *roster = A_ROSTER(m_abookAgregator);
+ EBook *book = osso_abook_roster_get_book(roster);
+ OssoABookContact *aContact = getAContact(contactId);
+ if (!OSSO_ABOOK_IS_CONTACT(aContact)){
+ qWarning() << "aCtontact is not a valid ABook contact";
+ return false;
+ }
+
+ // ASync => Sync
+ QEventLoop loop;
+ connect(this, SIGNAL(jobRemovingCompleted()), &loop, SLOT(quit()));
+
+ // Prepare shared data
+ if (m_deleteJobSD){
+ delete m_deleteJobSD;
+ m_deleteJobSD = 0;
+ }
+ m_deleteJobSD = new jobSharedData;
+ m_deleteJobSD->that = this;
+ m_deleteJobSD->result = &ok;
+
+ //Remove photos
+ EContactPhoto *photo = NULL;
+ GFile *file = NULL;
+ photo = (EContactPhoto*) e_contact_get(E_CONTACT (aContact), E_CONTACT_PHOTO);
+ if (photo) {
+ if (photo->type == E_CONTACT_PHOTO_TYPE_URI && photo->data.uri) {
+ file = g_file_new_for_uri(photo->data.uri);
+ g_file_delete(file, NULL, NULL);
+ g_object_unref (file);
+ }
+ e_contact_photo_free (photo);
+ }
+
+ //Remove all roster contacts from their roster
+ GList* rosterContacts = NULL;
+ rosterContacts = osso_abook_contact_get_roster_contacts(aContact);
+ const char *masterUid = CONST_CHAR(e_contact_get_const(E_CONTACT(aContact), E_CONTACT_UID));
+ while(rosterContacts){
+ OssoABookContact *rosterContact = A_CONTACT(rosterContacts->data);
+ osso_abook_contact_reject_for_uid(rosterContact, masterUid, NULL);
+ rosterContacts = rosterContacts->next;
+ }
+
+ // Remove contact
+ e_book_async_remove_contact(book, E_CONTACT(aContact),
+ delContactCB, m_deleteJobSD);
+
+ loop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents);
+
+ return ok;
+}
+
+static void commitContactCB(EBook* book, EBookStatus status, gpointer user_data)
+{
+ Q_UNUSED(book)
+ jobSharedData *sd = static_cast<jobSharedData*>(user_data);
+
+ *sd->result = (status == E_BOOK_ERROR_OK) ? true : false;
+ sd->that->_jobSavingCompleted();
+}
+
+static void addContactCB(EBook* book, EBookStatus status, const char *uid, gpointer user_data)
+{
+ jobSharedData *sd = static_cast<jobSharedData*>(user_data);
+ if (uid)
+ sd->uid = strdup(uid);
+
+ //osso_abook_contact_set_roster(OssoABookContact *contact, OssoABookRoster *roster)
+ commitContactCB(book, status, user_data);
+}
+
+bool QContactABook::saveContact(QContact* contact, QContactManager::Error* error)
+{
+ QMutexLocker locker(&m_saveContactMutex);
+
+ if (!contact) {
+ *error = QContactManager::BadArgumentError;
+ return false;
+ }
+
+ bool ok = false;
+
+ OssoABookContact *aContact = NULL;
+ const char *uid;
+ EBook *book;
+ {
+ OssoABookRoster* roster = reinterpret_cast<OssoABookRoster*>(m_abookAgregator);
+ book = osso_abook_roster_get_book(roster);
+ }
+
+ // Conver QContact to AContact
+ aContact = convert(contact);
+ if (!aContact){
+ *error = QContactManager::UnspecifiedError;
+ return false;
+ }
+
+ // ASync => Sync
+ QEventLoop loop;
+ connect(this, SIGNAL(jobSavingCompleted()), &loop, SLOT(quit()));
+
+ // Prepare shared data
+ if (m_saveJobSD){
+ delete m_saveJobSD;
+ m_saveJobSD = 0;
+ }
+ m_saveJobSD = new jobSharedData;
+ m_saveJobSD->that = this;
+ m_saveJobSD->result = &ok;
+
+ // Add/Commit the contact
+ uid = CONST_CHAR(e_contact_get_const(E_CONTACT (aContact), E_CONTACT_UID));
+ if (uid) {
+ osso_abook_contact_async_commit(aContact, book, commitContactCB, m_saveJobSD);
+ } else {
+ osso_abook_contact_async_add(aContact, book, addContactCB, m_saveJobSD);
+ }
+
+ loop.exec(QEventLoop::AllEvents|QEventLoop::WaitForMoreEvents);
+
+ // set the id of the contact.
+ QContactId cId;
+ cId.setLocalId(m_localIds[m_saveJobSD->uid]);
+ contact->setId(cId);
+ //free(m_saveJobSD->uid);
+
+ return ok;
+}
+
+QContactLocalId QContactABook::selfContactId(QContactManager::Error* errors) const
+{
+ QContactLocalId id;
+ EContact *self = E_CONTACT(osso_abook_self_contact_get_default());
+ if (self) {
+ *errors = QContactManager::NoError;
+ const char* data = CONST_CHAR(e_contact_get_const(self, E_CONTACT_UID));
+ const QByteArray eContactUID(data);
+ QContactLocalId localId = m_localIds[eContactUID];
+ if (localId)
+ id = localId;
+ else {
+ m_localIds << eContactUID; //FIXME MemLeak
+ id = m_localIds[eContactUID];
+ QCM5_DEBUG << "eContactID " << eContactUID << "has been stored in m_localIDs with key" << id;
+ }
+ } else {
+ qWarning() << "Cannot find self contact";
+ *errors = QContactManager::DoesNotExistError;
+ id = 0;
+ }
+ g_object_unref(self);
+ return id;
+}
+
+bool QContactABook::contactActionsMatch(OssoABookContact *contact, QList<QContactActionDescriptor> descriptors) const
+{
+ OssoABookCapsFlags capsFlags = osso_abook_caps_get_capabilities(OSSO_ABOOK_CAPS(contact));
+
+ if(capsFlags & OSSO_ABOOK_CAPS_NONE)
+ return false;
+
+ /* ActionNames could be incorrect */
+ OssoABookCapsFlags actionFlags = OSSO_ABOOK_CAPS_NONE;
+ for(int i = 0; i < descriptors.size(); i++){
+ QString actionName = descriptors.at(i).actionName();
+ QCM5_DEBUG << actionName;
+ if(!actionName.compare("Phone"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_PHONE);
+ else if(!actionName.compare("Voice"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_VOICE);
+ else if(!actionName.compare("SendEmail"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_EMAIL);
+ else if(!actionName.compare("Chat"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_CHAT);
+ else if(!actionName.compare("ChatAdditional"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_CHAT_ADDITIONAL);
+ else if(!actionName.compare("VoiceAdditional"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_VOICE_ADDITIONAL);
+ else if(!actionName.compare("Video"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_VIDEO);
+ else if(!actionName.compare("Addressbook"))
+ actionFlags = (OssoABookCapsFlags)(actionFlags | OSSO_ABOOK_CAPS_ADDRESSBOOK);
+ }
+ return ((actionFlags & capsFlags) == actionFlags);
+}
+
+EBookQuery* QContactABook::convert(const QContactFilter& filter) const
+{
+ EBookQuery* query = NULL;
+
+ switch(filter.type()){
+ case QContactFilter::DefaultFilter:
+ {
+ QCM5_DEBUG << "QContactFilter::DefaultFilter";
+ query = e_book_query_any_field_contains(""); //Match all contacts
+ } break;
+ case QContactFilter::LocalIdFilter:
+ {
+ QCM5_DEBUG << "LocalIdFilter";
+ const QContactLocalIdFilter f(filter);
+ QList<QContactLocalId> ids = f.ids();
+ if (ids.isEmpty())
+ return NULL;
+
+ query= NULL;
+ foreach(const QContactLocalId id, ids){
+ EBookQuery* q = NULL;
+
+ // Looking for the eContact local id inside the localId hash
+ const char* eContactId = m_localIds[id];
+ if (!eContactId[0])
+ return NULL;
+
+ q = e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, eContactId);
+ if (!q)
+ continue;
+ if (query)
+ query = e_book_query_orv(query, q, NULL);
+ else
+ query = q;
+ }
+ } break;
+ case QContactFilter::ContactDetailFilter:
+ {
+ QCM5_DEBUG << "ContactDetailFilter";
+ const QContactDetailFilter f(filter);
+ QString queryStr;
+ if (!f.value().isValid())
+ return NULL;
+ switch (f.matchFlags()){
+ case QContactFilter::MatchContains: queryStr = "contains"; break;
+ case QContactFilter::MatchFixedString:
+ case QContactFilter::MatchCaseSensitive:
+ case QContactFilter::MatchExactly: queryStr = "is"; break;
+ case QContactFilter::MatchStartsWith: queryStr = "beginswith"; break;
+ case QContactFilter::MatchEndsWith: queryStr = "endswith"; break;
+ default:
+ queryStr = "contains";
+ qWarning() << "Match flag not supported";
+ }
+ static QHash<QString,QString> hash;
+ if (hash.isEmpty()){
+ hash[QContactAddress::DefinitionName] = "address";
+ hash[QContactBirthday::DefinitionName] = "birth-date";
+ hash[QContactDisplayLabel::DefinitionName] = "full-name"; //hack
+ hash[QContactEmailAddress::DefinitionName] = "email";
+ hash[QContactName::DefinitionName] = "full-name";
+ hash[QContactNickname::DefinitionName] = "nickname";
+ hash[QContactNote::DefinitionName] = "note";
+ hash[QContactOrganization::DefinitionName] = "title";
+ hash[QContactPhoneNumber::DefinitionName] = "phone";
+ hash[QContactUrl::DefinitionName] = "homepage-url";
+ }
+
+ QString eDetail = hash[f.detailDefinitionName()];
+ if (eDetail.isEmpty()){
+ qWarning() << "Unable to found an ebook detail for " << f.detailDefinitionName();
+ return NULL;
+ }
+ queryStr = queryStr + " \"" + eDetail + "\" \"" + f.value().toString() + "\"";
+ query = e_book_query_from_string(qPrintable(queryStr));
+ } break;
+ case QContactFilter::ActionFilter:
+ QCM5_DEBUG << "ActionFilter"; //eQuery doesn't support ActionFilter
+ break;
+ case QContactFilter::IntersectionFilter:
+ {
+ QCM5_DEBUG << "IntersectionFilter";
+ const QContactIntersectionFilter f(filter);
+ const QList<QContactFilter> fs= f.filters();
+ QContactFilter i;
+ foreach(i, fs){
+ EBookQuery* q = convert(i);
+ if (!q){
+ qWarning() << "Query is null";
+ continue;
+ }
+ if (query)
+ query = e_book_query_andv(query, q, NULL);
+ else
+ query = q;
+ }
+ } break;
+ case QContactFilter::UnionFilter:
+ {
+ QCM5_DEBUG << "UnionFilter";
+ const QContactUnionFilter f(filter);
+ const QList<QContactFilter> fs= f.filters();
+ QContactFilter i;
+ foreach(i, fs){
+ EBookQuery* q = convert(i);
+ if (!q){
+ qWarning() << "Query is null";
+ continue;
+ }
+ if (query)
+ query = e_book_query_orv(query, q, NULL);
+ else
+ query = q;
+ }
+ } break;
+ case QContactFilter::InvalidFilter:
+ {
+ QCM5_DEBUG << "InvalidFilter";
+ query = e_book_query_from_string("(is \"id\" \"-1\")");
+ } break;
+ default:
+ QCM5_DEBUG << "Filter not supported";
+ }
+
+ //Debugging
+ const char *queryString = e_book_query_to_string(query);
+ QCM5_DEBUG << "QUERY" << queryString;
+ FREE(queryString);
+
+ return query;
+}
+
+QContact* QContactABook::convert(EContact *eContact) const
+{
+ QContact *contact = new QContact();
+ QList<QContactDetail*> detailList;
+
+ /* Id */
+ contact->setId(getContactId(eContact));
+
+ /* Address */
+ QList<QContactAddress*> addressList = getAddressDetail(eContact);
+ QContactAddress* address;
+ foreach(address, addressList)
+ detailList << address;
+
+ /* Avatar */
+ detailList << getAvatarDetail(eContact); // XXX TODO: FIXME
+ detailList << getThumbnailDetail(eContact);
+
+ /* BirthDay */
+ detailList << getBirthdayDetail(eContact);
+
+ /* Email */
+ QList<QContactEmailAddress*> emailList = getEmailDetail(eContact);
+ QContactEmailAddress* email;
+ foreach(email, emailList)
+ detailList << email;
+
+ /* Gender */
+ detailList << getGenderDetail(eContact);
+
+ /* Global UID*/
+ detailList << getGuidDetail(eContact);
+
+ /* Name & NickName*/
+ detailList << getNameDetail(eContact);
+ detailList << getNicknameDetail(eContact);
+
+ /* Note */
+ detailList << getNoteDetail(eContact);
+
+ /* Online Account */
+ QList<QContactOnlineAccount*> onlineAccountList = getOnlineAccountDetail(eContact);
+ QContactOnlineAccount* onlineAccount;
+ foreach(onlineAccount, onlineAccountList)
+ detailList << onlineAccount;
+
+ /* Organization */
+ detailList << getOrganizationDetail(eContact);
+
+ /* Phone*/
+ QList<QContactPhoneNumber*> phoneNumberList = getPhoneDetail(eContact);
+ QContactPhoneNumber* phoneNumber;
+ foreach(phoneNumber, phoneNumberList)
+ detailList << phoneNumber;
+
+ /* TimeStamp */
+ detailList << getTimestampDetail(eContact);
+
+ /* Url */
+ detailList << getUrlDetail(eContact);
+
+ bool ok;
+ QContactDetail* detail;
+
+ foreach(detail, detailList){
+ if (detail->isEmpty()){
+ delete detail;
+ continue;
+ }
+
+ ok = contact->saveDetail(detail);
+ if (!ok){
+ qWarning() << "Detail can't be saved into QContact";
+ delete detail;
+ continue;
+ }
+ delete detail;
+ }
+
+ return contact;
+}
+
+bool QContactABook::setDetailValues(const QVariantMap& data, QContactDetail* detail) const
+{
+ QMapIterator<QString, QVariant> i(data);
+ QVariant value;
+ while (i.hasNext()) {
+ i.next();
+ value = i.value();
+
+ if (value.isNull())
+ continue;
+
+ if (value.canConvert<QString>() && value.toString().isEmpty())
+ continue;
+
+ QCM5_DEBUG << "Set" << i.key() << i.value();
+ detail->setValue(i.key(), i.value());
+
+ }
+
+ if (detail->isEmpty())
+ return false;
+ return true;
+}
+
+OssoABookContact* QContactABook::getAContact(const QContactLocalId& contactId) const
+{
+ OssoABookContact* rtn = NULL;
+
+ QCM5_DEBUG << "Getting aContact with id " << m_localIds[contactId] << "local contactId is" << contactId;
+
+ if(QString(m_localIds[contactId]).compare("osso-abook-self") == 0) {
+ rtn = A_CONTACT(osso_abook_self_contact_get_default());
+ } else {
+ EBookQuery* query;
+ GList* contacts;
+
+ query = e_book_query_field_test(E_CONTACT_UID, E_BOOK_QUERY_IS, m_localIds[contactId]);
+ contacts = osso_abook_aggregator_find_contacts(m_abookAgregator, query);
+ if (query)
+ e_book_query_unref(query);
+
+ if (g_list_length(contacts) == 1) {
+ rtn = A_CONTACT(contacts->data);
+ } else {
+ qWarning("List is empty or several contacts have the same UID or contactId belongs to a roster contact.");
+ }
+ if (contacts)
+ g_list_free(contacts);
+ }
+
+ return rtn;
+}
+
+QContactId QContactABook::getContactId(EContact *eContact) const
+{
+ QContactId rtn;
+ /* Set LocalId */
+ {
+ const char* data = CONST_CHAR(e_contact_get_const(eContact, E_CONTACT_UID));
+ const QByteArray eContactUID(data);
+ QContactLocalId localId = m_localIds[eContactUID];
+ if (!localId)
+ qWarning("Unable to get valid localId for the specified eContaact UID");
+ rtn.setLocalId(localId);
+ }
+ return rtn;
+}
+
+QList<QContactAddress*> QContactABook::getAddressDetail(EContact *eContact) const
+{
+ QList<QContactAddress*> rtnList;
+
+ //Ordered list of Fields
+ QStringList addressFields;
+ addressFields << QContactAddress::FieldPostOfficeBox
+ << "Estension" //FIXME I'm not sure we have to use a new field
+ << QContactAddress::FieldStreet
+ << QContactAddress::FieldLocality
+ << QContactAddress::FieldRegion
+ << QContactAddress::FieldPostcode
+ << QContactAddress::FieldCountry;
+
+ GList* attrList = osso_abook_contact_get_attributes(eContact, EVC_ADR);
+
+ for (GList *node = g_list_last(attrList); node != NULL; node = g_list_previous(node)) {
+ QContactAddress *address = new QContactAddress;
+ QVariantMap map;
+
+ EVCardAttribute *attr = static_cast<EVCardAttribute*>(node->data);
+
+
+ // Set Address Context using attribute parameter value
+ EVCardAttributeParam *param = NULL;
+ GList* p = e_vcard_attribute_get_params(attr);
+
+ if (p)
+ param = static_cast<EVCardAttributeParam*>(p->data);
+
+ if (param){
+ GList *v = e_vcard_attribute_param_get_values(param);
+ QString context = CONST_CHAR(v->data);
+ if (context == "HOME")
+ address->setContexts(QContactDetail::ContextHome);
+ else if (context == "WORK")
+ address->setContexts(QContactDetail::ContextWork);
+ }
+
+ // Set Address Values
+ GList *v = NULL;
+ v =e_vcard_attribute_get_values(attr);
+ if (!v)
+ qFatal("ADR attribute data is corrupted");
+ if (g_list_length(v) != 7){
+ g_list_free(v);
+ qCritical() << "ADR attribute data is corrupted";
+ }
+ int i = 0;
+ while (v){
+ map[addressFields[i]] = QString::fromLatin1(CONST_CHAR(v->data));
+ i++;
+ v = v->next;
+ }
+ g_list_free(v);
+ map[QContactDetail::FieldDetailUri] = QString::number(g_list_position(attrList, node));
+ setDetailValues(map, address);
+
+ rtnList << address;
+ }
+
+ g_list_free(attrList);
+
+ return rtnList;
+}
+
+QContactName* QContactABook::getNameDetail(EContact *eContact) const
+{
+ QContactName* rtn = new QContactName;
+ QVariantMap map;
+
+ //Try to get the structure (looks that this is not supported in Maemo5)
+ EContactName* eContactName = static_cast<EContactName*> (e_contact_get(eContact, E_CONTACT_NAME));
+ if (eContactName){
+ map[QContactName::FieldCustomLabel] = eContactName->additional;
+ map[QContactName::FieldFirstName] = eContactName->given;
+ map[QContactName::FieldLastName] = eContactName->family;
+ //map[QContactName::FieldMiddleName] = eContactName->
+ map[QContactName::FieldPrefix] = eContactName->prefixes;
+ map[QContactName::FieldSuffix] = eContactName->suffixes;
+ e_contact_name_free (eContactName);
+ } else {
+ //Looks that Maemo use just these two fields
+ map[QContactName::FieldFirstName] = CONST_CHAR(e_contact_get_const(eContact, E_CONTACT_GIVEN_NAME));
+ map[QContactName::FieldLastName] = CONST_CHAR(e_contact_get_const(eContact, E_CONTACT_FAMILY_NAME));
+ }
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+QContactNickname* QContactABook::getNicknameDetail(EContact *eContact) const
+{
+ QContactNickname* rtn = new QContactNickname;
+ QVariantMap map;
+ map[QContactNickname::FieldNickname] = CONST_CHAR (e_contact_get_const(eContact, E_CONTACT_NICKNAME));
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+QList<QContactEmailAddress*> QContactABook::getEmailDetail(EContact *eContact) const
+{
+ QList<QContactEmailAddress*> rtnList;
+
+ GList* attrList = osso_abook_contact_get_attributes(eContact, EVC_EMAIL); //FIXME MemLeak
+
+ for (GList *node = g_list_last(attrList); node != NULL; node = g_list_previous(node)) {
+ QContactEmailAddress *email = new QContactEmailAddress;
+ QVariantMap map;
+
+ EVCardAttribute *attr = static_cast<EVCardAttribute*>(node->data);
+
+ // Set Address Context using attribute parameter value
+ EVCardAttributeParam *param = NULL;
+ GList* p = e_vcard_attribute_get_params(attr);
+
+ if (p)
+ param = static_cast<EVCardAttributeParam*>(p->data);
+
+ if (param){
+ GList *v = e_vcard_attribute_param_get_values(param);
+ QString context = CONST_CHAR(v->data);
+ if (context == "HOME")
+ email->setContexts(QContactDetail::ContextHome);
+ else if (context == "WORK")
+ email->setContexts(QContactDetail::ContextWork);
+ }
+
+ // Set Address Values
+ GList *v = e_vcard_attribute_get_values(attr);
+ int i = 0;
+ while (v){
+ map[QContactEmailAddress::FieldEmailAddress] = QString::fromLatin1(CONST_CHAR(v->data));
+ i++;
+ v = v->next;
+ }
+ g_list_free(v);
+
+ map[QContactDetail::FieldDetailUri] = QString::number(g_list_position(attrList, node));
+ setDetailValues(map, email);
+ rtnList << email;
+ }
+ g_list_free(attrList);
+
+ return rtnList;
+}
+
+QContactAvatar* QContactABook::getAvatarDetail(EContact *eContact) const
+{
+ Q_UNUSED(eContact);
+// XXX TODO: FIXME
+// QContactAvatar* rtn = new QContactAvatar;
+// QVariantMap map;
+//
+// OssoABookContact *aContact = A_CONTACT(eContact);
+// if (!aContact)
+// return rtn;
+//
+// //GdkPixbuf* pixbuf = osso_abook_contact_get_avatar_pixbuf(aContact, NULL, NULL);
+// GdkPixbuf* pixbuf = osso_abook_avatar_get_image_rounded(OSSO_ABOOK_AVATAR(aContact), 64, 64, true, 4, NULL);
+// if (!GDK_IS_PIXBUF(pixbuf)){
+// FREE(pixbuf);
+// return rtn;
+// }
+//
+// const uchar* bdata = (const uchar*)gdk_pixbuf_get_pixels(pixbuf);
+// QSize bsize(gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf));
+//
+// //Convert GdkPixbuf to QPixmap
+// QImage converted(bdata, bsize.width(), bsize.height(), QImage::Format_ARGB32_Premultiplied);
+// map[QContactAvatar::FieldPixmap] = QPixmap::fromImage(converted);
+// g_object_unref(pixbuf);
+// setDetailValues(map, rtn);
+//
+// return rtn;
+
+ QContactAvatar* empty = new QContactAvatar;
+ return empty;
+}
+
+QContactBirthday* QContactABook::getBirthdayDetail(EContact *eContact) const
+{
+ QContactBirthday* rtn = new QContactBirthday;
+ QVariantMap map;
+ EContactDate *date =static_cast<EContactDate*>(e_contact_get(eContact, E_CONTACT_BIRTH_DATE));
+ if (!date)
+ return rtn;
+ QDate birthday(date->year, date->month, date->day);
+ e_contact_date_free(date);
+ map[QContactBirthday::FieldBirthday] = birthday;
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+QContactGender* QContactABook::getGenderDetail(EContact *eContact) const
+{
+ QContactGender* rtn = new QContactGender;
+ QVariantMap map;
+ const char* g = CONST_CHAR(osso_abook_contact_get_value(eContact, "X-GENDER"));
+ QString gender = QString::fromLatin1(g);
+ if (gender == "male")
+ gender = "Male";
+ else if (gender == "female")
+ gender = "Female";
+ else if (gender == "unspecified")
+ gender = "Unspecified";
+
+ map[QContactGender::FieldGender] = gender;
+ FREE(g);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+//NOTE Using UID as GUID
+QContactGuid* QContactABook::getGuidDetail(EContact *eContact) const
+{
+ QContactGuid* rtn = new QContactGuid;
+ QVariantMap map;
+ const char* uid = CONST_CHAR(e_contact_get(eContact, E_CONTACT_UID));
+ map[QContactGuid::FieldGuid] = uid;
+ FREE(uid);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+QContactNote* QContactABook::getNoteDetail(EContact *eContact) const
+{
+ QContactNote* rtn = new QContactNote;
+ QVariantMap map;
+ const char* note = CONST_CHAR(e_contact_get(eContact, E_CONTACT_NOTE));
+ map[QContactNote::FieldNote] = QString::fromLatin1(note);
+ FREE(note);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+static const QStringList vcardsManagedByTelepathy(){
+ QStringList rtn;
+ OssoABookAccountManager* accountMgr = osso_abook_account_manager_get_default();
+ const GList *vcardFields = osso_abook_account_manager_get_primary_vcard_fields(accountMgr);
+ while (vcardFields){
+ QString field = (const char*)vcardFields->data;
+ if (!rtn.contains(field))
+ rtn << field;
+ vcardFields = vcardFields->next;
+ }
+
+ return rtn;
+}
+
+QList<QContactOnlineAccount*> QContactABook::getOnlineAccountDetail(EContact *eContact) const
+{
+ QList<QContactOnlineAccount*> rtnList;
+
+ QStringList evcardToSkip = vcardsManagedByTelepathy();
+
+ // Gets info of online accounts from roster contacts associated to the master one
+ if (!osso_abook_contact_is_roster_contact (A_CONTACT(eContact))) {
+ QContactOnlineAccount* rtn = new QContactOnlineAccount;
+
+ GList *contacts = osso_abook_contact_get_roster_contacts(A_CONTACT(eContact));
+ GList *node;
+ for (node = contacts; node != NULL; node = g_list_next(node)){
+ OssoABookContact *rosterContact = A_CONTACT(node->data);
+
+ McProfile* id = osso_abook_contact_get_profile(rosterContact);
+ McAccount* account = osso_abook_contact_get_account(rosterContact);
+
+ // Avoid to look for Roster contacts into the VCard
+ QString accountVCard = QString::fromLatin1(mc_profile_get_vcard_field(id));
+ evcardToSkip.removeOne(accountVCard);
+
+ // Presence
+ OssoABookPresence *presence = OSSO_ABOOK_PRESENCE (rosterContact);
+ TpConnectionPresenceType presenceType = osso_abook_presence_get_presence_type (presence);
+ QString presenceTypeString;
+ QContactPresence::PresenceState presenceTypeEnum;
+ switch (presenceType) {
+ case TP_CONNECTION_PRESENCE_TYPE_UNSET: presenceTypeString = "Unset"; presenceTypeEnum = QContactPresence::PresenceUnknown; break;
+ case TP_CONNECTION_PRESENCE_TYPE_OFFLINE: presenceTypeString = "Offline"; presenceTypeEnum = QContactPresence::PresenceOffline; break;
+ case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE: presenceTypeString = "Available"; presenceTypeEnum = QContactPresence::PresenceAvailable; break;
+ case TP_CONNECTION_PRESENCE_TYPE_AWAY: presenceTypeString = "Away"; presenceTypeEnum = QContactPresence::PresenceAway; break;
+ case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY: presenceTypeString = "Extended Away"; presenceTypeEnum = QContactPresence::PresenceExtendedAway; break;
+ case TP_CONNECTION_PRESENCE_TYPE_HIDDEN: presenceTypeString = "Hidden"; presenceTypeEnum = QContactPresence::PresenceHidden; break;
+ case TP_CONNECTION_PRESENCE_TYPE_BUSY: presenceTypeString = "Busy"; presenceTypeEnum = QContactPresence::PresenceBusy; break;
+ case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN: presenceTypeString = "Unknown"; presenceTypeEnum = QContactPresence::PresenceUnknown; break;
+ case TP_CONNECTION_PRESENCE_TYPE_ERROR: presenceTypeString = "Error"; presenceTypeEnum = QContactPresence::PresenceUnknown; break;
+ default:
+ qCritical() << "Presence type is not vaild" << presenceType;
+ }
+
+ QVariantMap map;
+ map[QContactOnlineAccount::FieldServiceProvider] = mc_profile_get_unique_name(id);
+ map[QContactOnlineAccount::FieldDetailUri] = mc_profile_get_unique_name(id); // use this as detail URI so we can link to presence.
+ map["AccountPath"] = account->name; //MCAccount name: variable part of the D-Bus object path.
+
+ setDetailValues(map, rtn);
+ }
+ rtnList << rtn;
+ g_list_free (contacts);
+ }
+
+ /* Users can add Online account details manually. Eg: IRC username.
+ * evcardToSkip stringlist contains evCard attributes that have been already processed.
+ */
+ GList *attributeList = e_vcard_get_attributes((EVCard*)eContact);
+ GList *node;
+
+ if (attributeList) {
+ for (node = attributeList; node != NULL; node = g_list_next (node)) {
+ EVCardAttribute* attr = (EVCardAttribute*)node->data;
+ if (!attr)
+ continue;
+ QString attributeName = QString::fromLatin1(e_vcard_attribute_get_name(attr));
+
+ // Skip attributes processed scanning roster contacts.
+ if (!evcardToSkip.contains(attributeName))
+ continue;
+
+ GList *params = e_vcard_attribute_get_params(attr);
+ GList *nodeP;
+ QString type;
+ // If the parameter list lenght is 1, X-OSSO-VALID is not specified
+ bool ossoValidIsOk = (g_list_length(params) == 1) ? true : false;
+
+ for (nodeP = params; nodeP != NULL; nodeP = g_list_next (nodeP)) {
+ EVCardAttributeParam* p = (EVCardAttributeParam*) nodeP->data;
+ QString paramName = QString::fromLatin1(e_vcard_attribute_param_get_name(p));
+ bool attrIsType = false;
+ bool attrIsOssoValid = false;
+
+ //If type is empty check if the attribute is "TYPE"
+ if (type.isEmpty())
+ attrIsType = paramName.contains(EVC_TYPE);
+
+ if(!ossoValidIsOk)
+ attrIsOssoValid = paramName.contains("X-OSSO-VALID");
+
+ if (!attrIsType && !attrIsOssoValid) {
+ qWarning () << "Skipping attribute parameter checking for" << paramName;
+ continue;
+ }
+
+ GList *values = e_vcard_attribute_param_get_values(p);
+ GList *node;
+ for (node = values; node != NULL; node = g_list_next (node)) {
+ QString attributeParameterValue = QString::fromLatin1(CONST_CHAR(node->data));
+ if (attrIsOssoValid) {
+ ossoValidIsOk = (attributeParameterValue == "yes")? true : false;
+ if (!ossoValidIsOk) {
+ qWarning() << "X-OSSO-VALID is false.";
+ break;
+ }
+ } else if (type.isEmpty()) {
+ type = attributeParameterValue;
+ if (type.isEmpty())
+ qCritical() << "TYPE is empty";
+ }
+ }
+
+ if (ossoValidIsOk && !type.isEmpty()) {
+ QContactOnlineAccount* rtn = new QContactOnlineAccount;
+ QVariantMap map;
+ map[QContactOnlineAccount::FieldServiceProvider] = type;
+ setDetailValues(map, rtn);
+ rtnList << rtn;
+ }
+ }
+ }
+ }
+
+ return rtnList;
+}
+
+QContactOrganization* QContactABook::getOrganizationDetail(EContact *eContact) const
+{
+ QContactOrganization* rtn = new QContactOrganization;
+ QVariantMap map;
+ const char* title = CONST_CHAR(e_contact_get(eContact, E_CONTACT_TITLE));
+ map[QContactOrganization::FieldTitle] = title;
+ FREE(title);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+QList<QContactPhoneNumber*> QContactABook::getPhoneDetail(EContact *eContact) const
+{
+ QList<QContactPhoneNumber*> rtnList;
+
+ GList *l = osso_abook_contact_get_attributes(eContact, EVC_TEL);
+
+ for (GList *node = g_list_last(l); node != NULL; node = g_list_previous(node)) {
+ QContactPhoneNumber* phoneNumber = new QContactPhoneNumber;
+ QVariantMap map;
+
+ EVCardAttribute *attr = static_cast<EVCardAttribute*>(node->data);
+ GList* p = e_vcard_attribute_get_param(attr, EVC_TYPE);
+
+ //Set Contexts and SubTypes
+ while (p) {
+ QString value = QString::fromLatin1(CONST_CHAR(p->data));
+
+ if (value == "HOME")
+ phoneNumber->setContexts(QContactDetail::ContextHome);
+ else if (value == "WORK")
+ phoneNumber->setContexts(QContactDetail::ContextWork);
+ else
+ if (value == "CELL")
+ phoneNumber->setSubTypes(QContactPhoneNumber::SubTypeMobile);
+ else if (value == "VOICE")
+ phoneNumber->setSubTypes(QContactPhoneNumber::SubTypeVoice);
+
+ p = p->next;
+ }
+ g_list_free(p);
+
+ //Set Phone Number
+ GList* phoneNumbers = e_vcard_attribute_get_values(attr);
+ const char* normalized = e_normalize_phone_number(CONST_CHAR(phoneNumbers->data)); //FIXME Valgrind complains about this
+ QString phoneNumberStr(normalized);
+ FREE(normalized);
+ map[QContactPhoneNumber::FieldNumber] = phoneNumberStr;
+ map[QContactDetail::FieldDetailUri] = QString::number(g_list_position(l, node));
+ setDetailValues(map, phoneNumber);
+
+ rtnList << phoneNumber;
+ }
+ g_list_free(l);
+
+ return rtnList;
+}
+
+
+QList<QContactPresence*> QContactABook::getPresenceDetail(EContact *eContact) const
+{
+ QList<QContactPresence*> rtnList;
+
+ QStringList evcardToSkip = vcardsManagedByTelepathy();
+
+ // Gets info of online accounts from roster contacts associated to the master one
+ if (!osso_abook_contact_is_roster_contact (A_CONTACT(eContact))) {
+ QContactPresence* rtn = new QContactPresence;
+
+ GList *contacts = osso_abook_contact_get_roster_contacts(A_CONTACT(eContact));
+ GList *node;
+ for (node = contacts; node != NULL; node = g_list_next(node)){
+ OssoABookContact *rosterContact = A_CONTACT(node->data);
+
+ McProfile* id = osso_abook_contact_get_profile(rosterContact);
+ McAccount* account = osso_abook_contact_get_account(rosterContact);
+
+ // Avoid to look for Roster contacts into the VCard
+ QString accountVCard = QString::fromLatin1(mc_profile_get_vcard_field(id));
+ evcardToSkip.removeOne(accountVCard);
+
+ // Presence
+ OssoABookPresence *presence = OSSO_ABOOK_PRESENCE (rosterContact);
+ TpConnectionPresenceType presenceType = osso_abook_presence_get_presence_type (presence);
+ QString presenceTypeString;
+ QContactPresence::PresenceState presenceTypeEnum;
+ switch (presenceType) {
+ case TP_CONNECTION_PRESENCE_TYPE_UNSET: presenceTypeString = "Unset"; presenceTypeEnum = QContactPresence::PresenceUnknown; break;
+ case TP_CONNECTION_PRESENCE_TYPE_OFFLINE: presenceTypeString = "Offline"; presenceTypeEnum = QContactPresence::PresenceOffline; break;
+ case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE: presenceTypeString = "Available"; presenceTypeEnum = QContactPresence::PresenceAvailable; break;
+ case TP_CONNECTION_PRESENCE_TYPE_AWAY: presenceTypeString = "Away"; presenceTypeEnum = QContactPresence::PresenceAway; break;
+ case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY: presenceTypeString = "Extended Away"; presenceTypeEnum = QContactPresence::PresenceExtendedAway; break;
+ case TP_CONNECTION_PRESENCE_TYPE_HIDDEN: presenceTypeString = "Hidden"; presenceTypeEnum = QContactPresence::PresenceHidden; break;
+ case TP_CONNECTION_PRESENCE_TYPE_BUSY: presenceTypeString = "Busy"; presenceTypeEnum = QContactPresence::PresenceBusy; break;
+ case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN: presenceTypeString = "Unknown"; presenceTypeEnum = QContactPresence::PresenceUnknown; break;
+ case TP_CONNECTION_PRESENCE_TYPE_ERROR: presenceTypeString = "Error"; presenceTypeEnum = QContactPresence::PresenceUnknown; break;
+ default:
+ qCritical() << "Presence type is not valid" << presenceType;
+ }
+
+ QVariantMap map; // XXX FIXME
+ map[QContactPresence::FieldNickname] = osso_abook_contact_get_display_name(rosterContact);
+ map[QContactPresence::FieldPresenceState] = presenceTypeEnum;
+ map[QContactPresence::FieldPresenceStateText] = QString::fromLatin1(osso_abook_presence_get_presence_status_message(presence));
+ map[QContactPresence::FieldLinkedDetailUris] = mc_profile_get_unique_name(id); //use the unique name as a detail uri of the online account.
+ map["AccountPath"] = account->name; //MCAccount name: variable part of the D-Bus object path.
+
+ setDetailValues(map, rtn);
+ }
+ rtnList << rtn;
+ g_list_free (contacts);
+ }
+
+ /* Users can add Online account details manually. Eg: IRC username.
+ * evcardToSkip stringlist contains evCard attributes that have been already processed.
+ */
+ GList *attributeList = e_vcard_get_attributes((EVCard*)eContact);
+ GList *node;
+
+ if (attributeList) {
+ for (node = attributeList; node != NULL; node = g_list_next (node)) {
+ EVCardAttribute* attr = (EVCardAttribute*)node->data;
+ if (!attr)
+ continue;
+ QString attributeName = QString::fromLatin1(e_vcard_attribute_get_name(attr));
+
+ // Skip attributes processed scanning roster contacts.
+ if (!evcardToSkip.contains(attributeName))
+ continue;
+
+ GList *params = e_vcard_attribute_get_params(attr);
+ GList *nodeP;
+ QString type;
+ // If the parameter list lenght is 1, X-OSSO-VALID is not specified
+ bool ossoValidIsOk = (g_list_length(params) == 1) ? true : false;
+
+ for (nodeP = params; nodeP != NULL; nodeP = g_list_next (nodeP)) {
+ EVCardAttributeParam* p = (EVCardAttributeParam*) nodeP->data;
+ QString paramName = QString::fromLatin1(e_vcard_attribute_param_get_name(p));
+ bool attrIsType = false;
+ bool attrIsOssoValid = false;
+
+ //If type is empty check if the attribute is "TYPE"
+ if (type.isEmpty())
+ attrIsType = paramName.contains(EVC_TYPE);
+
+ if(!ossoValidIsOk)
+ attrIsOssoValid = paramName.contains("X-OSSO-VALID");
+
+ if (!attrIsType && !attrIsOssoValid) {
+ qWarning () << "Skipping attribute parameter checking for" << paramName;
+ continue;
+ }
+
+ GList *values = e_vcard_attribute_param_get_values(p);
+ GList *node;
+ for (node = values; node != NULL; node = g_list_next (node)) {
+ QString attributeParameterValue = QString::fromLatin1(CONST_CHAR(node->data));
+ if (attrIsOssoValid) {
+ ossoValidIsOk = (attributeParameterValue == "yes")? true : false;
+ if (!ossoValidIsOk) {
+ qWarning() << "X-OSSO-VALID is false.";
+ break;
+ }
+ } else if (type.isEmpty()) {
+ type = attributeParameterValue;
+ if (type.isEmpty())
+ qCritical() << "TYPE is empty";
+ }
+ }
+
+ if (ossoValidIsOk && !type.isEmpty()) {
+ QContactPresence* rtn = new QContactPresence;
+ QVariantMap map;
+ map[QContactPresence::FieldNickname] = QString::fromLatin1(e_vcard_attribute_get_value(attr));
+ map[QContactPresence::FieldLinkedDetailUris] = type; // XXX FIXME
+ setDetailValues(map, rtn);
+ rtnList << rtn;
+ }
+ }
+ }
+ }
+
+ return rtnList;
+}
+
+QContactTimestamp* QContactABook::getTimestampDetail(EContact *eContact) const
+{
+ QContactTimestamp* rtn = new QContactTimestamp;
+ QVariantMap map;
+ const char* rev = CONST_CHAR(e_contact_get(eContact, E_CONTACT_REV));
+ map[QContactTimestamp::FieldModificationTimestamp] = QDateTime::fromString(rev, Qt::ISODate);
+ FREE(rev);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+QContactThumbnail* QContactABook::getThumbnailDetail(EContact *eContact) const
+{
+ QContactThumbnail* rtn = new QContactThumbnail;
+ QVariantMap map;
+
+ OssoABookContact *aContact = A_CONTACT(eContact);
+ if (!aContact)
+ return rtn;
+
+ //GdkPixbuf* pixbuf = osso_abook_contact_get_avatar_pixbuf(aContact, NULL, NULL);
+ GdkPixbuf* pixbuf = osso_abook_avatar_get_image_rounded(OSSO_ABOOK_AVATAR(aContact), 64, 64, true, 4, NULL);
+ if (!GDK_IS_PIXBUF(pixbuf)){
+ FREE(pixbuf);
+ return rtn;
+ }
+
+ const uchar* bdata = (const uchar*)gdk_pixbuf_get_pixels(pixbuf);
+ QSize bsize(gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf));
+
+ //Convert GdkPixbuf to QPixmap
+ QImage converted(bdata, bsize.width(), bsize.height(), QImage::Format_ARGB32_Premultiplied);
+ map[QContactThumbnail::FieldThumbnail] = converted;
+ g_object_unref(pixbuf);
+ setDetailValues(map, rtn);
+
+ return rtn;
+}
+
+QContactUrl* QContactABook::getUrlDetail(EContact *eContact) const
+{
+ QContactUrl* rtn = new QContactUrl;
+ QVariantMap map;
+ const char* url = CONST_CHAR(e_contact_get(eContact, E_CONTACT_HOMEPAGE_URL));
+ map[QContactUrl::FieldUrl] = url;
+ FREE(url);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+static void addAttributeToAContact(const OssoABookContact* contact,
+ const QString& attrName, const QStringList& attrValues,
+ const QString& paramName = QString(), const QStringList& paramValues = QStringList(),
+ bool overwrite = true,
+ const int index = 0)
+{
+ if (!contact)
+ return;
+
+ EVCard *vcard = E_VCARD (contact);
+ EVCardAttribute *attr = NULL;
+ EVCardAttributeParam* param = NULL;
+
+ QCM5_DEBUG << "Adding attribute" << attrName << "AttrValues:" << attrValues
+ << "ParamName:" << paramName << "ParamValues:" << paramValues
+ << "overwrite" << overwrite << "Index" << index;
+
+ if (overwrite)
+ {
+ GList *attributeList = osso_abook_contact_get_attributes(E_CONTACT(contact), qPrintable(attrName));
+
+ for (GList *node = g_list_last(attributeList); node != NULL; node = g_list_previous(node)) {
+ EVCardAttribute* eAttr = (EVCardAttribute*)node->data;
+ int pos = g_list_position(attributeList, node);
+
+ if (index > pos){
+ qWarning() << "Attribute doesn't found at position" << index;
+ return;
+ }
+
+ if (index != pos)
+ continue;
+
+ // Select the current EVCard Attribute if it contains the same parameters of
+ // attribute we want to modify/add
+ int matchedParams = 0;
+ GList* p = NULL;
+ p = e_vcard_attribute_get_param(eAttr, qPrintable(paramName));
+
+ while (p){
+ foreach(QString paramV, paramValues){
+ QString value = CONST_CHAR(p->data);
+ if (paramV == value)
+ ++matchedParams;
+ }
+ p = p->next;
+ }
+ g_list_free(p);
+
+ if (matchedParams == paramValues.count()) {
+ attr = eAttr;
+ break;
+ }
+ }
+ }
+
+ // Check if attrValues contains something
+ bool noValues = true;
+ foreach(QString s, attrValues){
+ if (!s.isEmpty()){
+ noValues = false;
+ break;
+ }
+ }
+
+ if (attr) {
+ if (noValues){
+ e_vcard_remove_attribute(vcard, attr);
+ return;
+ } else {
+ e_vcard_attribute_remove_values(attr);
+ }
+ } else {
+ if (noValues)
+ return;
+
+ // Create Attribute with right parameters
+ attr = e_vcard_attribute_new(NULL, qPrintable(attrName));
+ if (!paramName.isEmpty()){
+ param = e_vcard_attribute_param_new(qPrintable(paramName));
+
+ foreach(QString paramV, paramValues)
+ e_vcard_attribute_param_add_value(param, qPrintable(paramV));
+
+ e_vcard_attribute_add_param(attr, param);
+ }
+ // Save the attribute to the VCard
+ e_vcard_add_attribute(vcard, attr);
+ }
+
+ // Add values to the attribute
+ foreach(QString attrV, attrValues) {
+ e_vcard_attribute_add_value(attr, qPrintable(attrV));
+ }
+
+ // Debugging
+ {
+ const char* dbgStr = e_vcard_to_string(vcard, EVC_FORMAT_VCARD_30);
+ QCM5_DEBUG << "Modified VCard" << dbgStr;
+ FREE(dbgStr);
+ }
+}
+
+OssoABookContact* QContactABook::convert(const QContact *contact) const
+{
+ Q_CHECK_PTR(contact);
+
+ OssoABookContact* rtn;
+
+ // Get aContact if it exists or create a new one if it doesn't
+ QContactLocalId id = contact->localId();
+ QCM5_DEBUG << "Converting QContact id:" << id << " to aContact";
+ if (id){
+ rtn = getAContact(id);
+ // It's not safe to commit changes to a contact that has been modified.
+ // This problem affects attributes with the same name and parameters such as
+ // EMail, Address...
+ QContactTimestamp* ts = getTimestampDetail(E_CONTACT(rtn));
+ if (*ts != contact->detail<QContactTimestamp>()){
+ delete ts;
+ return NULL;
+ }
+ delete ts;
+ } else {
+ rtn = osso_abook_contact_new();
+ }
+
+ QList<QContactDetail> allDetails = contact->details();
+ foreach(const QContactDetail &detail, allDetails){
+ QString definitionName = detail.definitionName();
+
+ QCM5_DEBUG << "Saving" << definitionName;
+
+ //QContactDisplayLabel::DefinitionName
+ if (definitionName == QContactAddress::DefinitionName){
+ setAddressDetail(rtn, detail);
+ } else
+ if (definitionName == QContactAvatar::DefinitionName){
+ setAvatarDetail(rtn, detail);
+ } else
+ if (definitionName == QContactBirthday::DefinitionName){
+ setBirthdayDetail(rtn, detail);
+ } else
+ if (definitionName == QContactEmailAddress::DefinitionName){
+ setEmailDetail(rtn, detail);
+ } else
+ if (definitionName == QContactGender::DefinitionName){
+ setGenderDetail(rtn, detail);
+ } else
+ if (definitionName == QContactName::DefinitionName){
+ setNameDetail(rtn, detail);
+ } else
+ if (definitionName == QContactNickname::DefinitionName){
+ setNicknameDetail(rtn, detail);
+ } else
+ if (definitionName == QContactNote::DefinitionName){
+ setNoteDetail(rtn, detail);
+ } else
+ if (definitionName == QContactOnlineAccount::DefinitionName){
+ setOnlineAccountDetail(rtn, detail);
+ } else
+ if (definitionName == QContactOrganization::DefinitionName){
+ setOrganizationDetail(rtn, detail);
+ } else
+ if (definitionName == QContactPhoneNumber::DefinitionName){
+ setPhoneDetail(rtn, detail);
+ } else
+ if (definitionName == QContactThumbnail::DefinitionName){
+ setThumbnailDetail(rtn, detail);
+ } else
+ if (definitionName == QContactUrl::DefinitionName){
+ setUrlDetail(rtn, detail);
+ }
+ }
+
+ return rtn;
+}
+
+void QContactABook::setAddressDetail(const OssoABookContact* aContact, const QContactAddress& detail) const
+{
+ if (!aContact) return;
+
+ uint detailUri;
+ const uint nAddressElems = 7;
+ QStringList adrAttrValues,
+ lblAttrValues,
+ paramValues;
+
+ // Get parameters
+ foreach(QString c, detail.contexts())
+ paramValues << c.toUpper();
+
+ // Initialize adrAttrValues;
+ for (uint i = 0; i < nAddressElems; ++i)
+ adrAttrValues << "";
+
+ // Fill adrAttrValues
+ QVariantMap vm = detail.variantValues();
+ QMapIterator<QString, QVariant> i(vm);
+
+ while (i.hasNext()) {
+ i.next();
+ int index = -1;
+ QString key = i.key();
+
+ if (key == QContactAddress::FieldPostOfficeBox) index = 0;
+ else if (key == "Estension") index = 1;
+ else if (key == QContactAddress::FieldStreet) index = 2;
+ else if (key == QContactAddress::FieldLocality) index = 3;
+ else if (key == QContactAddress::FieldRegion) index = 4;
+ else if (key == QContactAddress::FieldPostcode) index = 5;
+ else if (key == QContactAddress::FieldCountry) index = 6;
+ else if (key == QContactDetail::FieldContext) continue;
+ else if (key == QContactDetail::FieldDetailUri) detailUri = i.value().toInt();
+ else {
+ qWarning() << "Address contains an invalid field:" << key;
+ return;
+ }
+
+ if (index != -1)
+ adrAttrValues[index] = i.value().toString();
+ }
+
+ // Fill lblAttrValues
+ QStringList labelValues;
+ labelValues << adrAttrValues[1]
+ << adrAttrValues[2]
+ << adrAttrValues[0]
+ << adrAttrValues[3]
+ << adrAttrValues[4]
+ << adrAttrValues[5]
+ << adrAttrValues[6];
+ lblAttrValues << labelValues.join(", ");
+
+ // Skip if adrAttrValues contains only empty strings
+ bool noValues = true;
+ foreach(QString s, adrAttrValues){
+ if (!s.isEmpty()){
+ noValues = false;
+ break;
+ }
+ }
+ if (noValues)
+ return;
+
+ // Saving LABEL and ADR attributes into the VCard
+ addAttributeToAContact(aContact, EVC_ADR, adrAttrValues, EVC_TYPE, paramValues, true, detailUri);
+
+ //BUG Label attribute contains a bug
+ //It contains TYPE(TYPE) if ADDRESS doesn't contain any parameter value.
+ if (paramValues.isEmpty())
+ paramValues << EVC_TYPE;
+
+ addAttributeToAContact(aContact, EVC_LABEL, lblAttrValues, EVC_TYPE, paramValues, true, detailUri);
+}
+
+void QContactABook::setThumbnailDetail(const OssoABookContact* aContact, const QContactThumbnail& detail) const
+{
+ if (!aContact) return;
+
+ EBook *book;
+ {
+ OssoABookRoster* roster = A_ROSTER(m_abookAgregator);
+ book = osso_abook_roster_get_book(roster);
+ }
+
+ QImage image = detail.thumbnail();
+
+ if (image.isNull())
+ return;
+
+ if (image.format() != QImage::Format_ARGB32_Premultiplied)
+ image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(image.bits(), GDK_COLORSPACE_RGB,
+ image.hasAlphaChannel(), 8,
+ image.width(), image.height(),
+ image.bytesPerLine(), 0, 0);
+ osso_abook_contact_set_pixbuf((OssoABookContact*)aContact, pixbuf, book, 0);
+ g_object_unref(pixbuf);
+}
+
+void QContactABook::setAvatarDetail(const OssoABookContact* aContact, const QContactAvatar& detail) const
+{
+ Q_UNUSED(aContact)
+ Q_UNUSED(detail);
+// XXX TODO: FIXME
+// if (!aContact) return;
+//
+// EBook *book;
+// {
+// OssoABookRoster* roster = A_ROSTER(m_abookAgregator);
+// book = osso_abook_roster_get_book(roster);
+// }
+//
+// QImage image = detail.pixmap().toImage();
+// if (image.format() != QImage::Format_ARGB32_Premultiplied)
+// image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+// GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(image.bits(), GDK_COLORSPACE_RGB,
+// image.hasAlphaChannel(), 8,
+// image.width(), image.height(),
+// image.bytesPerLine(), 0, 0);
+// osso_abook_contact_set_pixbuf((OssoABookContact*)aContact, pixbuf, book, 0);
+// g_object_unref(pixbuf);
+}
+
+void QContactABook::setBirthdayDetail(const OssoABookContact* aContact, const QContactBirthday& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ attrValues << detail.value(QContactBirthday::FieldBirthday);
+
+ addAttributeToAContact(aContact, EVC_BDAY, attrValues);
+}
+
+void QContactABook::setEmailDetail(const OssoABookContact* aContact, const QContactEmailAddress& detail) const
+{
+ if (!aContact) return;
+ QStringList attrValues,
+ paramValues;
+
+ QVariantMap vm = detail.variantValues();
+ QMapIterator<QString, QVariant> i(vm);
+ while (i.hasNext()) {
+ i.next();
+ QString key = i.key();
+
+ // We don't want to save the Detail URI
+ if (key == QContactDetail::FieldDetailUri)
+ continue;
+
+ if (key == QContactDetail::FieldContext)
+ paramValues << i.value().toString().toUpper();
+ else
+ attrValues << i.value().toString();
+ }
+
+ addAttributeToAContact(aContact, EVC_EMAIL, attrValues, EVC_TYPE, paramValues, true, detail.detailUri().toInt());
+}
+
+void QContactABook::setGenderDetail(const OssoABookContact* aContact, const QContactGender& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ attrValues << detail.value(QContactGender::FieldGender).toLower();
+
+ addAttributeToAContact(aContact, "X-GENDER", attrValues);
+}
+
+void QContactABook::setNameDetail(const OssoABookContact* aContact, const QContactName& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ // Save First and Last name in the N vcard attribute
+ {
+ QStringList supportedDetailValues;
+ supportedDetailValues << QContactName::FieldFirstName << QContactName::FieldLastName;
+
+ foreach(QString key, supportedDetailValues){
+ attrValues << detail.value(key);
+ }
+
+ //REMOVE ME - We don't want to support custom label
+ if (attrValues[0].isEmpty()){
+ qWarning() << "QContactName::FieldFirstName is empty";
+ attrValues[0] = detail.customLabel();
+ }
+
+ addAttributeToAContact(aContact, EVC_N, attrValues);
+ }
+
+ // Save Fist + Last name in the FN card attribute
+ {
+ attrValues << attrValues.join(" ");
+ addAttributeToAContact(aContact, EVC_FN, attrValues);
+ }
+}
+
+void QContactABook::setNicknameDetail(const OssoABookContact* aContact, const QContactNickname& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ attrValues << detail.value(QContactNickname::FieldNickname);
+
+ addAttributeToAContact(aContact, EVC_NICKNAME, attrValues);
+}
+
+void QContactABook::setNoteDetail(const OssoABookContact* aContact, const QContactNote& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ attrValues << detail.value(QContactNote::FieldNote);
+
+ addAttributeToAContact(aContact, EVC_NOTE, attrValues);
+}
+
+/*NOTE: Online details comes from Telepathy or can be added manually by the user.
+ * OnlineDetals coming from Telepathy/Roster contacts can't be saved.
+ */
+void QContactABook::setOnlineAccountDetail(const OssoABookContact* aContact, const QContactOnlineAccount& detail) const
+{
+ if (!aContact)
+ return;
+
+ Q_UNUSED(detail);
+}
+
+void QContactABook::setOrganizationDetail(const OssoABookContact* aContact, const QContactOrganization& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ attrValues << detail.value(QContactOrganization::FieldTitle);
+
+ addAttributeToAContact(aContact, EVC_ORG, attrValues);
+}
+
+void QContactABook::setPhoneDetail(const OssoABookContact* aContact, const QContactPhoneNumber& detail) const
+{
+ if (!aContact) return;
+ QStringList attrValues,
+ paramValues;
+
+ QVariantMap vm = detail.variantValues();
+ QMapIterator<QString, QVariant> i(vm);
+ while (i.hasNext()) {
+ i.next();
+ const QString key = i.key();
+
+ // We don't want to save the Detail URI
+ if (key == QContactDetail::FieldDetailUri)
+ continue;
+
+ if (key == QContactDetail::FieldContext ||
+ key == QContactPhoneNumber::FieldSubTypes){
+ QString value = i.value().toString();
+ if (value == QContactPhoneNumber::SubTypeMobile)
+ value = "CELL";
+ paramValues << value.toUpper();
+ } else
+ attrValues << i.value().toString();
+ }
+
+ // Avoid unsupported type
+ if (paramValues.isEmpty())
+ paramValues << "VOICE";
+
+ addAttributeToAContact(aContact, EVC_TEL, attrValues, EVC_TYPE, paramValues, true, detail.detailUri().toInt());
+}
+
+void QContactABook::setUrlDetail(const OssoABookContact* aContact, const QContactUrl& detail) const
+{
+ if (!aContact) return;
+
+ QStringList attrValues;
+ attrValues << detail.value(QContactUrl::FieldUrl);
+
+ addAttributeToAContact(aContact, EVC_URL, attrValues);
+}