qtmobility/src/messaging/qmessageservice_win.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Wed, 23 Jun 2010 19:08:38 +0300
changeset 14 6fbed849b4f4
parent 11 06b8e2af4411
permissions -rw-r--r--
Revision: 201023 Kit: 2010125

/****************************************************************************
**
** 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 "qmessageservice.h"
#include "winhelpers_p.h"
#include "qmessagemanager.h"
#include "qmessageid_p.h"
#include "qmessagefolderid_p.h"
#include "qmessageaccountid_p.h"
#include "qmessage_p.h"
#include "qmessagestore_p.h"
#include "qmessagecontentcontainer_p.h"
#include "qmessagecontentcontainerid_p.h"
#include <QDebug>
#include <QThread>
#include <QTimer>
#include <mapix.h>
#include <objbase.h>
#include <mapiutil.h>
#include <qmobilityglobal.h>
#ifdef _WIN32_WCE
#include <cemapi.h>
#endif

using namespace QtMobility::WinHelpers;

QTM_BEGIN_NAMESPACE

static const unsigned long SmsCharLimit = 160;

class QMessageServicePrivate : public QObject
{
    Q_OBJECT

    Q_DECLARE_PUBLIC(QMessageService)

public:
    QMessageServicePrivate(QMessageService* parent);
    ~QMessageServicePrivate();

    bool send(const QMessage& message, bool showComposer = false);
    bool show(const QMessageId& id);
#ifdef _WIN32_WCE
    bool isPartiallyDownloaded(const QMessageId& id, bool considerAttachments = false);
    bool markForDownload(const QMessageId& id, bool includeAttachments = false);
    bool synchronize(const QMessageAccountId& id);
    bool registerUpdates(const QMessageId& id);
    void unregisterUpdates();
    bool retrieveBody(const QMessage& partialMessage);
#endif

    void setFinished(bool successful);

public slots:
    void completed();
    void reportMatchingIds();
    void reportMessagesCounted();

#ifdef _WIN32_WCE
    void messageUpdated(const QMessageId& id);
#endif

signals:
    void stateChanged(QMessageService::State);
    void messagesFound(const QMessageIdList&);
    void messagesCounted(int);
    void progressChanged(uint, uint);

public:
    QMessageService* q_ptr;
    QMessageManager _manager;
    bool _active;
    QMessageManager::Error _error;
    QMessageIdList _candidateIds;
    int _count;
    QMessageService::State _state;
    QMessageId m_bodyDownloadTarget;
    QMessageManager::NotificationFilterId m_bodyDownloadFilterId;
    bool m_registeredUpdates;
    QThread *m_queryThread;
    QList<QThread*> m_obsoleteThreads;
};

namespace {

class QueryThread : public QThread
{
    Q_OBJECT

    QMessageServicePrivate *_parent;
    QMessageFilter _filter;
    QString _body;
    QMessageDataComparator::MatchFlags _matchFlags;
    QMessageSortOrder _ordering;
    uint _limit;
    uint _offset;

    // Ensure that the main thread has instantiated the store before we access it from another thread:
    QMessageManager _manager;

public:
    QueryThread(QMessageServicePrivate *parent, const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset);
    void run();

signals:
    void completed();
};

QueryThread::QueryThread(QMessageServicePrivate *parent, const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset)
: QThread(),
    _parent(parent),
    _filter(filter),
    _body(body),
    _matchFlags(matchFlags),
    _ordering(sortOrder),
    _limit(limit),
    _offset(offset)
{
}

void QueryThread::run()
{
    // Ensure that this thread has initialized MAPI
    WinHelpers::MapiInitializationToken token(WinHelpers::initializeMapi());

    _parent->_candidateIds = _manager.queryMessages(_filter, _body, _matchFlags, _ordering, _limit, _offset);
    _parent->_error = _manager.error();
    emit completed();
}

}

void QMessageServicePrivate::completed()
{
    _active = false;
    _state = QMessageService::FinishedState;
    emit stateChanged(_state);
}

void QMessageServicePrivate::reportMatchingIds()
{
    if (_error == QMessageManager::NoError) {
        emit messagesFound(_candidateIds);
    }

    completed();
}

void QMessageServicePrivate::reportMessagesCounted()
{
    if (_error == QMessageManager::NoError) {
        emit messagesCounted(_candidateIds.count());
    }
    completed();
}

#ifdef _WIN32_WCE
void QMessageServicePrivate::messageUpdated(const QMessageId& id)
{
    if (id == m_bodyDownloadTarget) {
        bool isBodyDownloaded = !isPartiallyDownloaded(id);
        if (isBodyDownloaded) {
            unregisterUpdates();

            completed();
        }
    }
}

#endif

QMessageServicePrivate::QMessageServicePrivate(QMessageService* parent)
    :q_ptr(parent),
     _active(false),
     _state(QMessageService::InactiveState),
     m_registeredUpdates(false),
     m_queryThread(0)
{
}

QMessageServicePrivate::~QMessageServicePrivate()
{
    qDeleteAll(m_obsoleteThreads);
    delete m_queryThread;
    _manager.unregisterNotificationFilter(m_bodyDownloadFilterId);
}

static Lptstr createMCFRecipients(QMessageAddress::Type filterAddressType, const QMessageAddressList& addressList)
{
    QStringList temp;

    foreach(const QMessageAddress& a, addressList)
    {
        if(a.type() == filterAddressType)
            temp.append(a.addressee());
    }

    return temp.isEmpty() ? Lptstr(0) : LptstrFromQString(temp.join(";"));
}

bool QMessageServicePrivate::send(const QMessage& message, bool showComposer)
{
    //check message type

    if(message.type() == QMessage::AnyType || message.type() == QMessage::NoType)
    {
        qWarning() << "Unsupported message type for sending/composition";
        _error = QMessageManager::ConstraintFailure;
        return false;
    }

    //check account

    QMessageAccountId accountId = message.parentAccountId();

    if(!accountId.isValid())
    {
        accountId = QMessageAccount::defaultAccount(message.type());
        if(!accountId.isValid())
        {
            qWarning() << "Invalid account for sending/composition";
            _error = QMessageManager::InvalidId;
            return false;
        }
    }

    //check account/message type compatibility

    QMessageAccount account(accountId);
    if(!(account.messageTypes() & message.type()))
    {
        qWarning() << "Message type unsupported by account";
        _error = QMessageManager::ConstraintFailure;
        return false;
    }

#ifdef _WIN32_WCE
    if(showComposer)
    {
        MAILCOMPOSEFIELDS mcf;
        memset(&mcf,0,sizeof(mcf));

        Lptstr accountName = LptstrFromQString(QMessageAccount(accountId).name());
        Lptstr to;
        Lptstr cc;
        Lptstr bcc;
        Lptstr subject;
        Lptstr attachments;
        Lptstr bodyText;

        //account

        mcf.pszAccount = accountName;
        mcf.dwFlags = MCF_ACCOUNT_IS_NAME;

        if(message.type() == QMessage::Email)
        {
            //recipients

            to = createMCFRecipients(QMessageAddress::Email, message.to());
            cc = createMCFRecipients(QMessageAddress::Email, message.cc());
            bcc = createMCFRecipients(QMessageAddress::Email, message.bcc());
            mcf.pszTo = to;
            mcf.pszCc = cc;
            mcf.pszBcc = bcc;

            //subject

            subject = LptstrFromQString(message.subject());
            mcf.pszSubject = subject;

            //body

            QMessageContentContainerId bodyId = message.bodyId();
            if(bodyId.isValid())
            {
                const QMessageContentContainer& bodyContainer = message.find(bodyId);
                bodyText = LptstrFromQString(bodyContainer.textContent());
                mcf.pszBody = bodyText;
            }

            //attachments

            if(message.status() & QMessage::HasAttachments)
            {
                QStringList attachmentList;

                foreach(const QMessageContentContainerId& id, message.attachmentIds())
                {
                    const QMessageContentContainer& attachmentContainer = message.find(id);
                    attachmentList.append(QMessageContentContainerPrivate::attachmentFilename(attachmentContainer));
                }

                mcf.cAttachments = attachmentList.count();
                QChar nullChar(0);
                attachments = LptstrFromQString(attachmentList.join(nullChar));
                mcf.pszAttachments = attachments;
            }
        }
        else if(message.type() == QMessage::Sms)
        {
            //recipients

            to = createMCFRecipients(QMessageAddress::Phone, message.to());
            mcf.pszTo = to;

            //body

            QMessageContentContainerId bodyId = message.bodyId();
            if(bodyId.isValid())
            {
                const QMessageContentContainer& bodyContainer = message.find(bodyId);
                QString textContent = bodyContainer.textContent();
                if(textContent.length() > SmsCharLimit)
                {
                    textContent.truncate(SmsCharLimit);
                    qWarning() << "SMS messages may not exceed " << SmsCharLimit << " characters. Body trucated.";
                }
                bodyText = LptstrFromQString(textContent);
                mcf.pszBody = bodyText;
            }
        }

       mcf.cbSize = sizeof(mcf);

       if(FAILED(MailComposeMessage(&mcf)))
       {
           _error = QMessageManager::FrameworkFault;
           qWarning() << "MailComposeMessage failed";
           return false;
       }
    }
    else
    {
#endif

    //check recipients
    QMessageAddressList recipients = message.to() + message.bcc() + message.cc();
    if(recipients.isEmpty() && !showComposer)
    {
        qWarning() << "Message must have recipients for sending";
        _error = QMessageManager::ConstraintFailure;
        return false;
    }

    MapiSessionPtr mapiSession(MapiSession::createSession(&_error));
    if (_error != QMessageManager::NoError)
    {
        qWarning() << "Could not create MAPI session for sending";
        return false;
    }

    QMessage outgoing(message);

    //ensure the message is marked read otherwise MapiForm displays the message as incomming
    outgoing.setStatus(QMessage::Read,true);

    //set default account if unset
    if(!outgoing.parentAccountId().isValid())
        outgoing.setParentAccountId(accountId);

    MapiStorePtr mapiStore = mapiSession->findStore(&_error, outgoing.parentAccountId(),false);

    if(mapiStore.isNull() || _error != QMessageManager::NoError)
    {
        qWarning() << "Unable to retrieve MAPI store for account";
        return false;
    }

    //try first to create message in outbox for store, failing that attempt draft

    MapiFolderPtr mapiFolder = mapiStore->findFolder(&_error,QMessage::OutboxFolder);

    if( mapiFolder.isNull() || _error != QMessageManager::NoError ) {
        qWarning() << "Unable to retrieve outbox MAPI folder for sending, attempting drafts...";
        mapiFolder = mapiStore->findFolder(&_error,QMessage::DraftsFolder);
        if(mapiFolder.isNull() || _error != QMessageManager::NoError) {
            qWarning() << "Unable to retrieve drafts MAPI folder for sending";
            return false;
        }
    }

    IMessage* mapiMessage = mapiFolder->createMessage(&_error, outgoing, mapiSession, DontSavePropertyChanges);

    if(!mapiMessage || _error != QMessageManager::NoError)
    {
        qWarning() << "Unable to create MAPI message from source";
        mapiRelease(mapiMessage);
        return false;
    }

#ifndef _WIN32_WCE
    if(showComposer)
    {
        MapiForm* mapiForm = new MapiForm(mapiStore->store(),mapiSession->session(),mapiFolder->folder(),mapiMessage);
        bool result = mapiForm->show();
        mapiRelease(mapiForm);

        if(!result)
        {
            qWarning() << "MapiForm::Show failed";
            _error = QMessageManager::FrameworkFault;
            return false;
        }
    }
    else
#endif
    {
        if(FAILED(mapiMessage->SubmitMessage(0)))
        {
            qWarning() << "MAPI SubmitMessage failed.";
            _error = QMessageManager::FrameworkFault;
            mapiRelease(mapiMessage);
            return false;
        }
    }

#ifdef _WIN32_WCE
    }
#endif

    return true;
}

bool QMessageServicePrivate::show(const QMessageId& messageId)
{
    if(!messageId.isValid())
    {
        _error = QMessageManager::InvalidId;
        qWarning() << "Invalid QMessageId";
        return false;
    }

#ifdef _WIN32_WCE
    MapiEntryId entryId = QMessageIdPrivate::entryId(messageId);
    LPENTRYID entryIdPtr(reinterpret_cast<LPENTRYID>(const_cast<char*>(entryId.data())));
    if(FAILED(MailDisplayMessage(entryIdPtr,entryId.length())))
    {
        qWarning() << "MailDisplayMessage failed";
        _error = QMessageManager::FrameworkFault;
        return false;
    }
    return true;

#else

    MapiSessionPtr mapiSession(MapiSession::createSession(&_error));
    if (_error != QMessageManager::NoError)
    {
        qWarning() << "Could not create MAPI seesion";
        return false;
    }

    MapiEntryId entryId = QMessageIdPrivate::entryId(messageId);
    MapiRecordKey folderRecordKey = QMessageIdPrivate::folderRecordKey(messageId);
    MapiRecordKey storeRecordKey = QMessageIdPrivate::storeRecordKey(messageId);

    MapiStorePtr mapiStore = mapiSession->findStore(&_error,QMessageAccountIdPrivate::from(storeRecordKey));

    if(mapiStore.isNull() || _error != QMessageManager::NoError)
    {
        qWarning() << "Unable to retrieve MAPI store for account";
        return false;
    }

    MapiFolderPtr mapiFolder = mapiStore->openFolderWithKey(&_error,folderRecordKey);

    if( mapiFolder.isNull() || _error != QMessageManager::NoError ) {
        qWarning() << "Unable to retrieve MAPI folder for message";
        return false;
    }

    IMessage* mapiMessage = mapiFolder->openMessage(&_error,entryId);

    if(!mapiMessage || _error != QMessageManager::NoError)
    {
        qWarning() << "Unable to retrieve MAPI message";
        mapiRelease(mapiMessage);
        return false;
    }

    MapiForm* mapiForm = new MapiForm(mapiStore->store(),mapiSession->session(),mapiFolder->folder(),mapiMessage);
    bool result = mapiForm->show();
    mapiRelease(mapiForm);

    if(!result)
    {
        qWarning() << "MapiForm::show failed.";
        _error = QMessageManager::FrameworkFault;
        return false;
    }

    mapiRelease(mapiMessage);

    return result;

#endif
}

#ifdef _WIN32_WCE

bool QMessageServicePrivate::isPartiallyDownloaded(const QMessageId& id, bool considerAttachments)
{
    if(!id.isValid())
    {
        _error = QMessageManager::InvalidId;
        qWarning() << "Invalid QMessageId";
        return false;
    }

    MapiSessionPtr mapiSession(MapiSession::createSession(&_error));

    if (_error != QMessageManager::NoError)
    {
        qWarning() << "Could not create MAPI session";
        return false;
    }

    MapiStorePtr mapiStore = mapiSession->openStore(&_error,QMessageIdPrivate::storeRecordKey(id));

    if(mapiStore.isNull() || _error != QMessageManager::NoError) {
        qWarning() << "Unable to retrieve MAPI store for account";
        return false;
    }

    IMessage* message = mapiStore->openMessage(&_error,QMessageIdPrivate::entryId(id));

    ULONG status = 0;
    if(!getMapiProperty(message,PR_MSG_STATUS,&status)) {
        qWarning() << "Unable to get MAPI message status flags";
        _error = QMessageManager::FrameworkFault;
        return false;
    }
    else
    {
        mapiRelease(message);
        bool bodyNotDownloaded = (status & MSGSTATUS_HEADERONLY) || (status & MSGSTATUS_PARTIAL);
        bool attachmentsNotDownloaded = (status & MSGSTATUS_PENDING_ATTACHMENTS);
        return considerAttachments ? bodyNotDownloaded && attachmentsNotDownloaded : bodyNotDownloaded;
    }
}

bool QMessageServicePrivate::markForDownload(const QMessageId& id, bool includeAttachments)
{
    if(!id.isValid())
    {
        _error = QMessageManager::InvalidId;
        qWarning() << "Invalid QMessageId";
        return false;
    }

    MapiSessionPtr mapiSession(MapiSession::createSession(&_error));

    if (_error != QMessageManager::NoError)
    {
        qWarning() << "Could not create MAPI session";
        return false;
    }

    MapiStorePtr mapiStore = mapiSession->openStore(&_error,QMessageIdPrivate::storeRecordKey(id));

    if(mapiStore.isNull() || _error != QMessageManager::NoError)
    {
        qWarning() << "Unable to retrieve MAPI store for message account";
        return false;
    }

    IMessage* message = mapiStore->openMessage(&_error,QMessageIdPrivate::entryId(id));

    ULONG status = 0;

    if(!getMapiProperty(message,PR_MSG_STATUS,&status))
    {
        qWarning() << "Unable to get MAPI message status flags";
        _error = QMessageManager::FrameworkFault;
        return false;
    }
    else
    {
        //mark the message to download on the next sync

        status |= MSGSTATUS_REMOTE_DOWNLOAD;
        if(includeAttachments)
            status |= MSGSTATUS_REMOTE_DOWNLOAD_ATTACH;

        if(!setMapiProperty(message, PR_MSG_STATUS, status))
        {
            qWarning() << "Could not mark the MAPI message for download!";
            _error = QMessageManager::FrameworkFault;
            return false;
        }

        mapiRelease(message);

        //TODO investigate possiblity of interacting with mapi transport directly
        /*
        QString transportName = mapiStore->transportName();
        if(transportName.isEmpty())
        {
            qWarning() << "Could not get transport name for mapi store";
            return false;
        }
        */
    }
    return true;
}

