--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/contacts/maemo5/qcontactabook.cpp Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,1903 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 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>
+
+#include <libmcclient/mc-account-manager.h>
+
+/* Error handling Macros */
+#define FATAL_IF_ERROR(x) if(x) { \
+ QString message(x->message); \
+ g_error_free(x); \
+ qFatal(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;
+ QContactManager::Error *error;
+};
+
+/* 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){
+ 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;
+ }
+
+ if (!contactIds.isEmpty())
+ 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){
+ 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;
+ }
+
+ if (!contactIds.isEmpty())
+ 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){
+ return;
+ }
+
+ const char **p;
+ QList<QContactLocalId> contactIds;
+
+ for (p = ids; *p; ++p) {
+ QContactLocalId id = d->hash->take(*p);
+ if (id) {
+ QCM5_DEBUG << "Contact" << id << "has been removed";
+ contactIds << id;
+ }
+ }
+
+ if (!contactIds.isEmpty())
+ 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);
+
+ //TEST Lists supported fields
+ if (QCM5_DEBUG_ENABLED){
+ 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);
+ }
+}
+
+/*! 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) {
+ QCM5_DEBUG << "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);
+}
+
+//TODO Use native filters
+QList<QContactLocalId> QContactABook::contactIds(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, QContactManager::Error* error) const
+{
+ Q_UNUSED(sortOrders)
+ Q_UNUSED(filter);
+ *error = QContactManager::NoError;
+ return m_localIds.keys();
+
+ /*
+ // 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);
+ }
+
+ switch(filter.type()){
+ case QContactFilter::DefaultFilter: {
+ rtn = m_localIds.keys();
+ } break;
+ default: {
+ 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, error);
+ if (!aContact) {
+ return new QContact;
+ }
+
+ //Convert aContact => qContact
+ rtn = convert(E_CONTACT(aContact));
+
+ QContactId cId;
+ cId.setLocalId(contactId);
+ rtn->setId(cId);
+
+ return rtn;
+}
+
+static QContactManager::Error getErrorFromStatus(const EBookStatus status){
+ switch (status) {
+ case E_BOOK_ERROR_OK:
+ return QContactManager::NoError;
+ case E_BOOK_ERROR_INVALID_ARG:
+ return QContactManager::BadArgumentError;
+ case E_BOOK_ERROR_BUSY:
+ return QContactManager::LockedError;
+ case E_BOOK_ERROR_PERMISSION_DENIED:
+ case E_BOOK_ERROR_AUTHENTICATION_FAILED:
+ case E_BOOK_ERROR_AUTHENTICATION_REQUIRED:
+ return QContactManager::PermissionsError;
+ case E_BOOK_ERROR_CONTACT_NOT_FOUND:
+ return QContactManager::DoesNotExistError;
+ case E_BOOK_ERROR_CONTACT_ID_ALREADY_EXISTS:
+ return QContactManager::AlreadyExistsError;
+ case E_BOOK_ERROR_NO_SPACE:
+ return QContactManager::OutOfMemoryError;
+#if 0
+ case E_BOOK_ERROR_REPOSITORY_OFFLINE:
+ case E_BOOK_ERROR_NO_SUCH_BOOK:
+ case E_BOOK_ERROR_NO_SELF_CONTACT:
+ case E_BOOK_ERROR_SOURCE_NOT_LOADED:
+ case E_BOOK_ERROR_SOURCE_ALREADY_LOADED:
+ case E_BOOK_ERROR_PROTOCOL_NOT_SUPPORTED:
+ case E_BOOK_ERROR_CANCELLED:
+ case E_BOOK_ERROR_COULD_NOT_CANCEL:
+ case E_BOOK_ERROR_TLS_NOT_AVAILABLE:
+ case E_BOOK_ERROR_CORBA_EXCEPTION:
+ case E_BOOK_ERROR_NO_SUCH_SOURCE:
+ case E_BOOK_ERROR_OFFLINE_UNAVAILABLE:
+ case E_BOOK_ERROR_OTHER_ERROR:
+ case E_BOOK_ERROR_INVALID_SERVER_VERSION:
+#endif
+ default:
+ return QContactManager::UnspecifiedError;
+ }
+}
+
+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->error = getErrorFromStatus(status);
+
+ sd->that->_jobRemovingCompleted();
+}
+
+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, error);
+ if (!OSSO_ABOOK_IS_CONTACT(aContact)){
+ QCM5_DEBUG << "Specified contact 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;
+ m_deleteJobSD->error = error;
+
+ //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));
+ char *contactUidCopy = strdup(masterUid);
+ 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);
+
+ // update our list of ids...
+ QContactLocalId id = m_localIds[contactUidCopy];
+ m_localIds.remove(contactUidCopy);
+ if (contactUidCopy)
+ free(contactUidCopy);
+
+ if (id)
+ _contactsRemoved(QList<QContactLocalId>() << id);
+
+ return ok;
+}
+
+static void commitContactCB(EBook* book, EBookStatus status, gpointer user_data)
+{
+ Q_UNUSED(book)
+ jobSharedData *sd = static_cast<jobSharedData*>(user_data);
+ if (!sd)
+ return;
+
+ *sd->result = (status == E_BOOK_ERROR_OK) ? true : false;
+ *sd->error = getErrorFromStatus(status);
+ sd->that->_jobSavingCompleted();
+}
+
+static void addContactCB(EBook* book, EBookStatus status, const char *uid, gpointer user_data)
+{
+ jobSharedData *sd = static_cast<jobSharedData*>(user_data);
+ if (!sd)
+ return;
+
+ if (uid)
+ sd->uid = strdup(uid);
+
+ //### FIXME IS THIS LINE REALLY NEEDED: osso_abook_contact_set_roster(OssoABookContact *contact, OssoABookRoster *roster)
+ *sd->result = (status == E_BOOK_ERROR_OK) ? true : false;
+ 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);
+ }
+
+ // Convert QContact to AContact
+ aContact = convert(contact, error);
+ if (!aContact){
+ 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;
+ m_saveJobSD->error = error;
+ m_saveJobSD->uid = 0;
+
+ // Add/Commit the contact
+ uid = CONST_CHAR(e_contact_get_const(E_CONTACT (aContact), E_CONTACT_UID));
+ if (uid) {
+ m_saveJobSD->uid = strdup(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);
+
+ // save the newly saved contact's id in the hash.
+ m_localIds << m_saveJobSD->uid;
+
+ // set the id of the contact.
+ QContactId cId;
+ cId.setLocalId(m_localIds[m_saveJobSD->uid]);
+ contact->setId(cId);
+ if (m_saveJobSD->uid)
+ free(m_saveJobSD->uid);
+
+ return ok;
+}
+
+const QString QContactABook::getDisplayName(const QContact& contact) const{
+ //Get Osso ABook ID for the contact (stored as GUID detail)
+ const char* acontactID = NULL;
+ {
+ QContactGuid g = contact.detail(QContactGuid::DefinitionName);
+ acontactID = qPrintable(g.guid());
+ }
+
+ if (!acontactID){
+ QCM5_DEBUG << "The contact has not been saved yet and it doesn't have any GUID";
+ return QString();
+ }
+
+ //Get OssoABookContact
+ OssoABookContact *acontact= NULL;
+ {
+ GList* l= NULL;
+ l = osso_abook_aggregator_lookup(m_abookAgregator, acontactID);
+
+ if (g_list_length(l) == 1) {
+ acontact = A_CONTACT(l->data);
+ }
+ g_list_free(l);
+
+ }
+
+ if (!acontact){
+ QCM5_DEBUG << "AContact with ID:" << acontactID << "is null";
+ return QString();
+ }
+ //Get Display name;
+ const char* displayName = osso_abook_contact_get_display_name(acontact);
+
+ return QString::fromUtf8(displayName);
+}
+
+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 {
+ QCM5_DEBUG << "Cannot find the 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";
+ }
+ 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] = "org";
+ hash[QContactPhoneNumber::DefinitionName] = "phone";
+ hash[QContactUrl::DefinitionName] = "homepage-url";
+ }
+
+ QString eDetail = hash[f.detailDefinitionName()];
+ if (eDetail.isEmpty()){
+ 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)
+ 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){
+ 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";
+ query = convert(QContactInvalidFilter());
+ }
+
+ //Debugging
+ if (QCM5_DEBUG_ENABLED){
+ 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 & presences*/
+ QList<QContactOnlineAccount*> onlineAccounts;
+ QList<QContactPresence*> presences;
+ getOnlineAccountAndPresenceDetails(eContact, onlineAccounts, presences);
+
+ QContactOnlineAccount* onlineAccount;
+ foreach(onlineAccount, onlineAccounts)
+ detailList << onlineAccount;
+
+ QContactPresence* presence;
+ foreach(presence, presences)
+ detailList << presence;
+
+ /* 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){
+ 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, QContactManager::Error* error) const
+{
+ OssoABookContact* rtn = NULL;
+
+ QCM5_DEBUG << "Getting aContact with id " << m_localIds[contactId] << "local contactId is" << contactId;
+
+ if(QString::fromAscii(m_localIds[contactId]).compare("osso-abook-self") == 0) {
+ *error = QContactManager::NoError;
+ rtn = A_CONTACT(osso_abook_self_contact_get_default());
+ } else {
+ GList* c = osso_abook_aggregator_lookup(m_abookAgregator, m_localIds[contactId]);
+ if (c)
+ rtn = A_CONTACT(c->data);
+ *error = rtn ? QContactManager::NoError : QContactManager::DoesNotExistError;
+ return rtn;
+ }
+
+ 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)
+ rtn.setLocalId(localId);
+ }
+ return rtn;
+}
+
+QList<QContactAddress*> QContactABook::getAddressDetail(EContact *eContact) const
+{
+ QList<QContactAddress*> rtnList;
+
+ //Ordered list of Fields
+ QStringList addressFields;
+ addressFields << QContactAddress::FieldPostOfficeBox
+ << AddressFieldExtension //XXX 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) {
+ // ADR attribute data is corrupted. Skipping.
+ g_list_free(attrList);
+ return rtnList;
+ }
+
+ int i = 0;
+ while (v && i < 7) {
+ // we only deal with the first 7 fields: pobox, extension, street, locality, region, postcode, country.
+ map[addressFields[i]] = QString::fromUtf8(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::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] = QString::fromUtf8(CONST_CHAR(e_contact_get_const(eContact, E_CONTACT_GIVEN_NAME)));
+ map[QContactName::FieldLastName] = QString::fromUtf8(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] = QString::fromUtf8(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::fromUtf8(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
+ // Should be retrieved from EVC_PHOTO
+
+ 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 = 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::fromUtf8(note);
+ FREE(note);
+ setDetailValues(map, rtn);
+ return rtn;
+}
+
+static const QStringList vcardsManagedByTelepathy(){
+ static QStringList rtn;
+
+ if (rtn.isEmpty()){
+ 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) && field != "TEL")
+ rtn << field;
+ vcardFields = vcardFields->next;
+ }
+ FREE(vcardFields);
+ }
+
+ QCM5_DEBUG << "VCards managed by Telepathy:" << rtn;
+ return rtn;
+}
+
+static void populateProfilesCapabilitiesMap(QMap<QString, QStringList>* map){
+ GList* l = mc_profiles_list(); //Don't free this!!
+ GList* node;
+
+ map->clear();
+
+ for (node = l; node != NULL; node = g_list_next(node)) {
+ McProfile* p = (McProfile*) node->data; //Don't free this!!
+
+ //Get serviceProvider name from the profile
+ QString serviceProvider = mc_profile_get_unique_name(p);
+ if (serviceProvider.isEmpty())
+ continue;
+
+ // Get capabilities
+ McProfileCapabilityFlags caps = mc_profile_get_capabilities(p);
+ QStringList capList;
+
+ // MC_PROFILE_CAPABILITY_NONE skipped
+ if (caps & MC_PROFILE_CAPABILITY_CHAT_P2P)
+ capList << "CHAT_P2P";
+ if (caps & MC_PROFILE_CAPABILITY_CHAT_ROOM)
+ capList << "CHAT_ROOM";
+ if (caps & MC_PROFILE_CAPABILITY_CHAT_ROOM_LIST)
+ capList << "CHAT_ROOM_LIST";
+ if (caps & MC_PROFILE_CAPABILITY_VOICE_P2P)
+ capList << "VOICE_P2P";
+ if (caps & MC_PROFILE_CAPABILITY_CONTACT_SEARCH)
+ capList << "CONTACT_SEARCH";
+ if (caps & MC_PROFILE_CAPABILITY_SPLIT_ACCOUNT)
+ capList << "SPLIT_ACCOUNT";
+ if (caps & MC_PROFILE_CAPABILITY_REGISTRATION_UI)
+ capList << "REGISTRATION_UI";
+ if (caps & MC_PROFILE_CAPABILITY_SUPPORTS_AVATARS)
+ capList << "SUPPORTS_AVATARS";
+ if (caps & MC_PROFILE_CAPABILITY_SUPPORTS_ALIAS)
+ capList << "SUPPORTS_ALIAS";
+ if (caps & MC_PROFILE_CAPABILITY_SUPPORTS_ROSTER)
+ capList << "SUPPORTS_ROSTER";
+ if (caps & MC_PROFILE_CAPABILITY_VIDEO_P2P)
+ capList << "VIDEO_P2P";
+ if (caps & MC_PROFILE_CAPABILITY_CHAT_UPGRADE)
+ capList << "CHAT_UPGRADE";
+
+ // Store into the map
+ map->insert(serviceProvider, capList);
+ }
+
+}
+
+static const QStringList serviceProviderCapabilities(const QString& serviceProvider){
+ static QMap<QString, QStringList> map;
+
+ if (map.isEmpty())
+ populateProfilesCapabilitiesMap(&map);
+
+ if (serviceProvider.isEmpty())
+ return QStringList();
+
+ QStringList rtn = map.value(serviceProvider);
+ if (!rtn.isEmpty())
+ return rtn;
+ else
+ return QStringList();
+}
+
+#if 0
+// BUG? This code has been commented out because osso_abook_presence_get_presence_status returns empty strings
+static QContactPresence::PresenceState telepathyStatusToPresenceState(const QString& status){
+ if (status == "offline")
+ return QContactPresence::PresenceOffline;
+ else if (status == "unknown" || status == "error")
+ return QContactPresence::PresenceUnknown;
+ else if (status == "available")
+ return QContactPresence::PresenceAvailable;
+ else if (status == "away" || status == "brb" ) //Be Right Back (a more specific form of Away)
+ return QContactPresence::PresenceAway;
+ else if (status == "busy" || status == "dnd") //Do Not Disturb (a more specific form of Busy)
+ return QContactPresence::PresenceBusy;
+ else if (status == "xa") //Extended Away
+ return QContactPresence::PresenceExtendedAway;
+ else if (status == "hidden") // "Invisible" or "Appear Offline"
+ return QContactPresence::PresenceHidden;
+}
+#endif
+
+void QContactABook::getOnlineAccountAndPresenceDetails(EContact *eContact,
+ QList<QContactOnlineAccount*>& onlineAccounts,
+ QList<QContactPresence*>& presences) const
+{
+ const QStringList telepathyVCards = vcardsManagedByTelepathy();
+
+ // Parsing Attributes associated to the Telepathy VCards
+ GList *attributeList = e_vcard_get_attributes((EVCard*)eContact);
+ GList *node;
+
+ if (!attributeList)
+ return;
+
+ for (node = attributeList; node != NULL; node = g_list_next (node)) {
+ QContactOnlineAccount* onlineAccount = new QContactOnlineAccount;
+ QContactPresence* contactPresence = new QContactPresence;
+
+ const char* accountUri = NULL;
+ const char* serviceProvider = NULL;
+ const char* accountPath = NULL; // Outgoing account path eg: SERVICE_NAME/PROTOCOL_NAME/USER_NAME
+ EVCardAttribute* attr = NULL;
+ QVariantMap map;
+ QString presenceMsg, presenceIcon, presenceDisplayState, presenceNickname;
+ QStringList caps;
+
+ attr = (EVCardAttribute*)node->data;
+ if (!attr)
+ continue;
+
+ // Continue if the attribute doesn't contain Online Account info
+ QString attributeName = e_vcard_attribute_get_name(attr);
+ if (!telepathyVCards.contains(attributeName))
+ continue;
+
+ // Get the account URI
+ accountUri = e_vcard_attribute_get_value(attr);
+
+ // Get AccountPath and service provider for the roster contact associated to the attribute
+ // Note: there should be just one roster contact associated to the attribute
+ GList* rContacts = osso_abook_contact_find_roster_contacts_for_attribute(A_CONTACT(eContact), attr);
+ for (GList * node = rContacts; node != NULL; node = g_list_next(node)){
+ OssoABookContact* c = NULL;
+ c = A_CONTACT(node->data);
+ if (c) {
+ McAccount* a = NULL;
+ OssoABookPresence *p = NULL;
+
+ a = osso_abook_contact_get_account(c);
+ if (a){
+ accountPath = a->name;
+ serviceProvider = mc_account_compat_get_profile(a);
+ }
+
+ p = OSSO_ABOOK_PRESENCE(c);
+ presenceMsg = QString::fromUtf8(osso_abook_presence_get_presence_status_message(p));
+ presenceIcon = QString::fromUtf8(osso_abook_presence_get_icon_name(p));
+ // presenceState = QString::fromUtf8(osso_abook_presence_get_presence_status(p)); //BUG in osso_abook_presence_get_presence_status??
+ presenceDisplayState = QString::fromUtf8(osso_abook_presence_get_display_status(p));
+ presenceNickname = QString::fromUtf8(osso_abook_contact_get_display_name(c));
+ // Set OnlineAccount details
+ map.clear();
+ map[QContactOnlineAccount::FieldAccountUri] = accountUri;
+ map[QContactOnlineAccount::FieldCapabilities] = serviceProviderCapabilities(serviceProvider);
+ map[QContactOnlineAccount::FieldServiceProvider] = serviceProvider; // eg: facebook-chat,
+ map["AccountPath"] = accountPath;
+ setDetailValues(map, onlineAccount);
+ onlineAccounts << onlineAccount;
+
+ // Set OnlinePresence details
+ map.clear();
+ map[QContactDetail::FieldLinkedDetailUris] = accountUri;
+ map[QContactPresence::FieldCustomMessage] = presenceMsg;
+ map[QContactPresence::FieldPresenceStateImageUrl] = presenceIcon;
+ map[QContactPresence::FieldPresenceStateText] = presenceDisplayState;
+ map[QContactPresence::FieldNickname] = presenceNickname;
+ //map[QContactPresence::FieldPresenceState] = telepathyStatusToPresenceState(presenceState);
+ //map[QContactPresence::FieldTimestamp] = ;
+ setDetailValues(map, contactPresence);
+ presences << contactPresence;
+ }
+ }
+ }
+}
+
+QContactOrganization* QContactABook::getOrganizationDetail(EContact *eContact) const
+{
+ QContactOrganization* rtn = new QContactOrganization;
+ QVariantMap map;
+ const char* org = CONST_CHAR(e_contact_get(eContact, E_CONTACT_ORG));
+ map[QContactOrganization::FieldName] = QString::fromUtf8(org);
+ FREE(org);
+ 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 = 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;
+}
+
+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_avatar_get_image_rounded(OSSO_ABOOK_AVATAR(aContact), -1, -1, false, 0, 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::Format format = gdk_pixbuf_get_has_alpha(pixbuf) ? QImage::Format_ARGB32 : QImage::Format_RGB32;
+ int stride = gdk_pixbuf_get_rowstride(pixbuf);
+ QImage converted(bdata, bsize.width(), bsize.height(), stride, format);
+ converted = converted.rgbSwapped();
+ 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())
+{
+ 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;
+
+ // 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, QContactManager::Error* error) const
+{
+ Q_CHECK_PTR(contact);
+
+ // first, check for uniqueness constraints.
+ // currently, it is only addresses, email addresses, phone numbers
+ // and online accounts which are NOT unique.
+ if (contact->details<QContactAvatar>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactBirthday>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactGender>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactName>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactNickname>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactNote>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactOrganization>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactThumbnail>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+ if (contact->details<QContactUrl>().count() > 1) {
+ *error = QContactManager::LimitReachedError;
+ return 0;
+ }
+
+ 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, error);
+ EVCardAttribute *uidAttr = e_vcard_get_attribute(E_VCARD(rtn), e_contact_vcard_attribute(E_CONTACT_UID));
+
+ // remove all current attributes, since we rewrite them all.
+ EVCardAttribute *attr;
+ GList *attr_list = e_vcard_get_attributes (E_VCARD (rtn));
+ while (attr_list) {
+ attr = static_cast<EVCardAttribute*>(attr_list->data);
+ attr_list = attr_list->next;
+ if (!osso_abook_contact_attribute_is_readonly (attr)) {
+ if (attr != uidAttr) {
+ // we don't remove the uid, since we are updating the contact.
+ e_vcard_remove_attribute (E_VCARD (rtn), attr);
+ }
+ }
+ }
+ } 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 == AddressFieldExtension) 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);
+
+ //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);
+}
+
+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.hasAlphaChannel()) {
+ image = image.convertToFormat(QImage::Format_ARGB32);
+ image = image.rgbSwapped();
+ } else {
+ image = image.convertToFormat(QImage::Format_RGB888);
+ }
+
+ 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, 0, 0);
+ g_object_unref(pixbuf);
+}
+
+void QContactABook::setAvatarDetail(const OssoABookContact* aContact, const QContactAvatar& detail) const
+{
+ Q_UNUSED(aContact)
+ Q_UNUSED(detail);
+ // XXX TODO: FIXME
+ // We should set the path of the avatar in EVC_PHOTO
+}
+
+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);
+}
+
+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::FieldLastName << QContactName::FieldFirstName;
+
+ foreach(QString key, supportedDetailValues){
+ attrValues << detail.value(key);
+ }
+
+ //REMOVE ME - We don't want to support custom label
+ if (attrValues[1].isEmpty()){
+ //qWarning() << "QContactName::FieldFirstName is empty";
+ attrValues[1] = 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::FieldName);
+
+ 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";
+ else if (value == QContactPhoneNumber::SubTypeVoice)
+ value = "VOICE";
+ paramValues << value.toUpper();
+ } else
+ attrValues << i.value().toString();
+ }
+
+ // Avoid unsupported type
+ if (paramValues.isEmpty())
+ paramValues << "VOICE";
+
+ // new phone number detail.
+ addAttributeToAContact(aContact, EVC_TEL, attrValues, EVC_TYPE, paramValues);
+}
+
+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);
+}