/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QMutex>
#include <QDebug>
#include "qcontactmanager.h"
#include "qcontactchangeset.h"
#include "qtcontacts.h"
#define INITGUID
#include "qcontactwincebackend_p.h"
/*
* This file is most of the engine plumbing - conversion to/from POOM is in
* the contactconversions.cpp file.
*/
/*
* TODOS
*
* - refactor (much needed)
* - single properties should have common code
* - error reporting when we run out of room
* - use QScopedPointer
* - pronunciation (needs schema update)
* - any XXX comments
* - store extra metadata
* - Voice subtype for phone, don't always assume
* - Address formatting - PIMPR_HOME_ADDRESS seems to be read only
*/
QContactWinCEEngine::QContactWinCEEngine(ContactWinceFactory* factory, const QString& engineName, const QMap<QString, QString>& , QContactManager::Error* error)
: d(new QContactWinCEEngineData)
{
*error = QContactManager::NoError;
buildHashForContactDetailToPoomPropId();
d->m_engineName = engineName;
d->m_factory = factory;
if (SUCCEEDED(d->m_cominit.hr())) {
if (SUCCEEDED(CoCreateInstance(CLSID_Application, NULL,
CLSCTX_INPROC_SERVER, IID_IPOutlookApp2,
reinterpret_cast<void **>(&d->m_app)))) {
if(FAILED(d->m_app->Logon(NULL))) {
qWarning() << "Failed to log on";
d->m_app = 0;
} else {
if(SUCCEEDED(d->m_app->GetDefaultFolder(olFolderContacts, &d->m_folder))) {
if(SUCCEEDED(d->m_folder->get_Items(&d->m_collection))) {
// Register/retrieve our custom ids
LPCWSTR customIds[4] = { L"QTCONTACTS_PHONE_META", L"QTCONTACTS_EMAIL_META", L"QTCONTACTS_AVATAR_IMAGE_META", L"QTCONTACTS_AVATAR_VIDEO_META" };
CEPROPID outIds[4];
if (SUCCEEDED(d->m_app->GetIDsFromNames(4, customIds, PIM_CREATE | CEVT_LPWSTR, outIds))) {
d->m_phonemeta = outIds[0];
d->m_emailmeta = outIds[1];
d->m_avatarImageMeta = outIds[2];
d->m_avatarVideoMeta = outIds[3];
}
// get an IPOLItems2 pointer for the collection, too
if (SUCCEEDED(d->m_collection->QueryInterface<IPOlItems2>(&d->m_items2))) {
d->m_ids = convertP2QIdList(d->m_collection);
}
} else {
qWarning() << "Failed to get items";
d->m_collection = 0;
}
} else {
qWarning() << "Failed to get contacts folder";
d->m_folder = 0;
}
}
}
}
d->m_requestWorker.start();
}
QContactWinCEEngine::QContactWinCEEngine(const QContactWinCEEngine& other)
: QContactManagerEngine(), d(other.d)
{
}
QContactWinCEEngine& QContactWinCEEngine::operator=(const QContactWinCEEngine& other)
{
// assign
d = other.d;
return *this;
}
QContactWinCEEngine::~QContactWinCEEngine()
{
if (d->m_app) {
d->m_app->Logoff();
}
d->m_factory->resetEngine();
}
QString QContactWinCEEngine::managerName() const
{
return d->m_engineName;
}
QContact QContactWinCEEngine::contact(const QContactLocalId& contactId, const QtMobility::QContactFetchHint& hint, QContactManager::Error* error) const
{
// TODO: implementation for definitionRestrictions!
Q_UNUSED(hint);
QContact ret;
// id == 0 gives a bad argument error from POOM, so don't even try it
if (contactId != 0) {
// Fetch!
SimpleComPointer<IItem> item = 0;
HRESULT hr = d->m_app->GetItemFromOidEx(contactId, 0, &item);
if (SUCCEEDED(hr)) {
if (item) {
*error = QContactManager::NoError;
ret = convertToQContact(item);
} else {
*error = QContactManager::DoesNotExistError;
}
} else {
if (HRESULT_CODE(hr) == ERROR_NOT_FOUND) {
*error = QContactManager::DoesNotExistError;
} else {
qWarning() << "Failed to retrieve contact:" << HRESULT_CODE(hr);
*error = QContactManager::UnspecifiedError;
}
}
} else {
*error = QContactManager::DoesNotExistError;
}
return ret;
}
bool QContactWinCEEngine::saveContact(QContact* contact, QContactManager::Error* error)
{
if (contact == 0) {
*error = QContactManager::BadArgumentError;
return false;
}
QContactChangeSet cs;
// ensure that the contact's details conform to their definitions
if (!validateContact(*contact, error)) {
*error = QContactManager::InvalidDetailError;
return false;
}
SimpleComPointer<IItem> icontact;
bool wasOld = false;
// Figure out if this is a new or old contact
if (d->m_ids.contains(contact->localId())) {
// update existing contact
HRESULT hr = d->m_app->GetItemFromOidEx(contact->localId(), 0, &icontact);
if (SUCCEEDED(hr)) {
wasOld = true;
} else {
if (HRESULT_CODE(hr) == ERROR_NOT_FOUND) {
// Well, doesn't exist any more
*error = QContactManager::DoesNotExistError;
d->m_ids.removeAll(contact->localId());
} else {
qWarning() << "Didn't get old contact" << HRESULT_CODE(hr);
*error = QContactManager::UnspecifiedError;
}
}
} else if (contact->localId() == 0) {
// new contact!
SimpleComPointer<IDispatch> idisp = 0;
HRESULT hr = d->m_collection->Add(&idisp);
if (SUCCEEDED(hr)) {
// now get an actual item out of it (was IContact, which is not IItem)
hr = idisp->QueryInterface<IItem>(&icontact);
if (SUCCEEDED(hr)) {
} else {
qWarning() << "Failed to query interface" << HRESULT_CODE(hr);
*error = QContactManager::UnspecifiedError;
}
} else {
qWarning() << "Failed to create contact: "<< HRESULT_CODE(hr);
*error = QContactManager::OutOfMemoryError;
}
} else {
// Saving a contact with a non zero id, but that doesn't exist
*error = QContactManager::DoesNotExistError;
}
if (icontact) {
// Convert our QContact to the Icontact (via setProps)
if (convertFromQContact(*contact, icontact, *error)) {
HRESULT hr = icontact->Save();
if (SUCCEEDED(hr)) {
// yay! we also need to set the new contact id
long oid = 0;
hr = icontact->get_Oid(&oid);
if (SUCCEEDED(hr)) {
*error = QContactManager::NoError;
QContact c = this->contact((QContactLocalId)oid, QContactFetchHint(), error);
if (*error == QContactManager::NoError) {
*contact = c;
if (wasOld) {
cs.insertChangedContact(contact->localId());
} else {
cs.insertAddedContact(contact->localId());
d->m_ids.append(contact->localId());
}
}
cs.emitSignals(this);
return true;
}
qWarning() << "Saved contact, but couldn't retrieve id again??" << HRESULT_CODE(hr);
// Blargh.
*error = QContactManager::UnspecifiedError;
} else {
qWarning() << "Failed to save contact" << HRESULT_CODE(hr);
}
} else {
qWarning() << "Failed to convert contact";
}
}
// error should have been set.
return false;
}
bool QContactWinCEEngine::removeContact(const QContactLocalId& contactId, QContactManager::Error* error)
{
// Fetch an IItem* for this
if (contactId != 0) {
SimpleComPointer<IItem> item ;
QContactChangeSet cs;
HRESULT hr = d->m_app->GetItemFromOidEx(contactId, 0, &item);
if (SUCCEEDED(hr)) {
hr = item->Delete();
if (SUCCEEDED(hr)) {
*error = QContactManager::NoError;
d->m_ids.removeAll(contactId);
cs.insertRemovedContact(contactId);
cs.emitSignals(this);
return true;
}
qWarning() << "Failed to delete:" << HRESULT_CODE(hr);
*error = QContactManager::UnspecifiedError;
} else {
if (HRESULT_CODE(hr) == ERROR_NOT_FOUND) {
*error = QContactManager::DoesNotExistError;
} else {
qWarning() << "Failed to retrieve item pointer in delete" << HRESULT_CODE(hr);
*error = QContactManager::UnspecifiedError;
}
}
} else {
// Id 0 does not exist
*error = QContactManager::DoesNotExistError;
}
return false;
}
QMap<QString, QContactDetailDefinition> QContactWinCEEngine::detailDefinitions(const QString& contactType, QContactManager::Error* error) const
{
*error = QContactManager::NoError;
QMap<QString, QMap<QString, QContactDetailDefinition> > defns = QContactManagerEngine::schemaDefinitions();
// Remove the details we don't support
defns[contactType].remove(QContactSyncTarget::DefinitionName);
defns[contactType].remove(QContactGeoLocation::DefinitionName);
defns[contactType].remove(QContactTimestamp::DefinitionName);
defns[contactType].remove(QContactGuid::DefinitionName);
defns[contactType].remove(QContactGender::DefinitionName); // ? Surprising
QMap<QString, QContactDetailFieldDefinition> fields = defns[contactType][QContactAnniversary::DefinitionName].fields();
// Simple anniversarys
fields.remove(QContactAnniversary::FieldCalendarId);
fields.remove(QContactAnniversary::FieldEvent);
fields.remove(QContactAnniversary::FieldSubType);
defns[contactType][QContactAnniversary::DefinitionName].setFields(fields);
// No logo for organisation
fields = defns[contactType][QContactOrganization::DefinitionName].fields();
fields.remove(QContactOrganization::FieldLogoUrl);
defns[contactType][QContactOrganization::DefinitionName].setFields(fields);
// No subtypes for these details
fields = defns[contactType][QContactUrl::DefinitionName].fields();
fields.remove(QContactUrl::FieldSubType);
defns[contactType][QContactUrl::DefinitionName].setFields(fields);
// No contexts for these details
fields = defns[contactType][QContactAvatar::DefinitionName].fields();
fields.remove(QContactDetail::FieldContext);
defns[contactType][QContactAvatar::DefinitionName].setFields(fields);
// Simple phone number types (non multiple)
// defns[QContactPhoneNumber::DefinitionName].fields()[QContactPhoneNumber::FieldSubTypes].dataType = QVariant::String; // XXX doesn't work
fields = defns[contactType][QContactPhoneNumber::DefinitionName].fields();
fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeBulletinBoardSystem)));
fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeLandline)));
fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeMessagingCapable)));
fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeModem)));
fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeVideo)));
defns[contactType][QContactPhoneNumber::DefinitionName].setFields(fields);
// XXX temporary definitions that we should support but don't yet.
defns[contactType].remove(QContactOnlineAccount::DefinitionName);
return defns[contactType];
}
/*! \reimp */
void QContactWinCEEngine::requestDestroyed(QContactAbstractRequest* req)
{
d->m_requestWorker.removeRequest(req);
}
/*! \reimp */
bool QContactWinCEEngine::startRequest(QContactAbstractRequest* req)
{
return d->m_requestWorker.addRequest(req);
}
/*! \reimp */
bool QContactWinCEEngine::cancelRequest(QContactAbstractRequest* req)
{
return d->m_requestWorker.cancelRequest(req);
}
/*! \reimp */
bool QContactWinCEEngine::waitForRequestFinished(QContactAbstractRequest* req, int msecs)
{
return d->m_requestWorker.waitRequest(req, msecs) && req->isFinished();
}
/*! \reimp */
bool QContactWinCEEngine::hasFeature(QContactManager::ManagerFeature feature, const QString& contactType) const
{
Q_UNUSED(contactType);
// The Windows CE backend is an "isolated" backend
if (feature == QContactManager::Anonymous)
return true;
// Windows CE backend does not support Mutable Definitions, Relationships or Action Preferences
return false;
}
/* Synthesise the display label of a contact */
QString QContactWinCEEngine::synthesizedDisplayLabel(const QContact& contact, QContactManager::Error* error) const
{
Q_UNUSED(error)
// The POOM API (well, lack thereof) makes this a bit strange.
// It's basically just "Last, First" or "Company", if "FileAs" is not set.
QContactName name = contact.detail<QContactName>();
QContactOrganization org = contact.detail<QContactOrganization>();
// Basically we ignore any existing labels for this contact, since we're being
// asked what the synthesized label would be
// XXX For greatest accuracy we might be better off converting this contact to
// a real item (but don't save it), and then retrieve it...
if (!name.customLabel().isEmpty()) {
return name.customLabel();
}
else if (!name.lastName().isEmpty()) {
if (!name.firstName().isEmpty()) {
return QString(QLatin1String("%1, %2")).arg(name.lastName()).arg(name.firstName());
} else {
// Just last
return name.lastName();
}
} else if (!name.firstName().isEmpty()) {
return name.firstName();
} else if (!org.name().isEmpty()) {
return org.name();
} else {
// XXX grargh.
return QLatin1String("Unnamed");
}
}
PROPID QContactWinCEEngine::metaAvatarImage() const
{
return d->m_avatarImageMeta;
}
PROPID QContactWinCEEngine::metaAvatarVideo() const
{
return d->m_avatarVideoMeta;
}
PROPID QContactWinCEEngine::metaEmail() const
{
return d->m_emailmeta;
}
PROPID QContactWinCEEngine::metaPhone() const
{
return d->m_phonemeta;
}
/*! \reimp */
bool QContactWinCEEngine::isFilterSupported(const QContactFilter& filter) const
{
switch (filter.type()) {
case QContactFilter::InvalidFilter:
case QContactFilter::DefaultFilter:
case QContactFilter::LocalIdFilter:
case QContactFilter::ContactDetailFilter:
case QContactFilter::ContactDetailRangeFilter:
case QContactFilter::ActionFilter:
case QContactFilter::IntersectionFilter:
case QContactFilter::UnionFilter:
return true;
}
return false;
}
/*! \reimp */
int QContactWinCEEngine::managerVersion() const
{
return QTCONTACTS_VERSION;
}
/*! \reimp */
QList<QContact> QContactWinCEEngine::contacts(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, const QContactFetchHint& fetchHint, QContactManager::Error* error) const
{
QList<QContactLocalId> ids = contactIds(filter, sortOrders, error);
QList<QContact> cs;
if (*error == QContactManager::NoError) {
foreach (const QContactLocalId& id, ids) {
cs << contact(id, fetchHint, error);
}
}
return cs;
}
/*! \reimp */
bool QContactWinCEEngine::saveContacts(QList<QContact>* contacts, QMap<int, QContactManager::Error>* errorMap, QContactManager::Error* error)
{
bool ret = true;
for (int j = 0; j < contacts->size(); j++) {
if (!saveContact(&((*contacts)[j]), error)) {
ret = false;
}
if (*error != QContactManager::NoError) {
errorMap->insert(j, *error);
}
}
return ret;
}
/*! \reimp */
bool QContactWinCEEngine::removeContacts(const QList<QContactLocalId>& contactIds, QMap<int, QContactManager::Error>* errorMap, QContactManager::Error* error)
{
bool ret = true;
for (int j = 0; j < contactIds.size(); j++) {
if (!removeContact(contactIds[j], error)) {
ret = false;
}
if (*error != QContactManager::NoError) {
errorMap->insert(j, *error);
}
}
return ret;
}
/*!
* Returns the list of data types supported by the WinCE engine
*/
QList<QVariant::Type> QContactWinCEEngine::supportedDataTypes() const
{
QList<QVariant::Type> st;
st.append(QVariant::String);
st.append(QVariant::Int);
st.append(QVariant::UInt);
st.append(QVariant::Double);
st.append(QVariant::Date);
st.append(QVariant::DateTime);
return st;
}
/* The default constructor of wince contact manager engine factory */
ContactWinceFactory::ContactWinceFactory()
:m_engine(0)
{
}
/* Factory lives here in the basement */
QContactManagerEngine* ContactWinceFactory::engine(const QMap<QString, QString>& parameters, QContactManager::Error* error)
{
QMutexLocker locker(&m_mutex);
if (!m_engine) {
m_engine = new QContactWinCEEngine(this, managerName(), parameters, error);
}
return m_engine;
}
QString ContactWinceFactory::managerName() const
{
return QString("wince");
}
void ContactWinceFactory::resetEngine()
{
QMutexLocker locker(&m_mutex);
m_engine = 0;
}
Q_EXPORT_PLUGIN2(qtcontacts_wince, ContactWinceFactory);