bool QMessageServicePrivate::synchronize(const QMessageAccountId& id)
{
    if(!id.isValid())
    {
        qWarning() << "Cannot synchronize invalid QMessageAccountId";
        _error = QMessageManager::InvalidId;
        return false;
    }

    QMessageAccount account(id);
    if(FAILED(MailSyncMessages(LptstrFromQString(account.name()),MCF_ACCOUNT_IS_NAME | MCF_RUN_IN_BACKGROUND)))
    {
        qWarning() << "MailSyncMessages failed for account: " << account.name();
        _error = QMessageManager::FrameworkFault;
        return false;
    }
    return true;
}

bool QMessageServicePrivate::registerUpdates(const QMessageId& id)
{
    if(!id.isValid())
    {
        qWarning() << "Cannot register for update notifications on invalid QMessageId";
        _error = QMessageManager::InvalidId;
        return false;
    }

    if(!m_registeredUpdates)
    {
        connect(&_manager, SIGNAL(messageUpdated(const QMessageId&, const QMessageManager::NotificationFilterIdSet&)),this,SLOT(messageUpdated(const QMessageId&)));
        m_bodyDownloadFilterId = _manager.registerNotificationFilter(QMessageFilter::byId(id));
        m_bodyDownloadTarget = id;
        m_registeredUpdates = true;
    }
    return m_registeredUpdates;
}

