/**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt Mobility Components. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** 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, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "modestengine_maemo_p.h" #include "maemohelpers_p.h" #include "qmessage_p.h" #include "qmessageaccount.h" #include "qmessageaccount_p.h" #include "qmessageaccountfilter.h" #include "qmessageaccountfilter_p.h" #include "qmessagefolder_p.h" #include "qmessagestore_p_p.h" #include "qmessageservice_maemo_p.h" #include "qmessagecontentcontainer_maemo_p.h" #include #include #include #include #include #include #include #include #include #include // Marshall the ModestStringMap data into a D-Bus argument QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::ModestStringMap &map) { QtMobility::ModestStringMap::const_iterator iter; argument.beginMap (QVariant::String, QVariant::String); for (iter = map.constBegin(); iter != map.constEnd(); iter++) { argument.beginMapEntry(); argument << iter.key() << iter.value(); argument.endMapEntry(); } argument.endMap(); return argument; } // Retrieve the ModestStringMap data from the D-Bus argument const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::ModestStringMap &map) { map.clear(); argument.beginMap(); while (!argument.atEnd()) { QString key, value; argument.beginMapEntry(); argument >> key >> value; argument.endMapEntry(); map[key] = value; } argument.endMap(); return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::ModestUnreadMessageDBusStruct &unreadMessage) { argument.beginStructure(); argument << unreadMessage.timeStamp; argument << unreadMessage.subject; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::ModestUnreadMessageDBusStruct &unreadMessage) { argument.beginStructure(); argument >> unreadMessage.timeStamp; argument >> unreadMessage.subject; argument.endStructure(); return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::ModestAccountsUnreadMessagesDBusStruct &unreadMessages) { argument.beginStructure(); argument << unreadMessages.accountId; argument << unreadMessages.accountName; argument << unreadMessages.accountProtocol; argument << unreadMessages.unreadCount; argument << unreadMessages.unreadMessages; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::ModestAccountsUnreadMessagesDBusStruct &unreadMessages) { argument.beginStructure(); argument >> unreadMessages.accountId; argument >> unreadMessages.accountName; argument >> unreadMessages.accountProtocol; argument >> unreadMessages.unreadCount; argument >> unreadMessages.unreadMessages; argument.endStructure(); return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::ModestMessage &message) { argument.beginStructure(); argument << message.id; argument << message.subject; argument << message.folder; argument << message.sender; argument << message.size; argument << message.hasAttachment; argument << message.isUnread; argument << message.timeStamp; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::ModestMessage &message) { argument.beginStructure(); argument >> message.id; argument >> message.subject; argument >> message.folder; argument >> message.sender; argument >> message.size; argument >> message.hasAttachment; argument >> message.isUnread; argument >> message.timeStamp; argument.endStructure(); return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::MessagingModestMimePart &mimePart) { argument.beginStructure(); argument << mimePart.mimeType; argument << mimePart.isAttachment; argument << mimePart.fileName; argument << mimePart.contentId; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::MessagingModestMimePart &mimePart) { argument.beginStructure(); argument >> mimePart.mimeType; argument >> mimePart.isAttachment; argument >> mimePart.fileName; argument >> mimePart.contentId; argument.endStructure(); return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const QtMobility::MessagingModestFolder &folder) { argument.beginStructure(); argument << folder.parentAccountId; argument << folder.parentFolderId; argument << folder.modestId; argument << static_cast(folder.type); argument << folder.localizedName; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, QtMobility::MessagingModestFolder &folder) { argument.beginStructure(); argument >> folder.parentAccountId; QByteArray str = folder.parentAccountId.toUtf8(); gchar* escaped_string = gconf_escape_key(str.data(), str.length()); folder.parentAccountId = QString::fromUtf8(escaped_string); g_free(escaped_string); argument >> folder.parentFolderId; argument >> folder.modestId; uint type; argument >> type; folder.type = static_cast(type); argument >> folder.localizedName; argument.endStructure(); return argument; } QTM_BEGIN_NAMESPACE /* configuration key definitions for modest */ #define MODESTENGINE_ACCOUNT_NAMESPACE "/apps/modest/accounts" #define MODESTENGINE_SERVER_ACCOUNT_NAMESPACE "/apps/modest/server_accounts" #define MODESTENGINE_DEFAULT_ACCOUNT "/apps/modest/default_account" #define MODESTENGINE_ACCOUNT_ENABLED "enabled" #define MODESTENGINE_ACCOUNT_DISPLAY_NAME "display_name" #define MODESTENGINE_ACCOUNT_FULLNAME "fullname" #define MODESTENGINE_ACCOUNT_EMAIL "email" #define MODESTENGINE_ACCOUNT_STORE_ACCOUNT "store_account" #define MODESTENGINE_ACCOUNT_TRANSPORT_ACCOUNT "transport_account" #define MODESTENGINE_ACCOUNT_PROTOCOL "proto" #define MODESTENGINE_ACCOUNT_USERNAME "username" #define MODESTENGINE_ACCOUNT_HOSTNAME "hostname" #define MODESTENGINE_ACCOUNT_PORT "port" // The modest engine has a new plugin, we need service names for it #define MODESTENGINE_QTM_PLUGIN_PATH "/com/nokia/Qtm/Modest/Plugin" #define MODESTENGINE_QTM_PLUGIN_NAME "com.nokia.Qtm.Modest.Plugin" typedef enum { MODEST_DBUS_SEARCH_SUBJECT = (1 << 0), MODEST_DBUS_SEARCH_SENDER = (1 << 1), MODEST_DBUS_SEARCH_RECIPIENT = (1 << 2), MODEST_DBUS_SEARCH_SIZE = (1 << 3), MODEST_DBUS_SEARCH_BODY = (1 << 6) } ModestDBusSearchFlags; // Specific priority settings to translate to modest priorities #define MODESTENGINE_HIGH_PRIORITY 2 #define MODESTENGINE_NORMAL_PRIORITY 0 #define MODESTENGINE_LOW_PRIORITY 1 Q_GLOBAL_STATIC(ModestEngine,modestEngine); ModestEngine::ModestEngine() : m_filterId(0), m_allEmailMessagesInCache(false) { qWarning() << "ModestEngine::ModestEngine Starting to initialize"; g_type_init(); m_gconfclient = gconf_client_get_default(); if (!m_gconfclient) { qWarning("qtmessaging: could not get gconf client"); } else { updateEmailAccounts(); } qWarning() << "ModestEngine::ModestEngine Connecting to Modest DBus Interface"; // Setup DBus Interface for Modest m_ModestDBusInterface = new QDBusInterface(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE, QDBusConnection::sessionBus(), this); if (m_ModestDBusInterface->isValid()) { qWarning() << "ModestEngine::ModestEngine Connected to Modest DBus Interface"; } else { qWarning() << "ModestEngine::ModestEngine Unable to connect to Modest DBus Interface"; } // Get notifications of Incoming Messages m_ModestDBusInterface->connection().connect(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE, MODEST_DBUS_SIGNAL_FOLDER_UPDATED, this, SLOT(folderUpdatedSlot(QDBusMessage))); // Get notifications of message Read/Unread state changes m_ModestDBusInterface->connection().connect(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE, MODEST_DBUS_SIGNAL_MSG_READ_CHANGED, this, SLOT(messageReadChangedSlot(QDBusMessage))); qWarning() << "ModestEngine::ModestEngine Connecting to Qt Mobility Modest Plugin DBus Interface"; // Setup Qt Mobility Modest Plugin based DBus Interface for Modest m_QtmPluginDBusInterface = new QDBusInterface(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, QDBusConnection::sessionBus(), this); if (m_QtmPluginDBusInterface->isValid()) { qWarning() << "ModestEngine::ModestEngine Connected to Qt Mobility Modest Plugin DBus Interface"; } else { qWarning() << "ModestEngine::ModestEngine Unable to connect to Qt Mobility Modest Plugin DBus Interface"; } qDBusRegisterMetaType< ModestStringMap >(); qDBusRegisterMetaType< ModestStringMapList >(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >("QList"); connect(&m_MailFoldersWatcher, SIGNAL(fileChanged(int, QString, uint)), this, SLOT(fileChangedSlot(int, QString, uint))); watchAllKnownEmailFolders(); // Get latest messages from each account // => This ensures that notifications of all incoming messages will be sent int messagesPerAccount = 1; QDBusPendingCall pendingCall = m_ModestDBusInterface->asyncCall(MODEST_DBUS_METHOD_GET_UNREAD_MESSAGES, messagesPerAccount); QDBusPendingCallWatcher* pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall); pendingCallWatcher->setProperty("setOnlyDates", true); connect(pendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(pendingGetUnreadMessagesFinishedSlot(QDBusPendingCallWatcher*))); QList modestFolders; foldersFromModest(modestFolders); if (modestFolders.count() > 0) { for (int i=0; i < modestFolders.count(); i++) { if (modestFolders[i].type == MessagingModestFolderTypeInbox && modestFolders[i].parentAccountId != "local_folders") { QMessageAccountId accountId = accountIdFromModestAccountId(modestFolders[i].parentAccountId); if (accountExists(accountId)) { QString id = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+modestFolders[i].modestId; startObservingModestFolder(id); } } } } qWarning() << "ModestEngine::ModestEngine Initialized successfully"; } ModestEngine::~ModestEngine() { g_object_unref(m_gconfclient); m_gconfclient=NULL; if (m_observed_folders.count()) { for (int i = m_observed_folders.count()-1; i >= 0; i--) { stopObservingModestFolder(m_observed_folders[i]); } } } ModestEngine* ModestEngine::instance() { return modestEngine(); } MessagingModestMessage ModestEngine::messageFromModest(const QString& accountId, const QString &folderId, const QString& messageId) const { MessagingModestMessage modestMessage; if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return modestMessage; } QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("GetMessage", accountId, folderId, messageId); QDBusPendingCallWatcher pendingCallWatcher(pendingCall); pendingCallWatcher.waitForFinished(); QDBusMessage msg = pendingCallWatcher.reply(); if (msg.type() == QDBusMessage::ReplyMessage) { modestMessage.id = messageId; modestMessage.accountId = accountId; modestMessage.folderId = folderId; modestMessage.url = msg.arguments()[0].toString(); modestMessage.mimeType = msg.arguments()[1].toString(); modestMessage.from = msg.arguments()[2].toString(); modestMessage.to = msg.arguments()[3].toString(); modestMessage.cc = msg.arguments()[4].toString(); modestMessage.bcc = msg.arguments()[5].toString(); modestMessage.replyTo = msg.arguments()[6].toString(); modestMessage.subject = msg.arguments()[7].toString(); modestMessage.dateReceived = msg.arguments()[8].toLongLong(); modestMessage.dateSent = msg.arguments()[9].toLongLong(); modestMessage.size = msg.arguments()[10].toLongLong(); modestMessage.flags = static_cast(msg.arguments()[11].toUInt()); modestMessage.priority = static_cast(msg.arguments()[12].toUInt()); QVariant variant = msg.arguments()[13]; QDBusArgument argument = variant.value(); argument >> modestMessage.mimeParts; } else { modestMessage.dateReceived = 0; modestMessage.dateSent = 0; modestMessage.size = 0; modestMessage.flags = MessagingModestMessageNotDefined; modestMessage.priority = MessagingModestMessagePriorityNotDefined; } return modestMessage; } void ModestEngine::updateEmailAccounts() const { iDefaultEmailAccountId = QMessageAccountId(); iAccounts.clear(); GSList *accounts; GError *error = NULL; accounts = gconf_client_all_dirs(m_gconfclient, MODESTENGINE_ACCOUNT_NAMESPACE, &error); if (error) { #ifdef MESSAGING_DEBUG qDebug("qtmessaging: failed to list '%s': '%s'", MODESTENGINE_ACCOUNT_NAMESPACE, error->message); #endif g_error_free(error); } else { gchar *default_account_id = gconf_client_get_string(m_gconfclient, MODESTENGINE_DEFAULT_ACCOUNT, &error); if (error) { qWarning("qtmessaging: failed to get '%s': %s", MODESTENGINE_DEFAULT_ACCOUNT, error->message); g_error_free(error); } const size_t prefix_len = strlen(MODESTENGINE_ACCOUNT_NAMESPACE) + 1; GSList *iter = accounts; while (iter) { if (!(iter->data)) { iter = iter->next; continue; } const gchar* account_key = (const gchar*)iter->data; // account_key = /apps/modest/server_accounts/ // => take account id from account_key & unescape account id gchar* unescaped_account_id = gconf_unescape_key(account_key+prefix_len, strlen(account_key)-prefix_len); gboolean account_ok = FALSE; // Check if account is enabled if (account_key) { gchar* key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_ENABLED, NULL); account_ok = gconf_client_get_bool(m_gconfclient, key, NULL); g_free(key); } // Check if account store is defined if (account_ok) { gchar* key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL); gchar* server_account_name = gconf_client_get_string(m_gconfclient, key, NULL); if (server_account_name) { gchar* escaped_server_account_name = gconf_escape_key(server_account_name, strlen(server_account_name)); g_free(server_account_name); gchar* store_account_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_server_account_name, NULL); if (!gconf_client_dir_exists(m_gconfclient, store_account_key, NULL)) { account_ok = FALSE; } g_free(store_account_key); g_free(escaped_server_account_name); } g_free(key); } // Check if account transport is defined if (account_ok) { gchar* key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_TRANSPORT_ACCOUNT, NULL); gchar* server_account_name = gconf_client_get_string(m_gconfclient, key, NULL); if (server_account_name) { gchar* escaped_server_account_name = gconf_escape_key(server_account_name, strlen(server_account_name)); g_free(server_account_name); gchar* transport_account_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_server_account_name, NULL); if (!gconf_client_dir_exists(m_gconfclient, transport_account_key, NULL)) { account_ok = FALSE; } g_free(transport_account_key); g_free(escaped_server_account_name); } g_free(key); } if (account_ok) { gchar* escaped_account_id = gconf_escape_key(unescaped_account_id, strlen(unescaped_account_id)); QString accountId = "MO_"+QString::fromUtf8(escaped_account_id); g_free(escaped_account_id); gchar* name_key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_DISPLAY_NAME, NULL); gchar* account_name = gconf_client_get_string(m_gconfclient, name_key, NULL); QString accountName = QString::fromUtf8(account_name); g_free(account_name); g_free(name_key); gchar* email_key = g_strconcat(account_key, "/", MODESTENGINE_ACCOUNT_EMAIL, NULL); gchar* email = gconf_client_get_string(m_gconfclient, email_key, NULL); QString accountAddress = QString::fromUtf8(email); g_free(email); g_free(email_key); QMessageAccount account = QMessageAccountPrivate::from(QMessageAccountId(accountId), accountName, QMessageAddress(QMessageAddress::Email, accountAddress), QMessage::Email); iAccounts.insert(accountId, account); // Check if newly added account is default account if (default_account_id) { if (!strncmp(default_account_id, unescaped_account_id, strlen(default_account_id))) { iDefaultEmailAccountId = accountId; } } } g_free(unescaped_account_id); g_free(iter->data); iter->data = NULL; iter = g_slist_next(iter); } // strings were freed in while loop // => it's enough to just free accounts list g_slist_free(accounts); g_free(default_account_id); } } QMessageAccountIdList ModestEngine::queryAccounts(const QMessageAccountFilter &filter, const QMessageAccountSortOrder &sortOrder, uint limit, uint offset, bool &isFiltered, bool &isSorted) const { Q_UNUSED(sortOrder) Q_UNUSED(limit) Q_UNUSED(offset) QMessageAccountIdList accountIds; if (!m_QtmPluginDBusInterface->isValid()) { return accountIds; } updateEmailAccounts(); foreach (QMessageAccount value, iAccounts) { accountIds.append(value.id()); } MessagingHelper::filterAccounts(accountIds, filter); isFiltered = true; isSorted = false; return accountIds; } int ModestEngine::countAccounts(const QMessageAccountFilter &filter) const { bool isFiltered, isSorted; return queryAccounts(filter, QMessageAccountSortOrder(), 0, 0, isFiltered, isSorted).count(); } QMessageAccount ModestEngine::account(const QMessageAccountId &id) const { updateEmailAccounts(); return iAccounts[id.toString()]; } QMessageAccountId ModestEngine::defaultAccount() const { updateEmailAccounts(); return iDefaultEmailAccountId; } QFileInfoList ModestEngine::localFolders() const { QDir dir(localRootFolder()); dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QFileInfoList fileInfoList = dir.entryInfoList(); appendLocalSubFolders(fileInfoList, 0); return fileInfoList; } void ModestEngine::appendLocalSubFolders(QFileInfoList& fileInfoList, int startIndex) const { int endIndex = fileInfoList.count(); for (int i=startIndex; i < endIndex; i++) { QDir dir(fileInfoList[i].absoluteFilePath()); if (dir.exists()) { dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QFileInfoList dirs = dir.entryInfoList(); for (int j = 0; j < dirs.count(); j++) { QString fileName = dirs[j].fileName(); if (fileName != "cur" && fileName != "new" && fileName != "tmp") { fileInfoList.append(dirs[j]); } } } } if (fileInfoList.count() > endIndex) { appendLocalSubFolders(fileInfoList, endIndex); } } void ModestEngine::appendIMAPSubFolders(QFileInfoList& fileInfoList, int startIndex) const { int endIndex = fileInfoList.count(); for (int i=startIndex; i < endIndex; i++) { QDir dir(fileInfoList[i].absoluteFilePath()+QString("/subfolders")); if (dir.exists()) { dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); fileInfoList.append(dir.entryInfoList()); } } if (fileInfoList.count() > endIndex) { appendIMAPSubFolders(fileInfoList, endIndex); } } QString ModestEngine::localRootFolder() const { return QDir::home().absolutePath()+QString("/.modest/local_folders"); } QString ModestEngine::accountRootFolder(QMessageAccountId& accountId) const { QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId)); QString userName; QString hostName; QString port; QString protocol; gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL); gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL); g_free(store_account_key); if (store_account_name) { gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name)); g_free(store_account_name); gchar* username_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_USERNAME, NULL); gchar* account_username = gconf_client_get_string(m_gconfclient, username_key, NULL); userName = QString::fromUtf8(account_username); g_free(account_username); g_free(username_key); gchar* hostname_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_HOSTNAME, NULL); gchar* account_hostname = gconf_client_get_string(m_gconfclient, hostname_key, NULL); hostName = QString::fromUtf8(account_hostname); g_free(account_hostname); g_free(hostname_key); gchar* port_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PORT, NULL); gint account_port = gconf_client_get_int(m_gconfclient, port_key, NULL); port = QString::number(account_port); g_free(port_key); gchar* protocol_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PROTOCOL, NULL); gchar* account_protocol = gconf_client_get_string(m_gconfclient, protocol_key, NULL); protocol = QString::fromUtf8(account_protocol); g_free(account_protocol); g_free(protocol_key); g_free(escaped_store_account_name); if (protocol == "pop") { return QDir::home().absolutePath()+"/.modest/cache/mail/"+protocol+"/"+userName+"__"+hostName+"_"+port; } else if (protocol == "imap") { return QDir::home().absolutePath()+"/.modest/cache/mail/"+protocol+"/"+userName+"__"+hostName+"_"+port+"/folders"; } } return QString(); } QFileInfoList ModestEngine::accountFolders(QMessageAccountId& accountId) const { QFileInfoList fileInfoList; EmailProtocol protocol = accountEmailProtocol(accountId); if (protocol == ModestEngine::EmailProtocolPop3) { QFileInfo fileInfo = QFileInfo(accountRootFolder(accountId)+"/cache"); fileInfoList.append(fileInfo); } else if (protocol == ModestEngine::EmailProtocolIMAP) { QDir dir(accountRootFolder(accountId)); dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); fileInfoList = dir.entryInfoList(); appendIMAPSubFolders(fileInfoList, 0); } return fileInfoList; } ModestEngine::EmailProtocol ModestEngine::accountEmailProtocol(QMessageAccountId& accountId) const { EmailProtocol protocol = EmailProtocolUnknown; QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId)); gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL); gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL); g_free(store_account_key); if (store_account_name) { gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name)); g_free(store_account_name); gchar* protocol_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PROTOCOL, NULL); gchar* account_protocol = gconf_client_get_string(m_gconfclient, protocol_key, NULL); if (account_protocol) { if (QString("pop") == account_protocol) { protocol = EmailProtocolPop3; } else if (QString("imap") == account_protocol) { protocol = EmailProtocolIMAP; } g_free(account_protocol); } g_free(protocol_key); g_free(escaped_store_account_name); } return protocol; } bool ModestEngine::accountExists(const QMessageAccountId& accountId) const { bool retVal = false; QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId)); gchar* account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), NULL); retVal = gconf_client_dir_exists(m_gconfclient, account_key, NULL); g_free(account_key); return retVal; } QString ModestEngine::accountEmailProtocolAsString(const QMessageAccountId& accountId) const { QString protocol; QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId)); gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL); gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL); g_free(store_account_key); if (store_account_name) { gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name)); g_free(store_account_name); gchar* protocol_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_PROTOCOL, NULL); gchar* account_protocol = gconf_client_get_string(m_gconfclient, protocol_key, NULL); if (account_protocol) { protocol = QString::fromUtf8(account_protocol); g_free(account_protocol); } g_free(protocol_key); g_free(escaped_store_account_name); } return protocol; } QString ModestEngine::accountUsername(QMessageAccountId& accountId) const { QString username; QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId)); gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL); gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL); g_free(store_account_key); if (store_account_name) { gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name)); g_free(store_account_name); gchar* username_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_USERNAME, NULL); gchar* account_username = gconf_client_get_string(m_gconfclient, username_key, NULL); username = QString::fromUtf8(account_username); g_free(account_username); g_free(username_key); g_free(escaped_store_account_name); } return username; } QString ModestEngine::accountHostname(QMessageAccountId& accountId) const { QString host; QString modestAccountId = escapeString(modestAccountIdFromAccountId(accountId)); gchar* store_account_key = g_strconcat(MODESTENGINE_ACCOUNT_NAMESPACE, "/", modestAccountId.toUtf8().data(), "/", MODESTENGINE_ACCOUNT_STORE_ACCOUNT, NULL); gchar* store_account_name = gconf_client_get_string(m_gconfclient, store_account_key, NULL); g_free(store_account_key); if (store_account_name) { gchar* escaped_store_account_name = gconf_escape_key(store_account_name, strlen(store_account_name)); g_free(store_account_name); gchar* host_key = g_strconcat(MODESTENGINE_SERVER_ACCOUNT_NAMESPACE, "/", escaped_store_account_name, "/", MODESTENGINE_ACCOUNT_HOSTNAME, NULL); gchar* account_host = gconf_client_get_string(m_gconfclient, host_key, NULL); host = QString::fromUtf8(account_host); g_free(account_host); g_free(host_key); g_free(escaped_store_account_name); } return host; } void ModestEngine::foldersFromModest(QList& folders) const { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return; } QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("GetFolders"); QDBusPendingCallWatcher pendingCallWatcher(pendingCall); pendingCallWatcher.waitForFinished(); QDBusMessage msg = pendingCallWatcher.reply(); if (msg.type() == QDBusMessage::ReplyMessage) { QVariant variant = msg.arguments()[0]; QDBusArgument argument = variant.value(); argument >> folders; } } QMessageFolderIdList ModestEngine::queryFolders(const QMessageFolderFilter &filter, const QMessageFolderSortOrder &sortOrder, uint limit, uint offset, bool &isFiltered, bool &isSorted) const { Q_UNUSED(sortOrder) Q_UNUSED(limit) Q_UNUSED(offset) QMessageFolderIdList folderIds; if (!m_QtmPluginDBusInterface->isValid()) { return folderIds; } updateEmailAccounts(); if (iAccounts.isEmpty()) { return folderIds; } m_folderCache.clear(); QList modestFolders; foldersFromModest(modestFolders); if (modestFolders.count() > 0) { foreach (QMessageAccount value, iAccounts) { QMessageAccountId accountId = value.id(); // Add folder to cache QString modestAccountId = modestAccountIdFromAccountId(accountId); for (int j=0; j < modestFolders.count(); j++) { if (modestFolders[j].parentAccountId == modestAccountId) { MessagingModestFolder folder = modestFolders[j]; QString id = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+folder.modestId; folder.id = id; m_folderCache.insert(id, folder); folderIds.append(QMessageFolderId(id)); } } for (int j=0; j < modestFolders.count(); j++) { if (modestFolders[j].parentAccountId == "local_folders") { MessagingModestFolder folder = modestFolders[j]; QString id = accountId.toString()+"&"+"maildir"+"&"+folder.modestId; folder.id = id; m_folderCache.insert(id, folder); folderIds.append(QMessageFolderId(id)); } } } } else { QFileInfoList localFolders = this->localFolders(); QString localRootFolder = this->localRootFolder(); foreach (QMessageAccount value, iAccounts) { QMessageAccountId accountId = value.id(); QString rootFolder = accountRootFolder(accountId); QFileInfoList folders = this->accountFolders(accountId); for (int i=0; i < folders.count(); i++) { QString filePath = folders[i].absoluteFilePath(); QString modestId = filePath.right(filePath.size()-rootFolder.size()-1); modestId = modestId.remove("/subfolders"); QString id = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+modestId; folderIds.append(QMessageFolderId(id)); // Add folder to cache MessagingModestFolder folder; folder.id = id; folder.modestId = modestId; if (modestId.lastIndexOf('/') == -1) { // Folder does not have subfolders folder.name = modestId; if ((accountEmailProtocolAsString(accountId) == "pop") && (folder.name == "cache")) { folder.name = "Inbox"; } } else { // Folder has subfolders folder.name = modestId.right(modestId.length()-modestId.lastIndexOf('/')-1); folder.parentFolderId = modestId.left(modestId.lastIndexOf('/')); } folder.parentAccountId = modestAccountIdFromAccountId(accountId); folder.type = MessagingModestFolderTypeUnknown; m_folderCache.insert(id, folder); } // Each account sees local folders as account folders for (int i=0; i < localFolders.count(); i++) { QString filePath = localFolders[i].absoluteFilePath(); QString modestId = filePath.right(filePath.size()-localRootFolder.size()-1); QString id = accountId.toString()+"&"+"maildir"+"&"+modestId; folderIds.append(QMessageFolderId(id)); // Add folder to cache MessagingModestFolder folder; folder.id = id; folder.modestId = modestId; if (modestId.lastIndexOf('/') == -1) { // Folder does not have subfolders folder.name = modestId; if ((accountEmailProtocolAsString(accountId) == "pop") && (folder.name == "cache")) { folder.name = "Inbox"; } } else { // Folder has subfolders folder.name = modestId.right(modestId.length()-modestId.lastIndexOf('/')-1); folder.parentFolderId = modestId.left(modestId.lastIndexOf('/')); } folder.parentAccountId = "local_folders"; folder.type = MessagingModestFolderTypeUnknown; m_folderCache.insert(id, folder); } } } MessagingHelper::filterFolders(folderIds, filter); isFiltered = true; isSorted = false; return folderIds; } int ModestEngine::countFolders(const QMessageFolderFilter &filter) const { bool isFiltered, isSorted; return queryFolders(filter, QMessageFolderSortOrder(), 0, 0, isFiltered, isSorted).count(); } QMessageFolder ModestEngine::folder(const QMessageFolderId &id) const { QMessageFolder folder; QString idString = id.toString(); if (m_folderCache.contains(idString)) { MessagingModestFolder modestFolder = m_folderCache.value(idString); int endOfAccountId = idString.indexOf('&'); QString accountId = idString.left(endOfAccountId); QString folderPath = idString.right(idString.length()-idString.lastIndexOf('&')-1); QMessageFolderId parentId; QString name; if (folderPath.lastIndexOf('/') != -1) { // Folder has subfolders parentId = idString.left(idString.lastIndexOf('/')); } if (modestFolder.localizedName.isEmpty()) { folder = QMessageFolderPrivate::from(id, QMessageAccountId(accountId), parentId, modestFolder.name, folderPath); } else { folder = QMessageFolderPrivate::from(id, QMessageAccountId(accountId), parentId, modestFolder.localizedName, folderPath); } } return folder; } bool ModestEngine::startObservingModestFolder(const QMessageFolderId& folderId) const { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } if (!m_observed_folders.contains(folderId)) { QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("AddFolderObserver", QVariant::fromValue(modestAccountIdFromFolderId(folderId)), QVariant::fromValue(modestFolderIdFromFolderId(folderId))); QDBusPendingCallWatcher pendingCallWatcher(pendingCall); pendingCallWatcher.waitForFinished(); QDBusMessage msg = pendingCallWatcher.reply(); if (msg.type() == QDBusMessage::ReplyMessage) { if (m_observed_folders.count() == 0) { m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "FolderContentsChanged", (ModestEngine*)this, SLOT(modestFolderContentsChangedSlot(QDBusMessage))); } m_observed_folders.append(folderId); return true; } } else { return true; } return false; } bool ModestEngine::stopObservingModestFolder(const QMessageFolderId& folderId) const { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } if (m_observed_folders.contains(folderId)) { QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCall("RemoveFolderObserver", QVariant::fromValue(modestAccountIdFromFolderId(folderId)), QVariant::fromValue(modestFolderIdFromFolderId(folderId))); QDBusPendingCallWatcher pendingCallWatcher(pendingCall); pendingCallWatcher.waitForFinished(); QDBusMessage msg = pendingCallWatcher.reply(); if (msg.type() == QDBusMessage::ReplyMessage) { m_observed_folders.removeOne(folderId); if (m_observed_folders.count() == 0) { m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "FolderContentsChanged", (ModestEngine*)this, SLOT(modestFolderContentsChangedSlot(QDBusMessage))); } return true; } } return false; } void ModestEngine::modestFolderContentsChangedSlot(QDBusMessage msg) { QList arguments = msg.arguments(); QString modestAccountId = arguments.takeFirst().toString(); QString modestFolderId = arguments.takeFirst().toString(); QMessageAccountId accountId = accountIdFromModestAccountId(modestAccountId); QString protocol = accountEmailProtocolAsString(accountId); if ((protocol == "pop") && (modestFolderId == "INBOX")) { modestFolderId = "cache"; } QString folderId = accountId.toString()+"&"+accountEmailProtocolAsString(accountId)+"&"+modestFolderId; qlonglong change = arguments.takeFirst().toLongLong(); QVariant variant = arguments.takeFirst(); QList messageIds = variant.toStringList(); if (change == 4) { // Headers added for (int i = 0; i < messageIds.count(); i++) { QMessageId messageId = folderId+"/"+messageIds[i]; notification(messageId, ModestEngine::Added); } }/* else if (change == 8) { // Headers removed for (int i = 0; i < messageIds.count(); i++) { QMessageId messageId = folderId+"/"+messageIds[i]; notification(messageId, ModestEngine::Removed); } }*/ } void ModestEngine::watchAllKnownEmailFolders() { QFileInfoList localFolders = this->localFolders(); // Changes in local Email folders can be monitored using directory // monitoring. <=> All messages are stored as individual files. for (int i=0; i < localFolders.count(); i++) { m_MailFoldersWatcher.addDirectory(localFolders[i].absoluteFilePath()+"/cur", IN_MOVED_TO | IN_DELETE); } // Monitor changes also in root folder to see if new folders are added m_MailFoldersWatcher.addDirectory(localRootFolder(), IN_CREATE); // Changes in remote Email folders must be monitored using file monitoring. // That's because message headers are stored into summary.mmap file (and summary.mmap // file maybe the only place that contains message information). // => summary.mmap files will be monitored foreach (QMessageAccount value, iAccounts) { QMessageAccountId accountId = value.id(); QString rootFolder = accountRootFolder(accountId); EmailProtocol protocol = accountEmailProtocol(accountId); QFileInfoList folders = this->accountFolders(accountId); for (int i=0; i < folders.count(); i++) { if (protocol == ModestEngine::EmailProtocolPop3) { QString folder = folders[i].absoluteFilePath(); m_MailFoldersWatcher.addDirectory(folder, IN_CREATE); } else if (protocol == ModestEngine::EmailProtocolIMAP) { m_MailFoldersWatcher.addDirectory(folders[i].absoluteFilePath(), IN_MOVED_TO | IN_DELETE); } } } } void ModestEngine::fileChangedSlot(int watchDescriptor, QString filePath, uint events) { Q_UNUSED(watchDescriptor) if (events & IN_CREATE) { if (QFileInfo(filePath).isDir()) { // New directory was added // => Start watching new folder QString newDirPath = QString(filePath.toUtf8()); m_MailFoldersWatcher.addDirectory(newDirPath + "/cur"); return; } } int filenameBegin = filePath.lastIndexOf('/')+1; QString fileName = filePath.mid(filenameBegin,filePath.lastIndexOf('.')-filenameBegin); if (!fileName.endsWith("summary")) { if (events & (IN_MOVED_TO | IN_CREATE)) { if (events != (IN_MOVED_TO | IN_MOVED_FROM)) { // Wait a moment to make sure that Modest has finished adding message QEventLoop eventLoop; QTimer::singleShot(100, &eventLoop, SLOT(quit())); eventLoop.exec(); notification(messageIdFromModestMessageFilePath(filePath), ModestEngine::Added); } } else if (events & IN_DELETE) { notification(messageIdFromModestMessageFilePath(filePath), ModestEngine::Removed); } } } bool ModestEngine::sendEmail(QMessage &message) { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } ModestStringMap senderInfo; ModestStringMap recipients; ModestStringMap messageData; ModestStringMapList attachments; ModestStringMapList images; uint priority = 0; ModestStringMap headers; senderInfo = getModestSenderInfo(message); if (senderInfo.isEmpty()) { return false; } recipients = getModestRecipients(message); if (recipients.isEmpty()) { return false; } messageData = getModestMessageData(message); attachments = getModestAttachments(message); images = getModestImages(message); priority = getModestPriority(message); headers = getModestHeaders(message); qDebug() << "Sending D-BUS message"; QDBusPendingCall call = m_QtmPluginDBusInterface->asyncCall ( "SendEmail", QVariant::fromValue (senderInfo), QVariant::fromValue (recipients), QVariant::fromValue (messageData), QVariant::fromValue (attachments), QVariant::fromValue (images), priority, QVariant::fromValue (headers)); qDebug() << "Message sent"; if (call.isError()) { qWarning() << "Call failed! " << call.error(); return false; } QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher (call, this); connect (watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(sendEmailCallEnded(QDBusPendingCallWatcher*))); return true; } bool ModestEngine::composeEmail(const QMessage &message) { QString mailString("mailto:"); QList list(message.to()); if (!list.empty()){ for (int i = 0; i < list.size(); ++i) { if (i == 0) { mailString += list.at(i).addressee(); } else { mailString += QString("%2C%20"); mailString += list.at(i).addressee(); } } } bool firstParameter = true; QString subject = message.subject(); if (!subject.isEmpty()) { if (firstParameter) { mailString += QString("?"); firstParameter = false; } else { mailString += QString("&"); } mailString += QString("subject="); mailString += subject; } QString body = message.textContent(); if (!body.isEmpty()) { if (firstParameter) { mailString += QString("?"); firstParameter = false; } else { mailString += QString("&"); } mailString += QString("body="); mailString += QString(body); } QUrl url(mailString); QDBusInterface dbusif(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE); QDBusMessage msg = dbusif.call(MODEST_DBUS_METHOD_MAIL_TO, url.toString()); return true; } bool ModestEngine::showMessage(const QMessageId &id) { QMessage msg = message(id, false); QMessagePrivate* privateMessage = QMessagePrivate::implementation(msg); if (privateMessage->_url.isEmpty()) { return false; } else { m_ModestDBusInterface->call(MODEST_DBUS_METHOD_OPEN_MESSAGE, privateMessage->_url); } return true; } bool ModestEngine::exportUpdates(const QMessageAccountId &id) { Q_UNUSED(id) // Modest does not offer Account specific updates m_ModestDBusInterface->call(MODEST_DBUS_METHOD_SEND_RECEIVE); return true; } bool ModestEngine::retrieveMessageMimeInformation(QMessage& message) { QMessagePrivate* privateMessage = QMessagePrivate::implementation(message); if (!privateMessage->_mimeInformationRetrieved) { QString modestAccountId = modestAccountIdFromMessageId(message.id()); QString modestFolderId = modestFolderIdFromMessageId(message.id()); QString modestMessageId = modestMessageIdFromMessageId(message.id()); MessagingModestMessage modestMessage = messageFromModest(modestAccountId, modestFolderId, modestMessageId); if (modestMessage.flags & MessagingModestMessageDeleted) { return false; } if (modestMessage.accountId.isEmpty()) { return false; } privateMessage->_mimeInformationRetrieved = true; mimeInformationFromModestMessageToMessage(modestMessage, message); MessageCache::instance()->insert(message); } return true; } QMessage ModestEngine::message(const QMessageId &id, bool useCache) const { QMessage message; if (useCache) { message = MessageCache::instance()->message(id.toString()); } if (message.type() == QMessage::NoType) { QString modestAccountId = modestAccountIdFromMessageId(id); QString modestFolderId = modestFolderIdFromMessageId(id); QString modestMessageId = modestMessageIdFromMessageId(id); MessagingModestMessage modestMessage = messageFromModest(modestAccountId, modestFolderId, modestMessageId); if (modestMessage.flags & MessagingModestMessageDeleted) { return QMessage(); } if (modestMessage.accountId.isEmpty()) { return QMessage(); } message = messageFromModestMessage(modestMessage, accountIdFromMessageId(id)); MessageCache::instance()->insert(message); } return message; } QMessage::StandardFolder ModestEngine::standardFolderFromModestFolderId(const QString& modestFolderId) const { if (!QString::compare(modestFolderId, "INBOX", Qt::CaseInsensitive)) { return QMessage::InboxFolder; } else if (!QString::compare(modestFolderId, "drafts", Qt::CaseInsensitive)) { return QMessage::DraftsFolder; } else if (!QString::compare(modestFolderId, "sent", Qt::CaseInsensitive)) { return QMessage::SentFolder; } return QMessage::DraftsFolder; } QString ModestEngine::modestFolderIdFromStandardFolder(QMessage::StandardFolder standardFolder) const { switch (standardFolder) { case QMessage::InboxFolder: return "INBOX"; case QMessage::DraftsFolder: return "drafts"; case QMessage::SentFolder: return "sent"; case QMessage::OutboxFolder: return "outbox"; case QMessage::TrashFolder: return "thrash"; } return "drafts"; } QMessage ModestEngine::messageFromModestMessage(const MessagingModestMessage& modestMessage, QMessageAccountId accountId) const { QMessage message; QMessagePrivate* privateMessage = QMessagePrivate::implementation(message); // Type message.setType(QMessage::Email); // Parent Account Id QMessageAccountId parentAccountId; if (accountId.isValid()) { parentAccountId = accountId; } else { parentAccountId = realAccountId(modestMessage); } message.setParentAccountId(parentAccountId); // Parent Folder Id QMessageFolderId folderId; if (modestMessage.accountId == "local_folders") { folderId = folderIdFromModestFolderId(parentAccountId, true, modestMessage.folderId); } else { // Since Message is not in local folder, message status can be set to Incoming privateMessage->_status = privateMessage->_status | QMessage::Incoming; folderId = folderIdFromModestFolderId(parentAccountId, false, modestMessage.folderId); } privateMessage->_parentFolderId = folderId; // Message Id QMessageId messageId = QMessageId(folderId.toString()+"/"+modestMessage.id); privateMessage->_id = messageId; // Dates message.setDate(QDateTime::fromTime_t(modestMessage.dateSent)); message.setReceivedDate(QDateTime::fromTime_t(modestMessage.dateReceived)); // Priority switch (modestMessage.priority) { case QtMobility::MessagingModestMessageHighPriority: message.setPriority(QMessage::HighPriority); break; case QtMobility::MessagingModestMessageNormalPriority: message.setPriority(QMessage::NormalPriority); break; case QtMobility::MessagingModestMessageLowPriority: message.setPriority(QMessage::LowPriority); break; } // Standard Folder QMessagePrivate::setStandardFolder(message, standardFolderFromModestFolderId(modestMessage.folderId)); // MIME information (for actual message & attachments) mimeInformationFromModestMessageToMessage(modestMessage, message); // From if (modestMessage.from.size() > 0) { message.setFrom(QMessageAddress(QMessageAddress::Email, modestMessage.from)); QMessagePrivate::setSenderName(message, modestMessage.from); } // To if (modestMessage.to.size() > 0) { QMessageAddressList toAddresses; foreach (const QString &element, modestMessage.to.split(",", QString::SkipEmptyParts)) { QMessageAddress addr; addr.setType(QMessageAddress::Email); addr.setAddressee(element.trimmed()); toAddresses.append(addr); } message.setTo(toAddresses); } // Cc if (modestMessage.cc.size() > 0) { QMessageAddressList ccAddresses; foreach (const QString &element, modestMessage.cc.split(",", QString::SkipEmptyParts)) { QMessageAddress addr; addr.setType(QMessageAddress::Email); addr.setAddressee(element.trimmed()); ccAddresses.append(addr); } message.setCc(ccAddresses); } // Bcc if (modestMessage.bcc.size() > 0) { QMessageAddressList bccAddresses; foreach (const QString &element, modestMessage.bcc.split(",", QString::SkipEmptyParts)) { QMessageAddress addr; addr.setType(QMessageAddress::Email); addr.setAddressee(element.trimmed()); bccAddresses.append(addr); } message.setBcc(bccAddresses); } // Subject message.setSubject(modestMessage.subject); // Size privateMessage->_size = modestMessage.size; // Read Status if (modestMessage.flags & MessagingModestMessageSeen) { privateMessage->_status = privateMessage->_status | QMessage::Read; } // Attachments Status if (modestMessage.flags & MessagingModestMessageAttachments) { privateMessage->_status = privateMessage->_status | QMessage::HasAttachments; } // Modest specific url privateMessage->_url = modestMessage.url; // Modified flag privateMessage->_modified = false; return message; } void ModestEngine::mimeInformationFromModestMessageToMessage(const MessagingModestMessage& modestMessage, QMessage& message) const { QMessagePrivate* privateMessage = QMessagePrivate::implementation(message); QMessageContentContainerPrivate* container = QMessagePrivate::containerImplementation(message); QMessageId messageId = message.id(); // Message MIME type QString fullMimeType = modestMessage.mimeType; int slashIndex = fullMimeType.indexOf('/'); QByteArray mimeType = fullMimeType.left(slashIndex).toAscii(); QByteArray mimeSubType = fullMimeType.mid(slashIndex+1).toAscii(); container->_type = mimeType.data(); container->_subType = mimeSubType.data(); // Body & Attachments handling for (int i=0; i < modestMessage.mimeParts.count(); i++) { if (!modestMessage.mimeParts[i].isAttachment) { // Body QByteArray fullMimeType = modestMessage.mimeParts[i].mimeType.toUtf8(); QString contentId = modestMessage.mimeParts[i].contentId; QByteArray fileName = modestMessage.mimeParts[i].fileName.toAscii(); QByteArray mainType("text"); QByteArray subType("plain"); QByteArray charset; int index = fullMimeType.indexOf("/"); if (index != -1) { mainType = fullMimeType.left(index).trimmed(); subType = fullMimeType.mid(index + 1).trimmed(); index = subType.indexOf(";"); if (index != -1) { QString remainder = subType.mid(index + 1); subType = subType.left(index).trimmed(); QRegExp charsetPattern("charset=(\\S+)"); index = charsetPattern.indexIn(remainder); if (index != -1) { charset = charsetPattern.cap(1).toLatin1().toUpper(); } } } if (charset.isEmpty()) { charset = "UTF-8"; } // Accept only text type Body if (mainType.toLower() == "text") { QMessageContentContainerId existingBodyId(message.bodyId()); if (existingBodyId.isValid()) { if (existingBodyId == QMessageContentContainerPrivate::bodyContentId()) { // The body content is in the message itself container->_containingMessageId = messageId.toString(); container->_attachmentId = contentId; container->_name = fileName; container->_type = mainType; container->_subType = subType; container->_charset = charset; container->_size = 0; container->_available = true; } else { // The body content is in the first attachment QMessageContentContainerPrivate *attachmentContainer(QMessageContentContainerPrivate::implementation(*container->attachment(existingBodyId))); attachmentContainer->_containingMessageId = messageId.toString(); attachmentContainer->_attachmentId = contentId; attachmentContainer->_name = fileName; attachmentContainer->_type = mainType; attachmentContainer->_subType = subType; attachmentContainer->_charset = charset; attachmentContainer->_size = 0; attachmentContainer->_available = true; } } else { if (container->_attachments.isEmpty()) { // Put the content directly into the message container->_containingMessageId = messageId.toString(); container->_attachmentId = contentId; container->_name = fileName; container->_type = mainType; container->_subType = subType; container->_charset = charset; container->_size = 0; container->_available = true; privateMessage->_bodyId = QMessageContentContainerPrivate::bodyContentId(); } else { // Add the body as the first attachment QMessageContentContainer newBody; QMessageContentContainerPrivate *attachmentContainer = QMessageContentContainerPrivate::implementation(newBody); attachmentContainer->_containingMessageId = messageId.toString(); attachmentContainer->_attachmentId = contentId; attachmentContainer->_name = fileName; attachmentContainer->_type = mainType; attachmentContainer->_subType = subType; attachmentContainer->_charset = charset; attachmentContainer->_size = 0; attachmentContainer->_available = true; privateMessage->_bodyId = container->prependContent(newBody); } } } } else { // Attachment QString fullMimeType = modestMessage.mimeParts[i].mimeType; QString contentId = modestMessage.mimeParts[i].contentId; int slashIndex = fullMimeType.indexOf('/'); QByteArray mimeType = fullMimeType.left(slashIndex).toAscii(); QByteArray mimeSubType = fullMimeType.mid(slashIndex+1).toAscii(); // TODO: Attachment size QByteArray fileName = modestMessage.mimeParts[i].fileName.toAscii(); fileName = fileName.mid(fileName.lastIndexOf('/')+1); QString msgId = messageId.toString(); QMessageContentContainer attachment = QMessageContentContainerPrivate::from(msgId, contentId, fileName, mimeType, mimeSubType, 0); appendAttachmentToMessage(message, attachment); } } } void ModestEngine::appendAttachmentToMessage(QMessage& message, QMessageContentContainer& attachment) const { QMessagePrivate* privateMessage = QMessagePrivate::implementation(message); QMessageContentContainerPrivate* container = QMessagePrivate::containerImplementation(message); if (container->_attachments.isEmpty()) { QMessageContentContainerId existingBodyId(message.bodyId()); if (existingBodyId == QMessageContentContainerPrivate::bodyContentId()) { // The body content is in the message itself - move it to become the first attachment QMessageContentContainer newBody(message); QMessageContentContainerPrivate::implementation(newBody)->setDerivedMessage(0); container->setContentType("multipart", "mixed", ""); privateMessage->_bodyId = container->prependContent(newBody); } else { // This message is now multipart container->setContentType("multipart", "mixed", ""); } container->_available = true; } container->appendContent(attachment); bool haveAttachments = !container->_attachments.isEmpty(); message.setStatus(QMessage::HasAttachments,haveAttachments); privateMessage->_modified = true; } bool ModestEngine::addMessage(QMessage &message) { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } QString modestFolder; ModestStringMap senderInfo; ModestStringMap recipients; ModestStringMap messageData; ModestStringMapList attachments; ModestStringMapList images; uint priority = 0; ModestStringMap headers; qDebug() << __PRETTY_FUNCTION__; senderInfo = getModestSenderInfo (message); recipients = getModestRecipients (message); messageData = getModestMessageData (message); attachments = getModestAttachments (message); images = getModestImages (message); priority = getModestPriority (message); headers = getModestHeaders (message); QString accountName; if (message.parentFolderId().isValid()) { modestFolder = modestFolderIdFromFolderId (message.parentFolderId()); accountName = modestAccountIdFromFolderId(message.parentFolderId()); } else { modestFolder = modestFolderIdFromStandardFolder(message.standardFolder()); if (message.standardFolder() == QMessage::DraftsFolder) { accountName = "local_folders"; } else { accountName = modestAccountIdFromAccountId(message.parentAccountId()); } } senderInfo["account-name"] = accountName; QList arguments; arguments << modestFolder; arguments << QVariant::fromValue(senderInfo); arguments << QVariant::fromValue(recipients); arguments << QVariant::fromValue(messageData); arguments << QVariant::fromValue(attachments); arguments << QVariant::fromValue(images); arguments << priority; arguments << QVariant::fromValue(headers); QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCallWithArgumentList("AddMessage", arguments); if (pendingCall.isError()) { qWarning() << "DBus call failed! " << pendingCall.error(); return false; } QDBusPendingCallWatcher pendingCallWatcher(pendingCall); pendingCallWatcher.waitForFinished(); QDBusMessage msg = pendingCallWatcher.reply(); if (msg.type() == QDBusMessage::ErrorMessage) { qWarning() << "Failed to add message via modest: " << msg.errorMessage(); return false; } QMessagePrivate* privateMessage = QMessagePrivate::implementation(message); QString messageId; if (message.parentFolderId().isValid()) { messageId = message.parentFolderId().toString()+"/"+msg.arguments()[0].toString(); } else if (accountName == "local_folders") { messageId = message.parentAccountId().toString()+ "&maildir&"+modestFolder+"/"+msg.arguments()[0].toString(); } else { messageId = message.parentAccountId().toString()+"&"+ accountEmailProtocolAsString(message.parentAccountId())+ "&"+modestFolder+"/"+msg.arguments()[0].toString(); } privateMessage->_id = QMessageId(messageId); // Use update functionality to set Priority & Read status // <=> Modest Plugin can not set Priority & Read status // immediately after message creation in add call updateMessage(message); // Make sure that there will be instant notification about added message notification(message.id(), ModestEngine::Added); return true; } bool ModestEngine::updateMessage(QMessage &message) { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } if (!message.id().isValid()) { return false; } QString modestAccountId = modestAccountIdFromMessageId(message.id()); QString modestFolderId = modestFolderIdFromMessageId(message.id()); QString modestMessageId = modestMessageIdFromMessageId(message.id()); QString modestFolder; ModestStringMap senderInfo; ModestStringMap recipients; ModestStringMap messageData; ModestStringMapList attachments; ModestStringMapList images; uint priority = 0; ModestStringMap headers; qDebug() << __PRETTY_FUNCTION__; // Only read status & priority can be updated using Modest if (message.status() & QMessage::Read) { messageData["read"] = ""; } priority = getModestPriority (message); QList arguments; arguments << QVariant::fromValue(modestAccountId); arguments << QVariant::fromValue(modestFolderId); arguments << QVariant::fromValue(modestMessageId); arguments << QVariant::fromValue(senderInfo); arguments << QVariant::fromValue(recipients); arguments << QVariant::fromValue(messageData); arguments << QVariant::fromValue(attachments); arguments << QVariant::fromValue(images); arguments << priority; arguments << QVariant::fromValue(headers); QDBusPendingCall pendingCall = m_QtmPluginDBusInterface->asyncCallWithArgumentList("UpdateMessage", arguments); if (pendingCall.isError()) { qWarning() << "DBus call failed! " << pendingCall.error(); return false; } QDBusPendingCallWatcher pendingCallWatcher(pendingCall); pendingCallWatcher.waitForFinished(); QDBusMessage msg = pendingCallWatcher.reply(); if (msg.type() == QDBusMessage::ErrorMessage) { qWarning() << "Failed to update message via modest: " << msg.errorMessage(); return false; } return true; } bool ModestEngine::removeMessage(const QMessageId &id, QMessageManager::RemovalOption option) { Q_UNUSED(option) // TODO: QMessage msg = message(id, false); QMessagePrivate* privateMessage = QMessagePrivate::implementation(msg); if (privateMessage->_url.isEmpty()) { return false; } else { m_ModestDBusInterface->call(MODEST_DBUS_METHOD_DELETE_MESSAGE, privateMessage->_url); // Make sure that there will be instant notification about removed message notification(id, ModestEngine::Removed); } return true; } bool ModestEngine::filterMessage(const QMessage& message, QMessageFilterPrivate::SortedMessageFilterList filterList, int start) const { if (filterList.count() > start) { for (int j=start; j < filterList.count(); j++) { QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(filterList[j]); if (!pf->filter(message)) { return false; } } } return true; } QMessageIdList ModestEngine::queryMessagesSync(const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset, bool &isFiltered, bool &isSorted) const { QMessageIdList ids; if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return ids; } QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service); if (privateService->queryMessages(m_service, filter, sortOrder, limit, offset, QMessageServicePrivate::EnginesToCallModest)) { QObject::connect(&m_service, SIGNAL(stateChanged(QMessageService::State)), this, SLOT(stateChanged(QMessageService::State))); m_eventLoop.exec(); isSorted = m_isSorted; isFiltered = m_isFiltered; ids = m_ids; m_ids.clear(); } return ids; } QMessageIdList ModestEngine::queryMessagesSync(const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset, bool &isFiltered, bool &isSorted) const { QMessageIdList ids; if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return ids; } QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service); if (privateService->queryMessages(m_service, filter, body, matchFlags, sortOrder, limit, offset, QMessageServicePrivate::EnginesToCallModest)) { QObject::connect(&m_service, SIGNAL(stateChanged(QMessageService::State)), this, SLOT(stateChanged(QMessageService::State))); m_eventLoop.exec(); isSorted = m_isSorted; isFiltered = m_isFiltered; ids = m_ids; m_ids.clear(); } return ids; } int ModestEngine::countMessagesSync(const QMessageFilter &filter) const { int count; if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return 0; } QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service); if (privateService->countMessages(m_service, filter, QMessageServicePrivate::EnginesToCallModest)) { QObject::connect(&m_service, SIGNAL(stateChanged(QMessageService::State)), this, SLOT(stateChanged(QMessageService::State))); m_eventLoop.exec(); count = m_count; } return count; } void ModestEngine::stateChanged(QMessageService::State newState) { if (newState == QMessageService::FinishedState) { QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(m_service); m_ids = privateService->_ids; m_isSorted = privateService->_sorted; m_isFiltered = privateService->_filtered; m_count = privateService->_count; m_eventLoop.quit(); } } bool ModestEngine::queryMessages(QMessageService& messageService, const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset) const { return queryMessages(messageService, filter, QString(), 0, sortOrder, limit, offset); } bool ModestEngine::countMessages(QMessageService& messageService, const QMessageFilter &filter) { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } m_pendingMessageQueries.append(MessageQueryInfo()); MessageQueryInfo &queryInfo = m_pendingMessageQueries[m_pendingMessageQueries.count()-1]; queryInfo.queryId = -1; queryInfo.filter = filter; queryInfo.limit = 0; queryInfo.offset = 0; queryInfo.privateService = QMessageServicePrivate::implementation(messageService); queryInfo.currentFilterListIndex = 0; queryInfo.handledFiltersCount = 0; queryInfo.isQuery = false; queryInfo.returnWithSingleShot = false; queryInfo.allMessagesQuery = false; queryInfo.queryThread = 0; if (m_allEmailMessagesInCache) { queryInfo.queryThread = new MessageQueryThread(filter, QMessageSortOrder()); connect(queryInfo.queryThread, SIGNAL(queryFinished(void*, QList)), this, SLOT(messageQueryFinishedSlot(void*, QList))); queryInfo.queryThread->start(); } else { if (!startQueryingAndFilteringMessages(m_pendingMessageQueries[m_pendingMessageQueries.count()-1])) { m_pendingMessageQueries.removeAt(m_pendingMessageQueries.count()-1); if (m_pendingMessageQueries.count() == 0) { // This was last query in pending queries queue // => Disconnect from "GetHeaders" request related DBus signals // Note: Disconnecting signals is done to optimize signal handling // <=> Disconnecting prevents unnecessary handling of signals // which have been sent from other applications using // Qt Mobility Messaging API m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersReceived", (ModestEngine*)this, SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage))); m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersFetched", (ModestEngine*)this, SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage))); } return false; } } return true; } bool ModestEngine::queryMessages(QMessageService& messageService, const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset) const { if (!m_QtmPluginDBusInterface->isValid() || iAccounts.isEmpty()) { return false; } m_pendingMessageQueries.append(MessageQueryInfo()); MessageQueryInfo &queryInfo = m_pendingMessageQueries[m_pendingMessageQueries.count()-1]; queryInfo.queryId = -1; queryInfo.body = body; queryInfo.matchFlags = matchFlags; queryInfo.filter = filter; queryInfo.sortOrder = sortOrder; queryInfo.limit = limit; queryInfo.offset = offset; queryInfo.privateService = QMessageServicePrivate::implementation(messageService); queryInfo.currentFilterListIndex = 0; queryInfo.handledFiltersCount = 0; queryInfo.isQuery = true; queryInfo.returnWithSingleShot = false; queryInfo.allMessagesQuery = false; queryInfo.queryThread = 0; if (m_allEmailMessagesInCache) { queryInfo.queryThread = new MessageQueryThread(filter, sortOrder); connect(queryInfo.queryThread, SIGNAL(queryFinished(void*, QList)), this, SLOT(messageQueryFinishedSlot(void*, QList))); queryInfo.queryThread->start(); } else { if (!startQueryingAndFilteringMessages(m_pendingMessageQueries[m_pendingMessageQueries.count()-1])) { m_pendingMessageQueries.removeAt(m_pendingMessageQueries.count()-1); if (m_pendingMessageQueries.count() == 0) { // This was last query in pending queries queue // => Disconnect from "GetHeaders" request related DBus signals // Note: Disconnecting signals is done to optimize signal handling // <=> Disconnecting prevents unnecessary handling of signals // which have been sent from other applications using // Qt Mobility Messaging API m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersReceived", (ModestEngine*)this, SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage))); m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersFetched", (ModestEngine*)this, SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage))); } return false; } } return true; } bool ModestEngine::startQueryingAndFilteringMessages(MessageQueryInfo &msgQueryInfo) const { QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(msgQueryInfo.filter); if (pf->_filterList.count() == 0) { if ((pf->_field == QMessageFilterPrivate::None) && (pf->_filterList.count() == 0) && (pf->_notFilter)) { // There is only one filter: empty ~QMessageFilter() // => return empty QMessageIdList msgQueryInfo.ids.clear(); msgQueryInfo.returnWithSingleShot = true; QTimer::singleShot(0, (ModestEngine*)this, SLOT(returnQueryResultsSlot())); return true; } else { // One single filter to be handled QMessageFilter newFilter; QMessageFilterPrivate* pfNew = QMessageFilterPrivate::implementation(newFilter); pfNew->_filterList.append(QMessageFilterPrivate::SortedMessageFilterList()); pfNew->_filterList[0] << msgQueryInfo.filter; msgQueryInfo.filter = newFilter; } } return queryAndFilterMessages(msgQueryInfo); } void ModestEngine::returnQueryResultsSlot() { for (int i=m_pendingMessageQueries.count()-1; i >= 0; i--) { if (m_pendingMessageQueries[i].returnWithSingleShot) { if (m_pendingMessageQueries[i].isQuery) { m_pendingMessageQueries[i].privateService->messagesFound(m_pendingMessageQueries[i].ids, true, true); } else { m_pendingMessageQueries[i].privateService->messagesCounted(m_pendingMessageQueries[i].ids.count()); } m_pendingMessageQueries.removeAt(i); } } } bool ModestEngine::queryAndFilterMessages(MessageQueryInfo &msgQueryInfo) const { QStringList accountIds; QStringList folderUris; QDateTime startTimeStamp; QDateTime endTimeStamp; QDateTime startReceptionTimeStamp; QDateTime endReceptionTimeStamp; QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(msgQueryInfo.filter); // Filters have been sorted into matrix of filters: // - there is AND operation between every filter in one row // - there is OR operation between every row // => Every row can be handled individually // => Final result set can be created by combining result sets // from individual row based queries QMessageFilterPrivate::SortedMessageFilterList filters = pf->_filterList[msgQueryInfo.currentFilterListIndex]; msgQueryInfo.realAccountId = QString(); int handledFiltersCount = 0; pf = QMessageFilterPrivate::implementation(filters[handledFiltersCount]); if ((filters.count() == 1) && (pf->_field == QMessageFilterPrivate::None) && (pf->_filterList.count() == 0)) { if (pf->_notFilter) { // There is only one filter: empty ~QMessageFilter() // => this query results empty QMessageIdList // => Skip to next query round int index = -1; for (int i=0; i < m_pendingMessageQueries.count(); i++) { if (m_pendingMessageQueries[i].queryId == msgQueryInfo.queryId) { index = i; break; } } if (index >= 0) { handleQueryFinished(index); } return true; } else { // There is only one filter: empty QMessageFilter() // => return all messages handledFiltersCount++; } } bool handled = true; while (handledFiltersCount < filters.count() && handled) { handled = false; pf = QMessageFilterPrivate::implementation(filters[handledFiltersCount]); switch (pf->_field) { case QMessageFilterPrivate::Id: break; case QMessageFilterPrivate::ParentAccountId: { if (pf->_comparatorType == QMessageFilterPrivate::Equality) { if (accountIds.count()) { // Only one account/one query can be used break; } QMessageDataComparator::EqualityComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::Equal) { if (pf->_value.toString().length() > 0) { accountIds.append(modestAccountIdFromAccountId(pf->_value.toString())); // Local folders are not account specific // => Make sure that account specific messages are searched from local_folders account accountIds.append("local_folders"); handled = true; } } } break; } case QMessageFilterPrivate::ParentFolderId: { if (pf->_comparatorType == QMessageFilterPrivate::Equality) { QMessageDataComparator::EqualityComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::Equal) { if (pf->_value.toString().length() > 0) { folderUris.append(modestFolderIdFromFolderId(pf->_value.toString())); if (accountIds.count() == 0) { accountIds.append(modestAccountIdFromFolderId(pf->_value.toString())); // Note: Even though local folders belong to "local_folders" account // inside Modest, local folders can belong to any "real" // POP3 or IMAP account in Qt Mobility Messaging side // <=> Qt Mobility Messaging does not have "Local Folders" // account // If folder is local folder, "local_folders" accountId will be // added to accountIds list to enable correct filtering inside // Modest Plugin accountIds.append("local_folders"); // realAccountId will contain id of Qt Mobility Messaging account // (AccountId can be found from the beginning of folderId string) // <=> realAccountId will not ever be "local_folders" msgQueryInfo.realAccountId = accountIdFromFolderId(pf->_value.toString()).toString(); } handled = true; } } } break; } case QMessageFilterPrivate::AncestorFolderIds: break; case QMessageFilterPrivate::Type: break; case QMessageFilterPrivate::StandardFolder: if (pf->_comparatorType == QMessageFilterPrivate::Equality) { QMessageDataComparator::EqualityComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::Equal) { QMessage::StandardFolder standardFolder = static_cast(pf->_value.toInt()); if (standardFolder == QMessage::SentFolder) { // Possible "real" accountIds can be ignored in this phase // because messages are searched from "local_folders" account // <=> Actual account filtering is done when search from Modest // has finished accountIds.clear(); accountIds.append("local_folders"); folderUris.append("sent"); } else if (standardFolder == QMessage::DraftsFolder) { // Possible "real" accountIds can be ignored in this phase // because messages are searched from "local_folders" account // <=> Actual account filtering is done when search from Modest // has finished accountIds.clear(); accountIds.append("local_folders"); folderUris.append("drafts"); } handled = true; } } break; case QMessageFilterPrivate::ParentAccountIdFilter: break; case QMessageFilterPrivate::ParentFolderIdFilter: break; case QMessageFilterPrivate::TimeStamp: { if (pf->_comparatorType == QMessageFilterPrivate::Equality) { QMessageDataComparator::EqualityComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::Equal) { endTimeStamp = pf->_value.toDateTime(); startTimeStamp = pf->_value.toDateTime(); handled = true; } } if (pf->_comparatorType == QMessageFilterPrivate::Relation) { QMessageDataComparator::RelationComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::LessThan) { endTimeStamp = pf->_value.toDateTime(); handled = true; } else if (cmp == QMessageDataComparator::LessThanEqual) { endTimeStamp = pf->_value.toDateTime(); handled = true; } else if (cmp == QMessageDataComparator::GreaterThan) { startTimeStamp = pf->_value.toDateTime(); handled = true; } if (cmp == QMessageDataComparator::GreaterThanEqual) { startTimeStamp = pf->_value.toDateTime(); handled = true; } } break; } case QMessageFilterPrivate::ReceptionTimeStamp: { if (pf->_comparatorType == QMessageFilterPrivate::Equality) { QMessageDataComparator::EqualityComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::Equal) { endReceptionTimeStamp = pf->_value.toDateTime(); startReceptionTimeStamp = pf->_value.toDateTime(); handled = true; } } if (pf->_comparatorType == QMessageFilterPrivate::Relation) { QMessageDataComparator::RelationComparator cmp(static_cast(pf->_comparatorValue)); if (cmp == QMessageDataComparator::LessThan) { endReceptionTimeStamp = pf->_value.toDateTime(); handled = true; } else if (cmp == QMessageDataComparator::LessThanEqual) { endReceptionTimeStamp = pf->_value.toDateTime(); handled = true; } else if (cmp == QMessageDataComparator::GreaterThan) { startReceptionTimeStamp = pf->_value.toDateTime(); handled = true; } if (cmp == QMessageDataComparator::GreaterThanEqual) { startReceptionTimeStamp = pf->_value.toDateTime(); handled = true; } } break; } case QMessageFilterPrivate::Sender: break; case QMessageFilterPrivate::Recipients: break; case QMessageFilterPrivate::Subject: break; case QMessageFilterPrivate::Status: break; case QMessageFilterPrivate::Priority: break; case QMessageFilterPrivate::Size: break; case QMessageFilterPrivate::None: break; } handledFiltersCount++; } msgQueryInfo.handledFiltersCount = 0; // Do filtering also for filters which has been handled above return searchMessages(msgQueryInfo, accountIds, folderUris, msgQueryInfo.body, startTimeStamp, endTimeStamp, startReceptionTimeStamp, endReceptionTimeStamp); } bool ModestEngine::searchMessages(MessageQueryInfo &msgQueryInfo, const QStringList& accountIds, const QStringList& folderUris, const QString& body, const QDateTime& startTimeStamp, const QDateTime& endTimeStamp, const QDateTime& startReceptionTimeStamp, const QDateTime& endReceptionTimeStamp) const { Q_UNUSED(body) //TODO: Body search qulonglong sDate = 0; if (startTimeStamp.isValid() && startReceptionTimeStamp.isValid()) { if (startTimeStamp < startReceptionTimeStamp) { sDate = startTimeStamp.toTime_t(); } else { sDate = startReceptionTimeStamp.toTime_t(); } } else { if (startTimeStamp.isValid()) { sDate = startTimeStamp.toTime_t(); } else if (startReceptionTimeStamp.isValid()) { sDate = startReceptionTimeStamp.toTime_t(); } } qulonglong eDate = 0; if (endTimeStamp.isValid() && endReceptionTimeStamp.isValid()) { if (endTimeStamp > endReceptionTimeStamp) { eDate = endTimeStamp.toTime_t(); } else { eDate = endReceptionTimeStamp.toTime_t(); } } else { if (endTimeStamp.isValid()) { eDate = endTimeStamp.toTime_t(); } else if (endReceptionTimeStamp.isValid()) { eDate = endReceptionTimeStamp.toTime_t(); } } if (sDate == 0 && eDate == 0 && accountIds.isEmpty() && folderUris.isEmpty()) { msgQueryInfo.allMessagesQuery = true; } else { msgQueryInfo.allMessagesQuery = false; } if (m_pendingMessageQueries.count() == 1) { // This is the first query in pending queries queue // => connect to signals m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersReceived", (ModestEngine*)this, SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage))); m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersFetched", (ModestEngine*)this, SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage))); } QDBusMessage reply = m_QtmPluginDBusInterface->call("GetHeaders", accountIds, folderUris, sDate, eDate, false); if (reply.type() != QDBusMessage::ErrorMessage) { msgQueryInfo.queryId = reply.arguments().takeFirst().toInt(); } else { return false; } return true; } void ModestEngine::searchMessagesHeadersReceivedSlot(QDBusMessage msg) { QList arguments = msg.arguments(); int queryId = arguments.takeFirst().toInt(); int index = -1; for (int i=0; i < m_pendingMessageQueries.count(); i++) { if (m_pendingMessageQueries[i].queryId == queryId) { index = i; break; } } if (index == -1) { // Received DBus Message is not reply for the DBus query // that was sent from this process/instance of modest engine // => Continue waiting return; } MessageQueryInfo &queryInfo = m_pendingMessageQueries[index]; QString reportedAccountId = arguments.takeFirst().toString(); QString accountId; if (!queryInfo.realAccountId.isEmpty()) { // Search was done to Modest "local_folders" account // => Correct Qt Mobility Messaging AccountId // can be taken realAccountId field // => Transform Messaging AccountId to Modest accountId // by removing "MO_" from the beginning of accountId string accountId = queryInfo.realAccountId; accountId.remove(0,3); } else { accountId = reportedAccountId.remove("_store"); } QString folderId = arguments.takeFirst().toString(); QVariant variant = arguments.takeFirst(); QDBusArgument argument = variant.value(); QList > messages; argument >> messages; QMessageFilterPrivate::SortedMessageFilterList filters; int firstUnhandledFilterIndex = 0; QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(queryInfo.filter); if (pf->_filterList.count() == 0) { filters.append(queryInfo.filter); } else { if (queryInfo.handledFiltersCount < pf->_filterList[queryInfo.currentFilterListIndex].count()) { filters = pf->_filterList[queryInfo.currentFilterListIndex]; } } firstUnhandledFilterIndex = queryInfo.handledFiltersCount; for (int i=0; i < messages.count(); i++) { MessagingModestMessage modestMessage; modestMessage.accountId = accountId; modestMessage.folderId = folderId; modestMessage.dateReceived = 0; modestMessage.dateSent = 0; modestMessage.size = 0; modestMessage.flags = MessagingModestMessageNotDefined; modestMessage.priority = MessagingModestMessagePriorityNotDefined; QMapIterator j(messages[i]); while (j.hasNext()) { j.next(); if (j.key() == "url") { modestMessage.url = j.value().toString(); } else if (j.key() == "message-uid") { modestMessage.id = j.value().toString(); } else if (j.key() == "from") { modestMessage.from = j.value().toString(); } else if (j.key() == "to") { modestMessage.to = j.value().toString(); } else if (j.key() == "cc") { modestMessage.cc = j.value().toString(); } else if (j.key() == "bcc") { modestMessage.bcc = j.value().toString(); } else if (j.key() == "replyto") { modestMessage.replyTo = j.value().toString(); } else if (j.key() == "subject") { modestMessage.subject = j.value().toString(); } else if (j.key() == "date-received") { modestMessage.dateReceived = j.value().toLongLong(); } else if (j.key() == "date-sent") { modestMessage.dateSent = j.value().toLongLong(); } else if (j.key() == "size") { modestMessage.size = j.value().toLongLong(); } else if (j.key() == "flags") { qlonglong flags = j.value().toUInt(); qlonglong priority = flags & MessagingModestMessageHasPriority; modestMessage.priority = static_cast(priority); modestMessage.flags = static_cast(flags); } } QMessage *message = new QMessage(messageFromModestMessage(modestMessage)); QMessagePrivate* privateMessage = QMessagePrivate::implementation(*message); privateMessage->_mimeInformationRetrieved = false; if (reportedAccountId == "local_folders") { QString id = privateMessage->_id.toString(); QString newProtocol = "maildir"; replaceProtocol(id, newProtocol); privateMessage->_id = QMessageId(id); id = privateMessage->_parentFolderId.toString(); replaceProtocol(id, newProtocol); privateMessage->_parentFolderId = QMessageFolderId(id); } if (filterMessage(*message, filters, 0)) { if (!queryInfo.ids.contains(message->id())) { queryInfo.ids.append(message->id()); } } MessageCache::instance()->insertObject(message); } } void ModestEngine::searchMessagesHeadersFetchedSlot(QDBusMessage msg) { QList arguments = msg.arguments(); int queryId = arguments.takeFirst().toInt(); int index = -1; for (int i=0; i < m_pendingMessageQueries.count(); i++) { if (m_pendingMessageQueries[i].queryId == queryId) { index = i; break; } } if (index == -1) { // Received DBus Message is not reply for the DBus query // that was sent from this process/instance of modest engine // => Continue waiting return; } handleQueryFinished(index); } void ModestEngine::handleQueryFinished(int index) const { MessageQueryInfo &queryInfo = m_pendingMessageQueries[index]; queryInfo.currentFilterListIndex++; QMessageFilterPrivate* pf = QMessageFilterPrivate::implementation(queryInfo.filter); if (queryInfo.currentFilterListIndex < pf->_filterList.count()) { if (queryAndFilterMessages(queryInfo)) { // Continue searching return; } } MessagingHelper::orderMessages(queryInfo.ids, queryInfo.sortOrder); MessagingHelper::applyOffsetAndLimitToMessageIdList(queryInfo.ids, queryInfo.limit, queryInfo.offset); // Search finished if (queryInfo.isQuery) { queryInfo.privateService->messagesFound(queryInfo.ids, true, true); } else { queryInfo.privateService->messagesCounted(queryInfo.ids.count()); } if (queryInfo.allMessagesQuery && !MessageCache::instance()->isFull()) { m_allEmailMessagesInCache = true; } m_pendingMessageQueries.removeAt(index); if (m_pendingMessageQueries.count() == 0) { // This was last query in pending queries queue // => Disconnect from "GetHeaders" request related DBus signals // Note: Disconnecting signals is done to optimize signal handling // <=> Disconnecting prevents unnecessary handling of signals // which have been sent from other applications using // Qt Mobility Messaging API m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersReceived", (ModestEngine*)this, SLOT(searchMessagesHeadersReceivedSlot(QDBusMessage))); m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "HeadersFetched", (ModestEngine*)this, SLOT(searchMessagesHeadersFetchedSlot(QDBusMessage))); } } void ModestEngine::messageQueryFinishedSlot(void* queryThread, QList ids) { int index = -1; for (int i=0; i < m_pendingMessageQueries.count(); i++) { if (m_pendingMessageQueries[i].queryThread == queryThread) { index = i; break; } } if (index >= 0) { MessageQueryInfo &queryInfo = m_pendingMessageQueries[index]; if (queryInfo.isQuery) { queryInfo.privateService->messagesFound(ids, true, true); } else { queryInfo.privateService->messagesCounted(ids.count()); } m_pendingMessageQueries.removeAt(index); } } void ModestEngine::sendEmailCallEnded(QDBusPendingCallWatcher *watcher) { if (watcher->isError ()) { // TODO: Emit a failure qWarning() << "Failed to send email via modest: " << watcher->error(); } else { // TODO: Emit a success (or put to outbox) qDebug() << "Message should be outboxed now..."; } } void ModestEngine::addMessageCallEnded(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError ()) { // TODO: Emit a failure qWarning() << "Failed to add message via modest: " << reply.error(); } else { QString id = reply.argumentAt<0>(); // TODO: Emit a success ... with message's id? qDebug() << "Message with id" << id << "should be added now"; } } void ModestEngine::folderUpdatedSlot(QDBusMessage msg) { QList arguments = msg.arguments(); QString modestAccountId = arguments.takeFirst().toString(); QString modestFolderId = arguments.takeFirst().toString(); QMessageFolderId updatedFolderId; if (modestAccountId == "local_folders") { updatedFolderId = QMessageFolderId("MO_LOCAL&maildir&"+modestFolderId); } else { if (modestAccountId.endsWith("_store")) { modestAccountId = modestAccountId.mid(0,modestAccountId.length()-6); } if (modestFolderId == "inbox") { modestFolderId = "INBOX"; } QMessageAccountId accountId = QMessageAccountId("MO_"+escapeString(modestAccountId)); QString protocol = accountEmailProtocolAsString(accountId); if ((protocol == "pop") && (modestFolderId == "INBOX")) { modestFolderId = "cache"; } updatedFolderId = QMessageFolderId(accountId.toString()+"&"+protocol+"&"+modestFolderId); } int messagesPerAccount = 5; QDBusPendingCall pendingCall = m_ModestDBusInterface->asyncCall(MODEST_DBUS_METHOD_GET_UNREAD_MESSAGES, messagesPerAccount); QDBusPendingCallWatcher* pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall); pendingCallWatcher->setProperty("folderId", updatedFolderId.toString()); connect(pendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(pendingGetUnreadMessagesFinishedSlot(QDBusPendingCallWatcher*))); } void ModestEngine::pendingGetUnreadMessagesFinishedSlot(QDBusPendingCallWatcher* pendingCallWatcher) { if (pendingCallWatcher->isError()) { return; } QDBusMessage msg = pendingCallWatcher->reply(); if (msg.arguments().count() == 0) { return; } QVariant variant = msg.arguments().takeFirst(); QDBusArgument argument = variant.value(); QList accountsWithUnreadMessages; argument >> accountsWithUnreadMessages; bool setOnlyDates = false; if (pendingCallWatcher->property("setOnlyDates").isValid()) { setOnlyDates = true; } QMessageFolderId folderId; if (pendingCallWatcher->property("folderId").isValid()) { folderId = QMessageFolderId(pendingCallWatcher->property("folderId").toString()); } for (int i=0; i < accountsWithUnreadMessages.count(); i++) { QDateTime newLatestTimeStamp; QDateTime latestTimeStamp = accountsLatestTimestamp.take(accountsWithUnreadMessages[i].accountId); for (int j=0; j < accountsWithUnreadMessages[i].unreadMessages.count(); j++) { ModestUnreadMessageDBusStruct unreadMessage = accountsWithUnreadMessages[i].unreadMessages[j]; QDateTime time = QDateTime::fromTime_t(unreadMessage.timeStamp); if (time > newLatestTimeStamp) { newLatestTimeStamp = time; } if (!setOnlyDates) { if (time > latestTimeStamp) { searchNewMessages(unreadMessage.subject, QString(), time, time, MODEST_DBUS_SEARCH_SUBJECT, 0); } } } if (newLatestTimeStamp.isValid()) { accountsLatestTimestamp.remove(accountsWithUnreadMessages[i].accountId); accountsLatestTimestamp.insert(accountsWithUnreadMessages[i].accountId, newLatestTimeStamp); } else { accountsLatestTimestamp.insert(accountsWithUnreadMessages[i].accountId, QDateTime::currentDateTime()); } } } void ModestEngine::searchNewMessages(const QString& searchString, const QString& folderToSearch, const QDateTime& startDate, const QDateTime& endDate, int searchflags, uint minimumMessageSize) const { qlonglong sDate = 0; if (startDate.isValid()) { sDate = startDate.toTime_t(); } qlonglong eDate = 0; if (endDate.isValid()) { eDate = endDate.toTime_t(); } QDBusInterface interface(MODEST_DBUS_SERVICE, MODEST_DBUS_OBJECT, MODEST_DBUS_IFACE); QDBusPendingCall pendingCall = interface.asyncCall(MODEST_DBUS_METHOD_SEARCH, searchString, folderToSearch, sDate, eDate, searchflags, minimumMessageSize); QDBusPendingCallWatcher* pendingCallWatcher = new QDBusPendingCallWatcher(pendingCall); connect(pendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(pendingSearchFinishedSlot(QDBusPendingCallWatcher*))); } void ModestEngine::pendingSearchFinishedSlot(QDBusPendingCallWatcher* pendingCallWatcher) { if (pendingCallWatcher->isError()) { return; } QDBusMessage msg = pendingCallWatcher->reply(); if (msg.arguments().count() == 0) { return; } QVariant variant = msg.arguments().takeFirst(); QDBusArgument argument = variant.value(); QList messages; argument >> messages; for (int i=0; i < messages.count(); i++) { notification(messageIdFromModestMessageId(messages[i].id), ModestEngine::Added); } } void ModestEngine::messageReadChangedSlot(QDBusMessage msg) { QString changedMessageId = msg.arguments().takeFirst().toString(); notification(messageIdFromModestMessageId(changedMessageId), ModestEngine::Updated); } QMessageManager::NotificationFilterId ModestEngine::registerNotificationFilter(QMessageStorePrivate& messageStore, const QMessageFilter &filter, QMessageManager::NotificationFilterId id) { m_messageStore = &messageStore; int filterId = id; if (filterId == 0) { filterId = ++m_filterId; } m_filters.insert(filterId, filter); return filterId; } void ModestEngine::unregisterNotificationFilter(QMessageManager::NotificationFilterId notificationFilterId) { m_filters.remove(notificationFilterId); } QByteArray ModestEngine::getMimePart (const QMessageId &id, const QString &attachmentId) { QByteArray result; QString modestAccountId = modestAccountIdFromMessageId(id); QString modestFolderId = modestFolderIdFromMessageId(id); QString modestMessageId = modestMessageIdFromMessageId(id); QString filePath, mimeType; int mimeSize = -1; bool expunge = false, isAttachment = false; QDBusPendingReply reply = m_QtmPluginDBusInterface->asyncCall( "GetMimePart", QVariant::fromValue(modestAccountId), QVariant::fromValue(modestFolderId), QVariant::fromValue(modestMessageId), QVariant::fromValue(attachmentId)); reply.waitForFinished(); if (reply.isError()) { return result; } filePath = reply.argumentAt<0>(); mimeType = reply.argumentAt<1>(); mimeSize = reply.argumentAt<2>(); isAttachment = reply.argumentAt<3>(); expunge = reply.argumentAt<4>(); if (filePath.isEmpty()) { qWarning() << "Received empty file path!"; return result; } QFile file(filePath); QIODevice::OpenMode openMode = QIODevice::ReadOnly; if (expunge) { openMode = QIODevice::ReadWrite; } if (file.open(openMode) == false) { qWarning() << "Failed to open file" << filePath << ": " << file.errorString(); return result; } result = file.readAll(); if (expunge) { file.remove(); } else { file.close(); } return result; } bool ModestEngine::retrieveBody(QMessageService& messageService, const QMessageId &id) { if (!id.isValid()) return false; QMessage msg = message(id, true); QMessageContentContainerId attachmentId = msg.bodyId(); //if (!attachmentId.isValid()) return false; return retrieve(messageService, id, attachmentId, &msg); } bool ModestEngine::retrieve(QMessageService& messageService, const QMessageId &id, const QMessageContentContainerId &attachmentId, QMessage *msg) { QByteArray result; if (!id.isValid()) return false; //if (!attachmentId.isValid()) return false; QString modestAccountId = modestAccountIdFromMessageId(id); QString modestFolderId = modestFolderIdFromMessageId(id); QString modestMessageId = modestMessageIdFromMessageId(id); QString modestAttachmentId; int opId = -1; QMessage message; if (msg != 0) { message = *msg; } else { message = this->message(id, true); } QMessageContentContainer cont = message.find(attachmentId); QMessageContentContainerPrivate *contPrivate = QMessageContentContainerPrivate::implementation(cont); if (contPrivate != 0) { modestAttachmentId = contPrivate->_attachmentId; } if (m_pending_downloads.count() == 0) { m_QtmPluginDBusInterface->connection().connect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "PartDownloadFinished", (ModestEngine*)this, SLOT(mimePartDownloadFinishedSlot(QDBusMessage))); } QDBusPendingReply reply = m_QtmPluginDBusInterface->asyncCall("DownloadMimePart", QVariant::fromValue(modestAccountId), QVariant::fromValue(modestFolderId), QVariant::fromValue(modestMessageId), QVariant::fromValue(modestAttachmentId)); reply.waitForFinished(); if (reply.isError()) { if (m_pending_downloads.count() == 0) { m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "PartDownloadFinished", (ModestEngine*)this, SLOT(mimePartDownloadFinishedSlot(QDBusMessage))); } return false; } opId = reply.argumentAt<0>(); QMessageServicePrivate* privateService = QMessageServicePrivate::implementation(messageService); m_pending_downloads.insert(opId, privateService); return true; } void ModestEngine::mimePartDownloadFinishedSlot(QDBusMessage msg) { QList arguments = msg.arguments(); int operationId = arguments.takeFirst().toInt(); if (m_pending_downloads.contains(operationId)) { QMessageServicePrivate* privateService = m_pending_downloads.take(operationId); privateService->_pendingRequestCount--; bool success = arguments.takeFirst().toBool(); if (success) { privateService->setFinished(true); } else { privateService->setFinished(false); } if (m_pending_downloads.count() == 0) { m_QtmPluginDBusInterface->connection().disconnect(MODESTENGINE_QTM_PLUGIN_NAME, MODESTENGINE_QTM_PLUGIN_PATH, MODESTENGINE_QTM_PLUGIN_NAME, "PartDownloadFinished", (ModestEngine*)this, SLOT(mimePartDownloadFinishedSlot(QDBusMessage))); } } } void ModestEngine::notification(const QMessageId& messageId, NotificationType notificationType) const { QMessageId realMessageId = messageId; QString modestAccountId = modestAccountIdFromMessageId(messageId); QString modestFolderId = modestFolderIdFromMessageId(messageId); QString modestMessageId = modestMessageIdFromMessageId(messageId); // Make sure that there will not be many Added or Removed notifications // in a row for the same message // Make also sure that there will not be updated notification for a // message that has already been notified to be removed. QString searchId; if (notificationType == ModestEngine::Added) { searchId = "A"+modestAccountId+modestFolderId+modestMessageId; } else { searchId = "D"+modestAccountId+modestFolderId+modestMessageId; } if (!m_latestAddOrRemoveNotifications.contains(searchId)) { if ((notificationType == ModestEngine::Added) || (notificationType == ModestEngine::Removed)) { // Only Added & Removed notification will be checked if (m_latestAddOrRemoveNotifications.count() > 10) { // Remove the oldest notification from the beginning of the list m_latestAddOrRemoveNotifications.removeFirst(); } // Append new notification m_latestAddOrRemoveNotifications.append(searchId); } } else { // Add or Remove notification for the message was already handled! // => Skip unwanted notification return; } QMessageManager::NotificationFilterIdSet matchingFilters; QMessage message; MessagingModestMessage modestMessage = messageFromModest(modestAccountId, modestFolderId, modestMessageId); if (modestMessage.accountId.isEmpty()) { return; } if (modestMessage.flags & MessagingModestMessageDeleted) { notificationType = ModestEngine::Removed; } if (modestMessage.size == 0) { notificationType = ModestEngine::Removed; } message = messageFromModestMessage(modestMessage); realMessageId = message.id(); if (notificationType == ModestEngine::Added) { MessageCache::instance()->insert(message); } else if (notificationType == ModestEngine::Updated) { MessageCache::instance()->update(message); } else if (notificationType == ModestEngine::Removed) { MessageCache::instance()->remove(message.id()); } // Copy the filter map to protect against modification during traversal QMap filters(m_filters); QMap::const_iterator it = filters.begin(), end = filters.end(); int counter = 1; for ( ; it != end; ++it) { const QMessageFilter &filter(it.value()); counter++; if (filter.isEmpty()) { // Empty filter matches to all messages matchingFilters.insert(it.key()); } else { if (message.type() == QMessage::NoType) { continue; } QMessageFilterPrivate* privateMessageFilter = QMessageFilterPrivate::implementation(filter); if (privateMessageFilter->filter(message)) { matchingFilters.insert(it.key()); } } } if (matchingFilters.count() > 0) { if (notificationType == ModestEngine::Added) { m_messageStore->messageNotification(QMessageStorePrivate::Added, realMessageId, matchingFilters); } else if (notificationType == ModestEngine::Updated) { m_messageStore->messageNotification(QMessageStorePrivate::Updated, realMessageId, matchingFilters); } else if (notificationType == ModestEngine::Removed) { m_messageStore->messageNotification(QMessageStorePrivate::Removed, realMessageId, matchingFilters); } } } QMessageAccountId ModestEngine::accountIdFromModestMessageId(const QString& modestMessageId) const { // Modest messageId format is following: // ://@:... QMessageAccountId accountId; int usernameBegin = modestMessageId.indexOf("//")+2; int usernameEnd = modestMessageId.indexOf("@"); QString username = QUrl::fromPercentEncoding(modestMessageId.mid(usernameBegin, usernameEnd-usernameBegin).toUtf8()); int hostnameBegin = usernameEnd+1; int hostnameEnd = modestMessageId.indexOf(':',hostnameBegin); QString hostname = QUrl::fromPercentEncoding(modestMessageId.mid(hostnameBegin, hostnameEnd-hostnameBegin).toUtf8()); foreach (QMessageAccount value, iAccounts) { QMessageAccountId accId = value.id(); if ((accountUsername(accId) == username) && (accountHostname(accId) == hostname)) { accountId = accId; } } if (!accountId.isValid()) { if (modestMessageId.left(modestMessageId.indexOf(':')) == "maildir") { accountId = QMessageAccountId("MO_LOCAL"); } } return accountId; } QMessageFolderId ModestEngine::folderIdFromModestMessageId(const QString& modestMessageId, const QMessageAccountId accountId) const { // Modest messageId format is following: // ://@:... QMessageFolderId folderId; QString folderIdAsString; if (!accountId.isValid()) { folderIdAsString = accountIdFromModestMessageId(modestMessageId).toString(); } else { folderIdAsString = accountId.toString(); } int protocolEnd = modestMessageId.indexOf(':'); QString protocol = modestMessageId.left(protocolEnd); folderIdAsString += "&" + protocol; if (protocol == "pop") { folderIdAsString += "&cache"; } else if (protocol == "imap") { int pathBegin = modestMessageId.indexOf('/',modestMessageId.lastIndexOf(':'))+1; int pathEnd = modestMessageId.lastIndexOf('/'); folderIdAsString += "&" + modestMessageId.mid(pathBegin, pathEnd-pathBegin); } else if (protocol == "maildir") { int pathBegin = modestMessageId.indexOf('#')+1; int pathEnd = modestMessageId.lastIndexOf('/'); folderIdAsString += "&" + modestMessageId.mid(pathBegin, pathEnd-pathBegin); } folderId = QMessageFolderId(QUrl::fromPercentEncoding(folderIdAsString.toUtf8())); return folderId; } QString ModestEngine::modestAccountIdFromAccountId(const QMessageAccountId& accountId) const { // Just remove "MO_" prefix from the beginning of id string return accountId.toString().remove(0,3); } QString ModestEngine::modestFolderIdFromFolderId(const QMessageFolderId& folderId) const { QString modestFolderId; QString folderIdString = folderId.toString(); int protocolBegin = folderIdString.indexOf('&'); int protocolEnd = folderIdString.lastIndexOf('&'); modestFolderId = folderIdString.mid(protocolEnd+1); QString protocol = folderIdString.mid(protocolBegin+1,protocolEnd-protocolBegin-1); if ((protocol == "pop") && (modestFolderId == "cache")) { modestFolderId = "INBOX"; } return modestFolderId; } QString ModestEngine::modestFolderUriFromFolderId(const QMessageFolderId& folderId) const { Q_UNUSED(folderId) //TODO: return QString(); } QString ModestEngine::modestAccountIdFromMessageId(const QMessageId& messageId, bool checkProtocol) const { QString id = messageId.toString(); int protocolBegin = id.indexOf('&'); int protocolEnd = id.lastIndexOf('&'); QString protocol = id.mid(protocolBegin+1,protocolEnd-protocolBegin-1); if (checkProtocol && protocol == "maildir") { return "local_folders"; } return unescapeString(id.left(protocolBegin).remove(0,3)); } QMessageAccountId ModestEngine::accountIdFromFolderId(const QMessageFolderId& folderId) const { QString id = folderId.toString(); int protocolBegin = id.indexOf('&'); return QMessageAccountId(id.left(protocolBegin)); } QMessageAccountId ModestEngine::accountIdFromMessageId(const QMessageId& messageId) const { QString id = messageId.toString(); int protocolBegin = id.indexOf('&'); return QMessageAccountId(id.left(protocolBegin)); } QString ModestEngine::modestAccountIdFromFolderId(const QMessageFolderId& folderId, bool checkProtocol) const { QString id = folderId.toString(); int protocolBegin = id.indexOf('&'); int protocolEnd = id.lastIndexOf('&'); QString protocol = id.mid(protocolBegin+1,protocolEnd-protocolBegin-1); if (checkProtocol && protocol == "maildir") { return "local_folders"; } return unescapeString(id.left(protocolBegin).remove(0,3)); } QString ModestEngine::modestFolderIdFromMessageId(const QMessageId& messageId) const { QString id = messageId.toString(); int protocolBegin = id.indexOf('&'); int protocolEnd = id.lastIndexOf('&'); int folderEnd = id.lastIndexOf('/'); QString protocol = id.mid(protocolBegin+1,protocolEnd-protocolBegin-1); id = id.mid(protocolEnd+1,folderEnd-protocolEnd-1); if ((protocol == "pop") && (id == "cache")) { id = "INBOX"; } return id; } void ModestEngine::replaceProtocol(QString& id, const QString& newProtocol) const { int protocolBegin = id.indexOf('&'); int protocolEnd = id.lastIndexOf('&'); id.remove(protocolBegin+1,protocolEnd-protocolBegin-1); id.insert(protocolBegin+1, newProtocol); } QMessageAccountId ModestEngine::realAccountId(const MessagingModestMessage& modestMessage) const { QMessageAccountId accountId; if (modestMessage.accountId == "local_folders") { QString accountIdString; // Message is in local foldar, but message can be linked // to actual account using 'From', 'To', 'Cc' or 'Bcc' fields foreach (QMessageAccount value, iAccounts) { QMessageAccountPrivate* privAccount = QMessageAccountPrivate::implementation(value); if (modestMessage.from.contains(privAccount->_address.addressee())) { accountIdString = value.id().toString(); break; } else if (modestMessage.to.contains(privAccount->_address.addressee())) { accountIdString = value.id().toString(); break; } else if (modestMessage.cc.contains(privAccount->_address.addressee())) { accountIdString = value.id().toString(); break; } else if (modestMessage.bcc.contains(privAccount->_address.addressee())) { accountIdString = value.id().toString(); break; } } if (!accountIdString.isEmpty()) { accountId = QMessageAccountId(accountIdString); } } else { accountId = accountIdFromModestAccountId(modestMessage.accountId); } return accountId; } QString ModestEngine::modestMessageIdFromMessageId(const QMessageId& messageId) const { QString id = messageId.toString(); return id.mid(id.lastIndexOf('/')+1); } QMessageAccountId ModestEngine::accountIdFromModestAccountId(const QString& accountId) const { // Just add "MO_" prefix to the beginning of id string & escape created Id return QMessageAccountId(escapeString("MO_"+accountId)); } QMessageFolderId ModestEngine::folderIdFromModestFolderId(const QMessageAccountId& accountId, bool isLocalFolder, const QString& modestFolderId) const { // Format: && QMessageFolderId folderId; if (isLocalFolder) { folderId = QMessageFolderId(accountId.toString()+"&maildir&"+modestFolderId); } else { QString protocol = accountEmailProtocolAsString(accountId); if ((protocol == "pop") && (modestFolderId == "INBOX")) { folderId = QMessageFolderId(accountId.toString()+"&"+protocol+"&cache"); } else { folderId = QMessageFolderId(accountId.toString()+"&"+protocol+"&"+modestFolderId); } } return folderId; } QMessageId ModestEngine::messageIdFromModestMessageId(const QString& messageId) const { QString messageIdString = folderIdFromModestMessageId(messageId).toString(); int idPartBegin = messageId.lastIndexOf('/'); messageIdString += messageId.mid(idPartBegin, idPartBegin-messageId.length()); return QMessageId(messageIdString); } QMessageId ModestEngine::messageIdFromModestMessageFilePath(const QString& messageFilePath) const { QString messageIdString; QString filePath = messageFilePath; QString localRootFolder = this->localRootFolder(); if (filePath.startsWith(localRootFolder)) { messageIdString = "MO_LOCAL&maildir&"; filePath.remove(0,localRootFolder.length()+1); filePath.remove("/cur"); messageIdString += filePath.left(filePath.lastIndexOf('!')); } else { foreach (QMessageAccount value, iAccounts) { QMessageAccountId accountId = value.id(); QString rootFolder = accountRootFolder(accountId); if (filePath.startsWith(rootFolder)) { QString protocol = accountEmailProtocolAsString(accountId); messageIdString = accountId.toString()+"&"+protocol+"&"; filePath.remove(0,rootFolder.length()+1); filePath.remove("/subfolders"); messageIdString += filePath.left(filePath.lastIndexOf('.')); if (protocol == "pop") { QDir dir(messageFilePath); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); QFileInfoList dirs = dir.entryInfoList(); if (dirs.count() > 0) { QString fileName = dirs[0].fileName(); // Remove folder that contains actual message messageIdString = messageIdString.left(messageIdString.lastIndexOf('/')+1); // Add message name messageIdString = messageIdString+fileName; } } } } } return QMessageId(messageIdString); } QString ModestEngine::unescapeString(const QString& string) { QString unescapedString; QByteArray str = string.toUtf8(); gchar* unescaped_string = gconf_unescape_key(str.data(), str.length()); unescapedString = QString::fromUtf8(unescaped_string); g_free(unescaped_string); return unescapedString; } QString ModestEngine::escapeString(const QString& string) { QString escapedString; QByteArray str = unescapeString(string).toUtf8(); gchar* escaped_string = gconf_escape_key(str.data(), str.length()); escapedString = QString::fromUtf8(escaped_string); g_free(escaped_string); return escapedString; } INotifyWatcher::INotifyWatcher() { // Initialize inotify instance // => returned file descriptor is associated with // a new inotify event queue // O_CLOEXEC flag makes sure that file descriptor // is closed if execution is transferred // from this process to a new program // (Check more info from 'execve' documentation) #ifdef IN_CLOEXEC m_inotifyFileDescriptor = inotify_init1(IN_CLOEXEC); #else m_inotifyFileDescriptor = inotify_init(); if (m_inotifyFileDescriptor >= 0) { ::fcntl(m_inotifyFileDescriptor, F_SETFD, FD_CLOEXEC); } #endif if (m_inotifyFileDescriptor >= 0) { // Change thread affinity for this object to this // thread. // => Event processing (for this objects events) will // be done in this thread moveToThread(this); } } INotifyWatcher::~INotifyWatcher() { // Tell the thread's event loop to exit // => thread returns from the call to exec() exit(); // Wait until this thread has finished execution // <=> waits until thread returns from run() wait(); clear(); // Close file descriptor that's referring to inotify instance // => The underlying inotify object and its resources are freed ::close(m_inotifyFileDescriptor); } void INotifyWatcher::clear() { // Remove all watches from inotify instance watch list QMapIterator i(m_dirs); while (i.hasNext()) { inotify_rm_watch(m_inotifyFileDescriptor, i.next().key()); } m_dirs.clear(); QMapIterator j(m_files); while (j.hasNext()) { inotify_rm_watch(m_inotifyFileDescriptor, j.next().key()); } m_files.clear(); } void INotifyWatcher::run() { // Start listening inotify QSocketNotifier socketNotifier(m_inotifyFileDescriptor, QSocketNotifier::Read, this); connect(&socketNotifier, SIGNAL(activated(int)), SLOT(notifySlot())); // Enter the thread event loop (void) exec(); } int INotifyWatcher::addFile(const QString& path, uint eventsToWatch) { int watchDescriptor = 0; QMutexLocker locker(&m_mutex); if (m_inotifyFileDescriptor >= 0) { int watchDescriptor = 0; if (eventsToWatch == 0) { watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor, QFile::encodeName(path), 0 | IN_ATTRIB | IN_MODIFY | IN_MOVE | IN_MOVE_SELF | IN_DELETE_SELF); } else { watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor, QFile::encodeName(path), eventsToWatch); } if (watchDescriptor > 0) { m_files.insert(watchDescriptor, path); } else { watchDescriptor = 0; } } // Start thread (if thread is not already running) start(); return watchDescriptor; } int INotifyWatcher::addDirectory(const QString& path, uint eventsToWatch) { int watchDescriptor = 0; QMutexLocker locker(&m_mutex); if (m_inotifyFileDescriptor >= 0) { int watchDescriptor = 0; if (eventsToWatch == 0) { watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor, QFile::encodeName(path), 0 | IN_ATTRIB | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF); } else { watchDescriptor = inotify_add_watch(m_inotifyFileDescriptor, QFile::encodeName(path), eventsToWatch); } if (watchDescriptor > 0) { m_dirs.insert(watchDescriptor, path); } else { watchDescriptor = 0; } } // Start thread (if thread is not already running) start(); return watchDescriptor; } QStringList INotifyWatcher::directories() const { return m_dirs.values(); } QStringList INotifyWatcher::files() const { return m_dirs.values(); } void INotifyWatcher::notifySlot() { QMutexLocker locker(&m_mutex); int bufferSize = 0; ioctl(m_inotifyFileDescriptor, FIONREAD, (char*) &bufferSize); QVarLengthArray buffer(bufferSize); bufferSize = read(m_inotifyFileDescriptor, buffer.data(), bufferSize); const char* at = buffer.data(); const char* const end = at + bufferSize; QMap eventsForWatchedFile; QMap eventsForFileInWatchedDirectory; while (at < end) { const inotify_event *event = reinterpret_cast(at); if (m_files.contains(event->wd)) { // File event handling if (eventsForWatchedFile.contains(event->wd)) { // There is already unhandled event for this file in queue // => Mask is ORed to existing event eventsForWatchedFile[event->wd].mask |= event->mask; } else { // There is no event for this file in queue // => New event is created INotifyEvent inotifyEvent; inotifyEvent.watchDescriptor = event->wd; inotifyEvent.mask = event->mask; inotifyEvent.fileName = QString::fromAscii(event->name, event->len); eventsForWatchedFile.insert(event->wd, inotifyEvent); } } else { // Directory event handling QString changeForFileInDirectory = QString::fromAscii(event->name, event->len); // Remove unnecessary postfix (starting with '!') from the file name changeForFileInDirectory = changeForFileInDirectory.left(changeForFileInDirectory.lastIndexOf('!')); if (!changeForFileInDirectory.isEmpty()) { QString eventId = QString::number(event->wd)+changeForFileInDirectory; if (eventsForFileInWatchedDirectory.contains(eventId)) { // There is already unhandled event for this file in queue // => Mask is ORed to existing event eventsForFileInWatchedDirectory[eventId].mask |= event->mask; } else { // There is no event for this file in queue // => New event is created INotifyEvent inotifyEvent; inotifyEvent.watchDescriptor = event->wd; inotifyEvent.mask = event->mask; inotifyEvent.fileName = QString::fromAscii(event->name, event->len); eventsForFileInWatchedDirectory.insert(eventId, inotifyEvent); } } } at += sizeof(inotify_event) + event->len; } QMap::const_iterator it = eventsForWatchedFile.constBegin(); while (it != eventsForWatchedFile.constEnd()) { INotifyEvent event = *it; QString file = m_files.value(event.watchDescriptor); if (!file.isEmpty()) { emit fileChanged(event.watchDescriptor, file, event.mask); } ++it; } QMap::const_iterator jt = eventsForFileInWatchedDirectory.constBegin(); while (jt != eventsForFileInWatchedDirectory.constEnd()) { INotifyEvent event = *jt; QString file = m_dirs.value(event.watchDescriptor)+"/"+event.fileName; emit fileChanged(event.watchDescriptor, file, event.mask); ++jt; } } ModestStringMap ModestEngine::getModestSenderInfo(QMessage &message) { QMessageAddress address; ModestStringMap senderInfo; QMessageAccountId accountId; QString value; accountId = message.parentAccountId(); if (accountId.isValid() == false) { qWarning () << "Account ID is invalid"; return ModestStringMap(); } senderInfo["account-name"] = unescapeString(modestAccountIdFromAccountId(accountId)); QMessageAccount messageAccount = account(accountId); QMessageAccountPrivate* privAccount = QMessageAccountPrivate::implementation(messageAccount); address = privAccount->_address; value = address.addressee(); if (value.isEmpty() == false && value.isNull() == false) { senderInfo["from"] = value; } return senderInfo; } ModestStringMap ModestEngine::getModestRecipients(QMessage &message) { QMessageAddressList addresses; QMessageAddress address; ModestStringMap recipients; QString value; addresses = message.to(); value.clear(); for (int i = 0; i < addresses.length(); i++) { address = addresses[i]; if (value.isEmpty()) { value = address.addressee(); } else { value.append (","); value.append (address.addressee()); } } if (value.isEmpty() == false && value.isNull() == false) { recipients["to"] = value; } addresses = message.cc(); value.clear(); for (int i = 0; i < addresses.length(); i++) { address = addresses[i]; if (value.isEmpty()) { value = address.addressee(); } else { value.append (","); value.append (address.addressee()); } } if (value.isEmpty() == false && value.isNull() == false) { recipients["cc"] = value; } addresses = message.bcc(); value.clear(); for (int i = 0; i < addresses.length(); i++) { address = addresses[i]; if (value.isEmpty()) { value = address.addressee(); } else { value.append (","); value.append (address.addressee()); } } if (value.isEmpty() == false && value.isNull() == false) { recipients["bcc"] = value; } return recipients; } ModestStringMap ModestEngine::getModestMessageData(QMessage &message) { QMessageContentContainerId bodyId; QMessageContentContainer body; ModestStringMap messageData; QString value; value = message.subject(); if (value.isEmpty() == false && value.isNull() == false) { messageData["subject"] = value; } bodyId = message.bodyId(); if (bodyId.isValid()) { body = message.find (bodyId); } else { body = message; } value = body.contentType(); if (value == "text") { QString key, data; bool hasContent = false; value = body.contentSubType(); if ((hasContent = body.isContentAvailable()) == true) { data = body.textContent(); } if (value == "plain") { key = "plain-body"; } else if (value == "html") { key = "html-body"; } if (key.isEmpty() == false && key.isNull() == false && hasContent) { messageData[key] = data; } } if (message.status() & QMessage::Read) { messageData["read"] = ""; } return messageData; } ModestStringMapList ModestEngine::getModestAttachments(QMessage &message) { QMessageContentContainerIdList attachmentIds; ModestStringMapList attachments; QMessage::StatusFlags messageStatus; QString value; messageStatus = message.status(); if (messageStatus & QMessage::HasAttachments) { attachmentIds = message.attachmentIds(); foreach (QMessageContentContainerId identifier, attachmentIds) { ModestStringMap attachmentData; QMessageContentContainer attachmentCont; if (identifier.isValid() == false) continue; attachmentCont = message.find (identifier); if (attachmentCont.isContentAvailable () == false) continue; attachmentData.clear(); value = attachmentCont.contentType(); if (value.isEmpty() == false) { value.append("/"); value.append (attachmentCont.contentSubType()); attachmentData["mime-type"] = value; qDebug() << "mime-type: " << value; } value = QMessageContentContainerPrivate::attachmentFilename ( attachmentCont); if (value.isEmpty() == false) { attachmentData["filename"] = value; qDebug() << "filename: " << value; } qDebug() << "Charset: " << attachmentCont.contentCharset(); qDebug() << "Headers: " << attachmentCont.headerFields(); if (attachmentData.isEmpty() == false) { attachmentData["content-id"] = identifier.toString(); attachments.append (attachmentData); } } } return attachments; } ModestStringMapList ModestEngine::getModestImages(QMessage &message) { Q_UNUSED(message); // Don't know if this even makes sense. Modest expects inlined images // to be in a separate list, but that doesn't make much sense? return ModestStringMapList(); } uint ModestEngine::getModestPriority(QMessage &message) { uint priority = 0; switch (message.priority()) { case QMessage::HighPriority: priority = MODESTENGINE_HIGH_PRIORITY; break; default: case QMessage::NormalPriority: priority = MODESTENGINE_NORMAL_PRIORITY; break; case QMessage::LowPriority: priority = MODESTENGINE_LOW_PRIORITY; break; } return priority; } ModestStringMap ModestEngine::getModestHeaders(QMessage &message) { Q_UNUSED(message); return ModestStringMap(); // stub } MessageQueryThread::MessageQueryThread(const QMessageFilter &filter, const QMessageSortOrder &sortOrder) : m_filter(filter), m_sortOrder(sortOrder) { } MessageQueryThread::~MessageQueryThread() { // Tell the thread's event loop to exit // => thread returns from the call to exec() exit(); // Wait until this thread has finished execution // <=> waits until thread returns from run() wait(); } void MessageQueryThread::run() { QMessageIdList ids = MessageCache::instance()->messageIds(); MessagingHelper::filterMessages(ids, m_filter & QMessageFilter::byType(QMessage::Email)); MessagingHelper::orderMessages(ids, m_sortOrder); emit queryFinished(this, ids); QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); // Enter the thread event loop (void) exec(); } #include "moc_modestengine_maemo_p.cpp" QTM_END_NAMESPACE