[Merge] lp:~renatofilho/buteo-sync-plugins-contacts/new-code into lp:buteo-sync-plugins-contacts
Michael Sheldon
michael.sheldon at canonical.com
Mon Jul 20 13:09:16 UTC 2015
Review: Needs Fixing
Highlighted a number of places where you're using qDebug (most of the rest of the code uses LOG_DEBUG), I've only checked on the part of the diff shown on the MR (the first 5000 lines) so you may want to quickly grep the rest of the code as well.
Diff comments:
>
> === added file 'buteo-contact-client/UContactsBackend.cpp'
> --- buteo-contact-client/UContactsBackend.cpp 1970-01-01 00:00:00 +0000
> +++ buteo-contact-client/UContactsBackend.cpp 2015-07-19 21:39:03 +0000
> @@ -0,0 +1,560 @@
> +/*
> + * This file is part of buteo-gcontact-plugins package
> + *
> + * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
> + * 2015 Canonical Ltd
> + *
> + * Contributors: Sateesh Kavuri <sateesh.kavuri at gmail.com>
> + * Mani Chandrasekar <maninc at gmail.com>
> + * Renato Araujo Oliveira Filho <renato.filho at canonical.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public License
> + * version 2.1 as published by the Free Software Foundation.
> + *
> + * This library is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include "config.h"
> +#include "UContactsBackend.h"
> +#include "UContactsCustomDetail.h"
> +
> +#include <LogMacros.h>
> +
> +#include <QContactTimestamp>
> +#include <QContactIdFilter>
> +#include <QContactIntersectionFilter>
> +#include <QContactSyncTarget>
> +#include <QContactDetailFilter>
> +#include <QContactGuid>
> +#include <QContactDisplayLabel>
> +#include <QContactExtendedDetail>
> +#include <QContactSyncTarget>
> +
> +#include <QBuffer>
> +#include <QSet>
> +#include <QHash>
> +
> +#include <QDBusInterface>
> +#include <QDBusReply>
> +
> +static const QString CPIM_SERVICE_NAME ("com.canonical.pim");
> +static const QString CPIM_ADDRESSBOOK_OBJECT_PATH ("/com/canonical/pim/AddressBook");
> +static const QString CPIM_ADDRESSBOOK_IFACE_NAME ("com.canonical.pim.AddressBook");
> +
> +UContactsBackend::UContactsBackend(const QString &managerName, QObject* parent)
> + : QObject (parent),
> + iMgr(new QContactManager(managerName))
> +{
> + FUNCTION_CALL_TRACE;
> +}
> +
> +UContactsBackend::~UContactsBackend()
> +{
> + FUNCTION_CALL_TRACE;
> +
> + delete iMgr;
> + iMgr = NULL;
> +}
> +
> +bool
> +UContactsBackend::init(uint syncAccount, const QString &syncTarget)
> +{
> + FUNCTION_CALL_TRACE;
> +
> + // create address book it it does not exists
> + // check if the source already exists
> + QContactDetailFilter filter;
> + filter.setDetailType(QContactDetail::TypeType, QContactType::FieldType);
> + filter.setValue(QContactType::TypeGroup);
> +
> + QList<QContact> sources = iMgr->contacts(filter);
> + Q_FOREACH(const QContact &contact, sources) {
> + QContactExtendedDetail exd = UContactsCustomDetail::getCustomField(contact,
> + "ACCOUNT-ID");
> + if (!exd.isEmpty() && (exd.data().toUInt() == syncAccount)) {
> + mSyncTargetId = contact.detail<QContactGuid>().guid();
> + return true;
> + }
> + }
> +
> + // memory/mock manager does not support syncTarget
> + if (iMgr->managerName() != "mock") {
> + // create a new source if necessary
> + QContact contact;
> + contact.setType(QContactType::TypeGroup);
> +
> + QContactDisplayLabel label;
> + label.setLabel(syncTarget);
> + contact.saveDetail(&label);
> +
> + // set the new source as default
> + QContactExtendedDetail isDefault;
> + isDefault.setName("IS-PRIMARY");
> + isDefault.setData(true);
> + contact.saveDetail(&isDefault);
> +
> + // Link source with account
> + QContactExtendedDetail accountId;
> + accountId.setName("ACCOUNT-ID");
> + accountId.setData(syncAccount);
> + contact.saveDetail(&accountId);
> +
> + if (!iMgr->saveContact(&contact)) {
> + qWarning() << "Fail to create contact source:" << syncTarget;
> + return false;
> + }
> +
> + mSyncTargetId = contact.detail<QContactGuid>().guid();
> + }
> +
> + return true;
> +}
> +
> +bool
> +UContactsBackend::uninit()
> +{
> + FUNCTION_CALL_TRACE;
> +
> + return true;
> +}
> +
> +QList<QContactId>
> +UContactsBackend::getAllContactIds()
> +{
> + FUNCTION_CALL_TRACE;
> + Q_ASSERT (iMgr);
> + return iMgr->contactIds(getSyncTargetFilter());
> +}
> +
> +RemoteToLocalIdMap
> +UContactsBackend::getAllNewContactIds(const QDateTime &aTimeStamp)
> +{
> + FUNCTION_CALL_TRACE;
> + LOG_DEBUG("Retrieve New Contacts Since " << aTimeStamp);
> +
> + RemoteToLocalIdMap idList;
> + const QContactChangeLogFilter::EventType eventType =
> + QContactChangeLogFilter::EventAdded;
> +
> + getSpecifiedContactIds(eventType, aTimeStamp, &idList);
> +
> + return idList;
> +}
> +
> +RemoteToLocalIdMap
> +UContactsBackend::getAllModifiedContactIds(const QDateTime &aTimeStamp)
> +{
> +
> + FUNCTION_CALL_TRACE;
> +
> + LOG_DEBUG("Retrieve Modified Contacts Since " << aTimeStamp);
> +
> + RemoteToLocalIdMap idList;
> + const QContactChangeLogFilter::EventType eventType =
> + QContactChangeLogFilter::EventChanged;
> +
> + getSpecifiedContactIds(eventType, aTimeStamp, &idList);
> +
> + return idList;
> +}
> +
> +RemoteToLocalIdMap
> +UContactsBackend::getAllDeletedContactIds(const QDateTime &aTimeStamp)
> +{
> + FUNCTION_CALL_TRACE;
> + LOG_DEBUG("Retrieve Deleted Contacts Since " << aTimeStamp);
> +
> + RemoteToLocalIdMap idList;
> + const QContactChangeLogFilter::EventType eventType =
> + QContactChangeLogFilter::EventRemoved;
> +
> + getSpecifiedContactIds(eventType, aTimeStamp, &idList);
> +
> + return idList;
> +}
> +
> +bool
> +UContactsBackend::addContacts(QList<QContact>& aContactList,
> + QMap<int, UContactsStatus> *aStatusMap)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_ASSERT(iMgr);
> + Q_ASSERT(aStatusMap);
> +
> + QMap<int, QContactManager::Error> errorMap;
> +
> + // Check if contact already exists if it exists set the contact id
> + // to cause an update instead of create a new one
> + for(int i=0; i < aContactList.size(); i++) {
> + QContact &c = aContactList[i];
> + QString remoteId = getRemoteId(c);
> + QContactId id = entryExists(remoteId);
> + if (!id.isNull()) {
> + c.setId(id);
> + } else {
> + // make sure that all contacts retrieved is saved on the correct sync target
> + QContactSyncTarget syncTarget = c.detail<QContactSyncTarget>();
> + syncTarget.setSyncTarget(syncTargetId());
> + c.saveDetail(&syncTarget);
> + }
> +
> + // remove guid field if it exists
> + QContactGuid guid = c.detail<QContactGuid>();
> + if (!guid.isEmpty()) {
> + c.removeDetail(&guid);
> + }
> + }
> +
> + bool retVal = iMgr->saveContacts(&aContactList, &errorMap);
> + if (!retVal) {
> + LOG_WARNING( "Errors reported while saving contacts:" << iMgr->error() );
> + }
> +
> + // QContactManager will populate errorMap only for errors, but we use this as a status map,
> + // so populate NoError if there's no error.
> + // TODO QContactManager populates indices from the qContactList, but we populate keys, is this OK?
> + for (int i = 0; i < aContactList.size(); i++)
> + {
> + UContactsStatus status;
> + status.id = i;
> + if (!errorMap.contains(i)) {
> + status.errorCode = QContactManager::NoError;
> + } else {
> + qDebug() << "Contact with id " << aContactList.at(i).id() << " and index " << i <<" is in error";
Shouldn't this be using LOG_DEBUG for consistency?
> + status.errorCode = errorMap.value(i);
> + }
> + aStatusMap->insert(i, status);
> + }
> +
> + return retVal;
> +}
> +
> +QMap<int,UContactsStatus>
> +UContactsBackend::modifyContacts(QList<QContact> *aContactList)
> +{
> + FUNCTION_CALL_TRACE;
> +
> + Q_ASSERT (iMgr);
> + UContactsStatus status;
> +
> + QMap<int,QContactManager::Error> errors;
> + QMap<int,UContactsStatus> statusMap;
> +
> + // WORKAROUND: Our backend uses GUid as contact id due problems with contact id serialization
> + // we can not use this field
> + for (int i = 0; i < aContactList->size(); i++) {
> + QContact &newContact = (*aContactList)[i];
> + QString remoteId = getRemoteId(newContact);
> +
> + // if the contact was created the remoteId will not exists on local database
> + QContactId localId = entryExists(remoteId);
> +
> + // int this case we should use the guid stored on contact
> + QContactGuid guid = newContact.detail<QContactGuid>();
> +
> + if (localId.isNull() && !guid.isEmpty()) {
> + // try the guid (should contains the local id) field
> + localId = QContactId::fromString(guid.guid());
> + }
> + newContact.setId(localId);
> + newContact.removeDetail(&guid);
> + }
> +
> + if(iMgr->saveContacts(aContactList , &errors)) {
> + LOG_DEBUG("Batch Modification of Contacts Succeeded");
> + } else {
> + LOG_DEBUG("Batch Modification of Contacts Failed");
> + }
> +
> + // QContactManager will populate errorMap only for errors, but we use this as a status map,
> + // so populate NoError if there's no error.
> + // TODO QContactManager populates indices from the aContactList, but we populate keys, is this OK?
> + for (int i = 0; i < aContactList->size(); i++) {
> + QContactId contactId = aContactList->at(i).id();
> + if( !errors.contains(i) ) {
> + LOG_DEBUG("No error for contact with id " << contactId << " and index " << i);
> + status.errorCode = QContactManager::NoError;
> + statusMap.insert(i, status);
> + } else {
> + LOG_DEBUG("contact with id " << contactId << " and index " << i <<" is in error");
> + QContactManager::Error errorCode = errors.value(i);
> + status.errorCode = errorCode;
> + statusMap.insert(i, status);
> + }
> + }
> + return statusMap;
> +}
> +
> +QMap<int, UContactsStatus>
> +UContactsBackend::deleteContacts(const QStringList &aContactIDList)
> +{
> + FUNCTION_CALL_TRACE;
> +
> + QList<QContactId> qContactIdList;
> + foreach (QString id, aContactIDList) {
> + qContactIdList.append(QContactId::fromString(id));
> + }
> +
> + return deleteContacts(qContactIdList);
> +}
> +
> +QMap<int, UContactsStatus>
> +UContactsBackend::deleteContacts(const QList<QContactId> &aContactIDList) {
> + FUNCTION_CALL_TRACE;
> +
> + Q_ASSERT (iMgr);
> + UContactsStatus status;
> + QMap<int, QContactManager::Error> errors;
> + QMap<int, UContactsStatus> statusMap;
> +
> + qDebug() << "WILL REMOVE CONTACTS:" << aContactIDList;
LOG_DEBUG?
> + if(iMgr->removeContacts(aContactIDList , &errors)) {
> + LOG_DEBUG("Successfully Removed all contacts ");
> + }
> + else {
> + LOG_WARNING("Failed Removing Contacts");
> + }
> +
> + // QContactManager will populate errorMap only for errors, but we use this as a status map,
> + // so populate NoError if there's no error.
> + // TODO QContactManager populates indices from the qContactList, but we populate keys, is this OK?
> + for (int i = 0; i < aContactIDList.size(); i++) {
> + QContactId contactId = aContactIDList.value(i);
> + if( !errors.contains(i) )
> + {
> + LOG_DEBUG("No error for contact with id " << contactId << " and index " << i);
> + status.errorCode = QContactManager::NoError;
> + statusMap.insert(i, status);
> + }
> + else
> + {
> + LOG_DEBUG("contact with id " << contactId << " and index " << i <<" is in error");
> + QContactManager::Error errorCode = errors.value(i);
> + status.errorCode = errorCode;
> + statusMap.insert(i, status);
> + }
> + }
> +
> + return statusMap;
> +}
> +
> +
> +void
> +UContactsBackend::getSpecifiedContactIds(const QContactChangeLogFilter::EventType aEventType,
> + const QDateTime& aTimeStamp,
> + RemoteToLocalIdMap *aIdList)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_ASSERT(aIdList);
> +
> + QList<QContactId> localIdList;
> + QContactChangeLogFilter filter(aEventType);
> + filter.setSince(aTimeStamp);
> +
> + localIdList = iMgr->contactIds(filter & getSyncTargetFilter());
> + LOG_DEBUG("Local ID added = " << localIdList.size() << " Datetime from when this " << aTimeStamp.toString());
> + // Filter out ids for items that were added after the specified time.
> + if (aEventType != QContactChangeLogFilter::EventAdded)
> + {
> + filter.setEventType(QContactChangeLogFilter::EventAdded);
> + QList<QContactId> addedList = iMgr->contactIds(filter & getSyncTargetFilter());
> + foreach (const QContactId &id, addedList)
> + {
> + localIdList.removeAll(id);
> + }
> + }
> +
> + // This is a defensive procedure to prevent duplicate items being sent.
> + // QSet does not allow duplicates, thus transforming QList to QSet and back
> + // again will remove any duplicate items in the original QList.
> + int originalIdCount = localIdList.size();
> + QSet<QContactId> idSet = localIdList.toSet();
> + int idCountAfterDupRemoval = idSet.size();
> +
> + LOG_DEBUG("Item IDs found (returned / incl. duplicates): " << idCountAfterDupRemoval << "/" << originalIdCount);
> + if (originalIdCount != idCountAfterDupRemoval) {
> + LOG_WARNING("Contacts backend returned duplicate items for requested list");
> + LOG_WARNING("Duplicate item IDs have been removed");
> + } // no else
> +
> + localIdList = idSet.toList();
> +
> + QContactFetchHint remoteIdHint;
> + QList <QContactDetail::DetailType> detailTypes;
> + detailTypes << QContactExtendedDetail::Type;
> + remoteIdHint.setDetailTypesHint(detailTypes);
> +
> + QList<QContact> contacts = iMgr->contacts(localIdList, remoteIdHint);
> + foreach (const QContact &contact, contacts) {
> + QString rid = getRemoteId(contact);
> + aIdList->insertMulti(rid, contact.id());
> + }
> +}
> +
> +/*!
> + \fn GContactsBackend::getContact(QContactId aContactId)
> + */
> +QContact
> +UContactsBackend::getContact(const QContactId& aContactId)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_ASSERT (iMgr);
> + QList<QContact> returnedContacts;
> +
> + LOG_DEBUG("Contact ID to be retreived = " << aContactId.toString());
> + returnedContacts = iMgr->contacts(QList<QContactId>() << aContactId);
> +
> + LOG_DEBUG("Contacts retreived from Contact manager = " << returnedContacts.count());
> + return returnedContacts.value(0, QContact());
> +}
> +
> +QContact
> +UContactsBackend::getContact(const QString& remoteId)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_ASSERT (iMgr);
> + LOG_DEBUG("Remote id to be searched for = " << remoteId);
> +
> + //FIXME: use guid field when supported by address-book-service
> + QContactIntersectionFilter remoteIdFilter = getRemoteIdFilter(remoteId);
> + QList<QContact> contactList = iMgr->contacts(remoteIdFilter & getSyncTargetFilter());
> + if (contactList.size() > 0) {
> + return contactList.at(0);
> + }
> + return QContact();
> +}
> +
> +QContactId
> +UContactsBackend::entryExists(const QString remoteId)
> +{
> + if (remoteId.isEmpty()) {
> + return QContactId();
> + }
> +
> + QContactFilter ridFilter = getRemoteIdFilter(remoteId);
> + QList<QContactId> idList = iMgr->contactIds(ridFilter & getSyncTargetFilter());
> + if (idList.size () > 0)
> + return idList.first ();
> + else
> + return QContactId();
> +}
> +
> +QString
> +UContactsBackend::syncTargetId() const
> +{
> + return mSyncTargetId;
> +}
> +
> +const QStringList
> +UContactsBackend::localIds(const QStringList remoteIds)
> +{
> + QStringList localIdList;
> + foreach (QString guid , remoteIds) {
> + QString localId = entryExists(guid).toString();
> + if (!localId.isEmpty()) {
> + localIdList << localId;
> + }
> + }
> + Q_ASSERT(localIdList.count() == remoteIds.count());
> + return localIdList;
> +}
> +
> +QContactFilter
> +UContactsBackend::getRemoteIdFilter(const QString &remoteId) const
> +{
> + QContactIntersectionFilter remoteFilter;
> +
> + QContactDetailFilter xDetailNameFilter;
> + xDetailNameFilter.setDetailType(QContactExtendedDetail::Type,
> + QContactExtendedDetail::FieldName);
> + xDetailNameFilter.setValue(UContactsCustomDetail::FieldRemoteId);
> +
> + QContactDetailFilter xDetailValueFilter;
> + xDetailValueFilter.setDetailType(QContactExtendedDetail::Type,
> + QContactExtendedDetail::FieldData);
> + xDetailValueFilter.setValue(remoteId);
> +
> + remoteFilter << xDetailNameFilter
> + << xDetailValueFilter;
> + return remoteFilter;
> +}
> +
> +QString
> +UContactsBackend::getRemoteId(const QContact &contact)
> +{
> + return UContactsCustomDetail::getCustomField(contact, UContactsCustomDetail::FieldRemoteId).data().toString();
> +}
> +
> +void UContactsBackend::setRemoteId(QContact &contact, const QString &remoteId)
> +{
> + UContactsCustomDetail::setCustomField(contact, UContactsCustomDetail::FieldRemoteId, QVariant(remoteId));
> +}
> +
> +QString UContactsBackend::getLocalId(const QContact &contact)
> +{
> + QContactGuid guid = contact.detail<QContactGuid>();
> + return guid.guid();
> +}
> +
> +void UContactsBackend::setLocalId(QContact &contact, const QString &localId)
> +{
> + QContactGuid guid = contact.detail<QContactGuid>();
> + guid.setGuid(localId);
> + contact.saveDetail(&guid);
> +}
> +
> +bool UContactsBackend::deleted(const QContact &contact)
> +{
> + QString deletedAt = UContactsCustomDetail::getCustomField(contact, UContactsCustomDetail::FieldDeletedAt).data().toString();
> + return !deletedAt.isEmpty();
> +}
> +
> +void
> +UContactsBackend::purgecontacts()
> +{
> + QDBusInterface iface(CPIM_SERVICE_NAME,
> + CPIM_ADDRESSBOOK_OBJECT_PATH,
> + CPIM_ADDRESSBOOK_IFACE_NAME);
> + QDBusReply<void> reply = iface.call("purgeContacts", QString(""), mSyncTargetId);
> + if (reply.error().isValid()) {
> + LOG_WARNING("Fail to purge contacts" << reply.error());
> + } else {
> + LOG_DEBUG("Purged backend contacts");
> + }
> +}
> +
> +QContactFilter
> +UContactsBackend::getSyncTargetFilter() const
> +{
> + // user enterred contacts, i.e. all other contacts that are not sourcing
> + // from restricted backends or instant messaging service
> + static QContactDetailFilter detailFilterDefaultSyncTarget;
> +
> + if (!mSyncTargetId.isEmpty() &&
> + detailFilterDefaultSyncTarget.value().isNull()) {
> + detailFilterDefaultSyncTarget.setDetailType(QContactSyncTarget::Type,
> + QContactSyncTarget::FieldSyncTarget + 1);
> + detailFilterDefaultSyncTarget.setValue(mSyncTargetId);
> + } else if (mSyncTargetId.isEmpty()) {
> + return QContactFilter();
> + }
> +
> + // return the union
> + return detailFilterDefaultSyncTarget;
> +}
> +
> +
> +QtContacts::QContactManager *UContactsBackend::manager() const
> +{
> + return iMgr;
> +}
>
> === added file 'buteo-contact-client/UContactsClient.cpp'
> --- buteo-contact-client/UContactsClient.cpp 1970-01-01 00:00:00 +0000
> +++ buteo-contact-client/UContactsClient.cpp 2015-07-19 21:39:03 +0000
> @@ -0,0 +1,949 @@
> +/*
> + * This file is part of buteo-gcontact-plugins package
> + *
> + * Copyright (C) 2013 Jolla Ltd. and/or its subsidiary(-ies).
> + * 2015 Canonical Ltd
> + *
> + * Contributors: Sateesh Kavuri <sateesh.kavuri at gmail.com>
> + * Mani Chandrasekar <maninc at gmail.com>
> + * Renato Araujo Oliveira Filho <renato.filho at canonical.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public License
> + * version 2.1 as published by the Free Software Foundation.
> + *
> + * This library is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include "UContactsClient.h"
> +#include "UContactsBackend.h"
> +#include "UAbstractRemoteSource.h"
> +#include "UAuth.h"
> +#include "config.h"
> +
> +//Buteo
> +#include <LogMacros.h>
> +#include <ProfileEngineDefs.h>
> +#include <ProfileManager.h>
> +
> +#include <QLibrary>
> +#include <QtNetwork>
> +#include <QDateTime>
> +#include <QContactGuid>
> +#include <QContactDetailFilter>
> +#include <QContactAvatar>
> +
> +class UContactsClientPrivate
> +{
> +public:
> + UContactsClientPrivate(const QString &serviceName)
> + : mAuth(0),
> + mContactBackend(0),
> + mRemoteSource(0),
> + mServiceName(serviceName)
> + {
> + }
> +
> + UAuth* mAuth;
> + UContactsBackend* mContactBackend;
> + UAbstractRemoteSource* mRemoteSource;
> + bool mSlowSync;
> + QString mServiceName;
> + // local database information
> + QSet<QContactId> mAllLocalContactIds;
> + RemoteToLocalIdMap mAddedContactIds;
> + RemoteToLocalIdMap mModifiedContactIds;
> + RemoteToLocalIdMap mDeletedContactIds;
> + // sync report
> + QMap<QString, Buteo::DatabaseResults> mItemResults;
> + Buteo::SyncResults mResults;
> + // sync profile
> + QString mSyncTarget;
> + qint32 mAccountId;
> + Buteo::SyncProfile::SyncDirection mSyncDirection;
> + Buteo::SyncProfile::ConflictResolutionPolicy mConflictResPolicy;
> +};
> +
> +UContactsClient::UContactsClient(const QString& aPluginName,
> + const Buteo::SyncProfile& aProfile,
> + Buteo::PluginCbInterface *aCbInterface, const QString &serviceName)
> + : ClientPlugin(aPluginName, aProfile, aCbInterface),
> + d_ptr(new UContactsClientPrivate(serviceName))
> +{
> + FUNCTION_CALL_TRACE;
> +}
> +
> +UContactsClient::~UContactsClient()
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + delete d->mAuth;
> + delete d->mRemoteSource;
> +}
> +
> +bool
> +UContactsClient::init()
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + if (lastSyncTime().isNull()) {
> + d->mSlowSync = true;
> + } else {
> + d->mSlowSync = false;
> + }
> +
> + LOG_DEBUG ("Last sync date:" << lastSyncTime() << "Using slow sync?" << d->mSlowSync);
> + if (!initConfig()) {
> + LOG_CRITICAL("Fail to init configuration");
> + return false;
> + }
> +
> + d->mAuth = crateAuthenticator(this);
> + if (!d->mAuth || !d->mAuth->init(d->mAccountId, d->mServiceName)) {
> + LOG_CRITICAL("Fail to create auth object");
> + goto init_fail;
> + }
> +
> + d->mContactBackend = createContactsBackend(this);
> + if (!d->mContactBackend || !d->mContactBackend->init(d->mAccountId,
> + d->mAuth->accountDisplayName())) {
> + LOG_CRITICAL("Fail to create contact backend");
> + goto init_fail;
> + }
> +
> +
> + // remote source must be initialized after mAuth because its uses the account name property
> + d->mRemoteSource = createRemoteSource(this);
> + if (!d->mRemoteSource) {
> + LOG_CRITICAL("Fail to create remote contact backend");
> + goto init_fail;
> + }
> +
> + // sign in.
> + connect(d->mAuth, SIGNAL(success()), SLOT(start()));
> + connect(d->mAuth, SIGNAL(failed()), SLOT(onAuthenticationError()));
> +
> + // syncStateChanged to signal changes from CONNECTING, RECEIVING
> + // SENDING, DISCONNECTING, CLOSED
> + connect(this,
> + SIGNAL(stateChanged(Sync::SyncProgressDetail)),
> + SLOT(onStateChanged(Sync::SyncProgressDetail)));
> +
> + // Take necessary action when sync is finished
> + connect(this,
> + SIGNAL(syncFinished(Sync::SyncStatus)),
> + SLOT(onSyncFinished(Sync::SyncStatus)));
> +
> + return true;
> +
> +init_fail:
> +
> + delete d->mRemoteSource;
> + delete d->mContactBackend;
> + delete d->mAuth;
> + d->mRemoteSource = 0;
> + d->mContactBackend = 0;
> + d->mAuth = 0;
> + return false;
> +}
> +
> +bool
> +UContactsClient::uninit()
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + delete d->mRemoteSource;
> + delete d->mContactBackend;
> + delete d->mAuth;
> + d->mRemoteSource = 0;
> + d->mContactBackend = 0;
> + d->mAuth = 0;
> +
> + return true;
> +}
> +
> +bool
> +UContactsClient::isReadyToSync() const
> +{
> + const Q_D(UContactsClient);
> + return (d->mContactBackend && d->mRemoteSource && d->mAuth);
> +}
> +
> +UContactsBackend *UContactsClient::createContactsBackend(QObject *parent) const
> +{
> + return new UContactsBackend(QCONTACTS_BACKEND_NAME, parent);
> +}
> +
> +UAuth *UContactsClient::crateAuthenticator(QObject *parent) const
> +{
> + return new UAuth(parent);
> +}
> +
> +bool
> +UContactsClient::startSync()
> +{
> + FUNCTION_CALL_TRACE;
> +
> + if (!isReadyToSync()) {
> + LOG_WARNING ("Ubuntu plugin is not ready to sync.");
> + return false;
> + }
> + /*
> + 1. If no previous sync, go for slow-sync. Fetch all contacts
> + from server
> + 2. Check if previous sync happened (from SyncLog). If yes,
> + fetch the time of last sync
> + 3. Using the last sync time, retrieve all contacts from server
> + that were added/modified/deleted
> + 4. Fetch all added/modified/deleted items from device
> + 5. Check for conflicts. Take the default policy as "server-wins"
> + 6. Save the list from the server to device
> + 7. Push "client changes" - "conflicting items" to the server
> + 8. Save the sync log
> + */
> +
> + Q_D(UContactsClient);
> + LOG_DEBUG ("Init done. Continuing with sync");
> +
> + return d->mAuth->authenticate();
> +}
> +
> +void
> +UContactsClient::abortSync(Sync::SyncStatus aStatus)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + d->mRemoteSource->abort();
> + emit syncFinished(Sync::SYNC_ABORTED);
> +}
> +
> +bool
> +UContactsClient::initConfig()
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + //TODO: support multiple remote databases "scopes"
> + QStringList accountList = iProfile.keyValues(Buteo::KEY_ACCOUNT_ID);
> + if (!accountList.isEmpty()) {
> + QString aId = accountList.first();
> + if (aId != NULL) {
> + d->mAccountId = aId.toInt();
> + }
> + } else {
> + d->mAccountId = 0;
> + LOG_WARNING("Account id not found in config profile");
> + return false;
> + }
> +
> + QStringList databaseName = iProfile.keyValues(Buteo::KEY_DISPLAY_NAME);
> + if (databaseName.isEmpty()) {
> + LOG_WARNING("\"Loal URI\" is missing on configuration file");
> + return false;
> + }
> + d->mSyncTarget = databaseName.first();
> + d->mSyncDirection = iProfile.syncDirection();
> + d->mConflictResPolicy = iProfile.conflictResolutionPolicy();
> + d->mItemResults.insert(d->mSyncTarget, Buteo::DatabaseResults());
> +
> + return true;
> +}
> +
> +void
> +UContactsClient::onAuthenticationError()
> +{
> + LOG_WARNING("Fail to authenticate with account");
> + emit syncFinished (Sync::SYNC_AUTHENTICATION_FAILURE);
> +}
> +
> +bool
> +UContactsClient::start()
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + // Remote source will be create after authentication since it needs some information
> + // about the authentication (auth-token, etc..)
> +
> + if (!d->mRemoteSource->init(remoteSourceProperties())) {
> + LOG_WARNING("Fail to init remote source");
> + return false;
> + }
> +
> + switch (d->mSyncDirection)
> + {
> + case Buteo::SyncProfile::SYNC_DIRECTION_TWO_WAY:
> + {
> + QDateTime sinceDate = d->mSlowSync ? QDateTime() : lastSyncTime();
> +
> + qDebug() << "load all contacts since" << sinceDate << sinceDate.isValid();
LOG_DEBUG?
> + // load changed contact since the last sync date or all contacts if not
> + // sync was done before
> + loadLocalContacts(sinceDate);
> +
> + // load remote contacts
> + if (d->mSlowSync) {
> + connect(d->mRemoteSource,
> + SIGNAL(contactsFetched(QList<QtContacts::QContact>,Sync::SyncStatus)),
> + SLOT(onRemoteContactsFetchedForSlowSync(QList<QtContacts::QContact>,Sync::SyncStatus)));
> + } else {
> + connect(d->mRemoteSource,
> + SIGNAL(contactsFetched(QList<QtContacts::QContact>,Sync::SyncStatus)),
> + SLOT(onRemoteContactsFetchedForFastSync(QList<QtContacts::QContact>,Sync::SyncStatus)));
> + }
> + d->mRemoteSource->fetchContacts(sinceDate, !d->mSlowSync, true);
> + break;
> + }
> + case Buteo::SyncProfile::SYNC_DIRECTION_FROM_REMOTE:
> + // Not required
> + break;
> + case Buteo::SyncProfile::SYNC_DIRECTION_TO_REMOTE:
> + // Not required
> + break;
> + case Buteo::SyncProfile::SYNC_DIRECTION_UNDEFINED:
> + // Not required
> + default:
> + // throw configuration error
> + return false;
> + break;
> + };
> +
> + return true;
> +}
> +
> +QList<QContact>
> +UContactsClient::prepareContactsToUpload(UContactsBackend *backend,
> + const QSet<QContactId> &ids)
> +{
> + QList<QContact> toUpdate;
> +
> + foreach(const QContactId &id, ids) {
> + QContact contact = backend->getContact(id);
> + if (!contact.isEmpty()) {
> + toUpdate << contact;
> + } else {
> + LOG_CRITICAL("Fail to find local contact with id:" << id);
> + return QList<QContact>();
> + }
> + }
> +
> + return toUpdate;
> +}
> +
> +void
> +UContactsClient::onRemoteContactsFetchedForSlowSync(const QList<QContact> contacts,
> + Sync::SyncStatus status)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> + if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
> + disconnect(d->mRemoteSource);
> + }
> +
> + if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
> + // save remote contacts locally
> + storeToLocalForSlowSync(contacts);
> +
> + if (status == Sync::SYNC_DONE) {
> + QList<QContact> toUpload = prepareContactsToUpload(d->mContactBackend, d->mAllLocalContactIds);
> + connect(d->mRemoteSource,
> + SIGNAL(transactionCommited(QList<QtContacts::QContact>,
> + QList<QtContacts::QContact>,
> + QStringList,Sync::SyncStatus)),
> + SLOT(onContactsSavedForSlowSync(QList<QtContacts::QContact>,
> + QList<QtContacts::QContact>,
> + QStringList,Sync::SyncStatus)));
> +
> + d->mRemoteSource->transaction();
> + d->mRemoteSource->saveContacts(toUpload);
> + d->mRemoteSource->commit();
> + }
> + } else {
> + emit syncFinished(status);
> + }
> +}
> +
> +void
> +UContactsClient::onContactsSavedForSlowSync(const QList<QtContacts::QContact> &createdContacts,
> + const QList<QtContacts::QContact> &updatedContacts,
> + const QStringList &removedContacts,
> + Sync::SyncStatus status)
> +{
> + Q_UNUSED(updatedContacts)
> + Q_UNUSED(removedContacts)
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + qDebug() << "AFTER UPLOAD(Slow sync):"
LOG_DEBUG?
> + << "\n\tCreated on remote:" << createdContacts.size()
> + << "\n\tUpdated on remote:" << updatedContacts.size()
> + << "\n\tRemoved from remote:" << removedContacts.size();
> +
> + if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
> + disconnect(d->mRemoteSource);
> + }
> +
> + if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
> + QList<QContact> changedContacts;
> +
> + changedContacts += createdContacts;
> + changedContacts += updatedContacts;
> + updateIdsToLocal(changedContacts);
> +
> + // sync report
> + addProcessedItem(Sync::ITEM_ADDED,
> + Sync::REMOTE_DATABASE,
> + syncTargetId(),
> + createdContacts.size());
> +
> + if (status == Sync::SYNC_PROGRESS) {
> + // sync still in progress
> + return;
> + } else {
> + // WORKARDOUND: 'galera' contacts service take a while to fir contacts
> + // changed singal, this can cause a new sync due the storage change plugin
> + // lets wait 2 secs before fire sync finished signal
> + QTimer::singleShot(2000, this, SLOT(fireSyncFinishedSucessfully()));
> + return;
> + }
> + }
> +
> + emit syncFinished(status);
> +}
> +
> +void UContactsClient::onRemoteContactsFetchedForFastSync(const QList<QContact> contacts,
> + Sync::SyncStatus status)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> + qDebug() << "REMOTE CONTACTS" << contacts.size();
LOG_DEBUG?
> +
> + if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
> + disconnect(d->mRemoteSource);
> + }
> +
> + if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
> + // save remote contacts locally
> + storeToLocalForFastSync(contacts);
> +
> + if (status == Sync::SYNC_DONE) {
> + QList<QContact> contactsToUpload;
> + QList<QContact> contactsToRemove;
> +
> + // Contacts created locally
> + LOG_DEBUG("Total number of Contacts ADDED : " << d->mAddedContactIds.count());
> + contactsToUpload = prepareContactsToUpload(d->mContactBackend,
> + d->mAddedContactIds.values().toSet());
> +
> + // Contacts modified locally
> + LOG_DEBUG("Total number of Contacts MODIFIED : " << d->mModifiedContactIds.count());
> + contactsToUpload += prepareContactsToUpload(d->mContactBackend,
> + d->mModifiedContactIds.values().toSet());
> +
> + // Contacts deleted locally
> + LOG_DEBUG("Total number of Contacts DELETED : " << d->mDeletedContactIds.count());
> + contactsToRemove = prepareContactsToUpload(d->mContactBackend,
> + d->mDeletedContactIds.values().toSet());
> +
> + connect(d->mRemoteSource,
> + SIGNAL(transactionCommited(QList<QtContacts::QContact>,
> + QList<QtContacts::QContact>,
> + QStringList,Sync::SyncStatus)),
> + SLOT(onContactsSavedForFastSync(QList<QtContacts::QContact>,
> + QList<QtContacts::QContact>,
> + QStringList,Sync::SyncStatus)));
> +
> + d->mRemoteSource->transaction();
> + d->mRemoteSource->saveContacts(contactsToUpload);
> + d->mRemoteSource->removeContacts(contactsToRemove);
> + d->mRemoteSource->commit();
> + }
> + } else {
> + emit syncFinished(status);
> + }
> +}
> +
> +void
> +UContactsClient::onContactsSavedForFastSync(const QList<QtContacts::QContact> &createdContacts,
> + const QList<QtContacts::QContact> &updatedContacts,
> + const QStringList &removedContacts,
> + Sync::SyncStatus status)
> +{
> + Q_UNUSED(updatedContacts)
> + Q_UNUSED(removedContacts)
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + qDebug() << "AFTER UPLOAD(Fast sync):" << status
LOG_DEBUG?
> + << "\n\tCreated on remote:" << createdContacts.size()
> + << "\n\tUpdated on remote:" << updatedContacts.size()
> + << "\n\tRemoved from remote:" << removedContacts.size();
> +
> + if ((status != Sync::SYNC_PROGRESS) && (status != Sync::SYNC_STARTED)) {
> + disconnect(d->mRemoteSource);
> + }
> +
> + if ((status == Sync::SYNC_PROGRESS) || (status == Sync::SYNC_DONE)) {
> + QList<QContact> changedContacts;
> +
> + changedContacts += createdContacts;
> + changedContacts += updatedContacts;
> +
> + updateIdsToLocal(changedContacts);
> +
> + // sync report
> + addProcessedItem(Sync::ITEM_ADDED,
> + Sync::REMOTE_DATABASE,
> + syncTargetId(),
> + createdContacts.size());
> + addProcessedItem(Sync::ITEM_MODIFIED,
> + Sync::REMOTE_DATABASE,
> + syncTargetId(),
> + updatedContacts.size());
> + addProcessedItem(Sync::ITEM_DELETED,
> + Sync::REMOTE_DATABASE,
> + syncTargetId(),
> + removedContacts.size());
> +
> + if (status == Sync::SYNC_PROGRESS) {
> + // sync still in progress
> + return;
> + } else {
> + // WORKARDOUND: 'galera' contacts service take a while to fir contacts
> + // changed singal, this can cause a new sync due the storage change plugin
> + // lets wait 2 secs before fire sync finished signal
> + QTimer::singleShot(2000, this, SLOT(fireSyncFinishedSucessfully()));
> + return;
> + }
> + }
> +
> + emit syncFinished(status);
> +}
> +
> +bool
> +UContactsClient::storeToLocalForSlowSync(const QList<QContact> &remoteContacts)
> +{
> + FUNCTION_CALL_TRACE;
> +
> + Q_D(UContactsClient);
> + Q_ASSERT(d->mSlowSync);
> +
> + bool syncSuccess = false;
> +
> + LOG_DEBUG ("@@@storeToLocal#SLOW SYNC");
> + // Since we request for all the deleted contacts, if
> + // slow sync is performed many times, even deleted contacts
> + // will appear in *remoteContacts. Filter them out while
> + // saving them to device
> + LOG_DEBUG ("TOTAL REMOTE CONTACTS:" << remoteContacts.size());
> +
> + if (!remoteContacts.isEmpty()) {
> + QMap<int, UContactsStatus> statusMap;
> + QList<QContact> cpyContacts(remoteContacts);
> + if (d->mContactBackend->addContacts(cpyContacts, &statusMap)) {
> + // TODO: Saving succeeded. Update sync results
> + syncSuccess = true;
> +
> + // sync report
> + addProcessedItem(Sync::ITEM_ADDED,
> + Sync::LOCAL_DATABASE,
> + syncTargetId(),
> + cpyContacts.count());
> + } else {
> + // TODO: Saving failed. Update sync results and probably stop sync
> + syncSuccess = false;
> + }
> + }
> +
> + return syncSuccess;
> +}
> +
> +bool
> +UContactsClient::storeToLocalForFastSync(const QList<QContact> &remoteContacts)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> + Q_ASSERT(!d->mSlowSync);
> +
> + bool syncSuccess = false;
> + LOG_DEBUG ("@@@storeToLocal#FAST SYNC");
> + QList<QContact> remoteAddedContacts, remoteModifiedContacts, remoteDeletedContacts;
> + filterRemoteAddedModifiedDeletedContacts(remoteContacts,
> + remoteAddedContacts,
> + remoteModifiedContacts,
> + remoteDeletedContacts);
> +
> + resolveConflicts(remoteModifiedContacts, remoteDeletedContacts);
> +
> + if (!remoteAddedContacts.isEmpty()) {
> + LOG_DEBUG ("***Adding " << remoteAddedContacts.size() << " contacts");
> + QMap<int, UContactsStatus> addedStatusMap;
> + syncSuccess = d->mContactBackend->addContacts(remoteAddedContacts, &addedStatusMap);
> +
> + if (syncSuccess) {
> + // sync report
> + addProcessedItem(Sync::ITEM_ADDED,
> + Sync::LOCAL_DATABASE,
> + syncTargetId(),
> + remoteAddedContacts.count());
> + }
> + }
> +
> + if (!remoteModifiedContacts.isEmpty()) {
> + LOG_DEBUG ("***Modifying " << remoteModifiedContacts.size() << " contacts");
> + QMap<int, UContactsStatus> modifiedStatusMap =
> + d->mContactBackend->modifyContacts(&remoteModifiedContacts);
> +
> + syncSuccess = (modifiedStatusMap.size() > 0);
> +
> + if (syncSuccess) {
> + // sync report
> + addProcessedItem(Sync::ITEM_MODIFIED,
> + Sync::LOCAL_DATABASE,
> + syncTargetId(),
> + modifiedStatusMap.size());
> + }
> + }
> +
> + if (!remoteDeletedContacts.isEmpty()) {
> + LOG_DEBUG ("***Deleting " << remoteDeletedContacts.size() << " contacts");
> + QStringList guidList;
> + for (int i=0; i<remoteDeletedContacts.size(); i++) {
> + guidList << UContactsBackend::getRemoteId(remoteDeletedContacts.at(i));
> + }
> +
> + QStringList localIdList = d->mContactBackend->localIds(guidList);
> + QMap<int, UContactsStatus> deletedStatusMap =
> + d->mContactBackend->deleteContacts(localIdList);
> +
> + syncSuccess = (deletedStatusMap.size() > 0);
> + if (syncSuccess) {
> + // sync report
> + addProcessedItem(Sync::ITEM_DELETED,
> + Sync::LOCAL_DATABASE,
> + syncTargetId(),
> + localIdList.size());
> + }
> + }
> +
> + return syncSuccess;
> +}
> +
> +bool
> +UContactsClient::cleanUp()
> +{
> + FUNCTION_CALL_TRACE;
> + //TODO
> + return true;
> +}
> +
> +void UContactsClient::connectivityStateChanged(Sync::ConnectivityType aType, bool aState)
> +{
> + FUNCTION_CALL_TRACE;
> + LOG_DEBUG("Received connectivity change event:" << aType << " changed to " << aState);
> +}
> +
> +void
> +UContactsClient::loadLocalContacts(const QDateTime &since)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + if (!since.isValid()) {
> + d->mAllLocalContactIds = d->mContactBackend->getAllContactIds().toSet();
> +
> + LOG_DEBUG ("Number of contacts:" << d->mAllLocalContactIds.size ());
> + } else {
> + d->mAddedContactIds = d->mContactBackend->getAllNewContactIds(since);
> + d->mModifiedContactIds = d->mContactBackend->getAllModifiedContactIds(since);
> + d->mDeletedContactIds = d->mContactBackend->getAllDeletedContactIds(since);
> +
> + LOG_DEBUG ("Number of local added contacts:" << d->mAddedContactIds.size());
> + LOG_DEBUG ("Number of local modified contacts:" << d->mModifiedContactIds.size());
> + LOG_DEBUG ("Number of local removed contacts:" << d->mDeletedContactIds.size());
> + }
> +}
> +
> +void
> +UContactsClient::onStateChanged(Sync::SyncProgressDetail aState)
> +{
> + FUNCTION_CALL_TRACE;
> +
> + switch(aState) {
> + case Sync::SYNC_PROGRESS_SENDING_ITEMS: {
> + emit syncProgressDetail(getProfileName(), Sync::SYNC_PROGRESS_SENDING_ITEMS);
> + break;
> + }
> + case Sync::SYNC_PROGRESS_RECEIVING_ITEMS: {
> + emit syncProgressDetail(getProfileName(), Sync::SYNC_PROGRESS_RECEIVING_ITEMS);
> + break;
> + }
> + case Sync::SYNC_PROGRESS_FINALISING: {
> + emit syncProgressDetail(getProfileName(), Sync::SYNC_PROGRESS_FINALISING);
> + break;
> + }
> + default:
> + //do nothing
> + break;
> + };
> +}
> +
> +void
> +UContactsClient::onSyncFinished(Sync::SyncStatus aState)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + switch(aState)
> + {
> + case Sync::SYNC_ERROR:
> + case Sync::SYNC_AUTHENTICATION_FAILURE:
> + case Sync::SYNC_DATABASE_FAILURE:
> + case Sync::SYNC_CONNECTION_ERROR:
> + case Sync::SYNC_NOTPOSSIBLE:
> + {
> + generateResults(false);
> + emit error(getProfileName(), "", aState);
> + break;
> + }
> + case Sync::SYNC_DONE:
> + // purge all deleted contacts
> + d->mContactBackend->purgecontacts();
> + case Sync::SYNC_ABORTED:
> + {
> + generateResults(true);
> + emit success(getProfileName(), QString::number(aState));
> + break;
> + }
> + case Sync::SYNC_QUEUED:
> + case Sync::SYNC_STARTED:
> + case Sync::SYNC_PROGRESS:
> + default:
> + {
> + generateResults(false);
> + emit error(getProfileName(), "", aState);
> + break;
> + }
> + }
> +}
> +
> +void UContactsClient::fireSyncFinishedSucessfully()
> +{
> + emit syncFinished(Sync::SYNC_DONE);
> +}
> +
> +
> +Buteo::SyncResults
> +UContactsClient::getSyncResults() const
> +{
> + return d_ptr->mResults;
> +}
> +
> +QString
> +UContactsClient::authToken() const
> +{
> + return d_ptr->mAuth->token();
> +}
> +
> +QString
> +UContactsClient::syncTargetId() const
> +{
> + return d_ptr->mContactBackend->syncTargetId();
> +}
> +
> +QString UContactsClient::accountName() const
> +{
> + if (d_ptr->mAuth) {
> + return d_ptr->mAuth->accountDisplayName();
> + }
> + return QString();
> +}
> +
> +const QDateTime
> +UContactsClient::lastSyncTime() const
> +{
> + FUNCTION_CALL_TRACE;
> +
> + Buteo::ProfileManager pm;
> + Buteo::SyncProfile* sp = pm.syncProfile (iProfile.name ());
> + QDateTime lastTime = sp->lastSuccessfulSyncTime();
> + if (!lastTime.isNull()) {
> + // return UTC time used by google
> + return lastTime.addSecs(6).toUTC();
> + } else {
> + return lastTime;
> + }
> +}
> +
> +
> +void
> +UContactsClient::filterRemoteAddedModifiedDeletedContacts(const QList<QContact> remoteContacts,
> + QList<QContact> &remoteAddedContacts,
> + QList<QContact> &remoteModifiedContacts,
> + QList<QContact> &remoteDeletedContacts)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + foreach (const QContact &contact, remoteContacts) {
> + if (UContactsBackend::deleted(contact)) {
> + remoteDeletedContacts.append(contact);
> + continue;
> + }
> +
> + QString remoteId = UContactsBackend::getRemoteId(contact);
> + QContactId localId = d->mContactBackend->entryExists(remoteId);
> + if (localId.isNull()) {
> + remoteAddedContacts.append(contact);
> + } else {
> + remoteModifiedContacts.append(contact);
> + }
> + }
> +}
> +
> +void
> +UContactsClient::resolveConflicts(QList<QContact> &modifiedRemoteContacts,
> + QList<QContact> &deletedRemoteContacts)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + // TODO: Handle conflicts. The steps:
> + // o Compare the list of local modified/deleted contacts with
> + // the list of remote modified/deleted contacts
> + // o Create a new list (a map maybe) that has the contacts to
> + // be modified/deleted using the conflict resolution policy
> + // (server-wins, client-wins, add-new)
> + // o Return the list
> +
> + //QListIterator<GContactEntry*> iter (modifiedRemoteContacts);
> + QList<QContact>::iterator iter;
> + for (iter = modifiedRemoteContacts.begin (); iter != modifiedRemoteContacts.end (); ++iter) {
> + QContact contact = *iter;
> + QString remoteId = UContactsBackend::getRemoteId(contact);
> +
> + if (d->mModifiedContactIds.contains(remoteId)) {
> + if (d->mConflictResPolicy == Buteo::SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES) {
> + modifiedRemoteContacts.erase(iter);
> + } else {
> + d->mModifiedContactIds.remove(remoteId);
> + }
> + }
> +
> + if (d->mDeletedContactIds.contains(remoteId)) {
> + if (d->mConflictResPolicy == Buteo::SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES) {
> + modifiedRemoteContacts.erase(iter);
> + } else {
> + d->mDeletedContactIds.remove(remoteId);
> + }
> + }
> + }
> +
> + for (iter = deletedRemoteContacts.begin (); iter != deletedRemoteContacts.end (); ++iter) {
> + QContact contact = *iter;
> + QString remoteId = UContactsBackend::getRemoteId(contact);
> +
> + if (d->mModifiedContactIds.contains(remoteId)) {
> + if (d->mConflictResPolicy == Buteo::SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES) {
> + deletedRemoteContacts.erase(iter);
> + } else {
> + d->mModifiedContactIds.remove(remoteId);
> + }
> + }
> +
> + if (d->mDeletedContactIds.contains(remoteId)) {
> + // If the entry is deleted both at the server and
> + // locally, then just remove it from the lists
> + // so that no further action need to be taken
> + deletedRemoteContacts.erase(iter);
> + d->mDeletedContactIds.remove(remoteId);
> + }
> + }
> +}
> +
> +void
> +UContactsClient::updateIdsToLocal(const QList<QContact> &contacts)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> + QList<QContact> newList(contacts);
> + d->mContactBackend->modifyContacts(&newList);
> +}
> +
> +void
> +UContactsClient::addProcessedItem(Sync::TransferType modificationType,
> + Sync::TransferDatabase database,
> + const QString &modifiedDatabase,
> + int count)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + Buteo::DatabaseResults& results = d->mItemResults[modifiedDatabase];
> + if (database == Sync::LOCAL_DATABASE) {
> + if (modificationType == Sync::ITEM_ADDED) {
> + results.iLocalItemsAdded += count;
> + } else if (modificationType == Sync::ITEM_MODIFIED) {
> + results.iLocalItemsModified += count;
> + } else if (modificationType == Sync::ITEM_DELETED) {
> + results.iLocalItemsDeleted += count;
> + }
> + } else if (database == Sync::REMOTE_DATABASE) {
> + if( modificationType == Sync::ITEM_ADDED) {
> + results.iRemoteItemsAdded += count;
> + } else if (modificationType == Sync::ITEM_MODIFIED) {
> + results.iRemoteItemsModified += count;
> + } else if (modificationType == Sync::ITEM_DELETED) {
> + results.iRemoteItemsDeleted += count;
> + }
> + }
> +}
> +
> +void
> +UContactsClient::generateResults(bool aSuccessful)
> +{
> + FUNCTION_CALL_TRACE;
> + Q_D(UContactsClient);
> +
> + d->mResults.setMajorCode(aSuccessful ? Buteo::SyncResults::SYNC_RESULT_SUCCESS :
> + Buteo::SyncResults::SYNC_RESULT_FAILED );
> + d->mResults.setTargetId(iProfile.name());
> + if (d->mItemResults.isEmpty()) {
> + LOG_DEBUG("No items transferred");
> + } else {
> + QMapIterator<QString, Buteo::DatabaseResults> i(d->mItemResults);
> + while (i.hasNext())
> + {
> + i.next();
> + const Buteo::DatabaseResults &r = i.value();
> + Buteo::TargetResults targetResults(i.key(), // Target name
> + Buteo::ItemCounts(r.iLocalItemsAdded,
> + r.iLocalItemsDeleted,
> + r.iLocalItemsModified),
> + Buteo::ItemCounts(r.iRemoteItemsAdded,
> + r.iRemoteItemsDeleted,
> + r.iRemoteItemsModified));
> + d->mResults.addTargetResults(targetResults);
> + LOG_DEBUG("Items for" << targetResults.targetName() << ":");
> + LOG_DEBUG("LA:" << targetResults.localItems().added <<
> + "LD:" << targetResults.localItems().deleted <<
> + "LM:" << targetResults.localItems().modified <<
> + "RA:" << targetResults.remoteItems().added <<
> + "RD:" << targetResults.remoteItems().deleted <<
> + "RM:" << targetResults.remoteItems().modified);
> + }
> + }
> +}
>
> === added file 'google/GContactStream.cpp'
> --- google/GContactStream.cpp 1970-01-01 00:00:00 +0000
> +++ google/GContactStream.cpp 2015-07-19 21:39:03 +0000
> @@ -0,0 +1,1449 @@
> +/****************************************************************************
> + **
> + ** Copyright (C) 2013-2014 Jolla Ltd. and/or its subsidiary(-ies).
> + ** 2015 Canonical Ltd.
> + **
> + ** Contributors: Sateesh Kavuri <sateesh.kavuri at gmail.com>
> + ** Mani Chandrasekar <maninc at gmail.com>
> + ** Chris Adams <chris.adams at jolla.com>
> + ** Renato Araujo Oliveira Filho <renato.filho at canonical.com>
> + **
> + ** This program/library is free software; you can redistribute it and/or
> + ** modify it under the terms of the GNU Lesser General Public License
> + ** version 2.1 as published by the Free Software Foundation.
> + **
> + ** This program/library is distributed in the hope that it will be useful,
> + ** but WITHOUT ANY WARRANTY; without even the implied warranty of
> + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + ** Lesser General Public License for more details.
> + **
> + ** You should have received a copy of the GNU Lesser General Public
> + ** License along with this program/library; if not, write to the Free
> + ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + ** 02110-1301 USA
> + **
> + ****************************************************************************/
> +
> +#include "GContactStream.h"
> +#include "GContactAtom.h"
> +#include "UContactsCustomDetail.h"
> +#include "buteosyncfw_p.h"
> +
> +#include <QDateTime>
> +#include <QtContacts/QContactId>
> +
> +GoogleContactStream::GoogleContactStream(bool response, const QString &accountEmail, QObject* parent)
> + : QObject(parent)
> + , mXmlReader(0)
> + , mAtom(0)
> + , mXmlWriter(0)
> + , mAccountEmail(accountEmail)
> +{
> + if (response == true) {
> + initResponseFunctionMap();
> + } else {
> + initFunctionMap();
> + }
> +}
> +
> +GoogleContactStream::~GoogleContactStream()
> +{
> +}
> +
> +GoogleContactAtom *GoogleContactStream::parse(const QByteArray &xmlBuffer)
> +{
> + mXmlReader = new QXmlStreamReader(xmlBuffer);
> + mAtom = new GoogleContactAtom;
> +
> + Q_CHECK_PTR(mXmlReader);
> + Q_CHECK_PTR(mAtom);
> +
> + while (!mXmlReader->atEnd() && !mXmlReader->hasError()) {
> + if (mXmlReader->readNextStartElement()) {
> + Handler handler = mAtomFunctionMap.value(mXmlReader->name().toString());
> + if (handler) {
> + (*this.*handler)();
> + }
> + }
> + }
> +
> + delete mXmlReader;
> + return mAtom;
> +}
> +
> +QByteArray GoogleContactStream::encode(const QMultiMap<GoogleContactStream::UpdateType, QPair<QContact, QStringList> > &updates)
> +{
> + QByteArray xmlBuffer;
> + mXmlWriter = new QXmlStreamWriter(&xmlBuffer);
> + startBatchFeed();
> +
> + QList<QPair<QContact, QStringList> > removedContacts = updates.values(GoogleContactStream::Remove);
> + for (int i = 0; i < removedContacts.size(); ++i) {
> + encodeContactUpdate(removedContacts[i].first, removedContacts[i].second, GoogleContactStream::Remove, true); // batchmode = true
> + }
> +
> + QList<QPair<QContact, QStringList> > addedContacts = updates.values(GoogleContactStream::Add);
> + for (int i = 0; i < addedContacts.size(); ++i) {
> + encodeContactUpdate(addedContacts[i].first, addedContacts[i].second, GoogleContactStream::Add, true); // batchmode = true
> + }
> +
> + QList<QPair<QContact, QStringList> > modifiedContacts = updates.values(GoogleContactStream::Modify);
> + for (int i = 0; i < modifiedContacts.size(); ++i) {
> + encodeContactUpdate(modifiedContacts[i].first, modifiedContacts[i].second, GoogleContactStream::Modify, true); // batchmode = true
> + }
> +
> + endBatchFeed();
> + mXmlWriter->writeEndDocument();
> + delete mXmlWriter;
> + return xmlBuffer;
> +}
> +
> +// ----------------------------------------
> +
> +void GoogleContactStream::initAtomFunctionMap()
> +{
> + mAtomFunctionMap.insert("updated", &GoogleContactStream::handleAtomUpdated);
> + mAtomFunctionMap.insert("category", &GoogleContactStream::handleAtomCategory);
> + mAtomFunctionMap.insert("author", &GoogleContactStream::handleAtomAuthor);
> + mAtomFunctionMap.insert("id", &GoogleContactStream::handleAtomId);
> + mAtomFunctionMap.insert("totalResults", &GoogleContactStream::handleAtomOpenSearch);
> + mAtomFunctionMap.insert("startIndex", &GoogleContactStream::handleAtomOpenSearch);
> + mAtomFunctionMap.insert("itemsPerPage", &GoogleContactStream::handleAtomOpenSearch);
> + mAtomFunctionMap.insert("link", &GoogleContactStream::handleAtomLink);
> + mAtomFunctionMap.insert("entry", &GoogleContactStream::handleAtomEntry);
> + mAtomFunctionMap.insert("generator", &GoogleContactStream::handleAtomGenerator);
> + mAtomFunctionMap.insert("title", &GoogleContactStream::handleAtomTitle);
> +}
> +
> +void GoogleContactStream::initResponseFunctionMap()
> +{
> + initAtomFunctionMap();
> + // TODO: move the batch request response handling stuff here.
> +}
> +
> +void GoogleContactStream::initFunctionMap()
> +{
> + initAtomFunctionMap();
> + mContactFunctionMap.insert("updated", &GoogleContactStream::handleEntryUpdated);
> + //mContactFunctionMap.insert("app:edited", &GoogleContactStream::handleEntryUpdated);
> + mContactFunctionMap.insert("gContact:birthday", &GoogleContactStream::handleEntryBirthday);
> + mContactFunctionMap.insert("gContact:gender", &GoogleContactStream::handleEntryGender);
> + mContactFunctionMap.insert("gContact:hobby", &GoogleContactStream::handleEntryHobby);
> + mContactFunctionMap.insert("gContact:nickname", &GoogleContactStream::handleEntryNickname);
> + mContactFunctionMap.insert("gContact:occupation", &GoogleContactStream::handleEntryOccupation);
> + mContactFunctionMap.insert("gContact:website", &GoogleContactStream::handleEntryWebsite);
> + mContactFunctionMap.insert("gContact:groupMembershipInfo", &GoogleContactStream::handleEntryGroup);
> + mContactFunctionMap.insert("gContact:event", &GoogleContactStream::handleEntryEvent);
> + mContactFunctionMap.insert("gContact:jot", &GoogleContactStream::handleEntryJot);
> + mContactFunctionMap.insert("gContact:relation", &GoogleContactStream::handleRelation);
> + mContactFunctionMap.insert("gd:email", &GoogleContactStream::handleEntryEmail);
> + mContactFunctionMap.insert("gd:im", &GoogleContactStream::handleEntryIm);
> + mContactFunctionMap.insert("gd:name", &GoogleContactStream::handleEntryName);
> + mContactFunctionMap.insert("gd:organization", &GoogleContactStream::handleEntryOrganization);
> + mContactFunctionMap.insert("gd:phoneNumber", &GoogleContactStream::handleEntryPhoneNumber);
> + mContactFunctionMap.insert("gd:structuredPostalAddress", &GoogleContactStream::handleEntryStructuredPostalAddress);
> + mContactFunctionMap.insert("gd:extendedProperty", &GoogleContactStream::handleEntryExtendedProperty);
> +
> +}
> +
> +// ----------------------------------------
> +
> +void GoogleContactStream::handleAtomUpdated()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "updated");
> + mAtom->setUpdated(mXmlReader->readElementText());
> +}
> +
> +void GoogleContactStream::handleAtomCategory()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "category");
> +
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + QString scheme, term;
> + if (attributes.hasAttribute("scheme")) {
> + scheme = attributes.value("scheme").toString();
> + }
> + if (attributes.hasAttribute("term")) {
> + term = attributes.value("term").toString();
> + }
> +
> + mAtom->setCategory(scheme, term);
> +}
> +
> +void GoogleContactStream::handleAtomAuthor()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "author");
> +
> + while (!(mXmlReader->tokenType() == QXmlStreamReader::EndElement && mXmlReader->name() == "author")) {
> + if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
> + if (mXmlReader->name() == "name") {
> + mAtom->setAuthorName(mXmlReader->readElementText());
> + } else if (mXmlReader->name() == "email") {
> + mAtom->setAuthorEmail(mXmlReader->readElementText());
> + }
> + }
> + mXmlReader->readNextStartElement();
> + }
> +}
> +
> +void GoogleContactStream::handleAtomOpenSearch()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->prefix() == "openSearch");
> +
> + if (mXmlReader->name() == "totalResults") {
> + mAtom->setTotalResults(mXmlReader->readElementText().toInt());
> + } else if (mXmlReader->name() == "startIndex") {
> + mAtom->setStartIndex(mXmlReader->readElementText().toInt());
> + } else if (mXmlReader->name() == "itemsPerPage") {
> + mAtom->setItemsPerPage(mXmlReader->readElementText().toInt());
> + }
> +}
> +
> +void GoogleContactStream::handleAtomLink()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "link");
> +
> + if (mXmlReader->attributes().hasAttribute("rel") && (mXmlReader->attributes().value("rel") == "next")) {
> + mAtom->setNextEntriesUrl(mXmlReader->attributes().value("href").toString());
> + }
> +}
> +
> +void GoogleContactStream::handleAtomId()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "id");
> + mAtom->setId(mXmlReader->readElementText());
> +}
> +
> +void GoogleContactStream::handleAtomGenerator()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "generator");
> +
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + QString name, version, uri;
> + if (attributes.hasAttribute("version")) {
> + version = attributes.value("version").toString();
> + }
> + if (attributes.hasAttribute("uri")) {
> + uri = attributes.value("uri").toString();
> + }
> + name = mXmlReader->readElementText();
> +
> + mAtom->setGenerator(name, version, uri);
> +}
> +
> +void GoogleContactStream::handleAtomTitle()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "title");
> + mAtom->setTitle(mXmlReader->readElementText());
> +}
> +
> +void GoogleContactStream::handleAtomEntry()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "entry");
> +
> + // the entry will either be a contact, a group, or a response to a batch update request.
> + // if it's a group, we need to store some information about it.
> + QString systemGroupId;
> + QString systemGroupAtomId;
> +
> + // the entry will be a contact if this is a response to a "read" request
> + QContact entryContact;
> + QStringList unsupportedElements;
> + bool isInGroup = false;
> + bool isDeleted = false;
> +
> + // or it will be a series of batch operation success/fail info
> + // if this xml is the response to a batch update/delete request.
> + bool isBatchOperationResponse = false;
> + GoogleContactAtom::BatchOperationResponse response;
> +
> + while (!((mXmlReader->tokenType() == QXmlStreamReader::EndElement) && (mXmlReader->name() == "entry"))) {
> + if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
> + isInGroup |= (mXmlReader->qualifiedName().toString() == QStringLiteral("gContact:groupMembershipInfo"));
> + DetailHandler handler = mContactFunctionMap.value(mXmlReader->qualifiedName().toString());
> + if (handler) {
> + QContactDetail convertedDetail = (*this.*handler)();
> + if (convertedDetail != QContactDetail()) {
> + //LOG_WARNING("Handle not found for " << mXmlReader->qualifiedName().toString());
> + // FIXME
> + // convertedDetail.setValue(QContactDetail__FieldModifiable, true);
> + entryContact.saveDetail(&convertedDetail);
> + }
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("gd:deleted")) {
> + isDeleted = true;
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("batch:id")) {
> + isBatchOperationResponse = true;
> + handleEntryBatchId(&response);
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("batch:operation")) {
> + isBatchOperationResponse = true;
> + handleEntryBatchOperation(&response);
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("batch:status")) {
> + isBatchOperationResponse = true;
> + handleEntryBatchStatus(&response);
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("link")) {
> + // There are several possible links:
> + // Avatar Photo link
> + // Self query link
> + // Edit link
> + // Batch link etc.
> +
> + // If it's an avatar, we grab it as a QContactAvatar detail
> + bool isAvatar = false;
> + QContactAvatar googleAvatar;
> + QString etag;
> + QString unsupportedElement = handleEntryLink(&googleAvatar, &isAvatar, &etag);
> + if (isAvatar) {
> + // check if we have already a google avatar
> + entryContact.saveDetail(&googleAvatar);
> + UContactsCustomDetail::setCustomField(entryContact,
> + UContactsCustomDetail::FieldContactAvatarETag,
> + etag);
> + }
> +
> + // Whether it's an avatar or not, we also store the element text.
> + if (!unsupportedElement.isEmpty()) {
> + unsupportedElements.append(unsupportedElement);
> + }
> + } else if (mXmlReader->name().toString() == QStringLiteral("entry")) {
> + // read the etag out of the entry.
> + response.eTag = mXmlReader->attributes().value("gd:etag").toString();
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("gContact:systemGroup")) {
> + systemGroupId = mXmlReader->attributes().value("id").toString();
> + } else if (mXmlReader->qualifiedName().toString() == QStringLiteral("id")) {
> + // either a contact id or a group id.
> + QContactDetail guidDetail = handleEntryId(&systemGroupAtomId);
> + entryContact.saveDetail(&guidDetail);
> + } else {
> + // This is some XML element which we don't handle.
> + // We should store it, so that we can send it back when we upload changes.
> + QString unsupportedElement = handleEntryUnknownElement();
> + if (!unsupportedElement.isEmpty()) {
> + unsupportedElements.append(unsupportedElement);
> + }
> + }
> + }
> + mXmlReader->readNextStartElement();
> + }
> +
> + if (!systemGroupId.isEmpty()) {
> + qDebug() << "this entry was a group";
LOG_DEBUG?
> + // this entry was a group
> + mAtom->addEntrySystemGroup(systemGroupId, systemGroupAtomId);
> + } else {
> + // this entry was a contact.
> + // the etag is the "version identifier". Save it into the QCOM detail.
> + if (!response.eTag.isEmpty()) {
> + QtContacts::QContactExtendedDetail etagDetail =
> + UContactsCustomDetail::getCustomField(entryContact, UContactsCustomDetail::FieldContactETag);
> + etagDetail.setData(response.eTag);
> + entryContact.saveDetail(&etagDetail);
> + }
> +
> + if (isInGroup) {
> + // Only sync the contact if it is in a "real" group
> + // as otherwise we get hundreds of "Other Contacts"
> + // (random email addresses etc).
> + if (isDeleted) {
> + // if contact is deleted set the deletedAt value
> + QContactExtendedDetail deletedAt =
> + UContactsCustomDetail::getCustomField(entryContact, UContactsCustomDetail::FieldDeletedAt);
> + deletedAt.setData(QDateTime::currentDateTime());
> + entryContact.saveDetail(&deletedAt);
> +
> + mAtom->addDeletedEntryContact(entryContact);
> + } else {
> + mAtom->addEntryContact(entryContact, unsupportedElements);
> + }
> + }
> + }
> +
> + if (isBatchOperationResponse) {
> + if (!entryContact.detail<QContactGuid>().guid().isEmpty()) {
> + response.contactGuid = entryContact.detail<QContactGuid>().guid();
> + }
> + mAtom->addBatchOperationResponse(response.operationId, response);
> + }
> +}
> +
> +QString GoogleContactStream::handleEntryLink(QContactAvatar *avatar,
> + bool *isAvatar,
> + QString *etag)
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "link");
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> +
> + // if a contact does not have a photo, then the photo link element has no gd:etag attribute.
> + *isAvatar = ((attributes.value("rel") == "http://schemas.google.com/contacts/2008/rel#photo") &&
> + attributes.hasAttribute("gd:etag"));
> +
> + if (*isAvatar) {
> + // this is an avatar photo for the contact entry
> + avatar->setImageUrl(attributes.value("href").toString());
> + *etag = attributes.value("gd:etag").toString();
> + }
> +
> + return handleEntryUnknownElement();
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryExtendedProperty()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() &&
> + mXmlReader->name() == "gd:extendedProperty");
> +
> + QContactExtendedDetail xd;
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + QString propName = attributes.value("name").toString();
> +
> + // FIXME:
> + // We use extendedProperty to store favorite property until we implment support
> + // for google groups sync
> + if (propName == "X-FAVORITE") {
> + QContactFavorite fav;
> + fav.setFavorite(attributes.value("value").toString() == "true");
> + return fav;
> + } else if (propName == "SOUND") {
> + QContactRingtone ring;
> + ring.setAudioRingtoneUrl(QUrl(attributes.value("value").toString()));
> + return ring;
> + }
> +
> + // handle as generic type
> + xd.setName(attributes.value("name").toString());
> + xd.setData(attributes.value("value").toString());
> + return xd;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryEvent()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() &&
> + mXmlReader->name() == "gContact:event");
> +
> +// <gContact:event rel="anniversary" label="memorial">
> +// <gd:when startTime="2005-06-06" endTime="2005-06-08" valueString="This month"/>
> +// </gContact:event>
> + static QMap<QString, QContactAnniversary::SubType> anniversaryTypes;
> + if (anniversaryTypes.isEmpty()) {
> + anniversaryTypes.insert(QString::fromLatin1("engagement"),
> + QContactAnniversary::SubTypeEngagement);
> + anniversaryTypes.insert(QString::fromLatin1("employment"),
> + QContactAnniversary::SubTypeEmployment);
> + anniversaryTypes.insert(QString::fromLatin1("memorial"),
> + QContactAnniversary::SubTypeMemorial);
> + anniversaryTypes.insert(QString::fromLatin1("house"),
> + QContactAnniversary::SubTypeHouse);
> + anniversaryTypes.insert(QString::fromLatin1("wedding"),
> + QContactAnniversary::SubTypeWedding);
> + }
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + if (attributes.value("rel") == "anniversary") {
> + QContactAnniversary anniversary;
> + anniversary.setSubType(anniversaryTypes.value(attributes.value("label").toString(),
> + QContactAnniversary::SubTypeWedding));
> + mXmlReader->readNextStartElement();
> + if (mXmlReader->qualifiedName() == "gd:when") {
> + attributes = mXmlReader->attributes();
> + anniversary.setOriginalDateTime(QDateTime::fromString(attributes.value("startTime").toString(),
> + Qt::ISODate));
> + anniversary.setEvent(attributes.value("valueString").toString());
> + // FIXME: missing endTime
> + // QContactAnniversary API does not support endTime
> + }
> + return anniversary;
> + }
> +
> + return QContactDetail();
> +}
> +
> +QContactDetail GoogleContactStream::handleRelation()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() &&
> + mXmlReader->name() == "gContact:relation");
> +
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + QString rel = attributes.hasAttribute("rel")
> + ? attributes.value("rel").toString()
> + : QString();
> +
> + QContactFamily family;
> + if (rel == "spouse") {
> + family.setSpouse(mXmlReader->readElementText());
> + } else if (rel == "child") {
> + family.setChildren(QStringList() << mXmlReader->readElementText());
> + } else {
> + LOG_WARNING("Family relation type not supported" << rel);
> + return QContactDetail();
> + }
> +
> + return family;
> +}
> +
> +QString GoogleContactStream::handleEntryUnknownElement()
> +{
> + Q_ASSERT(mXmlReader->isStartElement());
> +
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + QString attributesString;
> + for (int i = 0; i < attributes.size(); ++i) {
> + QString extra = QStringLiteral(" %1=\"%2\"")
> + .arg(attributes[i].qualifiedName().toString())
> + .arg(attributes[i].value().toString().toHtmlEscaped());
> + attributesString.append(extra);
> + }
> +
> + QString unknownElement = QStringLiteral("<%1%2>%3</%1>")
> + .arg(mXmlReader->qualifiedName().toString())
> + .arg(attributesString)
> + .arg(mXmlReader->text().toString());
> +
> + return unknownElement;
> +}
> +
> +QList<int> GoogleContactStream::handleContext(const QString &rel) const
> +{
> + QList<int> contexts;
> + QString name = rel.split("#").last();
> + if (name == QStringLiteral("home")) {
> + contexts << QContactDetail::ContextHome;
> + } else if (name == QStringLiteral("work")) {
> + contexts << QContactDetail::ContextWork;
> + } else if (!name.isEmpty()) {
> + contexts << QContactDetail::ContextOther;
> + }
> + return contexts;
> +}
> +
> +void GoogleContactStream::handleEntryBatchStatus(GoogleContactAtom::BatchOperationResponse *response)
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "status");
> +
> + response->code = mXmlReader->attributes().value("code").toString();
> + response->reason = mXmlReader->attributes().value("reason").toString();
> + response->reasonDescription = mXmlReader->readElementText();
> + response->isError = true;
> + if (response->code == QStringLiteral("200") // No error.
> + || response->code == QStringLiteral("201") // Created without error.
> + || response->code == QStringLiteral("304")) { // Not modified (no change since time specified)
> + // according to Google Data API these response codes signify success cases.
> + response->isError = false;
> + }
> +}
> +
> +void GoogleContactStream::handleEntryBatchOperation(GoogleContactAtom::BatchOperationResponse *response)
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "operation");
> + response->type = mXmlReader->attributes().value("type").toString();
> +}
> +
> +void GoogleContactStream::handleEntryBatchId(GoogleContactAtom::BatchOperationResponse *response)
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "id");
> + response->operationId = mXmlReader->readElementText();
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryId(QString *rawId)
> +{
> + *rawId = mXmlReader->readElementText();
> + QString idUrl = *rawId;
> + QContactGuid guid;
> + guid.setGuid(idUrl.split('/').last());
> + return guid;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryBirthday()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:birthday");
> +
> + QContactBirthday birthday;
> + birthday.setDate(QDate::fromString(mXmlReader->attributes().value("when").toString(), Qt::ISODate));
> +
> + if (birthday.dateTime().isValid()) {
> + return birthday;
> + } else {
> + LOG_WARNING("Birthday date not supported:" << mXmlReader->attributes().value("when").toString());
> + return QContactDetail();
> + }
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryGender()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:gender");
> +
> + QString genderStr = mXmlReader->attributes().value("value").toString().toLower();
> + QContactGender gender;
> + if (genderStr.startsWith('m')) {
> + gender.setGender(QContactGender::GenderMale);
> + } else if (genderStr.startsWith('f')) {
> + gender.setGender(QContactGender::GenderFemale);
> + } else {
> + gender.setGender(QContactGender::GenderUnspecified);
> + }
> +
> + return gender;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryHobby()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:hobby");
> +
> + QContactHobby hobby;
> + hobby.setHobby(mXmlReader->readElementText());
> + return hobby;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryNickname()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:nickname");
> +
> + QContactNickname nickname;
> + nickname.setNickname(mXmlReader->readElementText());
> + return nickname;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryOccupation()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:occupation");
> +
> + QContactOrganization org;
> + org.setRole(mXmlReader->readElementText());
> + return org;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryWebsite()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:website");
> +
> + QContactUrl url;
> + QXmlStreamAttributes attributes = mXmlReader->attributes();
> + QString rel = attributes.hasAttribute("rel")
> + ? attributes.value("rel").toString()
> + : QString();
> +
> + if (rel == "home-page") {
> + url.setSubType(QContactUrl::SubTypeHomePage);
> + } else if (rel == "blog") {
> + url.setSubType(QContactUrl::SubTypeBlog);
> + } else {
> + url.setSubType(QContactUrl::SubTypeFavourite);
> + }
> + url.setContexts(handleContext(rel));
> + url.setUrl(attributes.value("href").toString());
> + return url;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryJot()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gContact:jot");
> +
> + QString rel = mXmlReader->attributes().hasAttribute("rel")
> + ? mXmlReader->attributes().value("rel").toString()
> + : QString();
> +
> + QContactNote note;
> + note.setContexts(handleContext(rel));
> + note.setNote(mXmlReader->readElementText());
> + return note;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryEmail()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:email");
> +
> + QContactEmailAddress email;
> + email.setEmailAddress(mXmlReader->attributes().value("address").toString());
> +
> + QString rel = mXmlReader->attributes().hasAttribute("rel")
> + ? mXmlReader->attributes().value("rel").toString()
> + : QString();
> + email.setContexts(handleContext(rel));
> + return email;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryIm()
> +{
> + Q_ASSERT(mXmlReader->isStartElement () && mXmlReader->qualifiedName () == "gd:im");
> +
> + static QMap<QString, QContactOnlineAccount::Protocol> protocolMap;
> + if (protocolMap.isEmpty()) {
> + protocolMap.insert("AIM", QContactOnlineAccount::ProtocolAim);
> + protocolMap.insert("MSN", QContactOnlineAccount::ProtocolMsn);
> + protocolMap.insert("YAHOO", QContactOnlineAccount::ProtocolYahoo);
> + protocolMap.insert("SKYPE", QContactOnlineAccount::ProtocolSkype);
> + protocolMap.insert("GOOGLE_TALK", QContactOnlineAccount::ProtocolJabber);
> + protocolMap.insert("ICQ", QContactOnlineAccount::ProtocolIcq);
> + protocolMap.insert("JABBER", QContactOnlineAccount::ProtocolJabber);
> + protocolMap.insert("QQ", QContactOnlineAccount::ProtocolQq);
> + protocolMap.insert("IRC", QContactOnlineAccount::ProtocolIrc);
> + }
> + QString rel, protocol;
> + if (mXmlReader->attributes().hasAttribute("rel")) {
> + rel = mXmlReader->attributes().value("rel").toString();
> + }
> +
> + if (mXmlReader->attributes().hasAttribute ("protocol")) {
> + QString protocolUrl = mXmlReader->attributes().value("protocol").toString();
> + protocol = protocolUrl.split("#").last();
> + }
> +
> + QContactOnlineAccount imAccount;
> + imAccount.setAccountUri(mXmlReader->attributes().value("address").toString());
> + imAccount.setProtocol(protocolMap.value(protocol, QContactOnlineAccount::ProtocolUnknown));
> + if (!protocolMap.contains(protocol)) {
> + imAccount.setServiceProvider(protocol);
> + }
> + imAccount.setContexts(handleContext(rel));
> + return imAccount;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryName()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:name");
> +
> + QContactName name;
> + while (!(mXmlReader->tokenType() == QXmlStreamReader::EndElement && mXmlReader->qualifiedName() == "gd:name")) {
> + if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
> + if (mXmlReader->qualifiedName() == "gd:givenName") {
> + name.setFirstName(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:additionalName") {
> + name.setMiddleName(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:familyName") {
> + name.setLastName(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:namePrefix") {
> + name.setPrefix(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:nameSuffix") {
> + name.setSuffix(mXmlReader->readElementText());
> + }
> + }
> + mXmlReader->readNextStartElement();
> + }
> +
> + return name;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryOrganization()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:organization");
> +
> + QContactOrganization org;
> +
> + QString rel = mXmlReader->attributes().hasAttribute("rel")
> + ? mXmlReader->attributes().value("rel").toString()
> + : QString();
> + org.setContexts(handleContext(rel));
> +
> + while (!(mXmlReader->tokenType() == QXmlStreamReader::EndElement && mXmlReader->qualifiedName() == "gd:organization")) {
> + if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
> + if (mXmlReader->qualifiedName() == "gd:orgDepartment") {
> + QStringList dept = org.department();
> + dept.append(mXmlReader->readElementText());
> + org.setDepartment(dept);
> + } else if (mXmlReader->qualifiedName() == "gd:orgJobDescription") {
> + org.setRole(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:orgName") {
> + org.setName(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:orgSymbol") {
> + org.setLogoUrl(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:orgTitle") {
> + org.setTitle(mXmlReader->readElementText());
> + }
> + }
> + mXmlReader->readNextStartElement();
> + }
> +
> + return org;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryPhoneNumber()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:phoneNumber");
> +
> + QContactPhoneNumber phone;
> + QString rel = mXmlReader->attributes().hasAttribute("rel")
> + ? mXmlReader->attributes().value("rel").toString()
> + : QString();
> +
> + if (rel == QStringLiteral("http://schemas.google.com/g/2005#home")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeVoice);
> + phone.setContexts(QContactDetail::ContextHome);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#work")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeVoice);
> + phone.setContexts(QContactDetail::ContextWork);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#mobile")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeMobile);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#work_mobile")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeMobile);
> + phone.setContexts(QContactDetail::ContextWork);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#home_fax")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeFax);
> + phone.setContexts(QContactDetail::ContextHome);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#work_fax")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeFax);
> + phone.setContexts(QContactDetail::ContextWork);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#other_fax")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeFax);
> + phone.setContexts(QContactDetail::ContextOther);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#pager")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypePager);
> + phone.setContexts(QContactDetail::ContextHome);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#work_pager")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypePager);
> + phone.setContexts(QContactDetail::ContextWork);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#tty_tdd")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeModem);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#car")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeCar);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#telex")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeBulletinBoardSystem);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#assistant")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeAssistant);
> + } else if (rel == QStringLiteral("http://schemas.google.com/g/2005#other")) {
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeVoice);
> + phone.setContexts(QContactDetail::ContextOther);
> + } else { // else ignore it, malformed output from Google.
> + LOG_WARNING("Malformated phone tag: " << rel);
> + phone.setSubTypes(QList<int>() << QContactPhoneNumber::SubTypeVoice);
> + phone.setContexts(QContactDetail::ContextOther);
> + }
> +
> + phone.setNumber(mXmlReader->readElementText());
> + return phone;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryStructuredPostalAddress()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->qualifiedName() == "gd:structuredPostalAddress");
> +
> + QContactAddress address;
> +
> + QString rel = mXmlReader->attributes().hasAttribute("rel")
> + ? mXmlReader->attributes().value("rel").toString()
> + : QString();
> + address.setContexts(handleContext(rel));
> +
> + while (!(mXmlReader->tokenType() == QXmlStreamReader::EndElement && mXmlReader->qualifiedName() == "gd:structuredPostalAddress")) {
> + if (mXmlReader->tokenType() == QXmlStreamReader::StartElement) {
> + if (mXmlReader->qualifiedName() == "gd:street") {
> + address.setStreet(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:pobox") {
> + address.setPostOfficeBox(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:neighborhood") {
> + address.setLocality(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:city") {
> + address.setLocality(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:region") {
> + address.setRegion(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:postcode") {
> + address.setPostcode(mXmlReader->readElementText());
> + } else if (mXmlReader->qualifiedName() == "gd:country") {
> + address.setCountry(mXmlReader->readElementText());
> + }
> + }
> + mXmlReader->readNextStartElement();
> + }
> +
> + return address;
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryUpdated()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() &&
> + (mXmlReader->qualifiedName() == "updated" ||
> + mXmlReader->qualifiedName() == "app:edited"));
> +
> + QDateTime modTs = QDateTime::fromString(mXmlReader->readElementText(), Qt::ISODate);
> + if (modTs.isValid()) {
> + QContactTimestamp ts;
> + ts.setLastModified(modTs);
> + return ts;
> + }
> +
> + return QContactDetail();
> +}
> +
> +QContactDetail GoogleContactStream::handleEntryGroup()
> +{
> + Q_ASSERT(mXmlReader->isStartElement() &&
> + mXmlReader->qualifiedName() == "gContact:groupMembershipInfo");
> +
> + QXmlStreamAttributes attrs = mXmlReader->attributes();
> + QString groupId = attrs.value("href").toString().split('/').last();
> +
> + QContactExtendedDetail membershipDetail;
> + membershipDetail.setName(UContactsCustomDetail::FieldGroupMembershipInfo);
> + membershipDetail.setData(groupId);
> + return membershipDetail;
> +}
> +
> +// ----------------------------------------
> +
> +void GoogleContactStream::encodeContactUpdate(const QContact &qContact,
> + const QStringList &unsupportedElements,
> + const GoogleContactStream::UpdateType updateType,
> + const bool batch)
> +{
> + QList<QContactDetail> allDetails = qContact.details ();
> +
> + mXmlWriter->writeStartElement("atom:entry");
> + if (batch == true) {
> + // Etag encoding has to immediately succeed writeStartElement("atom:entry"),
> + // since etag is an attribute of this element.
> + encodeEtag(qContact, updateType == GoogleContactStream::Remove || updateType == GoogleContactStream::Modify);
> + encodeBatchTag(updateType, qContact.id().toString());
> + } else {
> + mXmlWriter->writeAttribute("xmlns:atom", "http://www.w3.org/2005/Atom");
> + mXmlWriter->writeAttribute("xmlns:gd", "http://schemas.google.com/g/2005");
> + mXmlWriter->writeAttribute("xmlns:gContact", "http://schemas.google.com/contact/2008");
> + }
> +
> + if (updateType == GoogleContactStream::Remove) {
> + encodeId(qContact, true);
> + mXmlWriter->writeEndElement();
> + return;
> + }
> +
> + encodeCategory();
> + if (updateType == GoogleContactStream::Modify) {
> + encodeId(qContact, true);
> + encodeUpdatedTimestamp(qContact);
> + }
> + encodeUnknownElements(unsupportedElements); // for an Add, this is just group membership.
> + bool hasGroup = false;
> +
> + Q_FOREACH (const QContactDetail &detail, allDetails) {
> + switch(detail.type()) {
> + case QContactDetail::TypeName: {
> + encodeName(detail);
> + } break;
> + case QContactDetail::TypePhoneNumber: {
> + encodePhoneNumber(detail);
> + } break;
> + case QContactDetail::TypeEmailAddress: {
> + encodeEmailAddress(detail);
> + } break;
> + case QContactDetail::TypeAddress: {
> + encodeAddress(detail);
> + } break;
> + case QContactDetail::TypeUrl: {
> + encodeUrl(detail);
> + } break;
> + case QContactDetail::TypeBirthday: {
> + encodeBirthday(detail);
> + } break;
> + case QContactDetail::TypeNote: {
> + encodeNote(detail);
> + } break;
> + case QContactDetail::TypeHobby: {
> + encodeHobby(detail);
> + } break;
> + case QContactDetail::TypeOrganization: {
> + encodeOrganization(detail);
> + } break;
> + case QContactDetail::TypeAvatar: {
> + encodeAvatar(detail, qContact);
> + } break;
> + case QContactDetail::TypeAnniversary: {
> + encodeAnniversary(detail);
> + } break;
> + case QContactDetail::TypeNickname: {
> + encodeNickname(detail);
> + } break;
> + case QContactDetail::TypeGender: {
> + encodeGender(detail);
> + } break;
> + case QContactDetail::TypeOnlineAccount: {
> + encodeOnlineAccount(detail);
> + } break;
> + case QContactDetail::TypeFamily: {
> + encodeFamily(detail);
> + } break;
> + case QContactDetail::TypeFavorite: {
> + encodeFavorite(detail);
> + } break;
> + case QContactDetail::TypeExtendedDetail: {
> + encodeExtendedProperty(detail, &hasGroup);
> + } break;
> + case QContactDetail::TypeRingtone: {
> + encodeRingTone(detail);
> + } break;
> + // TODO: handle the custom detail fields.
> + default: {
> + } break;
> + }
> + }
> + if (!hasGroup) {
> + // Make sure that the contact has at least one group
> + QContactExtendedDetail group;
> + group.setName(UContactsCustomDetail::FieldGroupMembershipInfo);
> + group.setData("6");
> + encodeGroup(group);
> + }
> +
> + mXmlWriter->writeEndElement();
> +}
> +
> +void GoogleContactStream::startBatchFeed()
> +{
> + mXmlWriter->writeStartElement("atom:feed");
> + mXmlWriter->writeAttribute("xmlns:atom", "http://www.w3.org/2005/Atom");
> + mXmlWriter->writeAttribute("xmlns:gContact", "http://schemas.google.com/contact/2008");
> + mXmlWriter->writeAttribute("xmlns:gd", "http://schemas.google.com/g/2005");
> + mXmlWriter->writeAttribute("xmlns:batch", "http://schemas.google.com/gdata/batch");
> +}
> +
> +void GoogleContactStream::endBatchFeed()
> +{
> + mXmlWriter->writeEndElement ();
> +}
> +
> +void GoogleContactStream::encodeBatchTag(const GoogleContactStream::UpdateType type, const QString &batchElementId)
> +{
> + mXmlWriter->writeTextElement("batch:id", batchElementId);
> + if (type == GoogleContactStream::Add) {
> + mXmlWriter->writeEmptyElement("batch:operation");
> + mXmlWriter->writeAttribute("type", "insert");
> + } else if (type == GoogleContactStream::Modify) {
> + mXmlWriter->writeEmptyElement("batch:operation");
> + mXmlWriter->writeAttribute("type", "update");
> + } else if (type == GoogleContactStream::Remove) {
> + mXmlWriter->writeEmptyElement("batch:operation");
> + mXmlWriter->writeAttribute("type", "delete");
> + }
> +}
> +
> +void GoogleContactStream::encodeCategory()
> +{
> + mXmlWriter->writeEmptyElement("atom:category");
> + mXmlWriter->writeAttribute("schema", "http://schemas.google.com/g/2005#kind");
> + mXmlWriter->writeAttribute("term", "http://schemas.google.com/contact/2008#contact");
> +}
> +
> +void GoogleContactStream::encodeId(const QContact &qContact, bool isUpdate)
> +{
> + QString remoteId = UContactsCustomDetail::getCustomField(qContact,
> + UContactsCustomDetail::FieldRemoteId).data().toString();
> + if (!remoteId.isEmpty()) {
> + remoteId = remoteId.mid(remoteId.indexOf(":")+1);
> + if (isUpdate) {
> + // according to the docs, this should be "base" instead of "full" -- but that actually fails.
> + if (mAccountEmail.isEmpty()) {
> + LOG_WARNING("account email not known - unable to build batch edit id!");
> + } else {
> + mXmlWriter->writeTextElement("atom:id", "http://www.google.com/m8/feeds/contacts/" + mAccountEmail + "/full/" + remoteId);
> + }
> + } else {
> + mXmlWriter->writeTextElement("atom:id", "http://www.google.com/m8/feeds/contacts/default/full/" + remoteId);
> + }
> + }
> +}
> +
> +void GoogleContactStream::encodeUpdatedTimestamp(const QContact &qContact)
> +{
> + QContactTimestamp timestamp = qContact.detail<QContactTimestamp>();
> + QDateTime updatedTimestamp = timestamp.lastModified(); // will be UTC from database.
> + if (!updatedTimestamp.isValid()) {
> + updatedTimestamp = QDateTime::currentDateTimeUtc();
> + }
> +
> + QString updatedStr = updatedTimestamp.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzzZ"));
> + mXmlWriter->writeTextElement("updated", updatedStr);
> +}
> +
> +void GoogleContactStream::encodeEtag(const QContact &qContact, bool needed)
> +{
> + QContactExtendedDetail etagDetail =
> + UContactsCustomDetail::getCustomField(qContact, UContactsCustomDetail::FieldContactETag);
> + QString etag = etagDetail.data().toString();
> + if (!etag.isEmpty()) {
> + mXmlWriter->writeAttribute("gd:etag", etag);
> + } else if (needed) {
> + // we're trying to delete a contact in a batch operation
> + // but we don't know the etag of the deleted contact.
> + LOG_WARNING("etag needed but not available! caller needs to prefill for deletion updates!");
> + }
> +}
> +
> +void GoogleContactStream::encodeName(const QContactName &name)
> +{
> + mXmlWriter->writeStartElement("gd:name");
> + if (!name.firstName().isEmpty())
> + mXmlWriter->writeTextElement("gd:givenName", name.firstName());
> + if (!name.middleName().isEmpty())
> + mXmlWriter->writeTextElement("gd:additionalName", name.middleName());
> + if (!name.lastName().isEmpty())
> + mXmlWriter->writeTextElement("gd:familyName", name.lastName());
> + if (!name.prefix().isEmpty())
> + mXmlWriter->writeTextElement("gd:namePrefix", name.prefix());
> + if (!name.suffix().isEmpty())
> + mXmlWriter->writeTextElement("gd:nameSuffix", name.suffix());
> + mXmlWriter->writeEndElement();
> +}
> +
> +void GoogleContactStream::encodePhoneNumber(const QContactPhoneNumber &phoneNumber)
> +{
> + if (phoneNumber.number().isEmpty()) {
> + return;
> + }
> +
> + bool isHome = phoneNumber.contexts().contains(QContactDetail::ContextHome);
> + bool isWork = phoneNumber.contexts().contains(QContactDetail::ContextWork);
> + int subType = phoneNumber.subTypes().isEmpty()
> + ? QContactPhoneNumber::SubTypeMobile // default to mobile
> + : phoneNumber.subTypes().first();
> +
> + QString rel = "http://schemas.google.com/g/2005#";
> + switch (subType) {
> + case QContactPhoneNumber::SubTypeVoice:
> + case QContactPhoneNumber::SubTypeLandline: {
> + if (isHome) {
> + rel += QString::fromLatin1("home");
> + } else if (isWork) {
> + rel += QString::fromLatin1("work");
> + } else {
> + rel += QString::fromLatin1("other");
> + }
> + } break;
> + case QContactPhoneNumber::SubTypeMobile: {
> + if (isHome) {
> + rel += QString::fromLatin1("mobile");
> + } else if (isWork) {
> + rel += QString::fromLatin1("work_mobile");
> + } else {
> + rel += QString::fromLatin1("mobile"); // we lose the non-homeness in roundtrip.
> + }
> + } break;
> + case QContactPhoneNumber::SubTypeFax: {
> + if (isHome) {
> + rel += QString::fromLatin1("home_fax");
> + } else if (isWork) {
> + rel += QString::fromLatin1("work_fax");
> + } else {
> + rel += QString::fromLatin1("other_fax");
> + }
> + } break;
> + case QContactPhoneNumber::SubTypePager: {
> + if (isHome) {
> + rel += QString::fromLatin1("pager");
> + } else if (isWork) {
> + rel += QString::fromLatin1("work_pager");
> + } else {
> + rel += QString::fromLatin1("pager"); // we lose the non-homeness in roundtrip.
> + }
> + } break;
> + case QContactPhoneNumber::SubTypeModem: {
> + rel += QString::fromLatin1("tty_tdd"); // we lose context in roundtrip.
> + } break;
> + case QContactPhoneNumber::SubTypeCar: {
> + rel += QString::fromLatin1("car"); // we lose context in roundtrip.
> + } break;
> + case QContactPhoneNumber::SubTypeBulletinBoardSystem: {
> + rel += QString::fromLatin1("telex"); // we lose context in roundtrip.
> + } break;
> + case QContactPhoneNumber::SubTypeAssistant: {
> + rel += QString::fromLatin1("assistant");
> + } break;
> + default: {
> + rel += QString::fromLatin1("other");
> + } break;
> + }
> +
> + mXmlWriter->writeStartElement("gd:phoneNumber");
> + mXmlWriter->writeAttribute("rel", rel);
> + mXmlWriter->writeCharacters(phoneNumber.number());
> + mXmlWriter->writeEndElement();
> +}
> +
> +void GoogleContactStream::encodeEmailAddress(const QContactEmailAddress &emailAddress)
> +{
> + if (!emailAddress.emailAddress().isEmpty()) {
> + mXmlWriter->writeEmptyElement("gd:email");
> + if (emailAddress.contexts().contains(QContactDetail::ContextHome)) {
> + mXmlWriter->writeAttribute("rel", "http://schemas.google.com/g/2005#home");
> + } else if (emailAddress.contexts().contains(QContactDetail::ContextWork)) {
> + mXmlWriter->writeAttribute("rel", "http://schemas.google.com/g/2005#work");
> + } else {
> + mXmlWriter->writeAttribute("rel", "http://schemas.google.com/g/2005#other");
> + }
> + mXmlWriter->writeAttribute("address", emailAddress.emailAddress());
> + }
> +}
> +
> +void GoogleContactStream::encodeAddress(const QContactAddress &address)
> +{
> + mXmlWriter->writeStartElement("gd:structuredPostalAddress");
> + // https://developers.google.com/google-apps/contacts/v3/reference#structuredPostalAddressRestrictions
> + // we cannot use mailClass attribute (for postal/parcel etc)
> + mXmlWriter->writeAttribute("rel",
> + QString("http://schemas.google.com/g/2005#%1").arg(encodeContext(address.contexts())));
> + if (!address.street().isEmpty())
> + mXmlWriter->writeTextElement("gd:street", address.street());
> + if (!address.locality().isEmpty())
> + mXmlWriter->writeTextElement("gd:neighborhood", address.locality());
> + if (!address.postOfficeBox().isEmpty())
> + mXmlWriter->writeTextElement("gd:pobox", address.postOfficeBox());
> + if (!address.region().isEmpty())
> + mXmlWriter->writeTextElement("gd:region", address.region());
> + if (!address.postcode().isEmpty())
> + mXmlWriter->writeTextElement("gd:postcode", address.postcode());
> + if (!address.country().isEmpty())
> + mXmlWriter->writeTextElement("gd:country", address.country());
> + mXmlWriter->writeEndElement();
> +}
> +
> +void GoogleContactStream::encodeUrl(const QContactUrl &url)
> +{
> + if (!url.url().isEmpty()) {
> + mXmlWriter->writeEmptyElement("gContact:website");
> + switch (url.subType()) {
> + case QContactUrl::SubTypeHomePage: {
> + mXmlWriter->writeAttribute("rel", "home-page");
> + } break;
> + case QContactUrl::SubTypeBlog: {
> + mXmlWriter->writeAttribute("rel", "blog");
> + } break;
> + default: {
> + mXmlWriter->writeAttribute("rel", encodeContext(url.contexts()));
> + } break;
> + }
> + mXmlWriter->writeAttribute("href", url.url());
> + }
> +}
> +
> +void GoogleContactStream::encodeBirthday(const QContactBirthday &birthday)
> +{
> + if (birthday.date().isValid()) {
> + mXmlWriter->writeEmptyElement("gContact:birthday");
> + mXmlWriter->writeAttribute("when", birthday.date().toString(Qt::ISODate));
> + }
> +}
> +
> +void
> +GoogleContactStream::encodeNote(const QContactNote ¬e)
> +{
> + if (!note.note().isEmpty()) {
> + mXmlWriter->writeStartElement("gContact:jot");
> + mXmlWriter->writeAttribute("rel", encodeContext(note.contexts()));
> + mXmlWriter->writeCharacters(note.note());
> + mXmlWriter->writeEndElement();
> + }
> +}
> +
> +void GoogleContactStream::encodeHobby(const QContactHobby &hobby)
> +{
> + if (!hobby.hobby().isEmpty()) {
> + mXmlWriter->writeTextElement ("gContact:hobby", hobby.hobby());
> + }
> +}
> +
> +void GoogleContactStream::encodeGeoLocation(const QContactGeoLocation &geolocation)
> +{
> + Q_UNUSED(geolocation);
> + LOG_DEBUG("skipping geolocation");
> +}
> +
> +void GoogleContactStream::encodeOrganization(const QContactOrganization &organization)
> +{
> + mXmlWriter->writeStartElement("gd:organization");
> + mXmlWriter->writeAttribute("rel", "http://schemas.google.com/g/2005#" + encodeContext(organization.contexts()));
> + if (!organization.title().isEmpty())
> + mXmlWriter->writeTextElement("gd:orgTitle", organization.title());
> + if (!organization.name().isEmpty())
> + mXmlWriter->writeTextElement("gd:orgName", organization.name());
> + if (!organization.department().isEmpty())
> + mXmlWriter->writeTextElement("gd:orgDepartment", organization.department().join(','));
> + if (!organization.role().isEmpty())
> + mXmlWriter->writeTextElement("gd:orgJobDescription", organization.role());
> + mXmlWriter->writeEndElement();
> +}
> +
> +void GoogleContactStream::encodeAvatar(const QContactAvatar &avatar, const QContact &qContact)
> +{
> + // XXX TODO: determine if it's a new local avatar, if so, push it up.
> + QUrl imageUrl(avatar.imageUrl());
> + if (imageUrl.isLocalFile()) {
> + LOG_DEBUG("have avatar:" << imageUrl << "but not upsyncing avatars");
> + mEncodedContactsWithAvatars << qContact.id();
> + }
> +}
> +
> +void GoogleContactStream::encodeGender(const QContactGender &gender)
> +{
> + mXmlWriter->writeEmptyElement ("gContact:gender");
> + mXmlWriter->writeAttribute ("value", gender.gender() == QContactGender::GenderMale ? "male" : "female");
> +}
> +
> +void GoogleContactStream::encodeNickname(const QContactNickname &nickname)
> +{
> + if (!nickname.nickname().isEmpty()) {
> + mXmlWriter->writeTextElement("gContact:nickname", nickname.nickname());
> + }
> +}
> +
> +void GoogleContactStream::encodeAnniversary(const QContactAnniversary &anniversary)
> +{
> + static QMap<int, QString> anniversaryTypes;
> + if (anniversaryTypes.isEmpty()) {
> + anniversaryTypes.insert(QContactAnniversary::SubTypeEngagement, QString::fromLatin1("engagement"));
> + anniversaryTypes.insert(QContactAnniversary::SubTypeEmployment, QString::fromLatin1("employment"));
> + anniversaryTypes.insert(QContactAnniversary::SubTypeMemorial, QString::fromLatin1("memorial"));
> + anniversaryTypes.insert(QContactAnniversary::SubTypeHouse, QString::fromLatin1("house"));
> + anniversaryTypes.insert(QContactAnniversary::SubTypeWedding, QString::fromLatin1("wedding"));
> + }
> +
> + if (!anniversary.event().isEmpty() && !anniversary.originalDate().isNull()) {
> + mXmlWriter->writeStartElement ("gContact:event");
> + mXmlWriter->writeAttribute("rel", "anniversary");
> + mXmlWriter->writeAttribute("label", anniversaryTypes.value(anniversary.subType(),
> + QString::fromLatin1("wedding")));
> + mXmlWriter->writeEmptyElement("gd:when");
> + mXmlWriter->writeAttribute("startTime", anniversary.originalDateTime().toString(Qt::ISODate));
> + mXmlWriter->writeAttribute("valueString", anniversary.event());
> + mXmlWriter->writeEndElement();
> + }
> +}
> +
> +void GoogleContactStream::encodeOnlineAccount(const QContactOnlineAccount &onlineAccount)
> +{
> + static QMap<QContactOnlineAccount::Protocol, QString> protocolMap;
> + if (onlineAccount.accountUri().isEmpty()) {
> + return;
> + }
> +
> + if (protocolMap.isEmpty()) {
> + protocolMap.insert(QContactOnlineAccount::ProtocolJabber, "JABBER");
> + protocolMap.insert(QContactOnlineAccount::ProtocolAim, "AIM");
> + protocolMap.insert(QContactOnlineAccount::ProtocolIcq, "ICQ");
> + protocolMap.insert(QContactOnlineAccount::ProtocolMsn, "MSN");
> + protocolMap.insert(QContactOnlineAccount::ProtocolQq, "QQ");
> + protocolMap.insert(QContactOnlineAccount::ProtocolYahoo, "YAHOO");
> + protocolMap.insert(QContactOnlineAccount::ProtocolSkype, "SKYPE");
> + protocolMap.insert(QContactOnlineAccount::ProtocolIrc, "IRC");
> + }
> +
> + QString protocolName = protocolMap.value(onlineAccount.protocol(),
> + onlineAccount.serviceProvider());
> + if (protocolName.isEmpty()) {
> + LOG_WARNING("Fail to parse online account protcol:" + onlineAccount.accountUri());
> + return;
> + }
> +
> + QString context = encodeContext(onlineAccount.contexts());
> + mXmlWriter->writeEmptyElement("gd:im");
> + mXmlWriter->writeAttribute("protocol", "http://schemas.google.com/g/2005#" + protocolName);
> + mXmlWriter->writeAttribute("rel", "http://schemas.google.com/g/2005#" + context);
> + mXmlWriter->writeAttribute("address", onlineAccount.accountUri());
> +}
> +
> +void GoogleContactStream::encodeFamily(const QContactFamily &family)
> +{
> + if (family.spouse().length() > 0) {
> + mXmlWriter->writeStartElement("gContact:relation");
> + mXmlWriter->writeAttribute("rel", "spouse");
> + mXmlWriter->writeCharacters(family.spouse());
> + mXmlWriter->writeEndElement();
> + }
> +
> + Q_FOREACH (const QString member, family.children()) {
> + mXmlWriter->writeStartElement("gContact:relation");
> + mXmlWriter->writeAttribute("rel", "child");
> + mXmlWriter->writeCharacters(member);
> + mXmlWriter->writeEndElement();
> + }
> +}
> +
> +void GoogleContactStream::encodeGroup(const QContactExtendedDetail &group)
> +{
> + static const QString groupHref = QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/%2");
> + QString groupId = group.data().toString();
> + QString accountName = mAccountEmail.isEmpty() ? QStringLiteral("default") : mAccountEmail;
> + if (groupId.isEmpty()) {
> + // FIXME: this is default id for "My Contacts"
> + groupId = "6";
> + }
> +
> + mXmlWriter->writeEmptyElement("gContact:groupMembershipInfo");
> + mXmlWriter->writeAttribute("deleted", "false");
> + mXmlWriter->writeAttribute("href", QString(groupHref).arg(accountName).arg(groupId));
> +}
> +
> +void GoogleContactStream::encodeFavorite(const QContactFavorite &favorite)
> +{
> + QContactExtendedDetail det;
> + det.setName("X-FAVORITE");
> + det.setData(favorite.isFavorite() ? "true" : "false");
> + encodeExtendedProperty(det, 0);
> +}
> +
> +void GoogleContactStream::encodeExtendedProperty(const QContactExtendedDetail &detail,
> + bool *isGroup)
> +{
> + static QStringList blackList;
> + QString detailName = detail.value(QContactExtendedDetail::FieldName).toString();
> +
> + // Avoid duplicate info in the remote side
> + // These fields are used locally to store remote info
> + if (blackList.isEmpty()) {
> + blackList << "X-REMOTE-ID"
> + << "X-AVATAR-REV"
> + << "X-GOOGLE-ETAG"
> + << "X-GROUP-ID"
> + << "X-CREATED-AT"
> + << "X-NORMALIZED_FN";
> + }
> +
> + if (detailName == UContactsCustomDetail::FieldGroupMembershipInfo) {
> + encodeGroup(detail);
> + if (isGroup) {
> + *isGroup |= true;
> + }
> + } else if (!detailName.isEmpty() && !blackList.contains(detailName)) {
> + mXmlWriter->writeEmptyElement("gd:extendedProperty");
> + mXmlWriter->writeAttribute("name", detail.name());
> + mXmlWriter->writeAttribute("value", detail.data().toString());
> + }
> +}
> +
> +void GoogleContactStream::encodeRingTone(const QContactRingtone &ringTone)
> +{
> + if (!ringTone.audioRingtoneUrl().isEmpty()) {
> + QContactExtendedDetail det;
> + det.setName("SOUND");
> + det.setData(ringTone.audioRingtoneUrl());
> + encodeExtendedProperty(det, 0);
> + }
> +}
> +
> +void GoogleContactStream::encodeUnknownElements(const QStringList &unknownElements)
> +{
> + // ugly hack to get the QXmlStreamWriter to write a pre-formatted element...
> + foreach (const QString &unknownElement, unknownElements) {
> + QString concat;
> + concat.append("<?xml version=\"1.0\"?>");
> + concat.append("<container>");
> + concat.append(unknownElement);
> + concat.append("</container>");
> +
> + QXmlStreamReader tokenizer(concat);
> + tokenizer.readNextStartElement(); // read past the xml document element start.
> + QString text = tokenizer.readElementText();
> + mXmlWriter->writeStartElement(tokenizer.qualifiedName().toString());
> + mXmlWriter->writeAttributes(tokenizer.attributes());
> + if (!text.isEmpty()) {
> + mXmlWriter->writeCharacters(text);
> + }
> + mXmlWriter->writeEndElement();
> + }
> +}
> +
> +QString GoogleContactStream::encodeContext(const QList<int> context) const
> +{
> + // TOOD: check if a list of context is necessary
> + switch(context.value(0, QContactDetail::ContextOther)) {
> + case QContactDetail::ContextHome:
> + return "home";
> + case QContactDetail::ContextWork:
> + return "work";
> + case QContactDetail::ContextOther:
> + default:
> + return "other";
> + }
> +}
> +
--
https://code.launchpad.net/~renatofilho/buteo-sync-plugins-contacts/new-code/+merge/264335
Your team Ubuntu Phablet Team is subscribed to branch lp:buteo-sync-plugins-contacts.
More information about the Ubuntu-reviews
mailing list