void QMessageServicePrivate::unregisterUpdates()
{
    disconnect(&_manager, SIGNAL(messageUpdated(const QMessageId&, const QMessageManager::NotificationFilterIdSet&)),this,SLOT(messageUpdated(const QMessageId&)));
    _manager.unregisterNotificationFilter(m_bodyDownloadFilterId);
    m_bodyDownloadFilterId = 0;
    m_registeredUpdates = false;
}

bool QMessageServicePrivate::retrieveBody(const QMessage& partialMessage)
{
    if(partialMessage.type() != QMessage::Email)
    {
        qWarning() << "Cannot retrieve body for non-email message type";
        _error = QMessageManager::ConstraintFailure;
        return false;
    }

    if(isPartiallyDownloaded(partialMessage.id()))
    {
        //only valid for Email
        if(markForDownload(partialMessage.id(),true))
            if(registerUpdates(partialMessage.id()))
                if(!synchronize(partialMessage.parentAccountId()))
                    unregisterUpdates();
    }
    else QTimer::singleShot(0,this,SLOT(completed()));

    return (_error == QMessageManager::NoError);
}

#endif

void QMessageServicePrivate::setFinished(bool successful)
{
    if (!successful && (_error == QMessageManager::NoError)) {
        _error = QMessageManager::RequestIncomplete;
    }

    completed();
}

