/**************************************************************************** ** ** Copyright (C) 2013 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 "qmfservice_maemo6_p.h" #include "qmfhelpers_maemo6_p.h" #include "qmessageservice_maemo6_p.h" #include "maemo6helpers_p.h" #include #include #define XDG_PROG "/usr/bin/xdg-email" #define SINGLESHOT(slot) \ do {\ QTimer *__t = timer();\ connect(__t, SIGNAL(timeout()), this, SLOT(slot));\ __t->start(0);\ } while (0) QTM_BEGIN_NAMESPACE namespace { struct TextPartSearcher { QString _search; Qt::CaseSensitivity _cs; TextPartSearcher(const QString &searchText, Qt::CaseSensitivity cs) : _search(searchText) , _cs(cs) {} bool operator()(const QMailMessagePart &part) { if (part.contentType().type().toLower() == "text") { if (part.body().data().contains(_search, _cs)) { // We have located some matching text - stop the traversal return false; } } return true; } }; } using namespace QmfHelpers; QMFService::QMFService(QMessageService *service, QObject *parent) : QObject(parent), m_service(service), m_timer(0), m_active(0), m_activeStoreAction(false), m_limit(0), m_offset(0) { // Ensure the message store is initialized QMessageManager(); connect(&m_transmit, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(transmitActivityChanged(QMailServiceAction::Activity))); connect(&m_transmit, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status))); connect(&m_retrieval, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(retrievalActivityChanged(QMailServiceAction::Activity))); connect(&m_retrieval, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status))); connect(&m_storage, SIGNAL(activityChanged(QMailServiceAction::Activity)), this, SLOT(storageActivityChanged(QMailServiceAction::Activity))); connect(&m_storage, SIGNAL(statusChanged(QMailServiceAction::Status)), this, SLOT(statusChanged(QMailServiceAction::Status))); } QMFService::~QMFService() { } bool QMFService::queryMessages(const QMessageFilter &filter, const QMessageSortOrder &sortOrder, uint limit, uint offset) { if (isBusy()) { return false; } m_active = 0; m_activeStoreAction = true; stateChanged(QMessageService::ActiveState); m_candidateIds = QMailStore::instance()->queryMessages(convert(filter), convert(sortOrder), limit, offset); QMessageManager::Error error = convert(QMailStore::instance()->lastError()); setError(error); if (error == QMessageManager::NoError) { m_lastFilter = QMessageFilter(); m_lastOrdering = QMessageSortOrder(); m_match = QString(); m_limit = static_cast(limit); m_offset = 0; m_matchingIds.clear(); SINGLESHOT(reportMatchingIds()); return true; } return false; } bool QMFService::queryMessages(const QMessageFilter &filter, const QString &body, QMessageDataComparator::MatchFlags matchFlags, const QMessageSortOrder &sortOrder, uint limit, uint offset) { if (body.isEmpty()) { return queryMessages(filter, sortOrder, limit, offset); } if (isBusy()) { return false; } m_active = 0; m_activeStoreAction = true; stateChanged(QMessageService::ActiveState); if ((filter == m_lastFilter) && (sortOrder == m_lastOrdering) && (body == m_match) && (matchFlags == m_matchFlags)) { // This is a continuation of the last search m_limit = static_cast(limit); m_offset = static_cast(offset); SINGLESHOT(findMatchingIds()); return true; } // Find all messages to perform the body search on m_candidateIds = QMailStore::instance()->queryMessages(convert(filter), convert(sortOrder)); QMessageManager::Error error = convert(QMailStore::instance()->lastError()); setError(error); if (error == QMessageManager::NoError) { m_lastFilter = filter; m_lastOrdering = sortOrder; m_match = body; m_limit = static_cast(limit); m_offset = static_cast(offset); m_matchFlags = matchFlags; m_matchingIds.clear(); SINGLESHOT(findMatchingIds()); return true; } return false; } bool QMFService::countMessages(const QMessageFilter &filter) { if (isBusy()) { return false; } m_active = 0; m_activeStoreAction = true; m_candidateIds = QMailStore::instance()->queryMessages(convert(filter)); QMessageManager::Error error = convert(QMailStore::instance()->lastError()); setError(error); if (error == QMessageManager::NoError) { m_lastFilter = QMessageFilter(); m_lastOrdering = QMessageSortOrder(); m_match = QString(); m_limit = 0; m_offset = 0; m_matchingIds.clear(); SINGLESHOT(reportMatchingCount()); return true; } return false; } bool QMFService::send(QMessage &message) { if (isBusy()) { return false; } m_active = 0; if (!message.parentAccountId().isValid()) { // Attach to the default account message.setParentAccountId(QMessageAccount::defaultAccount(message.type())); if (!message.parentAccountId().isValid()) { setError(QMessageManager::InvalidId); qWarning() << "Invalid message account ID"; return false; } } // Ensure the message contains a timestamp if (!message.date().isValid()) { message.setDate(QDateTime::currentDateTime()); } QMessageFolderId existingFolderId(message.parentFolderId()); // Move the message to the Outbox folder QMessagePrivate::setStandardFolder(message, QMessage::OutboxFolder); QMailMessage msg(convert(message)); // Ensure that the from address is added if (msg.from().isNull()) { QMailAccount account(msg.parentAccountId()); msg.setFrom(account.fromAddress()); } // Mark this message as outgoing msg.setStatus(QMailMessage::Outbox, true); if (msg.id().isValid()) { // Update the message if (!QMailStore::instance()->updateMessage(&msg)) { setError(QMessageManager::FrameworkFault); qWarning() << "Unable to mark message as outgoing"; return false; } } else { // Add this message to the store if (!QMailStore::instance()->addMessage(&msg)) { setError(QMessageManager::FrameworkFault); qWarning() << "Unable to store message for transmission"; return false; } // Update message QMessagePrivate *p = QMessagePrivate::implementation(message); p->_id = convert(msg.id()); p->_contentIdentifier = msg.contentIdentifier(); p->_contentScheme = msg.contentScheme(); } m_transmitIds = QMailStore::instance()->queryMessages(QMailMessageKey::status(QMailMessage::Outbox) & QMailMessageKey::parentAccountId(msg.parentAccountId())); setError(QMessageManager::NoError); m_active = &m_transmit; m_transmit.transmitMessages(msg.parentAccountId()); return true; } bool QMFService::compose(const QMessage &message) { if (isBusy()) { return false; } m_active = 0; setError(QMessageManager::NoError); QStringList arguments; if (message.subject().size()) arguments << "--subject" << message.subject(); if (message.textContent().size()) arguments << "--body" << message.textContent(); if (message.cc().size()) arguments << "--cc" << MessagingHelper::addressListToString(message.cc()); if (message.bcc().size()) arguments << "--bcc" << MessagingHelper::addressListToString(message.bcc()); if (message.to().size()) arguments << MessagingHelper::addressListToString(message.to()); //qDebug() << __PRETTY_FUNCTION__ << " starting:" << XDG_PROG << arguments; if (QProcess::execute(XDG_PROG, arguments) != 0) { qWarning() << __PRETTY_FUNCTION__ << "Cannot start email composer: " << XDG_PROG << arguments; setError(QMessageManager::FrameworkFault); return false; } //qDebug() << __PRETTY_FUNCTION__ << "execution completed"; return true; } bool QMFService::retrieveHeader(const QMessageId& id) { if (isBusy()) { return false; } m_active = 0; QMailMessageId messageId(convert(id)); if (!messageId.isValid()) { setError(QMessageManager::InvalidId); qWarning() << "Invalid message ID"; return false; } // Operation is not relevant to QMF - meta data retrieval always includes header information setError(QMessageManager::NoError); m_active = 0; SINGLESHOT(completed()); return true; } bool QMFService::retrieveBody(const QMessageId& id) { if (isBusy()) { return false; } m_active = 0; QMailMessageId messageId(convert(id)); if (!messageId.isValid()) { setError(QMessageManager::InvalidId); qWarning() << "Invalid message ID"; return false; } setError(QMessageManager::NoError); m_active = &m_retrieval; m_retrieval.retrieveMessages(QMailMessageIdList() << messageId, QMailRetrievalAction::Content); return true; } bool QMFService::retrieve(const QMessageId &messageId, const QMessageContentContainerId& id) { if (isBusy()) { return false; } m_active = 0; QMailMessagePart::Location location(convert(id)); if (!location.isValid()) { setError(QMessageManager::InvalidId); qWarning() << "Invalid message part location"; return false; } setError(QMessageManager::NoError); m_active = &m_retrieval; m_retrieval.retrieveMessagePart(location); return true; Q_UNUSED(messageId) } bool QMFService::show(const QMessageId& id) { if (isBusy()) { return false; } m_active = 0; setError(QMessageManager::NoError); if (!(id.toString().startsWith("QMF_") && id.isValid())) { setError(QMessageManager::InvalidId); return false; } //QMessage m(id); return compose(QMessage(id)); } bool QMFService::exportUpdates(const QMessageAccountId &id) { if (isBusy()) { return false; } m_active = 0; QMailAccountId accountId(convert(id)); if (!accountId.isValid()) { setError(QMessageManager::InvalidId); qWarning() << "Account ID is not valid"; return false; } setError(QMessageManager::NoError); m_active = &m_retrieval; m_retrieval.exportUpdates(accountId); return true; } QMessageService::State QMFService::state() const { if (m_active) { return convert(m_active->activity()); } else if(m_activeStoreAction) { return QMessageService::ActiveState; } return QMessageService::FinishedState; } void QMFService::cancel() { delete m_timer; m_timer = 0; if (m_active) { m_active->cancelOperation(); } else if (m_activeStoreAction) { m_activeStoreAction = false; } } bool QMFService::isBusy() const { if ((m_active && ((m_active->activity() == QMailServiceAction::Pending) || (m_active->activity() == QMailServiceAction::InProgress))) || m_activeStoreAction) { qWarning() << "Action is currently busy"; return true; } return false; } QTimer *QMFService::timer() { if (!m_timer) { m_timer = new QTimer(this); m_timer->setSingleShot(true); } else { if (m_timer->isActive()) { m_timer->stop(); } m_timer->disconnect(); } return m_timer; } void QMFService::transmitActivityChanged(QMailServiceAction::Activity a) { qDebug() << __PRETTY_FUNCTION__ << "action = " << a; if (a == QMailServiceAction::Successful) { if (!m_transmitIds.isEmpty()) { // If these messages were transmitted, we need to move them to Sent folder QMailMessageKey filter(QMailMessageKey::id(m_transmitIds)); foreach (const QMailMessageId &id, QMailStore::instance()->queryMessages(filter & QMailMessageKey::status(QMailMessage::Sent))) { QMessage message(convert(id)); QMessagePrivate::setStandardFolder(message, QMessage::SentFolder); if (!QMessageManager().updateMessage(&message)) { qWarning() << "Unable to mark message as sent!"; } } m_transmitIds.clear(); } } else if (a == QMailServiceAction::Failed) { m_transmitIds.clear(); if (error() == QMessageManager::NoError) { // We may have failed due to some part of the request remaining incomplete setError(QMessageManager::RequestIncomplete); } QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); p->setFinished(false); } if (a != QMailServiceAction::Failed) stateChanged(convert(a)); } void QMFService::statusChanged(const QMailServiceAction::Status &s) { qDebug() << __PRETTY_FUNCTION__ << "status error = " << s.errorCode; if (s.errorCode != QMailServiceAction::Status::ErrNoError) { qWarning() << QString("Service error %1: \"%2\"").arg(s.errorCode).arg(s.text); if (s.errorCode == QMailServiceAction::Status::ErrNotImplemented) { setError(QMessageManager::NotYetImplemented); } else if ((s.errorCode == QMailServiceAction::Status::ErrNonexistentMessage) || (s.errorCode == QMailServiceAction::Status::ErrEnqueueFailed) || (s.errorCode == QMailServiceAction::Status::ErrInvalidAddress) || (s.errorCode == QMailServiceAction::Status::ErrInvalidData)) { setError(QMessageManager::ConstraintFailure); } else { setError(QMessageManager::FrameworkFault); } } } void QMFService::retrievalActivityChanged(QMailServiceAction::Activity a) { qDebug() << __PRETTY_FUNCTION__ << "action = " << a; if (a == QMailServiceAction::Failed) { if (error() == QMessageManager::NoError) { // We may have failed due to some part of the request remaining incomplete setError(QMessageManager::RequestIncomplete); } QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); p->setFinished(false); } if (a != QMailServiceAction::Failed) stateChanged(convert(a)); } void QMFService::completed() { m_activeStoreAction = false; stateChanged(QMessageService::FinishedState); } bool QMFService::messageMatch(const QMailMessageId &messageId) { if (m_match.isEmpty()) { return true; } const QMailMessage candidate(messageId); if (candidate.id().isValid()) { Qt::CaseSensitivity cs = (m_matchFlags & QMessageDataComparator::MatchCaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; // Search only messages or message parts that are of type 'text/*' if (candidate.hasBody()) { if (candidate.contentType().type().toLower() == "text") { if (candidate.body().data().contains(m_match, cs)) return true; } } else if (candidate.multipartType() != QMailMessage::MultipartNone) { // Search all 'text/*' parts within this message TextPartSearcher searcher(m_match, cs); if (candidate.foreachPart(searcher) == false) { // We found a matching part in the message return true; } } } return false; } void QMFService::reportMatchingIds() { QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); // QMessageServicePrivate emits QMessageService::messagesFound() signal p->messagesFound(convert(m_candidateIds), true, true); m_activeStoreAction = false; } void QMFService::reportMatchingCount() { QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); // QMessageServicePrivate emits QMessageService::messagesCounted() signal p->messagesCounted(m_candidateIds.count()); m_activeStoreAction = false; } void QMFService::matchQueryCompleted() { QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); // QMessageServicePrivate emits QMessageService::messagesFound() signal p->messagesFound(m_resultIds, true, true); m_activeStoreAction = false; m_resultIds.clear(); } void QMFService::findMatchingIds() { int required = ((m_offset + m_limit) - m_matchingIds.count()); // Are any of the existing IDs part of the result set? if (required < m_limit) { m_resultIds = m_matchingIds.mid(m_offset, m_limit); } if ((required > 0 || m_limit == 0) && !m_candidateIds.isEmpty()) { SINGLESHOT(testNextMessage()); } else { matchQueryCompleted(); } } void QMFService::testNextMessage() { int required = ((m_offset + m_limit) - m_matchingIds.count()); if (required > 0 || m_limit==0) { QMailMessageId messageId(m_candidateIds.takeFirst()); if (messageMatch(messageId)) { m_matchingIds.append(convert(messageId)); --required; if (required < m_limit) { // This is within the result set m_resultIds << m_matchingIds.last(); } } if ((required > 0 || m_limit == 0) && !m_candidateIds.isEmpty()) { SINGLESHOT(testNextMessage()); return; } } matchQueryCompleted(); } QMessageManager::Error QMFService::error() const { return m_service->error(); } void QMFService::setError(QMessageManager::Error error) const { QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); p->_error = error; } void QMFService::stateChanged(QMessageService::State state) const { QMessageServicePrivate *p = QMessageServicePrivate::implementation(*m_service); // QMessageServicePrivate emits QMessageService::stateChanged() signal p->stateChanged(state); } void QMFService::storageActivityChanged(QMailServiceAction::Activity a) { if ((a == QMailServiceAction::Failed) && (error() == QMessageManager::NoError)) { setError(QMessageManager::RequestIncomplete); } stateChanged(convert(a)); } bool QMFService::moveMessages(const QMessageIdList &messageIds, const QMessageFolderId &toFolderId) { if (isBusy()) { return false; } m_active = 0; setError(QMessageManager::NoError); m_active = &m_storage; m_storage.moveMessages(convert(messageIds), convert(toFolderId)); return true; } bool QMFService::synchronize(const QMessageAccountId &id) { if (isBusy()) { return false; } m_active = 0; setError(QMessageManager::NoError); m_active = &m_retrieval; m_retrieval.synchronize(convert(id), 0); return true; } #include "moc_qmfservice_maemo6_p.cpp" QTM_END_NAMESPACE