--- /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 ¬ification(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