QMessageService::QMessageService(QObject *parent)
    : QObject(parent),
    d_ptr(new QMessageServicePrivate(this))
{
    connect(d_ptr, SIGNAL(stateChanged(QMessageService::State)),
        this, SIGNAL(stateChanged(QMessageService::State)));
    connect(d_ptr, SIGNAL(messagesFound(const QMessageIdList&)),
        this, SIGNAL(messagesFound(const QMessageIdList&)));
    connect(d_ptr, SIGNAL(messagesCounted(int)),
        this, SIGNAL(messagesCounted(int)));
    connect(d_ptr, SIGNAL(progressChanged(uint, uint)),
        this, SIGNAL(progressChanged(uint, uint)));
}

QMessageService::~QMessageService()
{
    delete d_ptr;
    d_ptr = 0;
}

bool QMessageService::queryMessages(const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset)
{
    return queryMessages(filter, QString(), QMessageDataComparator::MatchFlags(), sortOrder, limit, offset);
}

bool QMessageService::queryMessages(const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset)
{
    if (d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }


    d_ptr->_active = true;
    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_state = QMessageService::ActiveState;
    emit stateChanged(d_ptr->_state);

#if 0
    d_ptr->_candidateIds = d_ptr->_manager.queryMessages(filter, body, matchFlags, sortOrder, limit, offset);
    d_ptr->_error = d_ptr->_manager.error();
    QTimer::singleShot(0,d_ptr,SLOT(reportMatchingIds()));
#else
    // Perform the query in another thread to keep the UI thread free
    QueryThread *query = new QueryThread(d_ptr, filter, body, matchFlags, sortOrder, limit, offset);
    connect(query, SIGNAL(completed()), d_ptr, SLOT(reportMatchingIds()), Qt::QueuedConnection);
    query->start();

    if (d_ptr->m_queryThread) {
        // Don't delete the previous thread object immediately
        if (!d_ptr->m_obsoleteThreads.isEmpty()) {
            qDeleteAll(d_ptr->m_obsoleteThreads);
            d_ptr->m_obsoleteThreads.clear();
        }
        d_ptr->m_obsoleteThreads.append(d_ptr->m_queryThread);
    }
    d_ptr->m_queryThread = query;
#endif
    return true;
}

