diff options
| author | Linus Jahn <lnj@kaidan.im> | 2022-10-17 16:14:14 +0200 |
|---|---|---|
| committer | Linus Jahn <lnj@kaidan.im> | 2022-10-18 21:12:51 +0200 |
| commit | cbf32098d46b12b69b5ed5f491127023093374e4 (patch) | |
| tree | e88340aebf8f24cf8aa85f40f6e07dfa825ca621 /src/client/QXmppMamManager.cpp | |
| parent | ca56014bfc1293b31a934c080ba1ffc059d2faf1 (diff) | |
| download | qxmpp-cbf32098d46b12b69b5ed5f491127023093374e4.tar.gz | |
MamManager: Add future based interface with encryption support
Diffstat (limited to 'src/client/QXmppMamManager.cpp')
| -rw-r--r-- | src/client/QXmppMamManager.cpp | 264 |
1 files changed, 226 insertions, 38 deletions
diff --git a/src/client/QXmppMamManager.cpp b/src/client/QXmppMamManager.cpp index de5a04a5..7bbbdd74 100644 --- a/src/client/QXmppMamManager.cpp +++ b/src/client/QXmppMamManager.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2016 Niels Ole Salscheider <niels_ole@salscheider-online.de> +// SPDX-FileCopyrightText: 2022 Linus Jahn <lnj@kaidan.im> // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -7,11 +8,77 @@ #include "QXmppClient.h" #include "QXmppConstants_p.h" #include "QXmppDataForm.h" +#include "QXmppE2eeExtension.h" +#include "QXmppFutureUtils_p.h" #include "QXmppMamIq.h" #include "QXmppMessage.h" #include "QXmppUtils.h" +#include <unordered_map> + #include <QDomElement> +#include <QFuture> +#include <QFutureInterface> + +using namespace QXmpp::Private; + +struct RetrieveRequestState +{ + QFutureInterface<QXmppMamManager::RetrieveResult> interface; + QXmppMamResultIq iq; + QVector<QXmppMessage> messages; + + void reportFinished() + { + interface.reportResult( + QXmppMamManager::RetrievedMessages { + std::move(iq), + std::move(messages) }); + interface.reportFinished(); + } +}; + +class QXmppMamManagerPrivate +{ +public: + // std::string because older Qt 5 versions don't add std::hash support for QString + std::unordered_map<std::string, RetrieveRequestState> ongoingRequests; +}; + +/// +/// \struct QXmppMamManager::RetrievedMessages +/// +/// \brief Contains all retrieved messages and the result IQ that can be used for pagination. +/// +/// \since QXmpp 1.5 +/// + +/// +/// \var QXmppMamManager::RetrievedMessages::result +/// +/// The returned result IQ from the MAM server. +/// + +/// +/// \var QXmppMamManager::RetrievedMessages::messages +/// +/// A vector of retrieved QXmppMessages. +/// + +/// +/// \typedef QXmppMamManager::RetrieveResult +/// +/// Contains RetrievedMessages or an QXmppError. +/// +/// \since QXmpp 1.5 +/// + +QXmppMamManager::QXmppMamManager() + : d(std::make_unique<QXmppMamManagerPrivate>()) +{ +} + +QXmppMamManager::~QXmppMamManager() = default; /// \cond QStringList QXmppMamManager::discoveryFeatures() const @@ -22,23 +89,38 @@ QStringList QXmppMamManager::discoveryFeatures() const bool QXmppMamManager::handleStanza(const QDomElement &element) { + if (element.tagName() == "message") { QDomElement resultElement = element.firstChildElement("result"); if (!resultElement.isNull() && resultElement.namespaceURI() == ns_mam) { QDomElement forwardedElement = resultElement.firstChildElement("forwarded"); QString queryId = resultElement.attribute("queryid"); - if (!forwardedElement.isNull() && forwardedElement.namespaceURI() == ns_forwarding) { - QDomElement messageElement = forwardedElement.firstChildElement("message"); - QDomElement delayElement = forwardedElement.firstChildElement("delay"); - if (!messageElement.isNull()) { - QXmppMessage message; - message.parse(messageElement); - if (!delayElement.isNull() && delayElement.namespaceURI() == ns_delayed_delivery) { - const QString stamp = delayElement.attribute("stamp"); - message.setStamp(QXmppUtils::datetimeFromString(stamp)); - } - emit archivedMessageReceived(queryId, message); - } + + if (forwardedElement.isNull() || forwardedElement.namespaceURI() != ns_forwarding) { + return false; + } + + auto messageElement = forwardedElement.firstChildElement("message"); + auto delayElement = forwardedElement.firstChildElement("delay"); + + if (messageElement.isNull()) { + return false; + } + + QXmppMessage message; + message.parse(messageElement); + if (!delayElement.isNull() && delayElement.namespaceURI() == ns_delayed_delivery) { + const QString stamp = delayElement.attribute("stamp"); + message.setStamp(QXmppUtils::datetimeFromString(stamp)); + } + + auto itr = d->ongoingRequests.find(queryId.toStdString()); + if (itr != d->ongoingRequests.end()) { + // future-based API + itr->second.messages.append(std::move(message)); + } else { + // signal-based API + emit archivedMessageReceived(queryId, message); } return true; } @@ -53,31 +135,12 @@ bool QXmppMamManager::handleStanza(const QDomElement &element) } /// \endcond -/// Retrieves archived messages. For each received message, the -/// archiveMessageReceived() signal is emitted. Once all messages are received, -/// the resultsRecieved() signal is emitted. It returns a result set that can -/// be used to page through the results. -/// The number of results may be limited by the server. -/// -/// \param to Optional entity that should be queried. Leave this empty to query -/// the local archive. -/// \param node Optional node that should be queried. This is used when querying -/// a pubsub node. -/// \param jid Optional JID to filter the results. -/// \param start Optional start time to filter the results. -/// \param end Optional end time to filter the results. -/// \param resultSetQuery Optional Result Set Management query. This can be used -/// to limit the number of results and to page through the -/// archive. -/// \return query id of the request. This can be used to associate the -/// corresponding resultsRecieved signal. -/// -QString QXmppMamManager::retrieveArchivedMessages(const QString &to, - const QString &node, - const QString &jid, - const QDateTime &start, - const QDateTime &end, - const QXmppResultSetQuery &resultSetQuery) +static QXmppMamQueryIq buildRequest(const QString &to, + const QString &node, + const QString &jid, + const QDateTime &start, + const QDateTime &end, + const QXmppResultSetQuery &resultSetQuery) { QList<QXmppDataForm::Field> fields; @@ -118,6 +181,131 @@ QString QXmppMamManager::retrieveArchivedMessages(const QString &to, queryIq.setQueryId(queryId); queryIq.setForm(form); queryIq.setResultSetQuery(resultSetQuery); + return queryIq; +} + +/// +/// Retrieves archived messages. For each received message, the +/// archiveMessageReceived() signal is emitted. Once all messages are received, +/// the resultsRecieved() signal is emitted. It returns a result set that can +/// be used to page through the results. +/// The number of results may be limited by the server. +/// +/// \warning This API does not work with end-to-end encrypted messages. You can +/// use the new QFuture-based API (retrieveMessages()) for that. +/// +/// \param to Optional entity that should be queried. Leave this empty to query +/// the local archive. +/// \param node Optional node that should be queried. This is used when querying +/// a pubsub node. +/// \param jid Optional JID to filter the results. +/// \param start Optional start time to filter the results. +/// \param end Optional end time to filter the results. +/// \param resultSetQuery Optional Result Set Management query. This can be used +/// to limit the number of results and to page through the +/// archive. +/// \return query id of the request. This can be used to associate the +/// corresponding resultsRecieved signal. +/// +QString QXmppMamManager::retrieveArchivedMessages(const QString &to, + const QString &node, + const QString &jid, + const QDateTime &start, + const QDateTime &end, + const QXmppResultSetQuery &resultSetQuery) +{ + auto queryIq = buildRequest(to, node, jid, start, end, resultSetQuery); client()->sendPacket(queryIq); - return queryId; + return queryIq.id(); +} + +/// +/// Retrieves archived messages and reports all messages at once via a QFuture. +/// +/// This function tries to decrypt encrypted messages. +/// +/// The number of results may be limited by the server. +/// +/// \param to Optional entity that should be queried. Leave this empty to query +/// the local archive. +/// \param node Optional node that should be queried. This is used when querying +/// a pubsub node. +/// \param jid Optional JID to filter the results. +/// \param start Optional start time to filter the results. +/// \param end Optional end time to filter the results. +/// \param resultSetQuery Optional Result Set Management query. This can be used +/// to limit the number of results and to page through the +/// archive. +/// \return query id of the request. This can be used to associate the +/// corresponding resultsRecieved signal. +/// +/// \since QXmpp 1.5 +/// +QFuture<QXmppMamManager::RetrieveResult> QXmppMamManager::retrieveMessages(const QString &to, const QString &node, const QString &jid, const QDateTime &start, const QDateTime &end, const QXmppResultSetQuery &resultSetQuery) +{ + auto queryIq = buildRequest(to, node, jid, start, end, resultSetQuery); + + auto [itr, _] = d->ongoingRequests.insert({ queryIq.queryId().toStdString(), RetrieveRequestState() }); + + // retrieve messages + await(client()->sendIq(std::move(queryIq)), this, [this, queryId = queryIq.queryId()](QXmppClient::IqResult result) { + auto itr = d->ongoingRequests.find(queryId.toStdString()); + if (itr == d->ongoingRequests.end()) { + return; + } + + if (std::holds_alternative<QDomElement>(result)) { + auto &iq = itr->second.iq; + iq.parse(std::get<QDomElement>(result)); + + if (iq.type() == QXmppIq::Error) { + itr->second.interface.reportResult(QXmppError { iq.error().text(), iq.error() }); + itr->second.interface.reportFinished(); + d->ongoingRequests.erase(itr); + return; + } + + // decrypt encrypted messages + if (auto *e2eeExt = client()->encryptionExtension()) { + auto &messages = itr->second.messages; + auto running = std::make_shared<uint>(0); + + for (auto i = 0; i < messages.size(); i++) { + if (!e2eeExt->isEncrypted(messages.at(i))) { + continue; + } + + auto message = messages.at(i); + (*running)++; + await(e2eeExt->decryptMessage(std::move(message)), this, [this, i, running, queryId](auto result) { + (*running)--; + auto itr = d->ongoingRequests.find(queryId.toStdString()); + if (itr == d->ongoingRequests.end()) { + return; + } + + if (std::holds_alternative<QXmppMessage>(result)) { + itr->second.messages[i] = std::get<QXmppMessage>(std::move(result)); + } else { + warning(QStringLiteral("Error decrypting message.")); + } + if (*running == 0) { + itr->second.reportFinished(); + d->ongoingRequests.erase(itr); + } + }); + } + } else { + itr->second.reportFinished(); + d->ongoingRequests.erase(itr); + } + } else { + auto &error = std::get<QXmpp::SendError>(result); + itr->second.interface.reportResult(QXmppError { error.text, error }); + itr->second.interface.reportFinished(); + d->ongoingRequests.erase(itr); + } + }); + + return itr->second.interface.future(); } |
