qtmobility/src/messaging/winhelpers.cpp
changeset 1 2b40d63a9c3d
child 4 90517678cc4f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qtmobility/src/messaging/winhelpers.cpp	Fri Apr 16 15:51:22 2010 +0300
@@ -0,0 +1,5181 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef _UNICODE
+#define _UNICODE
+#endif
+
+#ifndef MDB_ONLINE
+#define MDB_ONLINE ((ULONG) 0x00000100)
+#endif
+
+#ifndef DELETE_HARD_DELETE
+#define DELETE_HARD_DELETE ((ULONG) 0x00000010)
+#endif
+
+#ifndef STORE_HTML_OK
+#define STORE_HTML_OK ((ULONG)0x00010000)
+#endif
+
+#ifndef PR_IS_NEWSGROUP
+#define PR_IS_NEWSGROUP PROP_TAG( PT_BOOLEAN, 0x6697 )
+#endif
+
+#ifndef PR_IS_NEWSGROUP_ANCHOR
+#define PR_IS_NEWSGROUP_ANCHOR PROP_TAG( PT_BOOLEAN, 0x6696 )
+#endif
+
+#ifndef PR_EXTENDED_FOLDER_FLAGS
+#define PR_EXTENDED_FOLDER_FLAGS PROP_TAG( PT_BINARY, 0x36DA )
+#endif
+
+#include "winhelpers_p.h"
+#include "qmessageid_p.h"
+#include "qmessagefolderid_p.h"
+#include "qmessageaccountid_p.h"
+#include "qmessage_p.h"
+#include "qmessagecontentcontainer_p.h"
+#include "qmessagefilter_p.h"
+#include "qmessagesortorder_p.h"
+#include "qmessagefolder_p.h"
+#include "qmessagefolderfilter_p.h"
+#include "qmessagefoldersortorder_p.h"
+#include "qmessageaccount_p.h"
+#include "qmessageaccountfilter_p.h"
+#include "qmessageaccountsortorder_p.h"
+#include "qmessagestore_p.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QFile>
+#include <QTextCodec>
+#include <QThreadStorage>
+#include <QTimer>
+#include <QMutexLocker>
+
+
+#include <shlwapi.h>
+#include <shlguid.h>
+#include <tchar.h>
+
+#ifdef _WIN32_WCE
+#include "win32wce/qmailmessage.h"
+#include <cemapi.h>
+#endif
+
+//unexported before Windows 7, include manually
+#ifndef IID_PPV_ARGS
+extern "C++"
+{
+    template<typename T> void** IID_PPV_ARGS_Helper(T** pp)
+    {
+        // make sure everyone derives from IUnknown
+        static_cast<IUnknown*>(*pp);
+
+        return reinterpret_cast<void**>(pp);
+    }
+}
+#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)
+#endif //IID_PPV_ARGSA
+
+QTM_BEGIN_NAMESPACE
+
+namespace WinHelpers
+{
+    bool setMapiProperty(IMAPIProp *object, ULONG tag, const QString &value)
+    {
+        SPropValue prop = { 0 };
+        prop.ulPropTag = tag;
+        prop.Value.LPSZ = reinterpret_cast<LPWSTR>(const_cast<quint16*>(value.utf16()));
+        return HR_SUCCEEDED(HrSetOneProp(object, &prop));
+    }
+
+    bool setMapiProperty(IMAPIProp *object, ULONG tag, LONG value)
+    {
+        SPropValue prop = { 0 };
+        prop.ulPropTag = tag;
+        prop.Value.l = value;
+        return HR_SUCCEEDED(HrSetOneProp(object, &prop));
+    }
+
+    bool setMapiProperty(IMAPIProp *object, ULONG tag, ULONG value)
+    {
+        SPropValue prop = { 0 };
+        prop.ulPropTag = tag;
+        prop.Value.ul = value;
+        return HR_SUCCEEDED(HrSetOneProp(object, &prop));
+    }
+
+    bool setMapiProperty(IMAPIProp *object, ULONG tag, bool value)
+    {
+        SPropValue prop = { 0 };
+        prop.ulPropTag = tag;
+        prop.Value.b = value;
+        return HR_SUCCEEDED(HrSetOneProp(object, &prop));
+    }
+
+    bool setMapiProperty(IMAPIProp *object, ULONG tag, FILETIME value)
+    {
+        SPropValue prop = { 0 };
+        prop.ulPropTag = tag;
+        prop.Value.ft = value;
+        return HR_SUCCEEDED(HrSetOneProp(object, &prop));
+    }
+
+    bool setMapiProperty(IMAPIProp *object, ULONG tag, MapiEntryId value)
+    {
+        SBinary s;
+        s.cb = value.count();
+        s.lpb = reinterpret_cast<LPBYTE>(value.data());
+        SPropValue prop = { 0 };
+        prop.ulPropTag = tag;
+        prop.Value.bin = s;
+        return HR_SUCCEEDED(HrSetOneProp(object, &prop));
+    }
+
+    bool getMapiProperty(IMAPIProp *object, ULONG tag, ULONG *value)
+    {
+        bool result(false);
+
+        SPropValue *prop;
+        HRESULT rv = HrGetOneProp(object, tag, &prop);
+        if (HR_SUCCEEDED(rv)) {
+            if (prop->ulPropTag == tag) {
+                *value = prop->Value.ul;
+                result = true;
+            }
+
+            MAPIFreeBuffer(prop);
+        }
+
+        return result;
+    }
+
+    bool getMapiProperty(IMAPIProp *object, ULONG tag, LONG *value)
+    {
+        bool result(false);
+
+        SPropValue *prop;
+        HRESULT rv = HrGetOneProp(object, tag, &prop);
+        if (HR_SUCCEEDED(rv)) {
+            if (prop->ulPropTag == tag) {
+                *value = prop->Value.l;
+                result = true;
+            }
+
+            MAPIFreeBuffer(prop);
+        }
+
+        return result;
+    }
+
+    bool getMapiProperty(IMAPIProp *object, ULONG tag, QByteArray *value)
+    {
+        bool result(false);
+
+        SPropValue *prop;
+        HRESULT rv = HrGetOneProp(object, tag, &prop);
+        if (HR_SUCCEEDED(rv)) {
+            if (prop->ulPropTag == tag) {
+                *value = QByteArray(reinterpret_cast<const char*>(prop->Value.bin.lpb), prop->Value.bin.cb);
+                result = true;
+            }
+
+            MAPIFreeBuffer(prop);
+        }
+
+        return result;
+    }
+
+    bool getMapiProperty(IMAPIProp *object, ULONG tag, QString *value)
+    {
+        bool result(false);
+
+        SPropValue *prop;
+        HRESULT rv = HrGetOneProp(object, tag, &prop);
+        if (HR_SUCCEEDED(rv)) {
+            if (prop->ulPropTag == tag) {
+                *value = QStringFromLpctstr(prop->Value.lpszW);
+                result = true;
+            }
+            MAPIFreeBuffer(prop);
+        }
+
+        return result;
+    }
+}
+
+using namespace WinHelpers;
+
+namespace {
+
+    typedef QWeakPointer<WinHelpers::MapiInitializer> InitRecord;
+    typedef InitRecord *InitRecordPtr;
+
+    QThreadStorage<InitRecordPtr> initializer;
+
+    GUID GuidPublicStrings = { 0x00020329, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
+
+    FILETIME toFileTime(const QDateTime &dt)
+    {
+        FILETIME ft = {0};
+
+        QDate date(dt.date());
+        QTime time(dt.time());
+
+        SYSTEMTIME st = {0};
+        st.wYear = date.year();
+        st.wMonth = date.month();
+        st.wDay = date.day();
+        st.wHour = time.hour();
+        st.wMinute = time.minute();
+        st.wSecond = time.second();
+        st.wMilliseconds = time.msec();
+
+        SystemTimeToFileTime(&st, &ft);
+        return ft;
+    }
+
+    QDateTime fromFileTime(const FILETIME &ft)
+    {
+        SYSTEMTIME st = {0};
+        FileTimeToSystemTime(&ft, &st);
+        QString dateStr(QString("yyyy%1M%2d%3h%4m%5s%6z%7").arg(st.wYear).arg(st.wMonth).arg(st.wDay).arg(st.wHour).arg(st.wMinute).arg(st.wSecond).arg(st.wMilliseconds));
+        QDateTime dt(QDateTime::fromString(dateStr, "'yyyy'yyyy'M'M'd'd'h'h'm'm's's'z'z"));
+        dt.setTimeSpec(Qt::UTC);
+        return dt;
+    }
+
+    struct AccountFilterPredicate
+    {
+        const QMessageAccountFilter &_filter;
+
+        AccountFilterPredicate(const QMessageAccountFilter &filter) : _filter(filter) {}
+
+        bool operator()(const MapiStorePtr &store) const
+        {
+            return QMessageAccountFilterPrivate::matchesStore(_filter, store);
+        }
+    };
+
+    //used in preference to HrQueryAllRows
+    //as per: http://blogs.msdn.com/stephen_griffin/archive/2009/03/23/try-not-to-query-all-rows.aspx
+
+    class QueryAllRows
+    {
+        static const int BatchSize = 20;
+        public:
+        QueryAllRows(LPMAPITABLE ptable,
+                LPSPropTagArray ptaga,
+                LPSRestriction pres,
+                LPSSortOrderSet psos,
+                bool setPosition = true);
+        ~QueryAllRows();
+
+        LONG rowCount();
+        bool query();
+        LPSRowSet rows() const;
+        QMessageManager::Error error() const;
+
+        private:
+        LPMAPITABLE m_table;
+        LPSPropTagArray m_tagArray;
+        LPSRestriction m_restriction;
+        LPSSortOrderSet m_sortOrderSet;
+        LPSRowSet m_rows;
+        QMessageManager::Error m_error;
+    };
+
+    QueryAllRows::QueryAllRows(LPMAPITABLE ptable,
+                               LPSPropTagArray ptaga,
+                               LPSRestriction pres,
+                               LPSSortOrderSet psos,
+                               bool setPosition)
+    :
+        m_table(ptable),
+        m_tagArray(ptaga),
+        m_restriction(pres),
+        m_sortOrderSet(psos),
+        m_rows(0),
+        m_error(QMessageManager::NoError)
+    {
+        bool initFailed = false;
+
+#ifndef _WIN32_WCE
+        unsigned long flags = TBL_BATCH;
+#else
+        unsigned long flags = 0;
+#endif
+
+        if(m_tagArray)
+            initFailed |= FAILED(m_table->SetColumns(m_tagArray, flags));
+
+        if(m_restriction)
+            initFailed |= FAILED(m_table->Restrict(m_restriction, flags));
+
+        if(m_sortOrderSet)
+            initFailed |= FAILED(m_table->SortTable(m_sortOrderSet, flags));
+
+        if(setPosition) {
+            if(initFailed |= FAILED(m_table->SeekRow(BOOKMARK_BEGINNING,0, NULL)))
+                qWarning() << "SeekRow function failed. Ensure it's not being called on hierarchy tables or message stores tables";
+        }
+
+        if(initFailed)
+            m_error = QMessageManager::ContentInaccessible;
+    }
+
+    QueryAllRows::~QueryAllRows()
+    {
+        FreeProws(m_rows);
+        m_rows = 0;
+    }
+
+    LONG QueryAllRows::rowCount()
+    {
+        if (m_error != QMessageManager::NoError)
+            return -1;
+
+        ULONG count(0);
+        HRESULT rv = m_table->GetRowCount(0, &count);
+        if (HR_FAILED(rv)) {
+            m_error = QMessageManager::ContentInaccessible;
+            return -1;
+        }
+
+        return count;
+    }
+
+    bool QueryAllRows::query()
+    {
+        if (m_error != QMessageManager::NoError)
+            return false;
+
+        FreeProws(m_rows);
+        m_rows = 0;
+        m_error = QMessageManager::NoError;
+
+        HRESULT rv = m_table->QueryRows( QueryAllRows::BatchSize, NULL, &m_rows);
+        if (HR_FAILED(rv)) {
+            m_error = QMessageManager::ContentInaccessible;
+            return false;
+        }
+
+        return (m_rows && m_rows->cRows);
+    }
+
+    LPSRowSet QueryAllRows::rows() const
+    {
+        return m_rows;
+    }
+
+    QMessageManager::Error QueryAllRows::error() const
+    {
+        return m_error;
+    }
+
+    ADRLIST *createAddressList(int count, int propertyCount)
+    {
+        ADRLIST *list(0);
+
+        uint size = CbNewADRLIST(count);
+        MAPIAllocateBuffer(size, reinterpret_cast<LPVOID*>(&list));
+        if (list) {
+            memset(list, 0, size);
+            list->cEntries = count;
+
+            for (int i = 0; i < count; ++i) {
+                list->aEntries[i].cValues = propertyCount;
+                MAPIAllocateBuffer(propertyCount * sizeof(SPropValue), reinterpret_cast<LPVOID*>(&list->aEntries[i].rgPropVals));
+            }
+        }
+
+        return list;
+    }
+
+    void fillAddressEntry(ADRENTRY &entry, const QMessageAddress &addr, LONG type, QList<LPTSTR> &addresses)
+    {
+        entry.rgPropVals[0].ulPropTag = PR_RECIPIENT_TYPE;
+        entry.rgPropVals[0].Value.l = type;
+
+#ifdef _WIN32_WCE
+        QString addressStr = addr.recipient();
+#else
+        QString addressStr("[%1:%2]");
+        addressStr = addressStr.arg(addr.type() == QMessageAddress::Phone ? "SMS" : "SMTP");
+        addressStr = addressStr.arg(addr.recipient());
+#endif
+
+        // TODO: Escape illegal characters, as per: http://msdn.microsoft.com/en-us/library/cc842281.aspx
+
+        uint len = addressStr.length();
+        LPTSTR address = new TCHAR[len + 1];
+        memcpy(address, addressStr.utf16(), len * sizeof(TCHAR));
+        address[len] = 0;
+
+#ifdef _WIN32_WCE
+
+        entry.rgPropVals[1].ulPropTag = PR_EMAIL_ADDRESS;
+        entry.rgPropVals[1].Value.LPSZ = address;
+
+        if(addr.type() == QMessageAddress::Email)
+        {
+            //Set the address type(SMTP is the only type currently supported)
+            entry.rgPropVals[2].ulPropTag = PR_ADDRTYPE;
+            entry.rgPropVals[2].Value.LPSZ = L"SMTP";
+        }
+        else if(addr.type() == QMessageAddress::Phone)
+        {
+            //Set the address type(SMTP is the only type currently supported)
+            entry.rgPropVals[2].ulPropTag = PR_ADDRTYPE;
+            entry.rgPropVals[2].Value.LPSZ = L"SMS";
+        }
+        else
+            qWarning() << "Unrecognized address type";
+
+#else
+        entry.rgPropVals[1].ulPropTag = PR_DISPLAY_NAME;
+        entry.rgPropVals[1].Value.LPSZ = address;
+#endif
+
+        addresses.append(address);
+    }
+
+    bool resolveAddressList(ADRLIST *list, IMAPISession *session)
+    {
+        bool result(false);
+
+        if (session) {
+            IAddrBook *book(0);
+            HRESULT rv = session->OpenAddressBook(0, 0, AB_NO_DIALOG, &book);
+            if (HR_SUCCEEDED(rv)) {
+                rv = book->ResolveName(0, MAPI_UNICODE, 0, list);
+                if (HR_SUCCEEDED(rv)) {
+                    result = true;
+                } else {
+                    qWarning() << "Unable to resolve addresses.";
+                }
+
+                book->Release();
+            } else {
+                qWarning() << "Unable to open address book.";
+            }
+        }
+
+        return result;
+    }
+
+    void destroyAddressList(ADRLIST *list, QList<LPTSTR> &addresses)
+    {
+        foreach (LPTSTR address, addresses) {
+            delete [] address;
+        }
+
+        addresses.clear();
+
+        for (uint i = 0; i < list->cEntries; ++i) {
+            MAPIFreeBuffer(list->aEntries[i].rgPropVals);
+        }
+
+        MAPIFreeBuffer(list);
+    }
+
+    ULONG addAttachment(IMessage* message, const QMessageContentContainer& attachmentContainer)
+    {
+        IAttach *attachment(0);
+        ULONG attachmentNumber(0);
+
+        if (HR_SUCCEEDED(message->CreateAttach(NULL, 0, &attachmentNumber, &attachment))) {
+            QString fileName(attachmentContainer.suggestedFileName());
+            QString extension;
+            int index = fileName.lastIndexOf(".");
+            if (index != -1) {
+                extension = fileName.mid(index);
+            }
+
+            WinHelpers::Lptstr suggestedFileNameLptstr(WinHelpers::LptstrFromQString(fileName));
+            WinHelpers::Lptstr extensionLptstr(WinHelpers::LptstrFromQString(extension));
+
+            const int nProperties = 5;
+            SPropValue prop[nProperties] = { 0 };
+
+            prop[0].ulPropTag = PR_ATTACH_METHOD;
+            prop[0].Value.ul = ATTACH_BY_VALUE;
+            prop[1].ulPropTag = PR_RENDERING_POSITION;
+            prop[1].Value.l = -1;
+            prop[2].ulPropTag = PR_ATTACH_LONG_FILENAME;
+            prop[2].Value.LPSZ = suggestedFileNameLptstr;
+            prop[3].ulPropTag = PR_ATTACH_FILENAME;
+            prop[3].Value.LPSZ = suggestedFileNameLptstr;
+            prop[4].ulPropTag = PR_ATTACH_EXTENSION;
+            prop[4].Value.LPSZ = extensionLptstr;
+
+            if (HR_SUCCEEDED(attachment->SetProps(nProperties, prop, NULL))) {
+                IStream *os(0);
+                if (HR_SUCCEEDED(attachment->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN*)&os))) {
+                    const int BUF_SIZE=4096;
+                    char pData[BUF_SIZE];
+                    ULONG ulSize=0,ulRead,ulWritten;
+
+                    QDataStream attachmentStream(attachmentContainer.content());
+
+                    ulRead=attachmentStream.readRawData(static_cast<char*>(pData), BUF_SIZE);
+                    while (ulRead) {
+                        os->Write(pData,ulRead, &ulWritten);
+
+                        ulSize += ulRead;
+                        ulRead = attachmentStream.readRawData(static_cast<char*>(pData), BUF_SIZE);
+                    }
+
+                    ULARGE_INTEGER uli = { 0 };
+                    uli.LowPart = ulWritten;
+                    os->SetSize(uli);
+
+                    os->Commit(STGC_DEFAULT);
+
+                    mapiRelease(os);
+
+                    prop[0].ulPropTag=PR_ATTACH_SIZE;
+                    prop[0].Value.ul=ulSize;
+                    attachment->SetProps(1, prop, NULL);
+#ifndef _WIN32_WCE //unsupported
+                    attachment->SaveChanges(KEEP_OPEN_READONLY);
+#endif
+                } else {
+                    qWarning() << "Could not open MAPI attachment data stream";
+                }
+            } else {
+                qWarning() << "Could not set MAPI attachment properties";
+            }
+
+            mapiRelease(attachment);
+        } else {
+            qWarning() << "Could not create MAPI attachment";
+        }
+
+        return attachmentNumber;
+    }
+
+    template<typename T> QString getLastError(T& mapiType, HRESULT hr)
+    {
+        LPMAPIERROR err;
+
+        HRESULT thisResult = mapiType.GetLastError(hr,MAPI_UNICODE,&err);
+
+        if(thisResult != S_OK || !err)
+        {
+            qWarning() << "Could not get last MAPI error string";
+            return QString();
+        }
+
+        QString mapiErrorMsg = QStringFromLpctstr(err->lpszError);
+        QString mapiComponent = QStringFromLpctstr(err->lpszComponent);
+
+        QString result = QString("MAPI Error: %1 ; MAPI Component: %2").arg(mapiErrorMsg).arg(mapiComponent);
+
+        MAPIFreeBuffer(err);
+
+        return result;
+    }
+
+    typedef QPair<QString, QString> StringPair;
+
+    QList<StringPair> decomposeHeaders(const QString &headers)
+    {
+        QList<StringPair> result;
+
+        if (!headers.isEmpty()) {
+            int lastIndex = 0;
+            int index = -2;
+
+            do {
+                index += 2;
+                lastIndex = index;
+
+                // Find CRLF not followed by whitespace
+                QRegExp lineSeparator("\r\n(?!\\s)");
+                index = headers.indexOf(lineSeparator, lastIndex);
+
+                QString line = headers.mid(lastIndex, (index == -1 ? -1 : (index - lastIndex)));
+
+                // Split the current line
+                QRegExp headerIdentifier("\\s*(\\w+)\\s*:");
+                if (line.indexOf(headerIdentifier) == 0) {
+                    result.append(qMakePair(headerIdentifier.cap(1), line.mid(headerIdentifier.cap(0).length()).trimmed()));
+                } else {
+                    // Assume the whole line is an identifier
+                    result.append(qMakePair(line, QString()));
+                }
+            } while (index != -1);
+        }
+
+        return result;
+    }
+
+    QMessageAddress createAddress(const QString &name, const QString &address)
+    {
+        QMessageAddress result;
+
+        if (!name.isEmpty() || !address.isEmpty()) {
+            QString from;
+            if (!name.isEmpty() && !address.isEmpty() && (name != address)) {
+                from = name + " <" + address + ">";
+            } else {
+                from = (!name.isEmpty() ? name : address);
+            }
+
+            result = QMessageAddress(QMessageAddress::Email, from);
+        }
+
+        return result;
+    }
+
+    ULONG streamSize(QMessageManager::Error* error, IStream* is)
+    {
+        ULONG size = 0;
+        STATSTG stg = { 0 };
+        HRESULT rv = is->Stat(&stg, STATFLAG_NONAME);
+        if (HR_SUCCEEDED(rv)) {
+            size = stg.cbSize.LowPart;
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+        }
+        return size;
+    }
+
+    QByteArray readStream(QMessageManager::Error *error, IStream *is)
+    {
+        QByteArray result;
+
+        STATSTG stg = { 0 };
+        HRESULT rv = is->Stat(&stg, STATFLAG_NONAME);
+        if (HR_SUCCEEDED(rv)) {
+            ULONG sz = stg.cbSize.LowPart;
+            result.resize(sz);
+            ULONG bytes = 0;
+            rv = is->Read(result.data(), sz, &bytes);
+            if (HR_FAILED(rv)) {
+                *error = QMessageManager::ContentInaccessible;
+            }
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+        }
+
+        return result;
+    }
+
+    bool writeStream(QMessageManager::Error *error, IStream *os, const void *address, ULONG bytes, bool truncate)
+    {
+        ULONG existingSize = truncate ? streamSize(error, os) : 0;
+        if (*error == QMessageManager::NoError) {
+            HRESULT rv = S_OK;
+            if (existingSize > bytes) {
+                ULARGE_INTEGER uli = { 0 };
+                uli.LowPart = bytes;
+                rv = os->SetSize(uli);
+            }
+
+            if (HR_SUCCEEDED(rv)) {
+                ULONG written(0);
+                rv = os->Write(address, bytes, &written);
+                if (HR_SUCCEEDED(rv)) {
+                    if (written < bytes) {
+                        qWarning() << "Only wrote partial data to output stream.";
+                    } else {
+                        rv = os->Commit(STGC_DEFAULT);
+                        if (HR_SUCCEEDED(rv)) {
+                            return true;
+                        } else {
+                            qWarning() << "Unable to commit write to output stream.";
+                        }
+                    }
+                } else {
+                    qWarning() << "Unable to write data to output stream.";
+                }
+            } else {
+                qWarning() << "Unable to resize output stream.";
+            }
+        }
+
+        *error = QMessageManager::FrameworkFault;
+        return false;
+    }
+
+    QString decodeContent(const QByteArray &data, const QByteArray &charset, int length = -1)
+    {
+        QString result;
+
+        QTextCodec *codec = QTextCodec::codecForName(charset);
+        if (codec) {
+            if (length == -1) {
+                // Convert the entirety
+                result = codec->toUnicode(data);
+            } else {
+                // Convert only the first length bytes
+                QTextCodec::ConverterState state;
+                result = codec->toUnicode(data, length, &state);
+            }
+        } else {
+            qWarning() << "No codec for charset:" << charset;
+        }
+
+        return result;
+    }
+
+#ifndef _WIN32_WCE
+    QByteArray extractPlainText(const QByteArray &rtf)
+    {
+        // Attempt to extract the HTML from the RTF
+        // as per CMapiEx, http://www.codeproject.com/KB/IP/CMapiEx.aspx
+        QByteArray text;
+
+        const QByteArray startTag("\\fs20");
+        int index = rtf.indexOf(startTag);
+        if (index != -1) {
+            const QByteArray par("\\par");
+            const QByteArray tab("\\tab");
+            const QByteArray li("\\li");
+            const QByteArray fi("\\fi-");
+            const QByteArray pntext("\\pntext");
+
+            const char zero = '\0';
+            const char space = ' ';
+            const char openingBrace = '{';
+            const char closingBrace = '}';
+            const char ignore[] = { openingBrace, closingBrace, '\r', '\n' };
+
+            QByteArray::const_iterator rit = rtf.constBegin() + index, rend = rtf.constEnd();
+            while ((rit != rend) && (*rit != zero)) {
+                if (*rit == ignore[0] || *rit == ignore[1] || *rit == ignore[2] || *rit == ignore[3]) {
+                    ++rit;
+                } else {
+                    bool skipSection(false);
+                    bool skipDigits(false);
+                    bool skipSpace(false);
+
+                    const QByteArray remainder(QByteArray::fromRawData(rit, (rend - rit)));
+
+                    if (remainder.startsWith(par)) {
+                        rit += par.length();
+                        text += "\r\n";
+                        skipSpace = true;
+                    } else if (remainder.startsWith(tab)) {
+                        rit += tab.length();
+                        text += "\t";
+                        skipSpace = true;
+                    } else if (remainder.startsWith(li)) {
+                        rit += li.length();
+                        skipDigits = true;
+                        skipSpace = true;
+                    } else if (remainder.startsWith(fi)) {
+                        rit += fi.length();
+                        skipDigits = true;
+                        skipSpace = true;
+                    } else if (remainder.startsWith(QByteArray("\\'"))) {
+                        rit += 2;
+                        QByteArray encodedChar(QByteArray::fromRawData(rit, 2));
+                        rit += 2;
+                        text += char(encodedChar.toUInt(0, 16));
+                    } else if (remainder.startsWith(pntext)) {
+                        rit += pntext.length();
+                        skipSection = true;
+                    } else if (remainder.startsWith(QByteArray("\\{"))) {
+                        rit += 2;
+                        text += "{";
+                    } else if (remainder.startsWith(QByteArray("\\}"))) {
+                        rit += 2;
+                        text += "}";
+                    } else {
+                        text += *rit;
+                        ++rit;
+                    }
+
+                    if (skipSection) {
+                        while ((rit != rend) && (*rit != closingBrace)) {
+                            ++rit;
+                        }
+                    }
+                    if (skipDigits) {
+                        while ((rit != rend) && isdigit(*rit)) {
+                            ++rit;
+                        }
+                    }
+                    if (skipSpace) {
+                        if ((rit != rend) && (*rit == space)) {
+                            ++rit;
+                        }
+                    }
+                }
+            }
+        }
+
+        return text;
+    }
+
+    int digitValue(char n)
+    {
+        if (n >= '0' && n <= '9') {
+            return (n - '0');
+        }
+
+        return 0;
+    }
+
+    QByteArray extractHtml(const QByteArray &rtf)
+    {
+        // Attempt to extract the HTML from the RTF
+        // as per CMapiEx, http://www.codeproject.com/KB/IP/CMapiEx.aspx
+        QByteArray html;
+
+        const QByteArray htmltag("\\*\\htmltag");
+        int index = rtf.indexOf("<html", Qt::CaseInsensitive);
+        if (index == -1) {
+            index = rtf.indexOf(htmltag, Qt::CaseInsensitive);
+        }
+        if (index != -1) {
+            const QByteArray mhtmltag("\\*\\mhtmltag");
+            const QByteArray par("\\par");
+            const QByteArray tab("\\tab");
+            const QByteArray li("\\li");
+            const QByteArray fi("\\fi-");
+            const QByteArray pntext("\\pntext");
+            const QByteArray htmlrtf("\\htmlrtf");
+
+            const char zero = '\0';
+            const char space = ' ';
+            const char openingBrace = '{';
+            const char closingBrace = '}';
+            const char ignore[] = { openingBrace, closingBrace, '\r', '\n' };
+
+            int tagIgnored = -1;
+
+            QByteArray::const_iterator rit = rtf.constBegin() + index, rend = rtf.constEnd();
+            while ((rit != rend) && (*rit != zero)) {
+                if (*rit == ignore[0] || *rit == ignore[1] || *rit == ignore[2] || *rit == ignore[3]) {
+                    ++rit;
+                } else {
+                    bool skipSection(false);
+                    bool skipDigits(false);
+                    bool skipSpace(false);
+
+                    const QByteArray remainder(QByteArray::fromRawData(rit, (rend - rit)));
+
+                    if (remainder.startsWith(htmltag)) {
+                        rit += htmltag.length();
+
+                        int tagNumber = 0;
+                        while (isdigit(*rit)) {
+                            tagNumber = (tagNumber * 10) + digitValue(*rit);
+                            ++rit;
+                        }
+                        skipSpace = true;
+
+                        if (tagNumber == tagIgnored) {
+                            skipSection = true;
+                            tagIgnored = -1;
+                        }
+                    } else if (remainder.startsWith(mhtmltag)) {
+                        rit += mhtmltag.length();
+
+                        int tagNumber = 0;
+                        while (isdigit(*rit)) {
+                            tagNumber = (tagNumber * 10) + digitValue(*rit);
+                            ++rit;
+                        }
+                        skipSpace = true;
+
+                        tagIgnored = tagNumber;
+                    } else if (remainder.startsWith(par)) {
+                        rit += par.length();
+                        html += char('\r');
+                        html += char('\n');
+                        skipSpace = true;
+                    } else if (remainder.startsWith(tab)) {
+                        rit += tab.length();
+                        html += char('\t');
+                        skipSpace = true;
+                    } else if (remainder.startsWith(li)) {
+                        rit += li.length();
+                        skipDigits = true;
+                        skipSpace = true;
+                    } else if (remainder.startsWith(fi)) {
+                        rit += fi.length();
+                        skipDigits = true;
+                        skipSpace = true;
+                    } else if (remainder.startsWith(QByteArray("\\'"))) {
+                        rit += 2;
+
+                        QByteArray encodedChar(QByteArray::fromRawData(rit, 2));
+                        rit += 2;
+                        html += char(encodedChar.toUInt(0, 16));
+                    } else if (remainder.startsWith(pntext)) {
+                        rit += pntext.length();
+                        skipSection = true;
+                    } else if (remainder.startsWith(htmlrtf)) {
+                        rit += htmlrtf.length();
+
+                        // Find the terminating tag
+                        const QByteArray terminator("\\htmlrtf0");
+                        int index = remainder.indexOf(terminator, htmlrtf.length());
+                        if (index == -1) {
+                            rit = rend;
+                        } else {
+                            rit += (index + terminator.length() - htmlrtf.length());
+                            skipSpace = true;
+                        }
+                    } else if (remainder.startsWith(QByteArray("\\{"))) {
+                        rit += 2;
+                        html += openingBrace;
+                    } else if (remainder.startsWith(QByteArray("\\}"))) {
+                        rit += 2;
+                        html += closingBrace;
+                    } else {
+                        html += *rit;
+                        ++rit;
+                    }
+
+                    if (skipSection) {
+                        while ((rit != rend) && (*rit != closingBrace)) {
+                            ++rit;
+                        }
+                    }
+                    if (skipDigits) {
+                        while ((rit != rend) && isdigit(*rit)) {
+                            ++rit;
+                        }
+                    }
+                    if (skipSpace) {
+                        if ((rit != rend) && (*rit == space)) {
+                            ++rit;
+                        }
+                    }
+                }
+            }
+        }
+
+        return html;
+    }
+#endif
+
+    void storeMessageProperties(QMessageManager::Error *error, const QMessage &source, IMessage *message)
+    {
+        if (!setMapiProperty(message, PR_SUBJECT, source.subject())) {
+            qWarning() << "Unable to set subject in message.";
+            *error = QMessageManager::FrameworkFault;
+        } else {
+            QString emailAddress = source.from().recipient();
+            if (!setMapiProperty(message, PR_SENDER_EMAIL_ADDRESS, emailAddress)) {
+                qWarning() << "Unable to set sender address in message.";
+                *error = QMessageManager::FrameworkFault;
+            } else {
+#ifdef _WIN32_WCE
+                unsigned long msgType  = 0;
+                if(source.type() == QMessage::Email)
+                    msgType = MSGSTATUS_RECTYPE_SMTP;
+                else if(source.type() == QMessage::Sms)
+                    msgType = MSGSTATUS_RECTYPE_SMS;
+                else
+                    qWarning() << "Unrecognized message type";
+
+                if(msgType && !setMapiProperty(message, PR_MSG_STATUS, static_cast<long>(msgType)))
+                {
+                    qWarning() << "Unable to set type of message.";
+                    *error = QMessageManager::FrameworkFault;
+                }
+                else
+                {
+#endif
+                    if (!setMapiProperty(message, PR_CLIENT_SUBMIT_TIME, toFileTime(source.date()))) {
+                        qWarning() << "Unable to set submit time in message.";
+                        *error = QMessageManager::FrameworkFault;
+                    } else {
+                        QDateTime receivedDate(source.receivedDate());
+                        if (receivedDate.isValid()) {
+                            if (!setMapiProperty(message, PR_MESSAGE_DELIVERY_TIME, toFileTime(receivedDate))) {
+                                qWarning() << "Unable to set delivery time in message.";
+                                *error = QMessageManager::FrameworkFault;
+                            }
+                        }
+
+                        if (*error == QMessageManager::NoError) {
+                            // Mark this message as read/unread
+#ifndef _WIN32_WCE
+                            LONG flags = (source.status() & QMessage::Read ? 0 : CLEAR_READ_FLAG);
+                            HRESULT rv = message->SetReadFlag(flags);
+                            if (HR_FAILED(rv)) {
+                                qWarning() << "Unable to set flags in message.";
+                                *error = QMessageManager::FrameworkFault;
+                            }
+#else
+                            LONG flags = (source.status() & QMessage::Read ? MSGFLAG_READ : 0);
+                            if (!setMapiProperty(message, PR_MESSAGE_FLAGS, flags)) {
+                                qWarning() << "Unable to set flags in message.";
+                                *error = QMessageManager::FrameworkFault;
+                            }
+#endif
+                        }
+
+                        if (*error == QMessageManager::NoError) {
+                            LONG priority = (source.priority() == QMessage::HighPriority ? PRIO_URGENT : (source.priority() == QMessage::NormalPriority ? PRIO_NORMAL : PRIO_NONURGENT));
+                            if (!setMapiProperty(message, PR_PRIORITY, priority)) {
+                                qWarning() << "Unable to set priority in message.";
+                                *error = QMessageManager::FrameworkFault;
+                            }
+                        }
+
+                        if (*error == QMessageManager::NoError) {
+                            QStringList headers;
+                            foreach (const QByteArray &name, source.headerFields()) {
+                                foreach (const QString &value, source.headerFieldValues(name)) {
+                                    // TODO: Do we need soft line-breaks?
+                                    headers.append(QString("%1: %2").arg(QString(name)).arg(value));
+                                }
+                            }
+                            if (!headers.isEmpty()) {
+                                QString transportHeaders = headers.join("\r\n").append("\r\n\r\n");
+                                if (!setMapiProperty(message, PR_TRANSPORT_MESSAGE_HEADERS, transportHeaders)) {
+                                    qWarning() << "Unable to set transport headers in message.";
+                                    *error = QMessageManager::FrameworkFault;
+                                }
+                            }
+                        }
+                    }
+                }
+#ifdef _WIN32_WCE
+            }
+#endif
+        }
+    }
+
+    void replaceMessageRecipients(QMessageManager::Error *error, const QMessage &source, IMessage *message, IMAPISession *session)
+    {
+#ifdef _WIN32_WCE
+        Q_UNUSED(session)
+
+        // For CE, the only available option is to replace the existing list
+        HRESULT rv;
+#else
+        // Find any existing recipients and remove them
+        IMAPITable *recipientsTable(0);
+        HRESULT rv = message->GetRecipientTable(MAPI_UNICODE, &recipientsTable);
+        if (HR_SUCCEEDED(rv)) {
+            ULONG count(0);
+            rv = recipientsTable->GetRowCount(0, &count);
+            if (HR_SUCCEEDED(rv)) {
+                if (count > 0) {
+                    ADRLIST *list = createAddressList(count, 1);
+                    if (list) {
+                        int index = 0;
+                        SizedSPropTagArray(1, columns) = {1, {PR_ROWID}};
+
+                        QueryAllRows qar(recipientsTable, reinterpret_cast<LPSPropTagArray>(&columns), 0, 0);
+                        while (qar.query()) {
+                            for (uint n = 0; n < qar.rows()->cRows; ++n) {
+                                ULONG rowId = qar.rows()->aRow[n].lpProps[0].Value.l;
+                                if (rowId) {
+                                    // Add this row's ID to the removal list
+                                    ADRENTRY &entry(list->aEntries[index]);
+                                    entry.rgPropVals[0].ulPropTag = PR_ROWID;
+                                    entry.rgPropVals[0].Value.l = rowId;
+                                    ++index;
+                                }
+                            }
+                        }
+
+                        if (qar.error() != QMessageManager::NoError) {
+                            *error = qar.error();
+                        } else {
+                            rv = message->ModifyRecipients(MODRECIP_REMOVE, list);
+                            if (HR_FAILED(rv)) {
+                                qWarning() << "Unable to clear address list for message.";
+                                *error = QMessageManager::FrameworkFault;
+                            }
+                        }
+
+                        destroyAddressList(list, QList<LPTSTR>());
+                    } else {
+                        qWarning() << "Unable to allocate address list for message.";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+                }
+            } else {
+                qWarning() << "Unable to get row count for recipients table.";
+                *error = QMessageManager::ContentInaccessible;
+            }
+
+            mapiRelease(recipientsTable);
+        } else {
+            if (rv != MAPI_E_NO_RECIPIENTS) {
+                *error = QMessageManager::FrameworkFault;
+                qWarning() << "Unable to get recipients table from message.";
+            }
+        }
+#endif
+
+        if (*error == QMessageManager::NoError) {
+            // Add the current message recipients
+            uint recipientCount(source.to().count() + source.cc().count() + source.bcc().count());
+            if (recipientCount) {
+#ifdef _WIN32_WCE
+                unsigned int propertyCount = 3;
+#else
+                unsigned int propertyCount = 2;
+#endif
+
+                ADRLIST *list = createAddressList(recipientCount, propertyCount);
+                if (list) {
+                    int index = 0;
+                    QList<LPTSTR> addresses;
+
+                    foreach (const QMessageAddress &addr, source.to()) {
+                        ADRENTRY &entry(list->aEntries[index]);
+                        fillAddressEntry(entry, addr, MAPI_TO, addresses);
+                        ++index;
+                    }
+
+                    foreach (const QMessageAddress &addr, source.cc()) {
+                        ADRENTRY &entry(list->aEntries[index]);
+                        fillAddressEntry(entry, addr, MAPI_CC, addresses);
+                        ++index;
+                    }
+
+                    foreach (const QMessageAddress &addr, source.bcc()) {
+                        ADRENTRY &entry(list->aEntries[index]);
+                        fillAddressEntry(entry, addr, MAPI_BCC, addresses);
+                        ++index;
+                    }
+
+#ifndef _WIN32_WCE
+                    if (resolveAddressList(list, session)) {
+#endif
+                        rv = message->ModifyRecipients(MODRECIP_ADD, list);
+                        if (HR_FAILED(rv)) {
+                            qWarning() << "Unable to store address list for message.";
+                            *error = QMessageManager::FrameworkFault;
+                        }
+#ifndef _WIN32_WCE
+                    } else {
+                        qWarning() << "Unable to resolve address list for message.";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+#endif
+
+                    destroyAddressList(list, addresses);
+                } else {
+                    qWarning() << "Unable to allocate address list for message.";
+                    *error = QMessageManager::FrameworkFault;
+                }
+            }
+        }
+    }
+
+    void replaceMessageBody(QMessageManager::Error *error, const QMessage &source, IMessage *message, const MapiStorePtr &store)
+    {
+        Q_UNUSED(store);
+        // Remove any preexisting body elements
+#ifndef _WIN32_WCE
+        SizedSPropTagArray(2, props) = {2, {PR_BODY, PR_RTF_COMPRESSED}};
+#elif(_WIN32_WCE > 0x501)
+        SizedSPropTagArray(2, props) = {2, {PR_BODY_HTML_A, PR_BODY_W}};
+#else
+        SizedSPropTagArray(2, props) = {2, {PR_BODY, PR_BODY_W}};
+#endif
+        HRESULT rv = message->DeleteProps(reinterpret_cast<LPSPropTagArray>(&props), 0);
+#ifdef _WIN32_WCE
+        if (rv == E_FAIL) {
+            // On CE, deleting nonexisting properties fails... assume that they're not present
+            rv = S_OK;
+        }
+#endif
+        if (HR_SUCCEEDED(rv)) {
+            // Insert the current body data
+            QMessageContentContainerId bodyId(source.bodyId());
+            if (bodyId.isValid()) {
+                QMessageContentContainer bodyContent(source.find(bodyId));
+                QByteArray subType(bodyContent.contentSubType().toLower());
+
+#ifdef _WIN32_WCE
+                if (subType == "rtf") {
+                    // CE does not support RTF; just store it as plain text
+                    subType = "plain";
+                } else if (subType == "html") {
+                    // If the store doesn't support HTML then fallback to text
+                    if (!store->supports(STORE_HTML_OK)) {
+                        subType = "plain";
+                        qWarning() << "Store does not support HTML.";
+                    }
+                }
+
+                if (subType == "html") {
+                    IStream *os(0);
+#if(_WIN32_WCE > 0x501)
+                    HRESULT rv = message->OpenProperty(PR_BODY_HTML_A, 0, STGM_WRITE, MAPI_MODIFY | MAPI_CREATE,(LPUNKNOWN*)&os);
+#else
+                    // TODO: If we store the HTML in the body property, how do we know that it is HTML on extraction?
+                    HRESULT rv = message->OpenProperty(PR_BODY, 0, STGM_WRITE, MAPI_MODIFY | MAPI_CREATE,(LPUNKNOWN*)&os);
+#endif
+                    if (HR_SUCCEEDED(rv)) {
+                        QByteArray body(bodyContent.textContent().toLatin1());
+                        writeStream(error, os, body.data(), body.count(), true);
+
+                        mapiRelease(os);
+                    } else {
+                        qWarning() << "Unable to write HTML to body";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+#else
+                if ((subType == "rtf") || (subType == "html")) {
+                    QByteArray body(bodyContent.textContent().toLatin1());
+                    LONG textFormat(EDITOR_FORMAT_RTF);
+                    if (subType == "html") {
+                        // Encode the HTML within RTF
+                        TCHAR codePage[6] = _T("1252");
+                        GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, codePage, sizeof(codePage));
+
+                        QByteArray format("{\\rtf1\\ansi\\ansicpg" + 
+                                          QString::fromUtf16(reinterpret_cast<const quint16*>(codePage)).toLatin1() +
+                      "\\fromhtml1 {\\*\\htmltag1 ");
+                      body = format + body + "}}";
+                        textFormat = EDITOR_FORMAT_HTML;
+                    }
+
+                    // Mark this message as formatted
+                    if (!setMapiProperty(message, PR_MSG_EDITOR_FORMAT, textFormat)) {
+                        qWarning() << "Unable to set message editor format in message.";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+
+                    IStream *os(0);
+                    rv = message->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, STGM_CREATE | STGM_WRITE, MAPI_CREATE | MAPI_MODIFY, reinterpret_cast<LPUNKNOWN*>(&os));
+                    if (HR_SUCCEEDED(rv)) {
+                        IStream *compressor(0);
+                        rv = WrapCompressedRTFStream(os, MAPI_MODIFY, &compressor);
+                        if (HR_SUCCEEDED(rv)) {
+                            // The wrapper stream does not support Stat()
+                            writeStream(error, compressor, body.data(), body.length(), false);
+
+                            compressor->Release();
+                        } else {
+                            qWarning() << "Unable to open RTF compressor stream for write.";
+                            *error = QMessageManager::FrameworkFault;
+                        }
+
+                        os->Release();
+                    } else {
+                        qWarning() << "Unable to open compressed RTF stream for write.";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+#endif
+                } else {
+                    QString body(bodyContent.textContent());
+
+#ifdef _WIN32_WCE
+                    IStream *os(0);
+                    HRESULT rv = message->OpenProperty(PR_BODY_W, 0, STGM_WRITE, MAPI_MODIFY | MAPI_CREATE,(LPUNKNOWN*)&os);
+                    if (HR_SUCCEEDED(rv)) {
+                        writeStream(error, os, body.utf16(), body.count() * sizeof(WCHAR), true);
+
+                        mapiRelease(os);
+                    } else {
+                        qWarning() << "Unable to write text to body";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+#else
+                    // Mark this message as plain text
+                    LONG textFormat(EDITOR_FORMAT_PLAINTEXT);
+                    if (!setMapiProperty(message, PR_MSG_EDITOR_FORMAT, textFormat)) {
+                        qWarning() << "Unable to set message editor format in message.";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+
+                    if (!setMapiProperty(message, PR_BODY, body)) {
+                        qWarning() << "Unable to set body in message.";
+                        *error = QMessageManager::FrameworkFault;
+                    }
+#endif
+                }
+            }
+        } else {
+            qWarning() << "Unable to delete existing body properties from message.";
+            *error = QMessageManager::FrameworkFault;
+        }
+    }
+
+    void addMessageAttachments(QMessageManager::Error *error, const QMessage &source, IMessage *message, WinHelpers::SavePropertyOption saveOption = WinHelpers::SavePropertyChanges )
+    {
+        Q_UNUSED(saveOption);
+
+        QList<LONG> attachmentNumbers;
+
+        if (MapiSession::messageImpl(source)->_hasAttachments) {
+            // Find any existing attachments
+            IMAPITable *attachmentsTable(0);
+            HRESULT rv = message->GetAttachmentTable(MAPI_UNICODE, &attachmentsTable);
+            if (HR_SUCCEEDED(rv)) {
+                SizedSPropTagArray(1, columns) = {1, {PR_ATTACH_NUM}};
+
+                QueryAllRows qar(attachmentsTable, reinterpret_cast<LPSPropTagArray>(&columns), 0, 0, false);
+                while (qar.query()) {
+                    for (uint n = 0; n < qar.rows()->cRows; ++n) {
+                        attachmentNumbers.append(qar.rows()->aRow[n].lpProps[0].Value.l);
+                    }
+                }
+
+                *error = qar.error();
+                if (*error != QMessageManager::NoError) {
+                    qWarning() << "Unable to get attachments numbers from table.";
+                }
+                mapiRelease(attachmentsTable);
+            } else {
+                qWarning() << "Unable to get attachments table from message.";
+                *error = QMessageManager::FrameworkFault;
+            }
+        }
+
+        if (*error == QMessageManager::NoError) {
+            // Add any current attachments not present
+            foreach (const QMessageContentContainerId &attachmentId, source.attachmentIds()) {
+                QMessageContentContainer attachment(source.find(attachmentId));
+                bool isAttachment = (!attachment.suggestedFileName().isEmpty() && attachment.isContentAvailable());
+                if (isAttachment) {
+                    LONG number(MapiSession::containerImpl(attachment)->_attachmentNumber);
+                    if (!number || !attachmentNumbers.contains(number)) {
+                        addAttachment(message, attachment);
+                    }
+                }
+            }
+        }
+    }
+
+    struct FolderHeapNode
+    {
+        FolderHeapNode(const MapiFolderPtr &folder, const QMessageFilter &filter);
+
+        QMessageFilter _filter;
+        MapiFolderPtr _folder;
+        LPMAPITABLE _table;
+        QMessage _front;
+    };
+
+    FolderHeapNode::FolderHeapNode(const MapiFolderPtr &folder, const QMessageFilter &filter)
+        : _filter(filter),
+          _folder(folder)
+    {
+    }
+
+    typedef QSharedPointer<FolderHeapNode> FolderHeapNodePtr;
+
+    // FolderHeap is a binary heap used to merge sort messages from different folders and stores
+    class FolderHeap {
+    public:
+        FolderHeap(QMessageManager::Error *error, const QList<FolderHeapNodePtr> &protoHeap, const QMessageSortOrder &sortOrder);
+        ~FolderHeap();
+
+        QMessage takeFront(QMessageManager::Error *error);
+        bool isEmpty() const { return _heap.count() == 0; }
+
+    private:
+        void sink(int i); // Also known as sift-down
+
+        QMessageFilter _filter;
+        QMessageSortOrder _ordering;
+        QList<FolderHeapNodePtr> _heap;
+    };
+
+    FolderHeap::FolderHeap(QMessageManager::Error *error, const QList<FolderHeapNodePtr> &protoHeap, const QMessageSortOrder &sortOrder)
+    {
+        _ordering = sortOrder;
+
+        foreach (const FolderHeapNodePtr &node, protoHeap) {
+            if (!node->_folder) {
+                qWarning() << "Unable to access folder:" << node->_folder->name();
+                continue;
+            }
+
+            QMessageIdList messageIdList;
+            node->_table = node->_folder->queryBegin(error, node->_filter, sortOrder);
+            if (*error == QMessageManager::NoError) {
+                if (node->_table) {
+                    messageIdList = node->_folder->queryNext(error, node->_table, node->_filter);
+                    if (messageIdList.isEmpty() || (*error != QMessageManager::NoError)) {
+                        node->_folder->queryEnd(node->_table);
+                    }
+                }
+
+                if (!messageIdList.isEmpty()) {
+                    node->_front = node->_folder->message(error, messageIdList.front());
+
+                    if (*error == QMessageManager::NoError) {
+                        _heap.append(node);
+                    }
+                }
+            }
+        }
+
+        if (!_ordering.isEmpty()) {
+            for (int i = _heap.count()/2 - 1; i >= 0; --i)
+                sink(i);
+        }
+    }
+
+    FolderHeap::~FolderHeap()
+    {
+    }
+
+    QMessage FolderHeap::takeFront(QMessageManager::Error *error)
+    {
+        QMessage result(_heap[0]->_front);
+
+        FolderHeapNodePtr node(_heap[0]);
+
+        QMessageIdList messageIdList;
+        if (node->_table) {
+            messageIdList = node->_folder->queryNext(error, node->_table, node->_filter);
+            if (messageIdList.isEmpty() || (*error != QMessageManager::NoError)) {
+                node->_folder->queryEnd(node->_table);
+            }
+        }
+        if (*error != QMessageManager::NoError)
+            return result;
+
+        if (messageIdList.isEmpty()) {
+            if (_heap.count() > 1) {
+                _heap[0] = _heap.takeLast();
+            } else {
+                _heap.pop_back();
+            }
+        } else {
+            node->_front = node->_folder->message(error, messageIdList.front());
+            if (*error != QMessageManager::NoError)
+                return result;
+        }
+
+        if (!_ordering.isEmpty()) {
+            // Reposition this folder in the heap based on the new front message
+            sink(0);
+        }
+        return result;
+    }
+
+    void FolderHeap::sink(int i)
+    {
+        while (true) {
+            const int leftChild(2*i + 1);
+            if (leftChild >= _heap.count())
+                return;
+
+            const int rightChild(leftChild + 1);
+            int minChild(leftChild);
+            if ((rightChild < _heap.count()) && (QMessageSortOrderPrivate::lessThan(_ordering, _heap[rightChild]->_front, _heap[leftChild]->_front)))
+                minChild = rightChild;
+
+            // Reverse positions only if the child sorts before the parent
+            if (QMessageSortOrderPrivate::lessThan(_ordering, _heap[minChild]->_front, _heap[i]->_front)) {
+                FolderHeapNodePtr temp(_heap[minChild]);
+                _heap[minChild] = _heap[i];
+                _heap[i] = temp;
+                i = minChild;
+            } else {
+                return;
+            }
+        }
+    }
+
+    bool bodyMatches(const QMessage &message, const QString &body, QMessageDataComparator::MatchFlags matchFlags)
+    {
+        if (body.isEmpty())
+            return true;
+
+        QMessageContentContainer bodyContainer(message.find(message.bodyId()));
+        if (matchFlags & QMessageDataComparator::MatchCaseSensitive) {
+            return bodyContainer.textContent().contains(body, Qt::CaseSensitive);
+        }
+        return bodyContainer.textContent().contains(body, Qt::CaseInsensitive);
+    }
+
+    QMessageIdList filterMessages(QMessageManager::Error *error, QList<FolderHeapNodePtr> &folderNodes, const QMessageSortOrder &sortOrder, uint limit, uint offset, const QString &body = QString(), QMessageDataComparator::MatchFlags matchFlags = 0)
+    {
+        QMessageIdList result;
+        QHash<QMessageId, bool> avoidDuplicates; // For complex filters it's necessary to check for duplicates
+
+        FolderHeap folderHeap(error, folderNodes, sortOrder);
+        if (*error != QMessageManager::NoError)
+            return result;
+
+        int count = 0 - offset;
+        while (!folderHeap.isEmpty()) {
+            // Ideally would not retrieve unwanted messages using IMAPITable::SeekRow
+            // But this is not feasible on Windows Mobile http://msdn.microsoft.com/en-us/library/bb446068.aspx:
+            // 'If there are large numbers of rows in the table, the SeekRow oepration can be slow. Do not set lRowCount ot a numer greater than 50'
+            if (limit && (count == limit))
+                break;
+
+            QMessage front(folderHeap.takeFront(error));
+            if (*error != QMessageManager::NoError)
+                return result;
+
+            if (!avoidDuplicates.contains(front.id()) && bodyMatches(front, body, matchFlags)) {
+                avoidDuplicates.insert(front.id(), true);
+                if (count >= 0) {
+                    result.append(front.id());
+                }
+
+                ++count;
+            }
+        }
+
+        if (offset) {
+            return result.mid(offset, (limit ? limit : -1));
+        } else {
+            return result;
+        }
+    }
+
+}
+
+    QEvent::Type MapiSession::NotifyEvent::eventType()
+    {
+        static int result = QEvent::registerEventType();
+        return static_cast<QEvent::Type>(result);
+    }
+
+    MapiSession::NotifyEvent::NotifyEvent(MapiStore *store, const QMessageId &id, MapiSession::NotifyType type)
+        : QEvent(eventType()),
+          _store(store),
+          _id(id),
+          _notifyType(type)
+    {
+    }
+
+    QEvent::Type MapiSession::NotifyEvent::type()
+    {
+        return eventType();
+    }
+
+namespace WinHelpers {
+
+    // Note: UNICODE is always defined
+    QString QStringFromLpctstr(LPCTSTR lpszValue)
+    {
+        bool isBadStringPointer = false;
+#ifndef _WIN32_WCE
+        isBadStringPointer = ::IsBadStringPtr(lpszValue, (UINT_PTR)-1); // Don't crash when MAPI returns a bad string (and it does).
+#endif
+
+        if (!lpszValue || isBadStringPointer)
+            return QString();
+
+        return QString::fromUtf16(reinterpret_cast<const quint16*>(lpszValue));
+    }
+
+    Lptstr LptstrFromQString(const QString &src)
+    {
+        uint length(src.length());
+        Lptstr dst(length+1);
+
+        const quint16 *data = src.utf16();
+        const quint16 *it = data, *end = data + length;
+        TCHAR *oit = dst;
+        for ( ; it != end; ++it, ++oit) {
+            *oit = static_cast<TCHAR>(*it);
+        }
+        *oit = TCHAR('\0');
+        return dst;
+    }
+
+    ULONG createNamedProperty(IMAPIProp *object, const QString &name)
+    {
+        ULONG result = 0;
+
+        if (!name.isEmpty()) {
+            Lptstr nameBuffer = LptstrFromQString(name);
+
+            MAPINAMEID propName = { 0 };
+            propName.lpguid = &GuidPublicStrings;
+            propName.ulKind = MNID_STRING;
+            propName.Kind.lpwstrName = nameBuffer;
+
+            LPMAPINAMEID propNames = &propName;
+
+            SPropTagArray *props;
+            HRESULT rv = object->GetIDsFromNames(1, &propNames, MAPI_CREATE, &props);
+            if (HR_SUCCEEDED(rv)) {
+                result = props->aulPropTag[0] | PT_UNICODE;
+
+                MAPIFreeBuffer(props);
+            } else {
+                qWarning() << "createNamedProperty: GetIDsFromNames failed";
+            }
+        }
+
+        return result;
+    }
+
+    ULONG getNamedPropertyTag(IMAPIProp *object, const QString &name)
+    {
+        ULONG result = 0;
+
+        if (!name.isEmpty()) {
+            LPTSTR nameBuffer = LptstrFromQString(name);
+
+            MAPINAMEID propName = { 0 };
+            propName.lpguid = &GuidPublicStrings;
+            propName.ulKind = MNID_STRING;
+            propName.Kind.lpwstrName = nameBuffer;
+
+            LPMAPINAMEID propNames = &propName;
+
+            SPropTagArray *props;
+            HRESULT rv = object->GetIDsFromNames(1, &propNames, 0, &props);
+            if (HR_SUCCEEDED(rv)) {
+                if (props->aulPropTag[0] != PT_ERROR) {
+                    result = props->aulPropTag[0] | PT_UNICODE;
+                }
+
+                MAPIFreeBuffer(props);
+            } else {
+                qWarning() << "getNamedPropertyTag: GetIDsFromNames failed";
+            }
+        }
+
+        return result;
+    }
+
+    bool setNamedProperty(IMAPIProp *object, ULONG tag, const QString &value)
+    {
+        if (object && tag && !value.isEmpty()) {
+            SPropValue prop = { 0 };
+            prop.ulPropTag = tag;
+            prop.Value.LPSZ = reinterpret_cast<LPTSTR>(const_cast<quint16*>(value.utf16()));
+
+            HRESULT rv = object->SetProps(1, &prop, 0);
+            if (HR_SUCCEEDED(rv)) {
+                return true;
+            } else {
+                qWarning() << "setNamedProperty: SetProps failed";
+            }
+        }
+
+        return false;
+    }
+
+    QString getNamedProperty(IMAPIProp *object, ULONG tag)
+    {
+        QString result;
+
+        if (object && tag) {
+            SPropValue *prop(0);
+            HRESULT rv = HrGetOneProp(object, tag, &prop);
+            if (HR_SUCCEEDED(rv)) {
+                result = QStringFromLpctstr(prop->Value.LPSZ);
+
+                MAPIFreeBuffer(prop);
+            } else if (rv != MAPI_E_NOT_FOUND) {
+                qWarning() << "getNamedProperty: HrGetOneProp failed";
+            }
+        }
+
+        return result;
+    }
+
+    QByteArray contentTypeFromExtension(const QString &extension)
+    {
+        QByteArray result("application/octet-stream");
+
+        if (!extension.isEmpty()) {
+            // Create the extension string to search for
+            QString dotExtension(extension);
+            if (!dotExtension.startsWith('.')) {
+                dotExtension.prepend('.');
+            }
+
+            Lptstr ext = LptstrFromQString(dotExtension);
+
+#if !defined(_WIN32_WCE) && (_MSC_VER>=1500)
+            IQueryAssociations *associations(0);
+            HRESULT rv = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARGS(&associations));
+            if (HR_SUCCEEDED(rv)) {
+                rv = associations->Init(0, ext, 0, 0);
+                if (HR_SUCCEEDED(rv)) {
+                    // Find the length of the content-type string
+                    DWORD length = 0;
+                    rv = associations->GetString(0, ASSOCSTR_CONTENTTYPE, 0, 0, &length);
+                    if ((rv == S_FALSE) && length) {
+                        // Retrieve the string
+                        wchar_t *buffer = new wchar_t[length + 1];
+                        buffer[length] = '\0';
+                        rv = associations->GetString(0, ASSOCSTR_CONTENTTYPE, 0, buffer, &length);
+                        if (rv == S_OK) {
+                            QString output(QString::fromUtf16(reinterpret_cast<quint16*>(buffer)));
+                            result = output.toLatin1();
+                        }
+
+                        delete [] buffer;
+                    }
+                }
+                mapiRelease(associations);
+            }
+#elif  !defined(_WIN32_WCE) && (_MSC_VER<1500)
+            //TODO Windows 2005 and 2003 don't have IID_PPV_ARGS.
+            //find alternative
+#else
+            // Find any registry entry for this extension
+            HKEY key = { 0 };
+            LONG rv = RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, 0, &key);
+            if (rv == ERROR_SUCCESS) {
+                WCHAR value[512] = { 0 };
+                DWORD valueBytes = sizeof(value);
+                rv = RegQueryValueEx(key, L"Content Type", 0, 0, reinterpret_cast<LPBYTE>(&value), &valueBytes);
+                if (rv == ERROR_SUCCESS) {
+                    if (valueBytes > 1) {
+                        result = QStringFromLpctstr(value).toLatin1();
+                    }
+                } else {
+                    qWarning() << "Unable to query key for extension:" << extension;
+                }
+
+                RegCloseKey(key);
+            }
+#endif
+        }
+
+        return result;
+    }
+
+    MapiInitializer::MapiInitializer()
+        : _initialized(false)
+    {
+#ifndef QT_NO_THREAD
+        // Note MAPIINIT is ignored on Windows Mobile but used on Outlook 2007 see msdn ms862621 vs cc842343
+        MAPIINIT_0 MAPIINIT = { 0, MAPI_MULTITHREAD_NOTIFICATIONS };
+        LPVOID arg(&MAPIINIT);
+#else
+        LPVOID arg(0);
+#endif
+        HRESULT rv = MAPIInitialize(arg);
+        if (HR_SUCCEEDED(rv)) {
+            _initialized = true;
+        } else {
+            _initialized = false;
+            qWarning() << "Unable to initialize MAPI - rv:" << hex << (ULONG)rv;
+        }
+    }
+
+    MapiInitializer::~MapiInitializer()
+    {
+        if (_initialized) {
+            MAPIUninitialize();
+            _initialized = false;
+        }
+    }
+
+    MapiInitializationToken initializeMapi()
+    {
+        MapiInitializationToken result;
+
+        if (!initializer.hasLocalData()) {
+            initializer.setLocalData(new InitRecord);
+        }
+
+        InitRecordPtr &threadInitializer(initializer.localData());
+        if (!(*threadInitializer).isNull()) {
+            result = (*threadInitializer).toStrongRef();
+        } else {
+            result = MapiInitializationToken(new MapiInitializer());
+            (*threadInitializer) = result;
+        }
+
+        return result;
+    }
+
+    class StoreSortHelper {
+    public:
+        StoreSortHelper(const QMessageAccountSortOrder *sortOrder, MapiStorePtr storePtr)
+            :_ordering(sortOrder),
+             _storePtr(storePtr)
+        {}
+
+        MapiStorePtr store()
+        {
+            return _storePtr;
+        }
+
+        StoreSortHelper& operator=(const StoreSortHelper &other) {
+            if (&other == this)
+                return *this;
+            _ordering = other._ordering;
+            _storePtr = other._storePtr;
+            return *this;
+        }
+
+        bool operator<(const StoreSortHelper &other) const
+        {
+            bool result (_storePtr->name() < other._storePtr->name());
+            if (QMessageAccountSortOrderPrivate::order(*_ordering) == Qt::DescendingOrder)
+                result = !result;
+            return result;
+        }
+    private:
+        const QMessageAccountSortOrder *_ordering;
+        MapiStorePtr _storePtr;
+    };
+
+    class FolderSortHelper {
+    public:
+        FolderSortHelper(const QMessageFolderSortOrder *sortOrder, MapiFolderPtr folderPtr, const QMessageFolder &folder)
+            :_ordering(sortOrder),
+             _folderPtr(folderPtr),
+             _folder(folder)
+        {}
+
+        MapiFolderPtr mapiFolderPtr()
+        {
+            return _folderPtr;
+        }
+
+        QMessageFolder folder()
+        {
+            return _folder;
+        }
+
+        FolderSortHelper& operator=(const FolderSortHelper &other) {
+            if (&other == this)
+                return *this;
+            _ordering = other._ordering;
+            _folderPtr = other._folderPtr;
+            _folder = other._folder;
+            return *this;
+        }
+
+        bool operator<(const FolderSortHelper &other) const
+        {
+            return QMessageFolderSortOrderPrivate::lessthan(*_ordering, _folder, other._folder);
+        }
+    private:
+        const QMessageFolderSortOrder *_ordering;
+        MapiFolderPtr _folderPtr;
+        QMessageFolder _folder;
+    };
+}
+
+using namespace WinHelpers;
+
+MapiFolder::MapiFolder()
+    :_valid(false),
+     _folder(0),
+     _hasSubFolders(false),
+     _messageCount(0),
+     _init(false)
+{
+}
+
+MapiFolderPtr MapiFolder::createFolder(QMessageManager::Error *error, const MapiStorePtr &store, IMAPIFolder *folder, const MapiRecordKey &recordKey, const QString &name, const MapiEntryId &entryId, bool hasSubFolders, uint messageCount)
+{
+    Q_UNUSED(error);
+    MapiFolderPtr ptr = MapiFolderPtr(new MapiFolder(store, folder, recordKey, name, entryId, hasSubFolders, messageCount));
+    if (!ptr.isNull()) {
+        ptr->_self = ptr;
+    }
+    return ptr;
+}
+
+MapiFolder::MapiFolder(const MapiStorePtr &store, IMAPIFolder *folder, const MapiRecordKey &recordKey, const QString &name, const MapiEntryId &entryId, bool hasSubFolders, uint messageCount)
+    :_store(store),
+     _valid(folder != 0),
+     _folder(folder),
+     _key(recordKey),
+     _name(name),
+     _entryId(entryId),
+     _hasSubFolders(hasSubFolders),
+     _messageCount(messageCount),
+     _init(false)
+{
+}
+
+MapiFolder::~MapiFolder()
+{
+    mapiRelease(_folder);
+    _valid = false;
+    _init = false;
+}
+
+void MapiFolder::findSubFolders(QMessageManager::Error *error)
+{
+    if (!_folder) {
+        *error = QMessageManager::FrameworkFault;
+        qWarning() << "No folder to search for subfolders";
+    } else if (_hasSubFolders) {
+        IMAPITable *subFolders(0);
+        HRESULT rv = _folder->GetHierarchyTable(0, &subFolders);
+        if (HR_SUCCEEDED(rv)) {
+            SizedSPropTagArray(5, columns) = {5, {PR_ENTRYID, PR_IS_NEWSGROUP, PR_IS_NEWSGROUP_ANCHOR, PR_EXTENDED_FOLDER_FLAGS, PR_FOLDER_TYPE}};
+            rv = subFolders->SetColumns(reinterpret_cast<LPSPropTagArray>(&columns), 0);
+            if (HR_SUCCEEDED(rv)) {
+                // Extract the Entry IDs for the subfolders
+                while (true) {
+                    LPSRowSet rows(0);
+                    if (subFolders->QueryRows(1, 0, &rows) == S_OK) {
+                        if (rows->cRows == 1) {
+                            SRow *row(&rows->aRow[0]);
+
+                            MapiEntryId entryId(row->lpProps[0].Value.bin.lpb, row->lpProps[0].Value.bin.cb);
+                            bool isNewsGroup = (row->lpProps[1].ulPropTag == PR_IS_NEWSGROUP && row->lpProps[1].Value.b);
+                            bool isNewsGroupAnchor = (row->lpProps[2].ulPropTag == PR_IS_NEWSGROUP_ANCHOR && row->lpProps[2].Value.b);
+
+                            bool special(isNewsGroup || isNewsGroupAnchor);
+#ifndef _WIN32_WCE
+                            // Skip slow folders, necessary evil
+                            if (row->lpProps[3].ulPropTag == PR_EXTENDED_FOLDER_FLAGS) {
+                                QByteArray extendedFlags(reinterpret_cast<const char*>(row->lpProps[3].Value.bin.lpb), row->lpProps[3].Value.bin.cb);
+                                if (extendedFlags[2] & 8) { // Synchronization issues, skip like Outlook
+                                    special = true;
+                                }
+                            } else if (row->lpProps[4].ulPropTag == PR_FOLDER_TYPE) {
+                                if (row->lpProps[4].Value.ul != FOLDER_GENERIC) {
+                                    special = true;
+                                }
+                            } else {
+                                special = true;
+                            }
+#endif
+                            FreeProws(rows);
+
+                            if (special) {
+                                // Doesn't contain messages that should be searched...
+                            }  else {
+                                _subFolders.append(entryId);
+                            }
+                        } else {
+                            // We have retrieved all rows
+                            FreeProws(rows);
+                            break;
+                        }
+                    } else {
+                        *error = QMessageManager::ContentInaccessible;
+                        qWarning() << "Error getting rows from sub folder table.";
+                        break;
+                    }
+                }
+            } else {
+                *error = QMessageManager::ContentInaccessible;
+                qWarning() << "Unable to set columns on folder table.";
+            }
+
+            mapiRelease(subFolders);
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+            qWarning() << "Unable to get hierarchy table for folder:" << name();
+        }
+    }
+}
+
+MapiFolderPtr MapiFolder::nextSubFolder(QMessageManager::Error *error)
+{
+    MapiFolderPtr result;
+
+    if (!_hasSubFolders)
+        return result;
+
+    if (!_init) {
+        _init = true;
+
+        findSubFolders(error);
+        if (*error != QMessageManager::NoError) {
+            qWarning() << "Unable to find sub folders.";
+            return result;
+        }
+    }
+
+    if (!_subFolders.isEmpty()) {
+        result = _store->openFolder(error, _subFolders.takeFirst());
+    } else {
+        // Allow the traversal to be restarted
+        _init = false;
+    }
+
+    return result;
+}
+
+LPMAPITABLE MapiFolder::queryBegin(QMessageManager::Error *error, const QMessageFilter &filter, const QMessageSortOrder &sortOrder)
+{
+    if (!_valid || !_folder) {
+        Q_ASSERT(_valid && _folder);
+        *error = QMessageManager::FrameworkFault;
+        return 0;
+    }
+
+    MapiRestriction restriction(filter);
+    if (!restriction.isValid()) {
+        *error = QMessageManager::ConstraintFailure;
+        return 0;
+    }
+
+    LPMAPITABLE messagesTable(0);
+    HRESULT rv = _folder->GetContentsTable(MAPI_UNICODE, &messagesTable);
+    if (HR_SUCCEEDED(rv)) {
+        SizedSPropTagArray(2, columns) = {2, {PR_ENTRYID, PR_RECORD_KEY}};
+        rv = messagesTable->SetColumns(reinterpret_cast<LPSPropTagArray>(&columns), 0);
+        if (HR_SUCCEEDED(rv)) {
+            if (!sortOrder.isEmpty()) {
+                QMessageSortOrderPrivate::sortTable(error, sortOrder, messagesTable);
+            }
+            if (!restriction.isEmpty()) {
+                ULONG flags(0);
+                if (messagesTable->Restrict(restriction.sRestriction(), flags) != S_OK)
+                    *error = QMessageManager::ConstraintFailure;
+            }
+            if (*error != QMessageManager::NoError) {
+                return 0;
+            }
+            return messagesTable;
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+            return 0;
+        }
+
+        mapiRelease(messagesTable);
+        messagesTable = 0;
+    } else {
+        *error = QMessageManager::ContentInaccessible;
+        return 0;
+    }
+
+    return 0;
+}
+
+QMessageIdList MapiFolder::queryNext(QMessageManager::Error *error, LPMAPITABLE messagesTable, const QMessageFilter &filter)
+{
+    QMessageIdList result;
+    while (true) {
+        LPSRowSet rows(0);
+        HRESULT rv = messagesTable->QueryRows(1, 0, &rows);
+        if (HR_SUCCEEDED(rv)) {
+            if (rows->cRows == 1) {
+                LPSPropValue entryIdProp(&rows->aRow[0].lpProps[0]);
+                LPSPropValue recordKeyProp(&rows->aRow[0].lpProps[1]);
+                MapiRecordKey recordKey(recordKeyProp->Value.bin.lpb, recordKeyProp->Value.bin.cb);
+                MapiEntryId entryId(entryIdProp->Value.bin.lpb, entryIdProp->Value.bin.cb);
+        #ifdef _WIN32_WCE
+                QMessageId id(QMessageIdPrivate::from(_store->entryId(), entryId, recordKey, _entryId));
+        #else
+                QMessageId id(QMessageIdPrivate::from(_store->recordKey(), entryId, recordKey, _key));
+        #endif
+                FreeProws(rows);
+
+                if (QMessageFilterPrivate::matchesMessageRequired(filter)
+                    && !QMessageFilterPrivate::matchesMessageSimple(filter, QMessage(id)))
+                    continue;
+                result.append(id);
+                return result;
+            } else {
+                // We have retrieved all rows
+                FreeProws(rows);
+                return result;
+            }
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+            qWarning() << "Unable to query rows in message table.";
+            return result;
+        }
+    }
+}
+
+void MapiFolder::queryEnd(LPMAPITABLE messagesTable)
+{
+    mapiRelease(messagesTable);
+}
+
+uint MapiFolder::countMessages(QMessageManager::Error *error, const QMessageFilter &filter) const
+{
+    uint result(0);
+
+    if (!_valid || !_folder) {
+        Q_ASSERT(_valid && _folder);
+        *error = QMessageManager::FrameworkFault;
+        return result;
+    }
+
+    MapiRestriction restriction(filter);
+    if (!restriction.isValid()) {
+        *error = QMessageManager::ConstraintFailure;
+        return result;
+    }
+
+    IMAPITable *messagesTable(0);
+    HRESULT rv = _folder->GetContentsTable(MAPI_UNICODE, &messagesTable);
+    if (HR_SUCCEEDED(rv)) {
+        if (!restriction.isEmpty()) {
+            ULONG flags(0);
+            rv = messagesTable->Restrict(restriction.sRestriction(), flags);
+            if (HR_FAILED(rv)) {
+                *error = QMessageManager::ConstraintFailure;
+                qWarning() << "Unable to set restriction on message table.";
+            }
+        }
+        if (*error == QMessageManager::NoError) {
+            ULONG count(0);
+            rv = messagesTable->GetRowCount(0, &count);
+            if (HR_SUCCEEDED(rv)) {
+                result = count;
+            } else {
+                qWarning() << "Unable to count messages in folder.";
+            }
+        }
+
+        mapiRelease(messagesTable);
+    } else {
+        *error = QMessageManager::ContentInaccessible;
+        qWarning() << "Unable to get folder contents table.";
+    }
+
+    return result;
+}
+
+void MapiFolder::removeMessages(QMessageManager::Error *error, const QMessageIdList &ids)
+{
+    SBinary *bin(0);
+    if (MAPIAllocateBuffer(ids.count() * sizeof(SBinary), reinterpret_cast<LPVOID*>(&bin)) != S_OK) {
+        return;
+    }
+
+    int index = 0;
+    foreach (const QMessageId &id, ids) {
+        MapiEntryId entryId(QMessageIdPrivate::entryId(id));
+
+        bin[index].cb = entryId.count();
+        if (MAPIAllocateMore(bin[index].cb, bin, reinterpret_cast<LPVOID*>(&bin[index].lpb)) != S_OK) {
+            break;
+        }
+
+        memcpy(bin[index].lpb, entryId.constData(), bin[index].cb);
+        ++index;
+    }
+
+    if (index > 0) {
+        ENTRYLIST entryList = { 0 };
+        entryList.cValues = index;
+        entryList.lpbin = bin;
+
+        ULONG flags(0);
+        //flags |= DELETE_HARD_DELETE;  this flag is only available when the store is exchange...
+        HRESULT rv = _folder->DeleteMessages(&entryList, 0, 0, flags);
+        if (HR_FAILED(rv)) {
+            *error = QMessageManager::ContentInaccessible;
+            qWarning() << "Unable to delete messages from folder.";
+        }
+    }
+
+    MAPIFreeBuffer(bin);
+}
+
+MapiEntryId MapiFolder::messageEntryId(QMessageManager::Error *error, const MapiRecordKey &messageKey)
+{
+    MapiEntryId result;
+    MapiRecordKey key(messageKey);
+
+    IMAPITable *messagesTable(0);
+    HRESULT rv = _folder->GetContentsTable(MAPI_UNICODE, &messagesTable);
+    if (HR_SUCCEEDED(rv)) {
+        SPropValue keyProp;
+        keyProp.ulPropTag = PR_RECORD_KEY;
+        keyProp.Value.bin.cb = key.count();
+        keyProp.Value.bin.lpb = reinterpret_cast<LPBYTE>(key.data());
+
+        SRestriction restriction;
+        restriction.rt = RES_PROPERTY;
+        restriction.res.resProperty.relop = RELOP_EQ;
+        restriction.res.resProperty.ulPropTag = PR_RECORD_KEY;
+        restriction.res.resProperty.lpProp = &keyProp;
+
+        ULONG flags(0);
+        rv = messagesTable->Restrict(&restriction, flags);
+        if (HR_SUCCEEDED(rv)) {
+            SizedSPropTagArray(1, columns) = {1, {PR_ENTRYID}};
+            rv = messagesTable->SetColumns(reinterpret_cast<LPSPropTagArray>(&columns), 0);
+            if (HR_SUCCEEDED(rv)) {
+                LPSRowSet rows(0);
+                rv = messagesTable->QueryRows(1, 0, &rows);
+                if (HR_SUCCEEDED(rv)) {
+                    if (rows->cRows == 1) {
+                        LPSPropValue entryIdProp(&rows->aRow[0].lpProps[0]);
+                        result = MapiEntryId(entryIdProp->Value.bin.lpb, entryIdProp->Value.bin.cb);
+                    } else {
+                        *error = QMessageManager::InvalidId;
+                    }
+                    FreeProws(rows);
+                } else {
+                    *error = QMessageManager::ContentInaccessible;
+                    qWarning() << "Unable to query rows from message table.";
+                }
+            } else {
+                *error = QMessageManager::ContentInaccessible;
+                qWarning() << "Unable to set columns on message table.";
+            }
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+            qWarning() << "Unable to restrict content table.";
+        }
+
+        mapiRelease(messagesTable);
+    } else {
+        *error = QMessageManager::ContentInaccessible;
+        qWarning() << "Unable to get contents table for folder.";
+    }
+
+    return result;
+}
+
+IMessage *MapiFolder::openMessage(QMessageManager::Error *error, const MapiEntryId &entryId)
+{
+    IMessage *message(0);
+
+    LPENTRYID entryIdPtr(reinterpret_cast<LPENTRYID>(const_cast<char*>(entryId.data())));
+    ULONG objectType(0);
+    HRESULT rv = _folder->OpenEntry(entryId.count(), entryIdPtr, 0, MAPI_BEST_ACCESS, &objectType, reinterpret_cast<LPUNKNOWN*>(&message));
+    if (rv != S_OK) {
+        *error = QMessageManager::InvalidId;
+        qWarning() << "Invalid message entryId:" << entryId.toBase64();
+    }
+
+    return message;
+}
+
+QMessageFolder MapiFolder::folder(QMessageManager::Error *error, const QMessageFolderId& id) const
+{
+    return _store->folder(error, id);
+}
+
+QMessage MapiFolder::message(QMessageManager::Error *error, const QMessageId& id) const
+{
+    return _store->message(error, id);
+}
+
+QMessage::StandardFolder MapiFolder::standardFolder() const
+{
+    return _store->standardFolder(_entryId);
+}
+
+QMessageFolderId MapiFolder::id() const
+{
+#ifdef _WIN32_WCE
+    return QMessageFolderIdPrivate::from(_key, _store->entryId(), _entryId);
+#else
+    return QMessageFolderIdPrivate::from(_key, _store->recordKey(), _entryId);
+#endif
+}
+
+QMessageAccountId MapiFolder::accountId() const
+{
+    return _store->id();
+}
+
+QMessageFolderId MapiFolder::parentId() const
+{
+    MapiEntryId parentEntryId;
+    if (getMapiProperty(_folder, PR_PARENT_ENTRYID, &parentEntryId)) {
+        QMessageManager::Error ignoredError(QMessageManager::NoError);
+        MapiFolderPtr parent(_store->openFolder(&ignoredError, parentEntryId));
+        if (!parent.isNull()) {
+            QMessageManager::Error ignoredError(QMessageManager::NoError);
+            MapiFolderPtr root(_store->rootFolder(&ignoredError));
+            if ((ignoredError != QMessageManager::NoError) 
+                || !_store->session()->equal(parent->entryId(), root->entryId())) {
+                return parent->id();
+            }
+        }
+    }
+
+    return QMessageFolderId();
+}
+
+QList<QMessageFolderId> MapiFolder::ancestorIds() const
+{
+    QList<QMessageFolderId> result;
+
+    QMessageManager::Error ignoredError(QMessageManager::NoError);
+    MapiFolderPtr root(_store->rootFolder(&ignoredError));
+
+    if (ignoredError == QMessageManager::NoError) {
+        MapiFolderPtr current = _self.toStrongRef();
+        MapiSessionPtr session = _store->session();
+
+        while ((ignoredError == QMessageManager::NoError) && !session->equal(current->entryId(), root->entryId())) {
+            // Find the parent of this folder and append to the list of ancestors
+            MapiEntryId parentEntryId;
+            if (getMapiProperty(current->_folder, PR_PARENT_ENTRYID, &parentEntryId)) {
+                QMessageManager::Error ignoredError(QMessageManager::NoError);
+                current = _store->openFolder(&ignoredError, parentEntryId);
+                if (!current.isNull() && !session->equal(current->entryId(), root->entryId())) {
+                    result.append(current->id());
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+MapiRecordKey MapiFolder::storeKey() const
+{
+    return _store->recordKey();
+}
+
+#ifdef _WIN32_WCE
+MapiEntryId MapiFolder::storeEntryId() const
+{
+    return _store->entryId();
+}
+#endif
+
+static unsigned long commonFolderMap(QMessage::StandardFolder folder)
+{
+    static bool init = false;
+    static QMap<QMessage::StandardFolder,unsigned long> propertyMap;
+
+    if(!init)
+    {
+        propertyMap.insert(QMessage::DraftsFolder,PROP_TAG(PT_BINARY,0x36D7));
+        propertyMap.insert(QMessage::TrashFolder,PROP_TAG(PT_BINARY,0x35E3));
+        propertyMap.insert(QMessage::OutboxFolder,PROP_TAG(PT_BINARY,0x35E2));
+        propertyMap.insert(QMessage::SentFolder,PROP_TAG(PT_BINARY,0x35E4));
+
+        init = true;
+    }
+
+    return propertyMap.value(folder);
+}
+
+IMessage *MapiFolder::createMessage(QMessageManager::Error* error)
+{
+    IMessage *message = 0;
+
+    if(FAILED(_folder->CreateMessage(NULL, 0, &message)!=S_OK))
+    {
+        *error = QMessageManager::FrameworkFault;
+        mapiRelease(message);
+    }
+    return message;
+}
+
+IMessage* MapiFolder::createMessage(QMessageManager::Error* error, const QMessage& source, const MapiSessionPtr &session, SavePropertyOption saveOption )
+{
+    IMessage* mapiMessage(0);
+    HRESULT rv = _folder->CreateMessage(0, 0, &mapiMessage);
+    if (HR_SUCCEEDED(rv)) {
+        // Store the message properties
+        if (*error == QMessageManager::NoError) {
+            storeMessageProperties(error, source, mapiMessage);
+        }
+
+#ifndef _WIN32_WCE
+        //Ensure the message is moved to the sent folder after submission
+        //On Windows Mobile occurs by default and at discretion of mail client settings.
+
+        MapiFolderPtr sentFolder = _store->findFolder(error,QMessage::SentFolder);
+        if (!sentFolder.isNull() && *error == QMessageManager::NoError) {
+            if (!setMapiProperty(mapiMessage, PR_SENTMAIL_ENTRYID, sentFolder->entryId())) {
+                qWarning() << "Unable to set sent folder entry id on message";
+            }
+        }
+#endif
+
+        if (*error == QMessageManager::NoError) {
+            replaceMessageRecipients(error, source, mapiMessage, session->session());
+        }
+        if (*error == QMessageManager::NoError) {
+            replaceMessageBody(error, source, mapiMessage, _store);
+        }
+        if (*error == QMessageManager::NoError) {
+            addMessageAttachments(error, source, mapiMessage, saveOption );
+        }
+#ifndef _WIN32_WCE //unsupported
+        if (*error == QMessageManager::NoError && saveOption == SavePropertyChanges ) {
+            if (HR_FAILED(mapiMessage->SaveChanges(0))) {
+                qWarning() << "Unable to save changes for message.";
+            }
+        }
+#endif
+        if (*error != QMessageManager::NoError) {
+            mapiRelease(mapiMessage);
+        }
+    } else {
+        qWarning() << "Failed to create MAPI message";
+        *error = QMessageManager::FrameworkFault;
+    }
+
+    return mapiMessage;
+}
+
+MapiStorePtr MapiStore::createStore(QMessageManager::Error *error, const MapiSessionPtr &session, IMsgStore *store, const MapiRecordKey &key, const MapiEntryId &entryId, const QString &name, bool cachedMode)
+{
+    Q_UNUSED(error);
+    MapiStorePtr ptr = MapiStorePtr(new MapiStore(session, store, key, entryId, name, cachedMode));
+    if (!ptr.isNull()) {
+        ptr->_self = ptr;
+    }
+    return ptr;
+}
+
+MapiStore::MapiStore()
+    :_valid(false),
+     _store(0),
+     _cachedMode(true),
+     _adviseConnection(0)
+{
+}
+
+MapiStore::MapiStore(const MapiSessionPtr &session, IMsgStore *store, const MapiRecordKey &key, const MapiEntryId &entryId, const QString &name, bool cachedMode)
+    :_session(session),
+     _valid(true),
+     _store(store),
+     _key(key),
+     _entryId(entryId),
+     _name(name),
+     _cachedMode(cachedMode),
+     _adviseConnection(0)
+{
+    // Find which standard folders the store contains
+    foreach (QMessage::StandardFolder sf, QList<QMessage::StandardFolder>() << QMessage::InboxFolder
+                                                                            << QMessage::DraftsFolder
+                                                                            << QMessage::TrashFolder
+                                                                            << QMessage::SentFolder
+                                                                            << QMessage::OutboxFolder) {
+        QMessageManager::Error ignoredError(QMessageManager::NoError);
+        MapiEntryId entryId(standardFolderId(&ignoredError, sf));
+        if (!entryId.isEmpty()) {
+            _standardFolderId[sf] = entryId;
+        }
+    }
+}
+
+MapiStore::~MapiStore()
+{
+    _folderMap.clear();
+
+    if (_adviseConnection != 0) {
+        _store->Unadvise(_adviseConnection);
+    }
+    mapiRelease(_store);
+    _valid = false;
+}
+
+MapiFolderPtr MapiStore::findFolder(QMessageManager::Error *error, QMessage::StandardFolder sf)
+{
+    MapiFolderPtr result;
+
+    if (_standardFolderId.contains(sf)) {
+        result = openFolder(error, _standardFolderId[sf]);
+    }
+
+    return result;
+}
+
+MapiEntryId MapiStore::standardFolderId(QMessageManager::Error *error, QMessage::StandardFolder sf) const
+{
+    MapiEntryId result;
+
+#ifndef _WIN32_WCE
+    //check if the store supports the common folder (not possible for mobile)
+    bool commonFolderSupported(false);
+
+    unsigned long tag(0);
+    switch(sf)
+    {
+    case QMessage::InboxFolder:
+        tag = FOLDER_IPM_INBOX_VALID;
+        break;
+    case QMessage::OutboxFolder:
+        tag = FOLDER_IPM_OUTBOX_VALID;
+        break;
+    case QMessage::TrashFolder:
+        tag = FOLDER_IPM_WASTEBASKET_VALID;
+        break;
+    case QMessage::SentFolder:
+        tag = FOLDER_IPM_SENTMAIL_VALID;
+        break;
+    case QMessage::DraftsFolder:
+        //assume drafts exists in every store since there is no mask for it
+        commonFolderSupported = true;
+        break;
+    }
+
+    if (tag) {
+        ULONG folderSupportMask(0);
+        if (getMapiProperty(_store, PR_VALID_FOLDER_MASK, &folderSupportMask)) {
+            if (folderSupportMask & tag) {
+                commonFolderSupported = true;
+            }
+        } else {
+            qWarning() << "Could not get folder mask property for store:" << _name;
+        }
+    }
+
+    if (!commonFolderSupported) {
+        return result;
+    }
+#endif
+
+    if (sf == QMessage::InboxFolder) {
+        result = receiveFolderId(error);
+    } else {
+        IMAPIProp* source = _store;
+        MapiFolderPtr rf;
+
+#ifndef _WIN32_WCE
+        //the drafts entryid can only be queried on the inbox or root folder.
+
+        if(sf == QMessage::DraftsFolder) {
+            rf = receiveFolder(error);
+            if(*error != QMessageManager::NoError) {
+                //inbox failed, try root folder.
+                rf = rootFolder(error);
+                if(*error != QMessageManager::NoError)
+                {
+                    return result;
+                }
+            }
+            source = rf->folder();
+        }
+#endif
+
+        MapiEntryId entryId;
+        if (getMapiProperty(source, commonFolderMap(sf), &entryId)) {
+            result = entryId;
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+        }
+    }
+
+
+    return result;
+}
+
+QMessageFolderIdList MapiStore::folderIds(QMessageManager::Error *error) const
+{
+    QMessageFolderIdList folderIds;
+
+    MapiFolderPtr root(rootFolder(error));
+    if (*error == QMessageManager::NoError) {
+        QList<MapiFolderPtr> folders;
+        folders.append(root);
+        // No valid reason to list the root folder.
+
+        while (!folders.isEmpty()) {
+            MapiFolderPtr subFolder(folders.back()->nextSubFolder(error));
+            if (!subFolder.isNull() && subFolder->isValid()) {
+                folderIds.append(subFolder->id());
+                folders.append(subFolder);
+            } else {
+                folders.pop_back();
+            }
+        }
+    } else {
+        qWarning() << "Unable to open root folder for store:" << _name;
+    }
+
+    return folderIds;
+}
+
+QMessageFolder MapiStore::folderFromId(QMessageManager::Error *error, const QMessageFolderId &folderId)
+{
+    QMessageFolder result;
+    QList<MapiFolderPtr> folders;
+    MapiFolderPtr root(rootFolder(error));
+    if (!root.isNull() && root->isValid())
+        folders.append(root);
+
+    while (!folders.isEmpty()) {
+        MapiFolderPtr subFolder(folders.back()->nextSubFolder(error));
+        if (!subFolder.isNull() && subFolder->isValid()) {
+            if (folderId == subFolder->id()) {
+                QStringList path;
+                for (int i = 0; i < folders.count(); ++i) {
+                    if (!folders[i]->name().isEmpty()) {
+                        path.append(folders[i]->name());
+                    }
+                }
+                path.append(subFolder->name());
+                result = QMessageFolderPrivate::from(subFolder->id(), id(), folders.last()->id(), subFolder->name(), path.join("/"));
+                return result;
+            }
+            folders.append(subFolder);
+        } else {
+            folders.pop_back();
+        }
+    }
+
+    return result;
+}
+
+QList<MapiFolderPtr> MapiStore::filterFolders(QMessageManager::Error *error, const QMessageFolderFilter &afilter) const
+{
+    QList<MapiFolderPtr> result;
+    QMessageFolderFilter filter(QMessageFolderFilterPrivate::preprocess(error, _session, afilter));
+    if (*error != QMessageManager::NoError)
+        return result;
+
+#if 0 //(was ifndef _WIN32_WCE) TODO: fix issue with GetHierarchyTable only returning top level folders
+    MapiFolderPtr root(rootFolder(error));
+    if (*error == QMessageManager::NoError) {
+        IMAPITable *folderTable(0);
+        HRESULT rv = root->_folder->GetHierarchyTable(CONVENIENT_DEPTH | MAPI_UNICODE, &folderTable);
+        if (HR_SUCCEEDED(rv)) {
+            SizedSPropTagArray(5, columns) = {5, {PR_ENTRYID, PR_IS_NEWSGROUP, PR_IS_NEWSGROUP_ANCHOR, PR_EXTENDED_FOLDER_FLAGS, PR_FOLDER_TYPE}};
+            QueryAllRows qar(folderTable, reinterpret_cast<LPSPropTagArray>(&columns), 0, 0, false);
+            while (qar.query()) {
+                for (uint n = 0; n < qar.rows()->cRows; ++n) {
+                    SPropValue *props = qar.rows()->aRow[n].lpProps;
+
+                    bool isNewsGroup = (props[1].ulPropTag == PR_IS_NEWSGROUP && props[1].Value.b);
+                    bool isNewsGroupAnchor = (props[2].ulPropTag == PR_IS_NEWSGROUP_ANCHOR && props[2].Value.b);
+                    bool special(isNewsGroup || isNewsGroupAnchor);
+
+                    // Skip slow folders, necessary evil
+                    if (props[3].ulPropTag == PR_EXTENDED_FOLDER_FLAGS) {
+                        QByteArray extendedFlags(reinterpret_cast<const char*>(props[3].Value.bin.lpb), props[3].Value.bin.cb);
+                        if (extendedFlags[2] & 8) { // Synchronization issues, skip like Outlook
+                            special = true;
+                        }
+                    } else if (props[4].ulPropTag == PR_FOLDER_TYPE) {
+                        if (props[4].Value.ul != FOLDER_GENERIC) {
+                            special = true;
+                        }
+                    } else {
+                        special = true;
+                    }
+
+                    if (!special) {
+                        MapiEntryId entryId(props[0].Value.bin.lpb, props[0].Value.bin.cb);
+                        MapiFolderPtr folder(openFolder(error, entryId));
+                        if (QMessageFolderFilterPrivate::matchesFolder(filter, folder)) {
+                            result.append(folder);
+                        }
+                    }
+                }
+            }
+
+            *error = qar.error();
+
+            mapiRelease(folderTable);
+        }
+    }
+#else
+    // Windows mobile does not support CONVENIENT_DEPTH...
+    foreach (const QMessageFolderId &folderId, folderIds(error)) {
+        MapiEntryId entryId = QMessageFolderIdPrivate::entryId(folderId);
+        MapiFolderPtr folder(openFolder(error, entryId));
+        if (QMessageFolderFilterPrivate::matchesFolder(filter, folder)) {
+            result.append(folder);
+        }
+    }
+#endif
+
+    return result;
+}
+
+MapiEntryId MapiStore::messageEntryId(QMessageManager::Error *error, const MapiRecordKey &folderKey, const MapiRecordKey &messageKey)
+{
+    MapiEntryId result;
+
+    MapiFolderPtr folder(openFolderWithKey(error, folderKey));
+    if (*error == QMessageManager::NoError) {
+        result = folder->messageEntryId(error, messageKey);
+    }
+
+    return result;
+}
+
+MapiFolderPtr MapiStore::openFolder(QMessageManager::Error *error, const MapiEntryId& entryId) const
+{
+    MapiFolderPtr result(0);
+
+    // See if we can create a new pointer to an existing folder
+    QWeakPointer<MapiFolder> &existing = _folderMap[entryId];
+    if (!existing.isNull()) {
+        // Get a pointer to the existing folder
+        result = existing.toStrongRef();
+        if (!result.isNull()) {
+            return result;
+        }
+    }
+
+    // We need to create a new instance
+    IMAPIFolder *folder = openMapiFolder(error, entryId);
+    if (folder && (*error == QMessageManager::NoError)) {
+        SizedSPropTagArray(4, columns) = {4, {PR_DISPLAY_NAME, PR_RECORD_KEY, PR_CONTENT_COUNT, PR_SUBFOLDERS}};
+        SPropValue *properties(0);
+        ULONG count;
+        HRESULT rv = folder->GetProps(reinterpret_cast<LPSPropTagArray>(&columns), MAPI_UNICODE, &count, &properties);
+        if (HR_SUCCEEDED(rv)) {
+            QString name(QStringFromLpctstr(properties[0].Value.LPSZ));
+            MapiRecordKey recordKey(properties[1].Value.bin.lpb, properties[1].Value.bin.cb);
+            uint messageCount = properties[2].Value.ul;
+            bool hasSubFolders = properties[3].Value.b;
+
+            MapiFolderPtr folderPtr = MapiFolder::createFolder(error, _self.toStrongRef(), folder, recordKey, name, entryId, hasSubFolders, messageCount);
+            if (*error == QMessageManager::NoError) {
+                result = folderPtr;
+
+                // Add to the map
+                _folderMap.insert(entryId, result);
+            } else {
+                qWarning() << "Error creating folder object";
+            }
+
+            MAPIFreeBuffer(properties);
+        } else {
+            qWarning() << "Unable to access folder properties";
+            mapiRelease(folder);
+        }
+    } else {
+        qWarning() << "Unable to open folder.";
+    }
+
+    return result;
+}
+
+MapiFolderPtr MapiStore::openFolderWithKey(QMessageManager::Error *error, const MapiRecordKey &recordKey) const
+{
+#if 0 // Doesn't work on desktop or mobile, only searches top level folders
+    MapiFolderPtr result;
+
+    MapiFolderPtr root(rootFolder(error));
+    if (*error == QMessageManager::NoError) {
+        if (root->recordKey() == recordKey) {
+            result = root;
+        } else {
+            IMAPITable *folderTable(0);
+            // TODO: this won't work on windows mobile:
+            HRESULT rv = root->_folder->GetHierarchyTable(CONVENIENT_DEPTH | MAPI_UNICODE, &folderTable);
+            if (HR_SUCCEEDED(rv)) {
+                // Find the entry ID corresponding to this recordKey
+                SPropValue keyProp = { 0 };
+                keyProp.ulPropTag = PR_RECORD_KEY;
+                keyProp.Value.bin.cb = recordKey.count();
+                keyProp.Value.bin.lpb = reinterpret_cast<LPBYTE>(const_cast<char*>(recordKey.data()));
+
+                SRestriction restriction = { 0 };
+                restriction.rt = RES_PROPERTY;
+                restriction.res.resProperty.relop = RELOP_EQ;
+                restriction.res.resProperty.ulPropTag = PR_RECORD_KEY;
+                restriction.res.resProperty.lpProp = &keyProp;
+
+                ULONG flags(0);
+                rv = folderTable->Restrict(&restriction, flags);
+                if (HR_SUCCEEDED(rv)) {
+                    SizedSPropTagArray(1, columns) = {1, {PR_ENTRYID}};
+                    rv = folderTable->SetColumns(reinterpret_cast<LPSPropTagArray>(&columns), 0);
+                    if (HR_SUCCEEDED(rv)) {
+                        SRowSet *rows(0);
+                        if (folderTable->QueryRows(1, 0, &rows) == S_OK) {
+                            if (rows->cRows == 1) {
+                                MapiEntryId entryId(rows->aRow[0].lpProps[0].Value.bin.lpb, rows->aRow[0].lpProps[0].Value.bin.cb);
+
+                                 // Open the folder using its entry ID
+                                result = openFolder(error, entryId);
+                            } else {
+                                *error = QMessageManager::InvalidId;
+                            }
+                            FreeProws(rows);
+                        } else {
+                            *error = QMessageManager::InvalidId;
+                        }
+                    } else {
+                        *error = QMessageManager::ContentInaccessible;
+                        qWarning() << "Unable to set columns for folder table";
+                    }
+                } else {
+                    *error = QMessageManager::ContentInaccessible;
+                    qWarning() << "Unable to restrict folder table";
+                }
+
+                mapiRelease(folderTable);
+            } else {
+                *error = QMessageManager::ContentInaccessible;
+                qWarning() << "Unable to open hierarchy table for store.";
+            }
+        }
+    }
+
+    return result;
+#else
+    foreach (const QMessageFolderId &folderId, folderIds(error)) {
+        MapiRecordKey key = QMessageFolderIdPrivate::folderRecordKey(folderId);
+        if (key == recordKey) {
+            MapiEntryId entryId = QMessageFolderIdPrivate::entryId(folderId);
+            MapiFolderPtr folder(openFolder(error, entryId));
+            return folder;
+        }
+    }
+
+    return MapiFolderPtr();
+#endif
+}
+
+bool MapiStore::supports(ULONG featureFlag) const
+{
+    LONG supportMask(0);
+
+    if (getMapiProperty(store(), PR_STORE_SUPPORT_MASK, &supportMask)) {
+        return supportMask & featureFlag;
+    }
+
+    // Otherwise, we can't query the store support, so just assume that the feature is supported
+    return true;
+}
+
+IMessage *MapiStore::openMessage(QMessageManager::Error *error, const MapiEntryId &entryId)
+{
+    IMessage *message(0);
+
+    LPENTRYID entryIdPtr(reinterpret_cast<LPENTRYID>(const_cast<char*>(entryId.data())));
+    ULONG objectType(0);
+    HRESULT rv = _store->OpenEntry(entryId.count(), entryIdPtr, 0, MAPI_BEST_ACCESS, &objectType, reinterpret_cast<LPUNKNOWN*>(&message));
+    if (HR_SUCCEEDED(rv)) {
+        if (objectType != MAPI_MESSAGE) {
+            qWarning() << "Not a message - wrong object type:" << objectType;
+            mapiRelease(message);
+            *error = QMessageManager::InvalidId;
+        }
+    } else {
+        *error = QMessageManager::InvalidId;
+        qWarning() << "Invalid message entryId:" << entryId.toBase64();
+    }
+
+    return message;
+}
+
+IMAPIFolder *MapiStore::openMapiFolder(QMessageManager::Error *error, const MapiEntryId &entryId) const
+{
+    IMAPIFolder *folder(0);
+
+    // TODO:See MS KB article 312013, OpenEntry is not re-entrant, also true of MAPI functions in general?
+    // TODO:See ms859378, GetPropsExample for alternative memory allocation technique
+    LPENTRYID entryIdPtr(reinterpret_cast<LPENTRYID>(const_cast<char*>(entryId.data())));
+    ULONG objectType(0);
+    HRESULT rv = _store->OpenEntry(entryId.count(), entryIdPtr, 0, MAPI_BEST_ACCESS, &objectType, reinterpret_cast<LPUNKNOWN*>(&folder));
+    if (HR_SUCCEEDED(rv)) {
+        if (objectType != MAPI_FOLDER) {
+            qWarning() << "Not a folder - wrong object type:" << objectType;
+            mapiRelease(folder);
+            *error = QMessageManager::InvalidId;
+        }
+    } else {
+        *error = QMessageManager::InvalidId;
+        qWarning() << "Invalid folder entryId:" << entryId.toBase64();
+    }
+
+    return folder;
+}
+
+
+QMessageAccountId MapiStore::id() const
+{
+#ifdef _WIN32_WCE
+    return QMessageAccountIdPrivate::from(_entryId);
+#else
+    return QMessageAccountIdPrivate::from(_key);
+#endif
+}
+
+QMessage::TypeFlags MapiStore::types() const
+{
+    QMessage::TypeFlags flags(QMessage::Email);
+
+#ifdef _WIN32_WCE
+    if (name().toUpper() == "SMS") {
+        // On Windows Mobile SMS store is named "SMS"
+        flags = QMessage::Sms;
+    }
+#endif
+
+    return flags;
+}
+
+// TODO: can we remove this?
+QMessageAddress MapiStore::address() const
+{
+    QMessageAddress result;
+
+
+    return result;
+}
+
+MapiSessionPtr MapiStore::session() const
+{
+    return _session.toStrongRef();
+}
+
+MapiFolderPtr MapiStore::rootFolder(QMessageManager::Error *error) const
+{
+    MapiFolderPtr result;
+
+    MapiEntryId entryId(rootFolderId(error));
+    if (*error == QMessageManager::NoError) {
+        result = openFolder(error, entryId);
+    }
+
+    return result;
+}
+
+MapiEntryId MapiStore::rootFolderId(QMessageManager::Error *error) const
+{
+    MapiEntryId result;
+
+    if (!_valid || !_store) {
+        Q_ASSERT(_valid && _store);
+        *error = QMessageManager::FrameworkFault;
+        return result;
+    }
+
+    MapiEntryId entryId;
+    if (getMapiProperty(_store, PR_IPM_SUBTREE_ENTRYID, &entryId)) {
+        result = entryId;
+    } else {
+        *error = QMessageManager::ContentInaccessible;
+        qWarning() << "Unable to get root folder entry ID for store:" << _name;
+    }
+
+    return result;
+}
+
+MapiFolderPtr MapiStore::receiveFolder(QMessageManager::Error *error) const
+{
+    MapiFolderPtr result;
+
+    MapiEntryId entryId(receiveFolderId(error));
+    if (*error == QMessageManager::NoError) {
+        result = openFolder(error, entryId);
+    }
+
+    return result;
+}
+
+MapiEntryId MapiStore::receiveFolderId(QMessageManager::Error *error) const
+{
+    MapiEntryId result;
+
+    if (!_valid || !_store) {
+        Q_ASSERT(_valid && _store);
+        *error = QMessageManager::FrameworkFault;
+        return result;
+    }
+
+    ULONG entryIdSize = 0;
+    LPENTRYID entryIdPtr = 0;
+    HRESULT rv = _store->GetReceiveFolder(0, 0, &entryIdSize, &entryIdPtr, 0);
+    if (HR_SUCCEEDED(rv)) {
+        result = MapiEntryId(entryIdPtr, entryIdSize);
+
+        MAPIFreeBuffer(entryIdPtr);
+    } else {
+        *error = QMessageManager::FrameworkFault;
+    }
+
+    return result;
+}
+
+QMessageFolder MapiStore::folder(QMessageManager::Error *error, const QMessageFolderId& id) const
+{
+    return _session.toStrongRef()->folder(error, id);
+}
+
+QMessage MapiStore::message(QMessageManager::Error *error, const QMessageId& id) const
+{
+    return _session.toStrongRef()->message(error, id);
+}
+
+QMessage::StandardFolder MapiStore::standardFolder(const MapiEntryId &entryId) const
+{
+    QMap<QMessage::StandardFolder, MapiEntryId>::const_iterator it = _standardFolderId.begin(), end = _standardFolderId.end();
+    for ( ; it != end; ++it) {
+        if (_session.toStrongRef()->equal(it.value(), entryId)) {
+            return it.key();
+        }
+    }
+
+    return QMessage::InboxFolder;
+}
+
+bool MapiStore::setAdviseSink(ULONG mask, IMAPIAdviseSink *sink)
+{
+    if (_adviseConnection != 0) {
+        _store->Unadvise(_adviseConnection);
+    }
+
+    HRESULT rv = _store->Advise(0, 0, mask, sink, &_adviseConnection);
+    if (HR_FAILED(rv)) {
+        qWarning() << "Unable to register for notifications from store.";
+        return false;
+    }
+
+    return true;
+}
+
+void MapiStore::notifyEvents(ULONG mask)
+{
+    // Test whether this store supports notifications
+
+    if (supports(STORE_NOTIFY_OK)) {
+        AdviseSink *sink(new AdviseSink(this));
+        if (setAdviseSink(mask, sink)) {
+            // sink will be deleted when the store releases it
+        } else {
+            delete sink;
+        }
+    } else {
+        qWarning() << "Store does not support notifications.";
+    }
+
+ }
+
+#ifdef _WIN32_WCE
+
+QString MapiStore::transportName() const
+{
+    QString result;
+        if(!getMapiProperty(_store,PR_CE_TRANSPORT_NAME,&result))
+            qWarning() << "Could not query transport name for store " << name();
+
+    return result;
+}
+
+#endif
+
+HRESULT MapiStore::AdviseSink::QueryInterface(REFIID id, LPVOID FAR* o)
+{
+    if (id == IID_IUnknown) {
+        *o = this;
+        AddRef();
+        return S_OK;
+    }
+
+    return E_NOINTERFACE;
+}
+
+ULONG MapiStore::AdviseSink::AddRef()
+{
+    return InterlockedIncrement(&_refCount);
+}
+
+ULONG MapiStore::AdviseSink::Release()
+{
+    ULONG result = InterlockedDecrement(&_refCount);
+    if (result == 0) {
+        delete this;
+    }
+
+    return result;
+}
+
+ULONG MapiStore::AdviseSink::OnNotify(ULONG notificationCount, LPNOTIFICATION notifications)
+{
+    MapiSessionPtr session = _store->_session.toStrongRef();
+    if (!session.isNull()) {
+        for (uint i = 0; i < notificationCount; ++i) {
+            NOTIFICATION &notification(notifications[i]);
+
+            if (notification.ulEventType == fnevNewMail) {
+                // TODO: Do we need to process this, or is handling object-created sufficient?
+                //NEWMAIL_NOTIFICATION &newmail(notification.info.newmail);
+            } else {
+                OBJECT_NOTIFICATION &object(notification.info.obj);
+
+                if (object.ulObjType == MAPI_MESSAGE) {
+                    MapiEntryId entryId(object.lpEntryID, object.cbEntryID);
+
+                    if (!entryId.isEmpty()) {
+                        // Create a partial ID from the information we have (a message can be
+                        // retrieved using only the store key and the message entry ID)
+#ifdef _WIN32_WCE
+                        QMessageId messageId(QMessageIdPrivate::from(_store->entryId(),entryId));
+#else
+                        QMessageId messageId(QMessageIdPrivate::from(_store->recordKey(), entryId));
+#endif
+
+                        MapiSession::NotifyType notifyType;
+                        switch (notification.ulEventType)
+                        {
+                        case fnevObjectCopied: notifyType = MapiSession::Added; break;
+                        case fnevObjectCreated: notifyType = MapiSession::Added; break;
+                        case fnevObjectDeleted: notifyType = MapiSession::Removed; break;
+                        case fnevObjectModified: notifyType = MapiSession::Updated; break;
+                        case fnevObjectMoved: notifyType = MapiSession::Updated; break;
+                        }
+
+                        // Create an event to be processed by the UI thread
+                        QCoreApplication::postEvent(session.data(), new MapiSession::NotifyEvent(_store, messageId, notifyType));
+                    } else {
+                        qWarning() << "Received notification, but no entry ID";
+                    }
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+class SessionManager : public QObject
+{
+    Q_OBJECT
+
+public:
+    SessionManager();
+    bool initialize(MapiSession *newSession);
+
+    const MapiSessionPtr &session() const;
+
+private slots:
+    void appDestroyed();
+
+private:
+    MapiSessionPtr ptr;
+};
+
+SessionManager::SessionManager()
+{
+    connect(QCoreApplication::instance(), SIGNAL(destroyed()), this, SLOT(appDestroyed()));
+}
+
+bool SessionManager::initialize(MapiSession *newSession)
+{
+    if(newSession->_mapiSession)
+    {
+        ptr = MapiSessionPtr(newSession);
+        ptr->_self = ptr;
+        return true;
+    }
+    return false;
+}
+
+const MapiSessionPtr &SessionManager::session() const
+{
+    return ptr;
+}
+
+void SessionManager::appDestroyed()
+{
+    // We need to terminate the MAPI session before main ends
+    ptr.clear();
+}
+
+Q_GLOBAL_STATIC(SessionManager, manager);
+
+MapiSessionPtr MapiSession::createSession(QMessageManager::Error *error)
+{
+    static bool initialized(manager()->initialize(new MapiSession(error)));
+
+    MapiSessionPtr ptr(manager()->session());
+    if (ptr.isNull()) {
+        if (*error == QMessageManager::NoError) {
+            *error = QMessageManager::ContentInaccessible;
+        }
+        qWarning() << "Unable to instantiate session";
+    }
+
+    return ptr;
+}
+
+MapiSession::MapiSession()
+    :_token(0),
+     _mapiSession(0),
+     _filterId(0),
+     _registered(false)
+{
+}
+
+MapiSession::MapiSession(QMessageManager::Error *error)
+    :QObject(),
+     _token(WinHelpers::initializeMapi()),
+     _mapiSession(0),
+     _filterId(0),
+     _registered(false)
+{
+    if (!_token->_initialized) {
+        *error = QMessageManager::ContentInaccessible;
+    } else {
+        // Attempt to start a MAPI session on the default profile
+        HRESULT rv = MAPILogonEx(0, (LPTSTR)0, 0, MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION, &_mapiSession);
+        if (HR_FAILED(rv)) {
+            qWarning() << "Failed to login to MAPI session - rv:" << hex << (ULONG)rv;
+            *error = QMessageManager::ContentInaccessible;
+            _mapiSession = 0;
+        }
+
+        // This thread must run the message pump for the hidden MAPI window
+        // http://blogs.msdn.com/stephen_griffin/archive/2009/05/22/the-fifth-mapi-multithreading-rule.aspx
+        dispatchNotifications();
+    }
+}
+
+MapiSession::~MapiSession()
+{
+    _storeMap.clear();
+
+    if (_mapiSession) {
+        _mapiSession->Logoff(0, 0, 0);
+        mapiRelease(_mapiSession);
+    }
+}
+
+MapiStorePtr MapiSession::findStore(QMessageManager::Error *error, const QMessageAccountId &id, bool cachedMode) const
+{
+    MapiStorePtr result(0);
+    if (!_mapiSession) {
+        Q_ASSERT(_mapiSession);
+        *error = QMessageManager::FrameworkFault;
+        return result;
+    }
+
+    IMAPITable *mapiMessageStoresTable(0);
+    if (_mapiSession->GetMsgStoresTable(0, &mapiMessageStoresTable) != S_OK) {
+        *error = QMessageManager::ContentInaccessible;
+        return result;
+    }
+
+    const int nCols(3);
+    enum { defaultStoreColumn = 0, entryIdColumn, recordKeyColumn };
+    SizedSPropTagArray(nCols, columns) = {nCols, {PR_DEFAULT_STORE, PR_ENTRYID, PR_RECORD_KEY}};
+
+    QueryAllRows qar(mapiMessageStoresTable, reinterpret_cast<LPSPropTagArray>(&columns), 0, 0, false);
+
+    while(qar.query()) {
+        for (uint n = 0; n < qar.rows()->cRows; ++n) {
+            SPropValue *props = qar.rows()->aRow[n].lpProps;
+#ifdef _WIN32_WCE
+            MapiEntryId storeKey(props[entryIdColumn].Value.bin.lpb, props[entryIdColumn].Value.bin.cb);
+#else
+            MapiRecordKey storeKey(props[recordKeyColumn].Value.bin.lpb, props[recordKeyColumn].Value.bin.cb);
+#endif
+
+            if ((!id.isValid() && props[defaultStoreColumn].Value.b) ||  // default store found
+                (id.isValid() && (id == QMessageAccountIdPrivate::from(storeKey)))) {    // specified store found
+                MapiEntryId entryId(props[entryIdColumn].Value.bin.lpb, props[entryIdColumn].Value.bin.cb);
+                result = openStore(error, entryId, cachedMode);
+                break;
+            }
+        }
+    }
+
+    *error = qar.error();
+
+    mapiRelease(mapiMessageStoresTable);
+
+    return result;
+}
+
+template<typename Predicate, typename Ordering>
+QList<MapiStorePtr> MapiSession::filterStores(QMessageManager::Error *error, Predicate predicate, Ordering sortOrder, uint limit, uint offset, bool cachedMode) const
+{
+    if (!predicate._filter.isSupported()) {
+        *error = QMessageManager::ConstraintFailure;
+        return QList<MapiStorePtr>();
+    }
+
+    QList<MapiStorePtr> result;
+    if (!_mapiSession) {
+        Q_ASSERT(_mapiSession);
+        *error = QMessageManager::FrameworkFault;
+        return result;
+    }
+
+    IMAPITable *mapiMessageStoresTable(0);
+    if (_mapiSession->GetMsgStoresTable(0, &mapiMessageStoresTable) != S_OK) {
+        *error = QMessageManager::ContentInaccessible;
+    } else {
+        SizedSPropTagArray(1, columns) = {1, {PR_ENTRYID}};
+
+        QueryAllRows qar(mapiMessageStoresTable, reinterpret_cast<LPSPropTagArray>(&columns), 0, 0, false);
+
+        while (qar.query()) {
+            for (uint n = 0; n < qar.rows()->cRows; ++n) {
+                MapiEntryId entryId(qar.rows()->aRow[n].lpProps[0].Value.bin.lpb, qar.rows()->aRow[n].lpProps[0].Value.bin.cb);
+                MapiStorePtr store(openStore(error, entryId, cachedMode));
+                if (!store.isNull()) {
+#ifndef _WIN32_WCE
+                    // We only want stores that contain private messages
+                    if (store->supports(STORE_PUBLIC_FOLDERS)) {
+                        continue;
+                    }
+#endif
+                    if (predicate(store)) {
+                        result.append(store);
+                    }
+                }
+            }
+        }
+
+        *error = qar.error();
+
+        mapiRelease(mapiMessageStoresTable);
+    }
+
+    if (!sortOrder.isEmpty()) {
+        QList<StoreSortHelper> accountList;
+        foreach (MapiStorePtr storePtr, result) {
+            accountList.append(StoreSortHelper(&sortOrder, storePtr));
+        }
+        qSort(accountList.begin(), accountList.end());
+        result.clear();
+        foreach (StoreSortHelper alt, accountList) {
+            result.append(alt.store());
+        }
+        accountList.clear();
+    }
+
+    // See note in filerMessages explaining why IMAPITable::SeekRow can't be used to skip unwanted items
+    if (offset) {
+        return result.mid(offset, (limit ? limit : -1));
+    } else {
+        return result;
+    }
+}
+
+QList<MapiStorePtr> MapiSession::filterStores(QMessageManager::Error *error, const QMessageAccountFilter &filter, const QMessageAccountSortOrder &sortOrder, uint limit, uint offset, bool cachedMode) const
+{
+    AccountFilterPredicate pred(filter);
+    return filterStores<const AccountFilterPredicate&, const QMessageAccountSortOrder &>(error, pred, sortOrder, limit, offset, cachedMode);
+}
+
+QList<MapiStorePtr> MapiSession::allStores(QMessageManager::Error *error, bool cachedMode) const
+{
+    return filterStores(error, QMessageAccountFilter(), QMessageAccountSortOrder(), 0, 0, cachedMode);
+}
+
+QList<MapiFolderPtr> MapiSession::filterFolders(QMessageManager::Error *error, const QMessageFolderFilter &filter, const QMessageFolderSortOrder &sortOrder, uint limit, uint offset, bool cachedMode) const
+{
+    if (!filter.isSupported()) {
+        *error = QMessageManager::ConstraintFailure;
+        return QList<MapiFolderPtr>();
+    }
+
+    QList<MapiFolderPtr> result;
+    if (!_mapiSession) {
+        Q_ASSERT(_mapiSession);
+        *error = QMessageManager::FrameworkFault;
+        return result;
+    }
+
+    foreach (const MapiStorePtr &store, allStores(error, cachedMode)) {
+        result.append(store->filterFolders(error, filter));
+    }
+
+    if (!sortOrder.isEmpty()) {
+        QList<FolderSortHelper> folderList;
+        foreach (MapiFolderPtr folderPtr, result) {
+            QMessageFolder orderFolder(folder(error, folderPtr->id()));
+            if (*error != QMessageManager::NoError) {
+                folderList.clear();
+                break;
+            }
+            folderList.append(FolderSortHelper(&sortOrder, folderPtr, orderFolder));
+        }
+        qSort(folderList.begin(), folderList.end());
+        result.clear();
+        foreach (FolderSortHelper helper, folderList) {
+            result.append(helper.mapiFolderPtr());
+        }
+        folderList.clear();
+    }
+
+    // See note in filerMessages explaining why IMAPITable::SeekRow can't be used to skip unwanted items
+    if (offset) {
+        return result.mid(offset, (limit ? limit : -1));
+    } else {
+        return result;
+    }
+}
+
+IMsgStore *MapiSession::openMapiStore(QMessageManager::Error *error, const MapiEntryId &entryId, bool cachedMode) const
+{
+    IMsgStore *store(0);
+
+    LPENTRYID entryIdPtr(reinterpret_cast<LPENTRYID>(const_cast<char*>(entryId.data())));
+    unsigned long openFlags = MAPI_BEST_ACCESS | MDB_WRITE;
+
+    if(!cachedMode)
+        openFlags |= MDB_ONLINE;
+
+    HRESULT rv = _mapiSession->OpenMsgStore(0, entryId.count(), entryIdPtr, 0, openFlags, reinterpret_cast<LPMDB*>(&store));
+    if (HR_FAILED(rv)) {
+        *error = QMessageManager::InvalidId;
+        qWarning() << "Invalid store entryId:" << entryId.toBase64();
+    }
+
+    return store;
+}
+
+MapiStorePtr MapiSession::openStore(QMessageManager::Error *error, const MapiEntryId& entryId, bool cachedMode) const
+{
+    MapiStorePtr result(0);
+
+    // See if we can create a new pointer to an existing store
+    MapiStorePtr &existing = _storeMap[entryId];
+    if (!existing.isNull()) {
+        return existing;
+    }
+
+    // We need to create a new instance
+    IMsgStore *store = openMapiStore(error, entryId, cachedMode);
+    if (store && (*error == QMessageManager::NoError)) {
+        // Find the other properties of this store
+        SizedSPropTagArray(2, columns) = {2, {PR_DISPLAY_NAME, PR_RECORD_KEY}};
+        SPropValue *properties(0);
+        ULONG count;
+        HRESULT rv = store->GetProps(reinterpret_cast<LPSPropTagArray>(&columns), MAPI_UNICODE, &count, &properties);
+        if (HR_SUCCEEDED(rv)) {
+            QString name(QStringFromLpctstr(properties[0].Value.LPSZ));
+            MapiRecordKey recordKey(properties[1].Value.bin.lpb, properties[1].Value.bin.cb);
+
+            MapiStorePtr storePtr = MapiStore::createStore(error, _self.toStrongRef(), store, recordKey, entryId, name, cachedMode);
+            if (*error == QMessageManager::NoError) {
+                result = storePtr;
+
+                // Add to the map
+                _storeMap.insert(entryId, result);
+            } else {
+                qWarning() << "Error creating store object";
+            }
+
+            MAPIFreeBuffer(properties);
+        } else {
+            qWarning() << "Unable to access store properties";
+            mapiRelease(store);
+        }
+    }
+
+    return result;
+}
+
+MapiStorePtr MapiSession::openStoreWithKey(QMessageManager::Error *error, const MapiRecordKey &recordKey, bool cachedMode) const
+{
+    MapiStorePtr result;
+
+    IMAPITable *storesTable(0);
+    HRESULT rv = _mapiSession->GetMsgStoresTable(0, &storesTable);
+    if (HR_SUCCEEDED(rv)) {
+        // Find the entry ID corresponding to this recordKey
+        SPropValue keyProp = { 0 };
+        keyProp.ulPropTag = PR_RECORD_KEY;
+        keyProp.Value.bin.cb = recordKey.count();
+        keyProp.Value.bin.lpb = reinterpret_cast<LPBYTE>(const_cast<char*>(recordKey.data()));
+
+        SRestriction restriction = { 0 };
+        restriction.rt = RES_PROPERTY;
+        restriction.res.resProperty.relop = RELOP_EQ;
+        restriction.res.resProperty.ulPropTag = PR_RECORD_KEY;
+        restriction.res.resProperty.lpProp = &keyProp;
+
+        ULONG flags(0);
+        rv = storesTable->Restrict(&restriction, flags);
+        if (HR_SUCCEEDED(rv)) {
+            SizedSPropTagArray(1, columns) = {1, {PR_ENTRYID}};
+            rv = storesTable->SetColumns(reinterpret_cast<LPSPropTagArray>(&columns), 0);
+            if (HR_SUCCEEDED(rv)) {
+                SRowSet *rows(0);
+                if (storesTable->QueryRows(1, 0, &rows) == S_OK) {
+                    if (rows->cRows == 1) {
+                        MapiEntryId entryId(rows->aRow[0].lpProps[0].Value.bin.lpb, rows->aRow[0].lpProps[0].Value.bin.cb);
+
+                        result = openStore(error, entryId, cachedMode);
+                    } else {
+                        *error = QMessageManager::InvalidId;
+                    }
+                    FreeProws(rows);
+                } else {
+                    *error = QMessageManager::InvalidId;
+                }
+            } else {
+                *error = QMessageManager::ContentInaccessible;
+                qWarning() << "Unable to set columns for stores table";
+            }
+        } else {
+            *error = QMessageManager::ContentInaccessible;
+            qWarning() << "Unable to restrict stores table";
+        }
+
+        mapiRelease(storesTable);
+    } else {
+        *error = QMessageManager::ContentInaccessible;
+        qWarning() << "Unable to open stores table.";
+    }
+
+    return result;
+}
+
+QMessageAccountId MapiSession::defaultAccountId(QMessageManager::Error *error, QMessage::Type type) const
+{
+#ifdef _WIN32_WCE
+    if (type == QMessage::Sms) {
+        foreach (MapiStorePtr store, allStores(error)) {
+            if (store->name() == "SMS") {
+                return store->id();
+            }
+        }
+    }
+
+#endif
+    if (type == QMessage::Email) {
+        // Return the account of the default store
+        MapiStorePtr store(defaultStore(error));
+        if (*error == QMessageManager::NoError) {
+            return store->id();
+        }
+    }
+
+    return QMessageAccountId();
+}
+
+IMessage *MapiSession::openMapiMessage(QMessageManager::Error *error, const QMessageId &id, MapiStorePtr *storePtr) const
+{
+    IMessage *message(0);
+
+#ifdef _WIN32_WCE
+    MapiEntryId storeRecordKey(QMessageIdPrivate::storeRecordKey(id));
+#else
+    MapiRecordKey storeRecordKey(QMessageIdPrivate::storeRecordKey(id));
+#endif
+
+    MapiStorePtr mapiStore(findStore(error, QMessageAccountIdPrivate::from(storeRecordKey)));
+    if (*error == QMessageManager::NoError) {
+        MapiEntryId entryId(QMessageIdPrivate::entryId(id));
+
+        message = mapiStore->openMessage(error, entryId);
+        if (*error == QMessageManager::NoError) {
+            if (storePtr) {
+                *storePtr = mapiStore;
+            }
+        } else {
+            qWarning() << "Invalid message entryId:" << entryId.toBase64();
+            mapiRelease(message);
+        }
+    } else {
+        qWarning() << "Invalid store recordKey:" << storeRecordKey.toBase64();
+    }
+
+    return message;
+}
+
+MapiEntryId MapiSession::messageEntryId(QMessageManager::Error *error, const MapiRecordKey &storeKey, const MapiRecordKey &folderKey, const MapiRecordKey &messageKey)
+{
+    MapiEntryId result;
+
+    MapiStorePtr store(openStoreWithKey(error, storeKey));
+    if (*error == QMessageManager::NoError) {
+        result = store->messageEntryId(error, folderKey, messageKey);
+    }
+
+    return result;
+}
+
+MapiRecordKey MapiSession::messageRecordKey(QMessageManager::Error *error, const QMessageId &id)
+{
+    MapiRecordKey result;
+
+    IMessage *message = openMapiMessage(error, id);
+    if (*error == QMessageManager::NoError) {
+        if (!getMapiProperty(message, PR_RECORD_KEY, &result)) {
+            qWarning() << "Unable to query message record key.";
+        }
+
+        mapiRelease(message);
+    }
+
+    return result;
+}
+
+MapiRecordKey MapiSession::folderRecordKey(QMessageManager::Error *error, const QMessageId &id)
+{
+    MapiRecordKey result;
+
+    MapiStorePtr store;
+    IMessage *message = openMapiMessage(error, id, &store);
+    if (*error == QMessageManager::NoError) {
+        // Find the parent folder for the message
+        MapiEntryId folderId;
+        if (getMapiProperty(message, PR_PARENT_ENTRYID, &folderId)) {
+            MapiFolderPtr folder = store->openFolder(error, folderId);
+            if (*error == QMessageManager::NoError) {
+                result = folder->recordKey();
+            }
+        } else {
+            qWarning() << "Unable to query folder entry ID from message.";
+        }
+
+        mapiRelease(message);
+    }
+
+    return result;
+}
+
+#ifdef _WIN32_WCE
+
+MapiEntryId MapiSession::folderEntryId(QMessageManager::Error *error, const QMessageId &id)
+{
+    MapiEntryId result;
+
+    IMessage *message = openMapiMessage(error, id);
+    if (*error == QMessageManager::NoError) {
+        // Find the parent folder for the message
+        MapiEntryId folderId;
+        if (getMapiProperty(message, PR_PARENT_ENTRYID, &folderId)) {
+            result = folderId;
+        } else {
+            qWarning() << "Unable to query folder entry ID from message.";
+        }
+
+        mapiRelease(message);
+    }
+
+    return result;
+}
+
+#endif
+
+bool MapiSession::equal(const MapiEntryId &lhs, const MapiEntryId &rhs) const
+{
+    ULONG result(0);
+
+    LPENTRYID lhsEntryId(reinterpret_cast<LPENTRYID>(const_cast<char*>(lhs.data())));
+    LPENTRYID rhsEntryId(reinterpret_cast<LPENTRYID>(const_cast<char*>(rhs.data())));
+
+    HRESULT rv = _mapiSession->CompareEntryIDs(lhs.count(), lhsEntryId, rhs.count(), rhsEntryId, 0, &result);
+    if (HR_FAILED(rv)) {
+        qWarning() << "Unable to compare entry IDs.";
+    }
+
+    return (result != FALSE);
+}
+
+QMessageFolder MapiSession::folder(QMessageManager::Error *error, const QMessageFolderId& id) const
+{
+    QMessageFolder result;
+
+    if(!id.isValid())
+    {
+        *error = QMessageManager::InvalidId;
+        return result;
+    }
+
+#ifdef _WIN32_WCE
+    MapiEntryId storeRecordKey(QMessageFolderIdPrivate::storeRecordKey(id));
+#else
+    MapiRecordKey storeRecordKey(QMessageFolderIdPrivate::storeRecordKey(id));
+#endif
+    MapiStorePtr mapiStore(findStore(error, QMessageAccountIdPrivate::from(storeRecordKey)));
+    if (*error != QMessageManager::NoError)
+        return result;
+
+    // Find the root folder for this store
+    MapiFolderPtr storeRoot(mapiStore->rootFolder(error));
+    if (*error != QMessageManager::NoError)
+        return result;
+
+    MapiEntryId entryId(QMessageFolderIdPrivate::entryId(id));
+    MapiFolderPtr folder = mapiStore->openFolder(error, entryId);
+    if (folder && (*error == QMessageManager::NoError)) {
+#ifndef _WIN32_WCE
+        SizedSPropTagArray(3, columns) = {3, {PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_RECORD_KEY}};
+#else
+        SizedSPropTagArray(2, columns) = {2, {PR_DISPLAY_NAME, PR_PARENT_ENTRYID}};
+#endif
+        SPropValue *properties(0);
+        ULONG count;
+        HRESULT rv = folder->folder()->GetProps(reinterpret_cast<LPSPropTagArray>(&columns), MAPI_UNICODE, &count, &properties);
+        if (HR_SUCCEEDED(rv)) {
+            QString name(QStringFromLpctstr(properties[0].Value.LPSZ));
+            MapiEntryId parentEntryId(properties[1].Value.bin.lpb, properties[1].Value.bin.cb);
+#ifndef _WIN32_WCE
+            MapiRecordKey folderKey(properties[2].Value.bin.lpb, properties[2].Value.bin.cb);
+#else
+            MapiRecordKey folderKey;
+#endif
+
+            MAPIFreeBuffer(properties);
+
+            QMessageFolderId folderId(QMessageFolderIdPrivate::from(folderKey, storeRecordKey, entryId));
+
+            if (equal(parentEntryId, storeRoot->entryId())) {
+                // This folder is a direct child of the root folder
+                QMessageAccountId accountId(QMessageAccountIdPrivate::from(storeRecordKey));
+                return QMessageFolderPrivate::from(folderId, accountId, QMessageFolderId(), name, name);
+            }
+
+            QStringList path;
+            path.append(name);
+
+            QMessageFolderId parentId;
+            MapiEntryId ancestorEntryId(parentEntryId);
+            MapiFolderPtr ancestorFolder;
+
+            // Iterate through ancestors towards the root
+            while ((ancestorFolder = mapiStore->openFolder(error, ancestorEntryId)) &&
+                   (ancestorFolder && (*error == QMessageManager::NoError))) {
+                SPropValue *ancestorProperties(0);
+                if (ancestorFolder->folder()->GetProps(reinterpret_cast<LPSPropTagArray>(&columns), MAPI_UNICODE, &count, &ancestorProperties) == S_OK) {
+#ifndef _WIN32_WCE
+                    SPropValue &ancestorRecordKeyProp(ancestorProperties[2]);
+                    MapiRecordKey ancestorRecordKey(ancestorRecordKeyProp.Value.bin.lpb, ancestorRecordKeyProp.Value.bin.cb);
+                    bool reachedRoot(ancestorRecordKey == storeRoot->recordKey());
+#else
+                    MapiRecordKey ancestorRecordKey;
+                    bool reachedRoot(equal(ancestorEntryId, storeRoot->entryId()));
+#endif
+
+                    if (ancestorEntryId == parentEntryId) {
+                        // This ancestor is the parent of the folder being retrieved, create a QMessageFolderId for the parent
+                        parentId = QMessageFolderIdPrivate::from(ancestorRecordKey, storeRecordKey, parentEntryId);
+                    }
+
+                    SPropValue &entryIdProp(ancestorProperties[1]);
+                    ancestorEntryId = MapiEntryId(entryIdProp.Value.bin.lpb, entryIdProp.Value.bin.cb);
+
+                    QString ancestorName(QStringFromLpctstr(ancestorProperties[0].Value.LPSZ));
+
+                    MAPIFreeBuffer(ancestorProperties);
+
+                    if (reachedRoot) {
+                        // Reached the root and have a complete path for the folder being retrieved
+                        QMessageAccountId accountId(QMessageAccountIdPrivate::from(storeRecordKey));
+                        return QMessageFolderPrivate::from(folderId, accountId, parentId, name, path.join("/"));
+                    }
+
+                    // Prepare to consider next ancestor
+                    if (!ancestorName.isEmpty())
+                        path.prepend(ancestorName);
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+
+    // Failed to quickly retrieve the folder, fallback to an exhaustive search of all folders
+    result = mapiStore->folderFromId(error, id);
+    return result;
+}
+
+QMessage MapiSession::message(QMessageManager::Error *error, const QMessageId& id) const
+{
+    Q_UNUSED(error);
+
+    QMessage result = QMessagePrivate::from(id);
+
+    // How do we find what type of message this is?
+    result.setType(QMessage::Email);
+
+    result.d_ptr->_elementsPresent.properties = 0;
+    result.d_ptr->_elementsPresent.recipients = 0;
+    result.d_ptr->_elementsPresent.body = 0;
+    result.d_ptr->_elementsPresent.attachments = 0;
+
+    result.d_ptr->_modified = false;
+
+    return result;
+}
+
+bool MapiSession::updateMessageProperties(QMessageManager::Error *error, QMessage *msg) const
+{
+    bool result(false);
+
+    if (!msg->d_ptr->_elementsPresent.properties) {
+        bool isModified(msg->d_ptr->_modified);
+        msg->d_ptr->_elementsPresent.properties = 1;
+
+        MapiStorePtr store;
+        IMessage *message = openMapiMessage(error, msg->id(), &store);
+        if (*error == QMessageManager::NoError) {
+#ifndef _WIN32_WCE
+            const int np = 14;
+#else
+            const int np = 12;
+#endif
+            SizedSPropTagArray(np, msgCols) = {np, { PR_PARENT_ENTRYID,
+                                                     PR_MESSAGE_FLAGS,
+                                                     PR_MSG_STATUS,
+                                                     PR_MESSAGE_CLASS,
+                                                     PR_SENDER_NAME,
+                                                     PR_SENDER_EMAIL_ADDRESS,
+                                                     PR_CLIENT_SUBMIT_TIME,
+                                                     PR_MESSAGE_DELIVERY_TIME,
+                                                     PR_SUBJECT,
+                                                     PR_HASATTACH,
+                                                     PR_PRIORITY,
+#ifndef _WIN32_WCE
+                                                     PR_MSG_EDITOR_FORMAT,
+                                                     PR_RTF_IN_SYNC,
+#endif
+                                                     PR_MESSAGE_SIZE
+                                                     }};
+            ULONG count = 0;
+            LPSPropValue properties;
+            HRESULT rv = message->GetProps(reinterpret_cast<LPSPropTagArray>(&msgCols), MAPI_UNICODE, &count, &properties);
+            if (HR_SUCCEEDED(rv)) {
+                QString senderName;
+                QString senderAddress;
+                QString messageClass;
+                MapiEntryId parentEntryId;
+
+                QMessage::StatusFlags flags(0);
+
+                //assumption that stores support only a single message type
+                msg->d_ptr->_type = store->types() & QMessage::Sms ? QMessage::Sms : QMessage::Email;
+
+                for (ULONG n = 0; n < count; ++n) {
+                    SPropValue &prop(properties[n]);
+
+                    switch (prop.ulPropTag) {
+                    case PR_MESSAGE_FLAGS:
+                        if (prop.Value.ul & MSGFLAG_READ) {
+                            flags |= QMessage::Read;
+                        }
+                        break;
+                    case PR_PARENT_ENTRYID:
+                        parentEntryId = MapiEntryId(prop.Value.bin.lpb, prop.Value.bin.cb);
+                        break;
+                    case PR_MSG_STATUS:
+                        if (prop.Value.l & (MSGSTATUS_DELMARKED | MSGSTATUS_REMOTE_DELETE)) {
+                            flags |= QMessage::Removed;
+                        }
+#ifdef _WIN32_WCE
+                        if (prop.Value.l & MSGSTATUS_HAS_PR_BODY) {
+                            msg->d_ptr->_contentFormat = EDITOR_FORMAT_PLAINTEXT;
+#if(_WIN32_WCE > 0x501)
+                        } else if (prop.Value.l & MSGSTATUS_HAS_PR_BODY_HTML) {
+                            msg->d_ptr->_contentFormat = EDITOR_FORMAT_HTML;
+#endif
+                        } else if (prop.Value.l & MSGSTATUS_HAS_PR_CE_MIME_TEXT) {
+                            // This is how MS providers store HTML, as per http://msdn.microsoft.com/en-us/library/bb446140.aspx
+                            msg->d_ptr->_contentFormat = EDITOR_FORMAT_MIME;
+                        }
+#endif
+                        break;
+                    case PR_MESSAGE_CLASS:
+                        messageClass = QStringFromLpctstr(prop.Value.LPSZ);
+                        break;
+                    case PR_SENDER_NAME:
+                        senderName = QStringFromLpctstr(prop.Value.LPSZ);
+                        break;
+                    case PR_SENDER_EMAIL_ADDRESS:
+                        senderAddress = QStringFromLpctstr(prop.Value.LPSZ);
+                        break;
+                    case PR_CLIENT_SUBMIT_TIME:
+                        msg->setDate(fromFileTime(prop.Value.ft));
+                        break;
+                    case PR_MESSAGE_DELIVERY_TIME:
+                        msg->setReceivedDate(fromFileTime(prop.Value.ft));
+                        break;
+                    case PR_SUBJECT:
+                        msg->setSubject(QStringFromLpctstr(prop.Value.LPSZ));
+                        break;
+                    case PR_HASATTACH:
+                        msg->d_ptr->_hasAttachments = (prop.Value.b != FALSE);
+                        if (prop.Value.b) {
+                            flags |= QMessage::HasAttachments;
+                        }
+                        break;
+                    case PR_PRIORITY:
+                        if (prop.Value.l == PRIO_URGENT) {
+                            msg->setPriority(QMessage::HighPriority);
+                        } else if (prop.Value.l == PRIO_NONURGENT) {
+                            msg->setPriority(QMessage::LowPriority);
+                        } else {
+                            msg->setPriority(QMessage::NormalPriority);
+                        }
+                        break;
+#ifndef _WIN32_WCE
+                    case PR_MSG_EDITOR_FORMAT:
+                        msg->d_ptr->_contentFormat = prop.Value.l;
+                        break;
+                    case PR_RTF_IN_SYNC:
+                        msg->d_ptr->_rtfInSync = (prop.Value.b != FALSE);;
+                        break;
+#endif
+                    case PR_MESSAGE_SIZE:
+                        QMessagePrivate::setSize(*msg, prop.Value.l);
+                        break;
+                    default:
+                        break;
+                    }
+                }
+
+                msg->setStatus(flags);
+                msg->setParentAccountId(store->id());
+
+                if (!senderName.isEmpty() || !senderAddress.isEmpty()) {
+                    msg->setFrom(createAddress(senderName, senderAddress));
+                    QMessagePrivate::setSenderName(*msg, senderName);
+                }
+
+                if (!parentEntryId.isEmpty()) {
+                    QMessagePrivate::setStandardFolder(*msg, store->standardFolder(parentEntryId));
+
+                    // MAPI does not support the notion of incoming/outgoing messages.  Instead, we
+                    // will treat Outgoing as meaning 'in the Sent folder'
+                    msg->setStatus(QMessage::Incoming, (msg->standardFolder() != QMessage::SentFolder));
+
+                    MapiFolderPtr parentFolder(store->openFolder(error, parentEntryId));
+                    if (parentFolder) {
+                        QMessagePrivate::setParentFolderId(*msg, parentFolder->id());
+                    }
+                }
+
+                if (!isModified) {
+                    msg->d_ptr->_modified = false;
+                }
+                result = true;
+
+                MAPIFreeBuffer(properties);
+            } else {
+                *error = QMessageManager::ContentInaccessible;
+            }
+
+            mapiRelease(message);
+        }
+    }
+
+    return result;
+}
+
+bool MapiSession::updateMessageRecipients(QMessageManager::Error *error, QMessage *msg) const
+{
+    bool result(false);
+
+    if (!msg->d_ptr->_elementsPresent.recipients) {
+        bool isModified(msg->d_ptr->_modified);
+        msg->d_ptr->_elementsPresent.recipients = 1;
+
+        IMessage *message = openMapiMessage(error, msg->id());
+        if (*error == QMessageManager::NoError) {
+            // Extract the recipients for the message
+            IMAPITable *recipientsTable(0);
+            HRESULT rv = message->GetRecipientTable(0, &recipientsTable);
+            if (HR_SUCCEEDED(rv)) {
+                QMessageAddressList to;
+                QMessageAddressList cc;
+                QMessageAddressList bcc;
+
+#ifndef _WIN32_WCE
+                SizedSPropTagArray(3, rcpCols) = {3, { PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_TYPE}};
+
+                QueryAllRows qar(recipientsTable, reinterpret_cast<LPSPropTagArray>(&rcpCols), 0, 0);
+#else
+                // CE does not support SetColumns on recipient tables
+                QueryAllRows qar(recipientsTable, 0, 0, 0, false);
+#endif
+
+                while(qar.query()) {
+                    for (uint n = 0; n < qar.rows()->cRows; ++n) {
+                        QString name;
+                        QString address;
+                        LONG type(0);
+
+                        SPropValue *props = qar.rows()->aRow[n].lpProps;
+#ifndef _WIN32_WCE
+                        if (props[0].ulPropTag == PR_DISPLAY_NAME)
+                            name = QStringFromLpctstr(props[0].Value.LPSZ);
+                        if (props[1].ulPropTag == PR_EMAIL_ADDRESS)
+                            address = QStringFromLpctstr(props[1].Value.LPSZ);
+                        if (props[2].ulPropTag == PR_RECIPIENT_TYPE)
+                            type = props[2].Value.l;
+#else
+                        for (uint i = 0; i < qar.rows()->aRow[n].cValues; ++i) {
+                            if (props[i].ulPropTag == PR_DISPLAY_NAME) {
+                                name = QStringFromLpctstr(props[i].Value.LPSZ);
+                            } else if (props[i].ulPropTag == PR_EMAIL_ADDRESS) {
+                                address = QStringFromLpctstr(props[i].Value.LPSZ);
+                            } else if (props[i].ulPropTag == PR_RECIPIENT_TYPE){
+                                type = props[i].Value.l;
+                            }
+                        }
+#endif
+
+                        if (!name.isEmpty() || !address.isEmpty()) {
+                            QMessageAddressList *list = 0;
+
+                            switch (type) {
+                            case MAPI_TO:
+                                list = &to;
+                                break;
+                            case MAPI_CC:
+                                list = &cc;
+                                break;
+                            case MAPI_BCC:
+                                list = &bcc;
+                                break;
+                            default:
+                                break;
+                            }
+
+                            if (list) {
+                                list->append(createAddress(name, address));
+                            }
+                        }
+                    }
+                }
+
+                if (!to.isEmpty()) {
+                    msg->setTo(to);
+                }
+                if (!cc.isEmpty()) {
+                    msg->setCc(cc);
+                }
+                if (!bcc.isEmpty()) {
+                    msg->setBcc(bcc);
+                }
+
+                if (!isModified) {
+                    msg->d_ptr->_modified = false;
+                }
+
+                if (qar.error() != QMessageManager::NoError) {
+                    *error = qar.error();
+                    result = false;
+                } else {
+                    result = true;
+                }
+
+                mapiRelease(recipientsTable);
+            } else {
+#ifdef _WIN32_WCE
+                if (rv == MAPI_E_NO_RECIPIENTS) {
+                    updateMessageProperties(error, msg);
+                    if (*error == QMessageManager::NoError) {
+                        if (msg->d_ptr->_contentFormat == EDITOR_FORMAT_MIME) {
+                            // The recipient info can be extracted from the MIME body
+                            updateMessageBody(error, msg);
+                        }
+                    }
+                } else {
+#endif
+                    *error = QMessageManager::ContentInaccessible;
+#ifdef _WIN32_WCE
+                }
+#endif
+            }
+
+            mapiRelease(message);
+        }
+    }
+
+    return result;
+}
+
+bool MapiSession::updateMessageBody(QMessageManager::Error *error, QMessage *msg) const
+{
+    bool result(false);
+
+    // TODO: Signed messages are stored with the body as an attachment; we need to implement this
+
+    if (!msg->d_ptr->_elementsPresent.body) {
+        if (!msg->d_ptr->_elementsPresent.properties) {
+            // We need the properties before we can fetch the body
+            if (!updateMessageProperties(error, msg)) {
+                return false;
+            }
+        }
+
+        bool isModified(msg->d_ptr->_modified);
+        msg->d_ptr->_elementsPresent.body = 1;
+
+        QByteArray messageBody;
+        QByteArray bodySubType;
+
+        MapiStorePtr store;
+        IMessage *message = openMapiMessage(error, msg->id(), &store);
+        if (*error == QMessageManager::NoError) {
+            //SMS body stored in subject on CEMAPI
+            if (store->types() & QMessage::Sms) {
+                messageBody.append(reinterpret_cast<const char*>(msg->subject().utf16()),msg->subject().count()*sizeof(quint16));
+                bodySubType = "plain";
+            } else {
+                IStream *is(0);
+                bool asciiData(false);
+                LONG contentFormat(msg->d_ptr->_contentFormat);
+
+                if (contentFormat == EDITOR_FORMAT_DONTKNOW) {
+                    // Attempt to read HTML first
+                    contentFormat = EDITOR_FORMAT_HTML;
+                }
+                if (contentFormat == EDITOR_FORMAT_PLAINTEXT) {
+#ifdef _WIN32_WCE
+                    ULONG tags[] = { PR_BODY, PR_BODY_W, PR_BODY_A };
+#else
+                    ULONG tags[] = { PR_BODY };
+#endif
+                    const int n = sizeof(tags)/sizeof(tags[0]);
+                    for (int i = 0; i < n; ++i) {
+                        HRESULT rv = message->OpenProperty(tags[i], &IID_IStream, STGM_READ, 0, (IUnknown**)&is);
+                        if (HR_SUCCEEDED(rv)) {
+                            messageBody = readStream(error, is);
+                            bodySubType = "plain";
+                            if (i == 2) {
+                                asciiData = true;
+                            }
+                            break;
+                        } 
+                    }
+                    if (messageBody.isEmpty()) {
+                        qWarning() << "Unable to open PR_BODY!";
+                    }
+                } else if (contentFormat == EDITOR_FORMAT_HTML) {
+                    // See if there is a body HTML property
+                    // Correct variants discussed at http://blogs.msdn.com/raffael/archive/2008/09/08/mapi-on-windows-mobile-6-programmatically-retrieve-mail-body-sample-code.aspx
+#if(_WIN32_WCE > 0x501)
+                    ULONG tags[] = { PR_BODY_HTML, PR_BODY_HTML_W, PR_BODY_HTML_A };
+#else
+                    ULONG tags[] = { PR_BODY_HTML };
+#endif
+                    const int n = sizeof(tags)/sizeof(tags[0]);
+                    for (int i = 0; i < n; ++i) {
+                        HRESULT rv = message->OpenProperty(tags[i], &IID_IStream, STGM_READ, 0, (IUnknown**)&is);
+                        if (HR_SUCCEEDED(rv)) {
+                            messageBody = readStream(error, is);
+                            bodySubType = "html";
+                            if (i == 2) {
+                                asciiData = true;
+                            }
+                            break;
+                        }
+                    }
+                    if (messageBody.isEmpty()) {
+#ifdef _WIN32_WCE
+                        qWarning() << "Unable to open PR_BODY_HTML!";
+#else
+                        // We couldn't get HTML; try RTF
+                        contentFormat = EDITOR_FORMAT_DONTKNOW;
+#endif
+                    }
+#ifdef _WIN32_WCE
+                } else if (contentFormat == EDITOR_FORMAT_MIME) {
+                    // MIME format is only used on mobile
+                    HRESULT rv = message->OpenProperty(PR_CE_MIME_TEXT, &IID_IStream, STGM_READ, 0, (IUnknown**)&is);
+                    if (HR_SUCCEEDED(rv)) {
+                        messageBody = readStream(error, is);
+
+                        QMailMessage mimeMsg(QMailMessage::fromRfc2822(messageBody));
+
+                        // Extract the recipient info from the message headers
+                        QMessageAddressList addresses;
+                        foreach (const QMailAddress &addr, mimeMsg.to()) {
+                            QString addressString(addr.address());
+                            if(!addressString.isEmpty())
+                                addresses.append(QMessageAddress(QMessageAddress::Email, addressString));
+                        }
+                        if (!addresses.isEmpty()) {
+                            msg->setTo(addresses);
+                        }
+
+                        addresses.clear();
+                        foreach (const QMailAddress &addr, mimeMsg.cc()) {
+                            QString addressString(addr.address());
+                            if(!addressString.isEmpty())
+                                addresses.append(QMessageAddress(QMessageAddress::Email, addressString));
+                        }
+                        if (!addresses.isEmpty()) {
+                            msg->setCc(addresses);
+                        }
+
+                        addresses.clear();
+                        foreach (const QMailAddress &addr, mimeMsg.bcc()) {
+                            QString addressString(addr.address());
+                            addresses.append(QMessageAddress(QMessageAddress::Email, addressString));
+                        }
+                        if (!addresses.isEmpty()) {
+                            msg->setBcc(addresses);
+                        }
+
+                        // Extract the text of the message body
+                        if (mimeMsg.multipartType() == QMailMessage::MultipartNone) {
+                            if (mimeMsg.contentType().type().toLower() == "text") {
+                                QByteArray subType(mimeMsg.contentType().subType().toLower());
+                                if ((subType == "plain") || (subType == "html") || (subType == "rtf")) {
+                                    messageBody = QTextCodec::codecForName("utf-16")->fromUnicode(mimeMsg.body().data());
+                                    bodySubType = subType;
+                                }
+                            }
+                        } else if ((mimeMsg.multipartType() == QMailMessage::MultipartAlternative) ||
+                                   (mimeMsg.multipartType() == QMailMessage::MultipartMixed) ||
+                                   (mimeMsg.multipartType() == QMailMessage::MultipartRelated)) {
+                            // For multipart/related, just try the first part
+                            int maxParts(mimeMsg.multipartType() == QMailMessage::MultipartRelated ? 1 : mimeMsg.partCount());
+                            for (int i = 0; i < maxParts; ++i) {
+                                const QMailMessagePart &part(mimeMsg.partAt(i));
+
+                                if (part.contentType().type().toLower() == "text") {
+                                    QByteArray subType(part.contentType().subType().toLower());
+                                    if ((subType == "plain") || (subType == "html") || (subType == "rtf")) {
+                                        messageBody = QTextCodec::codecForName("utf-16")->fromUnicode(part.body().data());
+                                        bodySubType = subType;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        qWarning() << "Unable to open PR_CE_MIME_TEXT!";
+                    }
+#endif
+                }
+
+#ifndef _WIN32_WCE // RTF not supported on mobile
+                if (bodySubType.isEmpty()) {
+                    if (!msg->d_ptr->_rtfInSync) {
+                        // See if we need to sync the RTF
+                        if (!store->supports(STORE_RTF_OK)) {
+                            BOOL updated(FALSE);
+                            HRESULT rv = RTFSync(message, RTF_SYNC_BODY_CHANGED, &updated);
+                            if (HR_SUCCEEDED(rv)) {
+                                if (updated) {
+                                    if (HR_FAILED(message->SaveChanges(0))) {
+                                        qWarning() << "Unable to save changes after synchronizing RTF.";
+                                    }
+                                }
+                            } else {
+                                qWarning() << "Unable to synchronize RTF.";
+                            }
+                        }
+                    }
+
+                    // Either the body is in RTF, or we need to read the RTF to know that it is text...
+                    HRESULT rv = message->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, STGM_READ, 0, (IUnknown**)&is);
+                    if (HR_SUCCEEDED(rv)) {
+                        IStream *decompressor(0);
+                        if (WrapCompressedRTFStream(is, 0, &decompressor) == S_OK) {
+                            ULONG bytes = 0;
+                            char buffer[BUFSIZ] = { 0 };
+                            do {
+                                decompressor->Read(buffer, BUFSIZ, &bytes);
+                                messageBody.append(buffer, bytes);
+                            } while (bytes == BUFSIZ);
+
+                            decompressor->Release();
+
+                            // RTF is stored in ASCII
+                            asciiData = true;
+
+                            if (contentFormat == EDITOR_FORMAT_DONTKNOW) {
+                                // Inspect the message content to see if we can tell what is in it
+                                if (!messageBody.isEmpty()) {
+                                    QByteArray initialText(messageBody.left(256));
+                                    if (initialText.indexOf("\\fromtext") != -1) {
+                                        // This message originally contained text
+                                        contentFormat = EDITOR_FORMAT_PLAINTEXT;
+
+                                        // See if we can get the plain text version instead
+                                        IStream *ts(0);
+                                        rv = message->OpenProperty(PR_BODY, &IID_IStream, STGM_READ, 0, (IUnknown**)&ts);
+                                        if (HR_SUCCEEDED(rv)) {
+                                            messageBody = readStream(error, ts);
+                                            bodySubType = "plain";
+                                            asciiData = false;
+
+                                            ts->Release();
+                                        } else {
+                                            qWarning() << "Unable to prefer plain text body.";
+                                        }
+                                    } else if (initialText.indexOf("\\fromhtml1") != -1) {
+                                        // This message originally contained text
+                                        contentFormat = EDITOR_FORMAT_HTML;
+                                    }
+                                }
+                            }
+
+                            if (bodySubType.isEmpty()) {
+                                if (contentFormat == EDITOR_FORMAT_PLAINTEXT) {
+                                    messageBody = extractPlainText(messageBody);
+                                    bodySubType = "plain";
+                                } else if (contentFormat == EDITOR_FORMAT_HTML) {
+                                    messageBody = extractHtml(messageBody);
+                                    bodySubType = "html";
+                                } else {
+                                    // I guess we must just have RTF
+                                    bodySubType = "rtf";
+                                }
+                            }
+                        } else {
+                            *error = QMessageManager::ContentInaccessible;
+                            qWarning() << "Unable to decompress RTF";
+                            bodySubType = "plain";
+                        }
+                    }
+                }
+#endif
+                if (bodySubType.isEmpty()) {
+                    qWarning() << "Unable to locate body for message.";
+                }
+
+                mapiRelease(is);
+
+                if (asciiData) {
+                    // Convert the ASCII content back to UTF-16
+                    messageBody = QTextCodec::codecForName("utf-16")->fromUnicode(decodeContent(messageBody, "Latin-1"));
+                }
+            }
+
+            if (*error == QMessageManager::NoError) {
+                QMessageContentContainerPrivate *messageContainer(((QMessageContentContainer *)(msg))->d_ptr);
+
+                bool bodyDownloaded = true;
+#ifdef _WIN32_WCE
+                ULONG status = 0;
+                if(getMapiProperty(message,PR_MSG_STATUS,&status)) {
+                    bodyDownloaded = !((status & MSGSTATUS_HEADERONLY) || (status & MSGSTATUS_PARTIAL));
+                }
+#else
+                //TODO windows
+#endif
+
+                if (!msg->d_ptr->_hasAttachments) {
+                    // Make the body the entire content of the message
+                    messageContainer->setContent(messageBody, QByteArray("text"), bodySubType, QByteArray("utf-16"));
+                    msg->d_ptr->_bodyId = messageContainer->bodyContentId();
+                    messageContainer->_available = bodyDownloaded;
+                } else {
+                    // Add the message body data as the first part
+                    QMessageContentContainer bodyPart;
+                    {
+                        QMessageContentContainerPrivate *bodyContainer(((QMessageContentContainer *)(&bodyPart))->d_ptr);
+                        bodyContainer->setContent(messageBody, QByteArray("text"), bodySubType, QByteArray("utf-16"));
+                        bodyContainer->_available = bodyDownloaded;
+                    }
+
+                    messageContainer->setContentType(QByteArray("multipart"), QByteArray("mixed"), QByteArray());
+                    msg->d_ptr->_bodyId = messageContainer->appendContent(bodyPart);
+                }
+
+                if (!isModified) {
+                    msg->d_ptr->_modified = false;
+                }
+                result = true;
+            }
+
+            mapiRelease(message);
+        }
+    }
+
+    return result;
+}
+
+bool MapiSession::updateMessageAttachments(QMessageManager::Error *error, QMessage *msg) const
+{
+    bool result(false);
+
+    if (!msg->d_ptr->_elementsPresent.attachments) {
+        if (!msg->d_ptr->_elementsPresent.properties) {
+            // We need the properties before we can fetch the attachments
+            if (!updateMessageProperties(error, msg)) {
+                return false;
+            }
+        }
+
+        bool isModified(msg->d_ptr->_modified);
+        msg->d_ptr->_elementsPresent.attachments = 1;
+
+        if (msg->d_ptr->_hasAttachments) {
+            IMessage *message = openMapiMessage(error, msg->id());
+            if (*error == QMessageManager::NoError) {
+                QMessageContentContainerPrivate *messageContainer(((QMessageContentContainer *)(msg))->d_ptr);
+
+                // Find any attachments for this message
+                IMAPITable *attachmentsTable(0);
+                HRESULT rv = message->GetAttachmentTable(0, &attachmentsTable);
+                if (HR_SUCCEEDED(rv)) {
+                    // Find the properties of these attachments
+                    SizedSPropTagArray(7, attCols) = {7, { PR_ATTACH_NUM,
+                                                           PR_ATTACH_EXTENSION,
+                                                           PR_ATTACH_LONG_FILENAME,
+                                                           PR_ATTACH_FILENAME,
+                                                           PR_ATTACH_CONTENT_ID,
+                                                           PR_ATTACH_SIZE,
+                                                           PR_RENDERING_POSITION }};
+
+                    QueryAllRows qar(attachmentsTable, reinterpret_cast<LPSPropTagArray>(&attCols), NULL, NULL);
+                    while(qar.query()) {
+                        for (uint n = 0; n < qar.rows()->cRows; ++n) {
+                            LONG number(0);
+                            QString extension;
+                            QString filename;
+                            QString contentId;
+                            LONG size(0);
+                            LONG renderingPosition(0);
+
+                            // If not available, the output tag will not match our requested content tag
+                            if (qar.rows()->aRow[n].lpProps[0].ulPropTag == PR_ATTACH_NUM) {
+                                number = qar.rows()->aRow[n].lpProps[0].Value.l;
+                            } else {
+                                // We can't access this part...
+                                continue;
+                            }
+
+                            if (qar.rows()->aRow[n].lpProps[1].ulPropTag == PR_ATTACH_EXTENSION) {
+                                extension = QStringFromLpctstr(qar.rows()->aRow[n].lpProps[1].Value.LPSZ);
+                            }
+                            if (qar.rows()->aRow[n].lpProps[2].ulPropTag == PR_ATTACH_LONG_FILENAME) {
+                                filename = QStringFromLpctstr(qar.rows()->aRow[n].lpProps[2].Value.LPSZ);
+                            } else if (qar.rows()->aRow[n].lpProps[3].ulPropTag == PR_ATTACH_FILENAME) {
+                                filename = QStringFromLpctstr(qar.rows()->aRow[n].lpProps[3].Value.LPSZ);
+                            }
+                            if (qar.rows()->aRow[n].lpProps[4].ulPropTag == PR_ATTACH_CONTENT_ID) {
+                                contentId = QStringFromLpctstr(qar.rows()->aRow[n].lpProps[4].Value.LPSZ);
+                            }
+                            if (qar.rows()->aRow[n].lpProps[5].ulPropTag == PR_ATTACH_SIZE) {
+                                // Increase the size estimate by a third to allow for transfer encoding
+                                size = (qar.rows()->aRow[n].lpProps[5].Value.l * 4 / 3);
+                            }
+                            if (qar.rows()->aRow[n].lpProps[6].ulPropTag == PR_RENDERING_POSITION) {
+                                renderingPosition = qar.rows()->aRow[n].lpProps[6].Value.l;
+                            }
+
+                            WinHelpers::AttachmentLocator locator(msg->id(), number);
+
+                            QMessageContentContainer attachment(WinHelpers::fromLocator(locator));
+                            QMessageContentContainerPrivate *container(((QMessageContentContainer *)(&attachment))->d_ptr);
+
+                            if (!extension.isEmpty()) {
+                                QByteArray contentType(contentTypeFromExtension(extension));
+                                if (!contentType.isEmpty()) {
+                                    int index = contentType.indexOf('/');
+                                    if (index != -1) {
+                                        container->setContentType(contentType.left(index), contentType.mid(index + 1), QByteArray());
+                                    } else {
+                                        container->setContentType(contentType, QByteArray(), QByteArray());
+                                    }
+                                }
+                            }
+                            if (!contentId.isEmpty()) {
+                                container->setHeaderField("Content-ID", contentId.toAscii());
+                            }
+                            if (renderingPosition == -1) {
+                                QByteArray value("attachment");
+                                if (!filename.isEmpty()) {
+                                    value.append("; filename=" + filename.toAscii());
+                                }
+                                container->setHeaderField("Content-Disposition", value);
+                            }
+
+                            container->_name = filename.toAscii();
+                            container->_size = size;
+                            container->_available = haveAttachmentData(error,msg->id(),number);
+
+                            messageContainer->appendContent(attachment);
+                            if (!isModified) {
+                                msg->d_ptr->_modified = false;
+                            }
+                        }
+                    }
+
+                    if (qar.error() != QMessageManager::NoError) {
+                        *error = qar.error();
+                    } else {
+                        result = true;
+                    }
+
+                    mapiRelease(attachmentsTable);
+                } else {
+                    *error = QMessageManager::ContentInaccessible;
+                    qWarning() << "Unable to access attachments table.";
+                }
+
+                mapiRelease(message);
+            }
+        }
+    }
+
+    return result;
+}
+
+bool MapiSession::haveAttachmentData(QMessageManager::Error *error, const QMessageId& id, ULONG number) const
+{
+    bool result = false;
+
+    IMessage *message = openMapiMessage(error, id);
+    if (*error == QMessageManager::NoError) {
+        LPATTACH attachment(0);
+        HRESULT rv = message->OpenAttach(number, 0, 0, &attachment);
+        if (HR_SUCCEEDED(rv)) {
+            IStream *is(0);
+            rv = attachment->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, STGM_READ, 0, (IUnknown**)&is);
+            if (HR_SUCCEEDED(rv)) {
+                result = streamSize(error, is) > 0;
+                mapiRelease(is);
+            }
+            mapiRelease(attachment);
+        } else {
+            qWarning() << "Unable to open attachment:" << number;
+        }
+
+        mapiRelease(message);
+    }
+
+    return result;
+}
+
+
+QByteArray MapiSession::attachmentData(QMessageManager::Error *error, const QMessageId& id, ULONG number) const
+{
+    QByteArray result;
+
+    IMessage *message = openMapiMessage(error, id);
+    if (*error == QMessageManager::NoError) {
+        LPATTACH attachment(0);
+        HRESULT rv = message->OpenAttach(number, 0, 0, &attachment);
+        if (HR_SUCCEEDED(rv)) {
+            IStream *is(0);
+            rv = attachment->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, STGM_READ, 0, (IUnknown**)&is);
+            if (HR_SUCCEEDED(rv)) {
+                result = readStream(error, is);
+                mapiRelease(is);
+            }
+
+            mapiRelease(attachment);
+        } else {
+            qWarning() << "Unable to open attachment:" << number;
+        }
+
+        mapiRelease(message);
+    }
+
+    return result;
+}
+
+QMessageIdList MapiSession::queryMessages(QMessageManager::Error *error, const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset, const QString &body, QMessageDataComparator::MatchFlags matchFlags) const
+{
+    if (!filter.isSupported()) {
+        *error = QMessageManager::ConstraintFailure;
+        return QMessageIdList();
+    }
+
+    if (QMessageFilterPrivate::isNonMatching(filter)) { // Avoid unnecessary preprocessing/evaluation
+        return QMessageIdList();
+    }
+
+    QList<FolderHeapNodePtr> folderNodes;
+    QMessageFilter processedFilter(QMessageFilterPrivate::preprocess(error, _self.toStrongRef(), filter));
+    if (*error != QMessageManager::NoError)
+        return  QMessageIdList();
+
+    if (QMessageFilterPrivate::isNonMatching(processedFilter)) { // Filter maybe be simplified by preprocessing
+        return QMessageIdList();
+    }
+
+    foreach (QMessageFilter subfilter, QMessageFilterPrivate::subfilters(processedFilter)) {
+        MapiStoreIterator storeIt(QMessageFilterPrivate::storeIterator(subfilter, error, _self.toStrongRef()));
+        for (MapiStorePtr store(storeIt.next()); store && store->isValid(); store = storeIt.next()) {
+            MapiFolderIterator folderIt(QMessageFilterPrivate::folderIterator(subfilter, error, store));
+            for (MapiFolderPtr folder(folderIt.next()); folder && folder->isValid(); folder = folderIt.next()) {
+                QList<QMessageFilter> orderingFilters;
+                orderingFilters.append(subfilter);
+                foreach(QMessageFilter orderingFilter, QMessageSortOrderPrivate::normalize(orderingFilters, sortOrder)) {
+                    folderNodes.append(FolderHeapNodePtr(new FolderHeapNode(folder, orderingFilter)));
+                }
+            }
+        }
+    }
+
+    if (*error != QMessageManager::NoError)
+        return QMessageIdList();
+
+    return filterMessages(error, folderNodes, sortOrder, limit, offset, body, matchFlags);
+}
+
+void MapiSession::updateMessage(QMessageManager::Error* error, const QMessage& source)
+{
+    MapiStorePtr store;
+    IMessage *mapiMessage = openMapiMessage(error, source.id(), &store);
+    if (*error == QMessageManager::NoError) {
+        // Update the stored properties
+        if (*error == QMessageManager::NoError) {
+            storeMessageProperties(error, source, mapiMessage);
+        }
+        if (*error == QMessageManager::NoError) {
+            replaceMessageRecipients(error, source, mapiMessage, _mapiSession);
+        }
+        if (*error == QMessageManager::NoError) {
+            replaceMessageBody(error, source, mapiMessage, store);
+        }
+        if (*error == QMessageManager::NoError) {
+            // Attachments cannot be removed from a QMessage but new ones can be added
+            addMessageAttachments(error, source, mapiMessage);
+        }
+#ifndef _WIN32_WCE //unsupported
+        if (*error == QMessageManager::NoError) {
+            if (HR_FAILED(mapiMessage->SaveChanges(0))) {
+                qWarning() << "Unable to save changes for message.";
+            }
+        }
+#endif
+
+        mapiRelease(mapiMessage);
+    }
+}
+
+void MapiSession::removeMessages(QMessageManager::Error *error, const QMessageIdList &ids)
+{
+#ifdef _WIN32_WCE
+    typedef QPair<MapiEntryId, MapiEntryId> FolderKey;
+#else
+    typedef QPair<MapiRecordKey, MapiRecordKey> FolderKey;
+#endif
+
+    QMap<FolderKey, QMessageIdList> folderMessageIds;
+
+    // Group messages by folder
+    foreach (const QMessageId &id, ids) {
+        folderMessageIds[qMakePair(QMessageIdPrivate::storeRecordKey(id), QMessageIdPrivate::folderRecordKey(id))].append(id);
+    }
+
+    QMap<FolderKey, QMessageIdList>::const_iterator it = folderMessageIds.begin(), end = folderMessageIds.end();
+    for ( ; it != end; ++it) {
+
+#ifdef _WIN32_WCE
+        const MapiEntryId storeKey(it.key().first);
+        const MapiEntryId folderKey(it.key().second);
+#else
+        const MapiRecordKey storeKey(it.key().first);
+        const MapiRecordKey folderKey(it.key().second);
+#endif
+
+        QMessageManager::Error localError(QMessageManager::NoError);
+
+#ifdef _WIN32_WCE
+        MapiStorePtr store = openStore(&localError,storeKey,true);
+#else
+        MapiStorePtr store = openStoreWithKey(&localError, storeKey, true);
+#endif
+        if (localError == QMessageManager::NoError) {
+#ifdef _WIN32_WCE
+            MapiFolderPtr folder = store->openFolder(&localError, folderKey);
+#else
+            MapiFolderPtr folder = store->openFolderWithKey(&localError, folderKey);
+#endif
+            if (localError == QMessageManager::NoError) {
+                folder->removeMessages(&localError, it.value());
+            }
+        }
+
+        if ((localError != QMessageManager::NoError) && (*error == QMessageManager::NoError)) {
+            *error = localError;
+        }
+    }
+}
+
+bool MapiSession::event(QEvent *e)
+{
+    if (e->type() == NotifyEvent::eventType()) {
+        if (NotifyEvent *ne = static_cast<NotifyEvent*>(e)) {
+
+            QMutex* storeMutex = QMessageStorePrivate::mutex(QMessageManager());
+
+            if(!storeMutex->tryLock())
+                addToNotifyQueue(*ne);
+            else
+            {
+                notify(ne->_store, ne->_id, ne->_notifyType);
+                storeMutex->unlock();
+            }
+            return true;
+        }
+    }
+
+    return QObject::event(e);
+}
+
+void MapiSession::notify(MapiStore *store, const QMessageId &id, MapiSession::NotifyType notifyType)
+{
+    QMessageManager::NotificationFilterIdSet matchingFilterIds;
+
+    QMap<QMessageManager::NotificationFilterId, QMessageFilter>::const_iterator it = _filters.begin(), end = _filters.end();
+    for ( ; it != end; ++it) {
+        const QMessageFilter &filter(it.value());
+
+        if (!filter.isSupported())
+            continue;
+
+         // no message properties are available for a removed message, so only empty filter can match
+        if (notifyType == MapiSession::Removed) {
+            if (filter.isEmpty()) {
+                matchingFilterIds.insert(it.key());
+                break;
+            }
+            continue;
+        }
+
+        QMessageManager::Error ignoredError(QMessageManager::NoError);
+        QMessageFilter processedFilter(QMessageFilterPrivate::preprocess(&ignoredError, _self.toStrongRef(), filter));
+        if (ignoredError != QMessageManager::NoError)
+            continue;
+
+        QMessage message(id);
+        foreach (QMessageFilter subfilter, QMessageFilterPrivate::subfilters(processedFilter)) {
+            if (QMessageFilterPrivate::matchesMessage(subfilter, message, store)) {
+                matchingFilterIds.insert(it.key());
+                break; // subfilters are or'd together
+            }
+        }
+    }
+
+    if (!matchingFilterIds.isEmpty()) {
+        void (MapiSession::*signal)(const QMessageId &, const QMessageManager::NotificationFilterIdSet &) =
+            ((notifyType == Added) ? &MapiSession::messageAdded
+                                   : ((notifyType == Removed) ? &MapiSession::messageRemoved
+                                                              : &MapiSession::messageUpdated));
+        emit (this->*signal)(id, matchingFilterIds);
+    }
+}
+
+QMessageManager::NotificationFilterId MapiSession::registerNotificationFilter(QMessageManager::Error *error, const QMessageFilter &filter)
+{
+    QMessageManager::NotificationFilterId filterId = ++_filterId;
+    _filters.insert(filterId, filter);
+
+    if (!_registered) {
+        _registered = true;
+
+        foreach (MapiStorePtr store, allStores(error)) {
+            store->notifyEvents(fnevNewMail | fnevObjectCreated | fnevObjectCopied | fnevObjectDeleted | fnevObjectModified | fnevObjectMoved);
+        }
+    }
+
+    return filterId;
+}
+
+void MapiSession::unregisterNotificationFilter(QMessageManager::Error *error, QMessageManager::NotificationFilterId filterId)
+{
+    _filters.remove(filterId);
+
+    Q_UNUSED(error)
+}
+
+void MapiSession::dispatchNotifications()
+{
+    MSG msg = { 0 };
+    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+
+    QTimer::singleShot(1000, this, SLOT(dispatchNotifications()));
+}
+
+void MapiSession::processNotifyQueue()
+{
+     foreach(const NotifyEvent& e, _notifyEventQueue)
+     {
+         QMutex* storeMutex = QMessageStorePrivate::mutex(QMessageStore::instance());
+         if(storeMutex->tryLock())
+         {
+             NotifyEvent ne = _notifyEventQueue.dequeue();
+             notify(e._store,e._id,e._notifyType);
+             storeMutex->unlock();
+         }
+         else break;
+     }
+     if(!_notifyEventQueue.isEmpty())
+        qWarning() << QString("Notify queue processing interrupted: pending %1").arg(_notifyEventQueue.length());
+}
+
+QMessagePrivate *MapiSession::messageImpl(const QMessage &message)
+{
+    return message.d_ptr;
+}
+
+QMessageContentContainerPrivate *MapiSession::containerImpl(const QMessageContentContainer &container)
+{
+    return container.d_ptr;
+}
+
+void MapiSession::addToNotifyQueue(const NotifyEvent& e)
+{
+    _notifyEventQueue.enqueue(e);
+    qWarning() << QString("Store busy...adding notify to queue: pending %1").arg(_notifyEventQueue.length());
+}
+
+void MapiSession::flushNotifyQueue()
+{
+    QTimer::singleShot(0, this, SLOT(processNotifyQueue()));
+}
+
+#ifndef _WIN32_WCE
+
+MapiForm::MapiForm(IMsgStore* mapiStore,
+                   IMAPISession* mapiSession,
+                   IMAPIFolder* mapiFolder,
+                   IMessage* mapiMessage)
+:
+m_mapiStore(mapiStore),
+m_mapiSession(mapiSession),
+m_mapiFolder(mapiFolder),
+m_mapiMessage(mapiMessage),
+m_mapiPersistMessage(0),
+m_referenceCount(1)
+{
+    if (m_mapiStore) m_mapiStore->AddRef();
+    if (m_mapiSession) m_mapiSession->AddRef();
+    if (m_mapiFolder) m_mapiFolder->AddRef();
+    if (m_mapiMessage) m_mapiMessage->AddRef();
+}
+
+MapiForm::~MapiForm()
+{
+    releaseAll();
+}
+
+void MapiForm::releaseAll()
+{
+    releasePersistMessage();
+    mapiRelease(m_mapiMessage);
+    mapiRelease(m_mapiFolder);
+    mapiRelease(m_mapiStore);
+    mapiRelease(m_mapiSession);
+}
+
+STDMETHODIMP MapiForm::QueryInterface(REFIID riid, void** iface)
+{
+    *iface = 0;
+
+    if (riid == IID_IMAPIMessageSite)
+    {
+        *iface = (IMAPIMessageSite*)this;
+        AddRef();
+        return S_OK;
+    }
+
+    if (riid == IID_IUnknown)
+    {
+        *iface = (LPUNKNOWN)((IMAPIMessageSite*)this);
+        AddRef();
+        return S_OK;
+    }
+
+    return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) MapiForm::AddRef()
+{
+    long refCount = InterlockedIncrement(&m_referenceCount);
+
+    return refCount;
+}
+
+STDMETHODIMP_(ULONG) MapiForm::Release()
+{
+    long refCount = InterlockedDecrement(&m_referenceCount);
+
+    if (!refCount)
+          delete this;
+
+    return refCount;
+}
+
+STDMETHODIMP MapiForm::GetSession (IMAPISession* FAR * mapiSession)
+{
+    HRESULT hResult = S_FALSE;
+
+    if (mapiSession)
+    {
+        *mapiSession = m_mapiSession;
+        if (m_mapiSession)
+        {
+            m_mapiSession->AddRef();
+            hResult = S_OK;
+        }
+    }
+    return hResult;
+}
+
+STDMETHODIMP MapiForm::GetStore(IMsgStore* FAR * mapiStore)
+{
+    HRESULT hResult = S_FALSE;
+
+    if (mapiStore)
+    {
+        *mapiStore = m_mapiStore;
+        if (m_mapiStore)
+        {
+            m_mapiStore->AddRef();
+            hResult = S_OK;
+        }
+    }
+    return hResult;
+}
+
+STDMETHODIMP MapiForm::GetFolder(IMAPIFolder* FAR * mapiFolder)
+{
+    HRESULT hResult = S_FALSE;
+
+    if (mapiFolder)
+    {
+        *mapiFolder = m_mapiFolder;
+        if (m_mapiFolder)
+        {
+            m_mapiFolder->AddRef();
+            hResult = S_OK;
+        }
+    }
+    return hResult;
+}
+
+STDMETHODIMP MapiForm::GetMessage(IMessage* FAR * mapiMessage)
+{
+    HRESULT hResult = S_FALSE;
+
+    if (mapiMessage)
+    {
+        *mapiMessage = m_mapiMessage;
+        if(m_mapiMessage)
+        {
+            m_mapiMessage->AddRef();
+            hResult = S_OK;
+        }
+    }
+    return hResult;
+}
+
+STDMETHODIMP MapiForm::GetFormManager(IMAPIFormMgr* FAR * mapiFormManager)
+{
+    HRESULT hRes = S_OK;
+    hRes = MAPIOpenFormMgr(m_mapiSession,mapiFormManager);
+    if(FAILED(hRes))
+        qWarning() << "MAPIOpenFormMgr failed";
+    return hRes;
+}
+
+STDMETHODIMP MapiForm::NewMessage(ULONG isComposeInFolder,
+                                        IMAPIFolder* mapiFolder,
+                                        IPersistMessage* mapiPersistMessage,
+                                        IMessage* FAR * mapiMessage,
+                                        IMAPIMessageSite* FAR * mapiMessageSite,
+                                        LPMAPIVIEWCONTEXT FAR * mapiViewContext)
+{
+
+    *mapiMessage = 0;
+    *mapiMessageSite = 0;
+
+    HRESULT hRes = S_OK;
+
+    if (mapiViewContext) *mapiViewContext = 0;
+
+    if ((isComposeInFolder == false) || !mapiFolder)
+        mapiFolder = m_mapiFolder;
+
+    if (mapiFolder)
+    {
+        hRes = mapiFolder->CreateMessage(0, 0, mapiMessage);
+
+        if (*mapiMessage)
+        {
+            MapiForm *mapiForm = 0;
+            mapiForm = new MapiForm(m_mapiStore, m_mapiSession, mapiFolder, *mapiMessage);
+            if (mapiForm)
+            {
+                hRes = mapiForm->setPersistMessage(NULL,mapiPersistMessage);
+                *mapiMessageSite = (IMAPIMessageSite*)mapiForm;
+            }
+        }
+    }
+
+    return hRes;
+}
+
+STDMETHODIMP MapiForm::CopyMessage(IMAPIFolder*)
+{
+    return MAPI_E_NO_SUPPORT;
+}
+
+STDMETHODIMP MapiForm::MoveMessage(IMAPIFolder*, LPMAPIVIEWCONTEXT, LPCRECT)
+{
+    return MAPI_E_NO_SUPPORT;
+}
+
+STDMETHODIMP MapiForm::DeleteMessage(LPMAPIVIEWCONTEXT, LPCRECT)
+{
+    return MAPI_E_NO_SUPPORT;
+}
+
+STDMETHODIMP MapiForm::SaveMessage()
+{
+    HRESULT hRes = S_OK;
+
+    if (!m_mapiPersistMessage || !m_mapiMessage)
+        return MAPI_E_INVALID_PARAMETER;
+
+    hRes = m_mapiPersistMessage->Save(0, true);
+
+    if (FAILED(hRes))
+    {
+        qWarning() << "IMessage::Save failed";
+        return hRes;
+    }
+    else
+    {
+        hRes = (m_mapiMessage->SaveChanges(KEEP_OPEN_READWRITE));
+        hRes = (m_mapiPersistMessage->SaveCompleted(NULL));
+    }
+
+    return hRes;
+}
+
+STDMETHODIMP MapiForm::SubmitMessage(ulong flags)
+{
+    Q_UNUSED(flags);
+
+    HRESULT hRes = S_OK;
+    if (!m_mapiPersistMessage || !m_mapiMessage) return MAPI_E_INVALID_PARAMETER;
+
+    hRes = (m_mapiPersistMessage->Save(m_mapiMessage,true));
+
+    if (FAILED(hRes))
+    {
+        qWarning() << "IPersistMessage::Save failed";
+        return hRes;
+    }
+    else
+    {
+        hRes = (m_mapiPersistMessage->HandsOffMessage());
+        hRes = (m_mapiMessage->SubmitMessage(NULL));
+    }
+
+    mapiRelease(m_mapiMessage);
+
+    return hRes;
+}
+
+STDMETHODIMP MapiForm::GetSiteStatus(ULONG* status)
+{
+    *status = VCSTATUS_NEW_MESSAGE;
+
+    if (m_mapiPersistMessage)
+        *status |= VCSTATUS_SAVE | VCSTATUS_SUBMIT | NULL;
+
+    return S_OK;
+}
+
+bool MapiForm::show()
+{
+    IMAPIFormMgr* mapiFormManager = 0;
+
+    if(GetFormManager(&mapiFormManager) != S_OK)
+        return false;
+
+    IMAPIForm* MAPIForm = 0;
+
+    if(mapiFormManager->LoadForm((ULONG_PTR) 0, 0, 0, 0, 0, m_mapiFolder,
+                                 this,
+                                 m_mapiMessage,
+                                 0,
+                                 IID_IMAPIForm,
+                                 (LPVOID *) &MAPIForm) != S_OK)
+    {
+        qWarning() << "IMAPIFormMgr::LoadForm failed";
+        mapiFormManager->Release();
+        return false;
+    }
+
+    mapiFormManager->Release();
+
+    RECT rect;
+    bool result = true;
+
+    if (MAPIForm)
+    {
+            setPersistMessage(MAPIForm,NULL);
+
+            HRESULT hRes = S_OK;
+
+            hRes = MAPIForm->DoVerb(0,0,0,&rect);
+            if (S_OK != hRes)
+            {
+                rect.left = 0;
+                rect.right = 500;
+                rect.top = 0;
+                rect.bottom = 400;
+                hRes = MAPIForm->DoVerb(0, 0, 0, &rect);
+            }
+
+            if(S_OK != hRes)
+            {
+                qWarning() << "IMapiForm::DoVerb Failed";
+                result = false;
+            }
+
+            MAPIForm->Release();
+    }
+
+    return  result;
+}
+
+STDMETHODIMP MapiForm::GetLastError(HRESULT, ULONG, LPMAPIERROR FAR*)
+{
+    return MAPI_E_NO_SUPPORT;
+}
+
+void MapiForm::releasePersistMessage()
+{
+    if (m_mapiPersistMessage)
+    {
+        m_mapiPersistMessage->HandsOffMessage();
+        mapiRelease(m_mapiPersistMessage);
+    }
+}
+
+HRESULT MapiForm::setPersistMessage(LPMAPIFORM lpForm, IPersistMessage* mapiPersistMessage)
+{
+    HRESULT hRes = S_OK;
+    releasePersistMessage();
+
+    if (mapiPersistMessage)
+    {
+        m_mapiPersistMessage = mapiPersistMessage;
+        m_mapiPersistMessage->AddRef();
+    }
+    else if (lpForm) hRes = (lpForm->QueryInterface(IID_IPersistMessage ,(LPVOID *)&m_mapiPersistMessage));
+
+    return hRes;
+}
+
+#endif
+
+#include "winhelpers.moc"
+#include "moc_winhelpers_p.cpp"
+
+QTM_END_NAMESPACE