bool QMessageService::countMessages(const QMessageFilter &filter)
{
    if (d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }
    d_ptr->_active = true;
    d_ptr->_error = QMessageManager::NoError;
    d_ptr->_state = QMessageService::ActiveState;
    emit stateChanged(d_ptr->_state);

    // Perform the query in another thread to keep the UI thread free
    QueryThread *query = new QueryThread(d_ptr, filter, QString(), QMessageDataComparator::MatchFlags(), QMessageSortOrder(), 0, 0);
    connect(query, SIGNAL(completed()), d_ptr, SLOT(reportMessagesCounted()), Qt::QueuedConnection);
    query->start();

    if (d_ptr->m_queryThread) {
        // Don't delete the previous thread object immediately
        if (!d_ptr->m_obsoleteThreads.isEmpty()) {
            qDeleteAll(d_ptr->m_obsoleteThreads);
        }
        d_ptr->m_obsoleteThreads.append(d_ptr->m_queryThread);
    }
    d_ptr->m_queryThread = query;

    return true;
}

bool QMessageService::send(QMessage &message)
{
    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

    d_ptr->_active = true;
    d_ptr->_state = QMessageService::ActiveState;
    d_ptr->_error = QMessageManager::NoError;
    emit stateChanged(d_ptr->_state);

    bool result = d_ptr->send(message);
    d_ptr->setFinished(result);

    return result;
}

bool QMessageService::compose(const QMessage &message)
{
    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

    d_ptr->_active = true;
    d_ptr->_state = QMessageService::ActiveState;
    d_ptr->_error = QMessageManager::NoError;
    emit stateChanged(d_ptr->_state);

    bool result = d_ptr->send(message,true);
    d_ptr->setFinished(result);

    return result;
}

bool QMessageService::retrieveHeader(const QMessageId& id)
{
    Q_UNUSED(id);

    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

	if(!id.isValid())
	{
		qWarning() << "Invalid QMessageId";
		d_ptr->_error = QMessageManager::InvalidId;
		d_ptr->setFinished(true);
		return false;
	}

    d_ptr->_error = QMessageManager::NoError;
    d_ptr->setFinished(true);

    return true;
}

bool QMessageService::retrieveBody(const QMessageId& id)
{
    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

#ifdef _WIN32_WCE

    d_ptr->_active = true;
    d_ptr->_state = ActiveState;
    d_ptr->_error = QMessageManager::NoError;
    emit stateChanged(d_ptr->_state);

    if(!id.isValid())
    {
        qWarning() << "Invalid QMessageId";
        d_ptr->_error = QMessageManager::InvalidId;
    }

    QMessage message;

    if(d_ptr->_error == QMessageManager::NoError)
    {
        message = QMessage(id);
        d_ptr->_error = QMessageManager().error();
    }


    if(d_ptr->_error == QMessageManager::NoError)
        d_ptr->retrieveBody(message);

    //emit failure immediately
    if (d_ptr->_error != QMessageManager::NoError) {
        d_ptr->setFinished(false);
        return false;
    }

    return true;

#else
    Q_UNUSED(id);

    d_ptr->_error = QMessageManager::NotYetImplemented;
    d_ptr->setFinished(false);
    return false;
#endif
}

bool QMessageService::retrieve(const QMessageId& messageId, const QMessageContentContainerId& id)
{

    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

#ifdef _WIN32_WCE

    d_ptr->_active = true;
    d_ptr->_state = ActiveState;
    d_ptr->_error = QMessageManager::NoError;
    emit stateChanged(d_ptr->_state);

    if(!messageId.isValid())
    {
        qWarning() << "Invalid QMessageId";
        d_ptr->_error = QMessageManager::InvalidId;
    }

    QMessage message;

    if(d_ptr->_error == QMessageManager::NoError)
    {
        message = QMessage(messageId);
        d_ptr->_error = d_ptr->_manager.error();
    }

    if(d_ptr->_error == QMessageManager::NoError)
    {
        bool isBodyContainer = message.bodyId() == id;
        if(isBodyContainer)
            d_ptr->retrieveBody(message);
        //TODO downloading attachment programatically possible?
    }

    //emit failure immediately
    if (d_ptr->_error != QMessageManager::NoError) {
        d_ptr->setFinished(false);
        return false;
    }

    return true;

#else
    Q_UNUSED(messageId)
    Q_UNUSED(id)

    d_ptr->_error = QMessageManager::NotYetImplemented;
    d_ptr->setFinished(false);
#endif

    return false;
}

bool QMessageService::show(const QMessageId& id)
{
    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

    d_ptr->_active = true;
    d_ptr->_state = ActiveState;
    d_ptr->_error = QMessageManager::NoError;
    emit stateChanged(d_ptr->_state);

    bool result = d_ptr->show(id);
    d_ptr->setFinished(result);

    return result;
}

bool QMessageService::exportUpdates(const QMessageAccountId &id)
{
    Q_UNUSED(id);

    if(d_ptr->_active) {
        qWarning() << "Service is currently busy";
        return false;
    }

    d_ptr->_error = QMessageManager::NotYetImplemented;
    d_ptr->setFinished(false);

    return false;
}

QMessageService::State QMessageService::state() const
{
    return d_ptr->_state;
}

void QMessageService::cancel()
{
#ifdef _WIN32_WCE
    if(d_ptr->_active)
    {
        bool awaitingBodyRetrieval(d_ptr->m_bodyDownloadFilterId != 0);

        if(awaitingBodyRetrieval)
        {
            d_ptr->unregisterUpdates();
            d_ptr->_error = QMessageManager::NoError;
            d_ptr->_state = QMessageService::InactiveState;
            d_ptr->_active = false;
            emit stateChanged(d_ptr->_state);
        }
    }
#else
    //NOOP
#endif
}

QMessageManager::Error QMessageService::error() const
{
    return d_ptr->_error;
}

#include <qmessageservice_win.moc>

QTM_END_NAMESPACE