From b17284ee7d674416e0d11f1699f73fcc606262d4 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 16 Aug 2022 21:00:15 +0200 Subject: Introduce QXmppTask & QXmppPromise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #502. Co-authored-by: Jonah Brüchert --- src/base/QXmppFutureUtils_p.h | 56 ++++++--- src/base/QXmppPacket.cpp | 21 ++-- src/base/QXmppPacket_p.h | 12 +- src/base/QXmppPromise.h | 103 +++++++++++++++++ src/base/QXmppStream.cpp | 38 +++--- src/base/QXmppStream.h | 12 +- src/base/QXmppStreamManagement.cpp | 13 +-- src/base/QXmppTask.cpp | 85 ++++++++++++++ src/base/QXmppTask.h | 230 +++++++++++++++++++++++++++++++++++++ 9 files changed, 504 insertions(+), 66 deletions(-) create mode 100644 src/base/QXmppPromise.h create mode 100644 src/base/QXmppTask.cpp create mode 100644 src/base/QXmppTask.h (limited to 'src/base') diff --git a/src/base/QXmppFutureUtils_p.h b/src/base/QXmppFutureUtils_p.h index 311e7019..5e6b5ca8 100644 --- a/src/base/QXmppFutureUtils_p.h +++ b/src/base/QXmppFutureUtils_p.h @@ -16,6 +16,7 @@ // #include "QXmppIq.h" +#include "QXmppPromise.h" #include "QXmppSendResult.h" #include @@ -85,6 +86,21 @@ inline QFuture makeReadyFuture() } #endif +template +QXmppTask makeReadyTask(T &&value) +{ + QXmppPromise promise; + promise.finish(std::move(value)); + return promise.task(); +} + +inline QXmppTask makeReadyTask() +{ + QXmppPromise promise; + promise.finish(); + return promise.task(); +} + template void awaitLast(const QFuture &future, QObject *context, Handler handler) { @@ -123,18 +139,14 @@ void await(const QFuture &future, QObject *context, Handler handler) } template -auto chain(const QFuture &source, QObject *context, Converter task) -> QFuture +auto chain(QXmppTask &&source, QObject *context, Converter task) -> QXmppTask { - QFutureInterface resultInterface(QFutureInterfaceBase::Started); + QXmppPromise promise; - auto *watcher = new QFutureWatcher(context); - QObject::connect(watcher, &QFutureWatcherBase::finished, context, [=]() mutable { - resultInterface.reportResult(task(watcher->result())); - resultInterface.reportFinished(); - watcher->deleteLater(); + source.then(context, [=](Input &&input) mutable { + promise.finish(task(std::move(input))); }); - watcher->setFuture(source); - return resultInterface.future(); + return promise.task(); } template @@ -169,21 +181,21 @@ auto parseIq(Input &&sendResult) -> Result } template -auto chainIq(QFuture &&input, QObject *context, Converter convert) -> QFuture +auto chainIq(QXmppTask &&input, QObject *context, Converter convert) -> QXmppTask { using Result = decltype(convert({})); using IqType = std::decay_t>; - return chain(std::move(input), context, [convert { std::move(convert) }](Input &&input) -> Result { + return chain(std::move(input), context, [convert = std::move(convert)](Input &&input) -> Result { return parseIq(std::move(input), convert); }); } template -auto chainIq(QFuture &&input, QObject *context) -> QFuture +auto chainIq(QXmppTask &&input, QObject *context) -> QXmppTask { // IQ type is first std::variant parameter using IqType = std::decay_t(Result {}))>; - return chain(std::move(input), context, [](Input &&sendResult) { + return chain(std::move(input), context, [](Input &&sendResult) mutable { return parseIq(sendResult); }); } @@ -210,6 +222,24 @@ auto mapSuccess(std::variant var, Function lambda) std::move(var)); } +template +static auto taskFromFuture(QFuture &&future) -> QXmppTask +{ + QXmppPromise promise; + auto *watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcher::finished, [promise = std::move(promise), watcher]() mutable { + if constexpr (std::is_void_v) { + promise.finish(); + } else { + promise.finish(watcher->result()); + } + watcher->deleteLater(); + }); + watcher->setFuture(future); + + return promise.task(); +} + } // namespace QXmpp::Private #endif // QXMPPFUTUREUTILS_P_H diff --git a/src/base/QXmppPacket.cpp b/src/base/QXmppPacket.cpp index d61af9da..a0921973 100644 --- a/src/base/QXmppPacket.cpp +++ b/src/base/QXmppPacket.cpp @@ -5,7 +5,6 @@ #include "QXmppNonza.h" #include "QXmppPacket_p.h" -#include #include inline QByteArray serialize(const QXmppNonza &nonza) @@ -17,17 +16,16 @@ inline QByteArray serialize(const QXmppNonza &nonza) } /// \cond -QXmppPacket::QXmppPacket(const QXmppNonza &nonza, QFutureInterface interface) +QXmppPacket::QXmppPacket(const QXmppNonza &nonza, QXmppPromise interface) : QXmppPacket(serialize(nonza), nonza.isXmppStanza(), std::move(interface)) { } -QXmppPacket::QXmppPacket(const QByteArray &data, bool isXmppStanza, QFutureInterface interface) - : m_interface(std::move(interface)), +QXmppPacket::QXmppPacket(const QByteArray &data, bool isXmppStanza, QXmppPromise interface) + : m_promise(std::move(interface)), m_data(data), m_isXmppStanza(isXmppStanza) { - m_interface.reportStarted(); } QByteArray QXmppPacket::data() const @@ -40,18 +38,13 @@ bool QXmppPacket::isXmppStanza() const return m_isXmppStanza; } -QFuture QXmppPacket::future() +QXmppTask QXmppPacket::task() { - return m_interface.future(); + return m_promise.task(); } -void QXmppPacket::reportFinished() +void QXmppPacket::reportFinished(QXmpp::SendResult &&result) { - m_interface.reportFinished(); -} - -void QXmppPacket::reportResult(const QXmpp::SendResult &result) -{ - m_interface.reportResult(result); + m_promise.finish(std::move(result)); } /// \endcond diff --git a/src/base/QXmppPacket_p.h b/src/base/QXmppPacket_p.h index a7170a17..f5d95f85 100644 --- a/src/base/QXmppPacket_p.h +++ b/src/base/QXmppPacket_p.h @@ -6,6 +6,7 @@ #define QXMPPPACKET_H #include "QXmppGlobal.h" +#include "QXmppPromise.h" #include "QXmppSendResult.h" #include @@ -17,19 +18,18 @@ class QXmppNonza; class QXmppPacket { public: - QXmppPacket(const QXmppNonza &nonza, QFutureInterface = {}); - QXmppPacket(const QByteArray &data, bool isXmppStanza, QFutureInterface = {}); + QXmppPacket(const QXmppNonza &nonza, QXmppPromise = {}); + QXmppPacket(const QByteArray &data, bool isXmppStanza, QXmppPromise = {}); QByteArray data() const; bool isXmppStanza() const; - QFuture future(); + QXmppTask task(); - void reportFinished(); - void reportResult(const QXmpp::SendResult &); + void reportFinished(QXmpp::SendResult &&); private: - QFutureInterface m_interface; + QXmppPromise m_promise; QByteArray m_data; bool m_isXmppStanza; }; diff --git a/src/base/QXmppPromise.h b/src/base/QXmppPromise.h new file mode 100644 index 00000000..09bc5bcb --- /dev/null +++ b/src/base/QXmppPromise.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2022 Linus Jahn +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPPROMISE_H +#define QXMPPPROMISE_H + +#include "QXmppTask.h" + +/// +/// \brief Create and update QXmppTask objects to communicate results of asynchronous operations. +/// +/// Unlike QFuture, this is not thread-safe. This avoids the need to do mutex locking at every +/// access though. +/// +/// \ingroup Core classes +/// +/// \since QXmpp 1.5 +/// +template +class QXmppPromise +{ +public: + template> * = nullptr> + QXmppPromise() + : d(QXmpp::Private::TaskPrivate(nullptr)) + { + } + + template> * = nullptr> + QXmppPromise() + : d(QXmpp::Private::TaskPrivate([](void *r) { delete static_cast(r); })) + { + } + + /// + /// Report that the asynchronous operation has finished, and call the connected handler of the + /// QXmppTask belonging to this promise. + /// + /// \param value The result of the asynchronous computation + /// +#ifdef QXMPP_DOC + void reportFinished(T &&value) +#else + template && std::is_base_of_v> * = nullptr> + void finish(U &&value) +#endif + { + Q_ASSERT(!d.isFinished()); + d.setFinished(true); + if (d.continuation()) { + if (d.isContextAlive()) { + d.invokeContinuation(&value); + } + } else { + d.setResult(new U(std::move(value))); + } + } + + /// \cond + template && std::is_constructible_v && !std::is_base_of_v> * = nullptr> + void finish(U &&value) + { + Q_ASSERT(!d.isFinished()); + d.setFinished(true); + if (d.continuation()) { + if (d.isContextAlive()) { + T convertedValue { std::move(value) }; + d.invokeContinuation(&convertedValue); + } + } else { + d.setResult(new T(std::move(value))); + } + } + + template> * = nullptr> + void finish() + { + Q_ASSERT(!d.isFinished()); + d.setFinished(true); + if (d.continuation()) { + if (d.isContextAlive()) { + d.invokeContinuation(nullptr); + } + } + } + /// \endcond + + /// + /// Obtain a handle to this promise that allows to obtain the value that will be produced + /// asynchronously. + /// + QXmppTask task() + { + return QXmppTask(d); + } + +private: + QXmpp::Private::TaskPrivate d; +}; + +#endif // QXMPPPROMISE_H diff --git a/src/base/QXmppStream.cpp b/src/base/QXmppStream.cpp index cbe40b8a..d1a5ccd2 100644 --- a/src/base/QXmppStream.cpp +++ b/src/base/QXmppStream.cpp @@ -35,7 +35,7 @@ static bool randomSeeded = false; struct IqState { - QFutureInterface interface; + QXmppPromise interface; QString jid; }; @@ -172,7 +172,7 @@ bool QXmppStream::sendPacket(const QXmppNonza &nonza) /// /// \since QXmpp 1.5 /// -QFuture QXmppStream::send(QXmppNonza &&nonza) +QXmppTask QXmppStream::send(QXmppNonza &&nonza) { bool success; return send(QXmppPacket(nonza), success); @@ -183,13 +183,13 @@ QFuture QXmppStream::send(QXmppNonza &&nonza) /// /// \since QXmpp 1.5 /// -QFuture QXmppStream::send(QXmppPacket &&packet) +QXmppTask QXmppStream::send(QXmppPacket &&packet) { bool success; return send(std::move(packet), success); } -QFuture QXmppStream::send(QXmppPacket &&packet, bool &writtenToSocket) +QXmppTask QXmppStream::send(QXmppPacket &&packet, bool &writtenToSocket) { // the writtenToSocket parameter is just for backwards compat (see // QXmppStream::sendPacket()) @@ -198,7 +198,7 @@ QFuture QXmppStream::send(QXmppPacket &&packet, bool &written // handle stream management d->streamManager.handlePacketSent(packet, writtenToSocket); - return packet.future(); + return packet.task(); } /// @@ -208,7 +208,7 @@ QFuture QXmppStream::send(QXmppPacket &&packet, bool &written /// /// \since QXmpp 1.5 /// -QFuture QXmppStream::sendIq(QXmppIq &&iq) +QXmppTask QXmppStream::sendIq(QXmppIq &&iq) { using namespace QXmpp; @@ -233,18 +233,18 @@ QFuture QXmppStream::sendIq(QXmppIq &&iq) /// /// \since QXmpp 1.5 /// -QFuture QXmppStream::sendIq(QXmppPacket &&packet, const QString &id, const QString &to) +QXmppTask QXmppStream::sendIq(QXmppPacket &&packet, const QString &id, const QString &to) { using namespace QXmpp; if (id.isEmpty() || d->runningIqs.contains(id)) { - return makeReadyFuture(QXmppError { + return makeReadyTask(QXmppError { QStringLiteral("Invalid IQ id: empty or in use."), SendError::Disconnected }); } if (to.isEmpty()) { - return makeReadyFuture(QXmppError { + return makeReadyTask(QXmppError { QStringLiteral("The 'to' address must be set so the stream can match the response."), SendError::Disconnected }); } @@ -253,15 +253,13 @@ QFuture QXmppStream::sendIq(QXmppPacket &&packet, const Q if (sendFuture.isFinished()) { if (std::holds_alternative(sendFuture.result())) { // early exit (saves QFutureWatcher) - return makeReadyFuture(std::get(sendFuture.result())); + return makeReadyTask(std::get(sendFuture.result())); } } else { - awaitLast(sendFuture, this, [this, id](SendResult result) { + sendFuture.then(this, [this, id](SendResult result) { if (std::holds_alternative(result)) { if (auto itr = d->runningIqs.find(id); itr != d->runningIqs.end()) { - itr.value().interface.reportResult(std::get(result)); - itr.value().interface.reportFinished(); - + itr.value().interface.finish(std::get(result)); d->runningIqs.erase(itr); } } @@ -269,12 +267,12 @@ QFuture QXmppStream::sendIq(QXmppPacket &&packet, const Q } IqState state { - QFutureInterface(QFutureInterfaceBase::Started), + {}, to, }; - auto future = state.interface.future(); + auto task = state.interface.task(); d->runningIqs.insert(id, std::move(state)); - return future; + return task; } /// @@ -285,10 +283,9 @@ QFuture QXmppStream::sendIq(QXmppPacket &&packet, const Q void QXmppStream::cancelOngoingIqs() { for (auto &state : d->runningIqs) { - state.interface.reportResult(QXmppError { + state.interface.finish(QXmppError { QStringLiteral("IQ has been cancelled."), QXmpp::SendError::Disconnected }); - state.interface.reportFinished(); } d->runningIqs.clear(); } @@ -494,8 +491,7 @@ bool QXmppStream::handleIqResponse(const QDomElement &stanza) return false; } - itr.value().interface.reportResult(stanza); - itr.value().interface.reportFinished(); + itr.value().interface.finish(stanza); d->runningIqs.erase(itr); return true; diff --git a/src/base/QXmppStream.h b/src/base/QXmppStream.h index 464d576d..55dc7967 100644 --- a/src/base/QXmppStream.h +++ b/src/base/QXmppStream.h @@ -17,6 +17,8 @@ class QDomElement; template +class QXmppTask; +template class QFuture; template class QFutureInterface; @@ -41,12 +43,12 @@ public: virtual bool isConnected() const; bool sendPacket(const QXmppNonza &); - QFuture send(QXmppNonza &&); - QFuture send(QXmppPacket &&); + QXmppTask send(QXmppNonza &&); + QXmppTask send(QXmppPacket &&); using IqResult = std::variant; - QFuture sendIq(QXmppIq &&); - QFuture sendIq(QXmppPacket &&, const QString &id, const QString &to); + QXmppTask sendIq(QXmppIq &&); + QXmppTask sendIq(QXmppPacket &&, const QString &id, const QString &to); void cancelOngoingIqs(); bool hasIqId(const QString &id) const; @@ -97,7 +99,7 @@ private: friend class tst_QXmppStream; friend class TestClient; - QFuture send(QXmppPacket &&, bool &); + QXmppTask send(QXmppPacket &&, bool &); void processData(const QString &data); bool handleIqResponse(const QDomElement &); diff --git a/src/base/QXmppStreamManagement.cpp b/src/base/QXmppStreamManagement.cpp index 853327f9..f3b183e1 100644 --- a/src/base/QXmppStreamManagement.cpp +++ b/src/base/QXmppStreamManagement.cpp @@ -353,13 +353,12 @@ void QXmppStreamManager::handlePacketSent(QXmppPacket &packet, bool sentData) sendAcknowledgementRequest(); } else { if (sentData) { - packet.reportResult(QXmpp::SendSuccess { false }); + packet.reportFinished(QXmpp::SendSuccess { false }); } else { - packet.reportResult(QXmppError { + packet.reportFinished(QXmppError { QStringLiteral("Couldn't write data to socket. No stream management enabled."), QXmpp::SendError::SocketWriteError }); } - packet.reportFinished(); } } @@ -418,8 +417,7 @@ void QXmppStreamManager::setAcknowledgedSequenceNumber(unsigned int sequenceNumb { for (auto it = m_unacknowledgedStanzas.begin(); it != m_unacknowledgedStanzas.end();) { if (it.key() <= sequenceNumber) { - it->reportResult(QXmpp::SendSuccess { true }); - it->reportFinished(); + it->reportFinished(QXmpp::SendSuccess { true }); it = m_unacknowledgedStanzas.erase(it); } else { break; @@ -472,8 +470,9 @@ void QXmppStreamManager::sendAcknowledgementRequest() void QXmppStreamManager::resetCache() { for (auto &packet : m_unacknowledgedStanzas) { - packet.reportResult(QXmppError { QStringLiteral("Disconnected"), QXmpp::SendError::Disconnected }); - packet.reportFinished(); + packet.reportFinished(QXmppError { + QStringLiteral("Disconnected"), + QXmpp::SendError::Disconnected }); } m_unacknowledgedStanzas.clear(); diff --git a/src/base/QXmppTask.cpp b/src/base/QXmppTask.cpp new file mode 100644 index 00000000..c17a6e4f --- /dev/null +++ b/src/base/QXmppTask.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 Linus Jahn +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppTask.h" + +#include + +namespace QXmpp::Private { + +struct TaskData +{ + QPointer context; + std::function continuation; + void *result = nullptr; + void (*freeResult)(void *); + bool finished = false; + + ~TaskData() + { + if (freeResult) { + freeResult(result); + } + } +}; + +} // namespace QXmpp::Private + +QXmpp::Private::TaskPrivate::TaskPrivate(void (*freeResult)(void *)) + : d(std::make_shared()) +{ + d->freeResult = freeResult; +} + +QXmpp::Private::TaskPrivate::~TaskPrivate() +{ +} + +bool QXmpp::Private::TaskPrivate::isFinished() const +{ + return d->finished; +} + +void QXmpp::Private::TaskPrivate::setFinished(bool finished) +{ + d->finished = finished; +} + +bool QXmpp::Private::TaskPrivate::isContextAlive() +{ + return !d->context.isNull(); +} + +void QXmpp::Private::TaskPrivate::setContext(QObject *obj) +{ + d->context = obj; +} + +void *QXmpp::Private::TaskPrivate::result() const +{ + return d->result; +} + +void QXmpp::Private::TaskPrivate::setResult(void *result) +{ + if (d->freeResult) { + d->freeResult(d->result); + } + d->result = result; +} + +const std::function QXmpp::Private::TaskPrivate::continuation() const +{ + return d->continuation; +} + +void QXmpp::Private::TaskPrivate::setContinuation(std::function &&continuation) +{ + d->continuation = continuation; +} + +void QXmpp::Private::TaskPrivate::invokeContinuation(void *result) +{ + d->continuation(*this, result); +} diff --git a/src/base/QXmppTask.h b/src/base/QXmppTask.h new file mode 100644 index 00000000..c11557ac --- /dev/null +++ b/src/base/QXmppTask.h @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: 2022 Linus Jahn +// SPDX-FileCopyrightText: 2022 Jonah Brüchert +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPTASK_H +#define QXMPPTASK_H + +#include "qxmpp_export.h" + +#include +#include +#include + +#include +#include + +template +class QXmppPromise; + +namespace QXmpp::Private { + +struct TaskData; + +class QXMPP_EXPORT TaskPrivate +{ +public: + TaskPrivate(void (*freeResult)(void *)); + ~TaskPrivate(); + + bool isFinished() const; + void setFinished(bool); + bool isContextAlive(); + void setContext(QObject *); + void *result() const; + void setResult(void *); + void resetResult() { setResult(nullptr); } + const std::function continuation() const; + void setContinuation(std::function &&); + void invokeContinuation(void *result); + +private: + std::shared_ptr d; +}; + +} // namespace QXmpp::Private + +/// +/// Handle for an ongoing operation that finishes in the future. +/// +/// Tasks are generated by QXmppPromise and can be handled using QXmppTask::then(). +/// +/// It supports abstract types, i.e. you can create a QXmppTask and report MyIq +/// (derived from QXmppStanza) to it and when handling the result it is possible to cast the result +/// back to MyIq. +/// +/// Unlike QFuture, this is not thread-safe. This avoids the need to do mutex locking at every +/// access though. +/// +/// \ingroup Core classes +/// +/// \since QXmpp 1.5 +/// +template +class QXmppTask +{ +public: + ~QXmppTask() = default; + + /// + /// Registers a function that will be called with the result as parameter when the asynchronous + /// operation finishes. + /// + /// If the task is already finished when calling this (and still has a result), the function + /// will be called immediately. + /// + /// If another function was previously registered using .then(), the old function will be + /// replaced, and only the new one will be called. + /// + /// Example usage: + /// ``` + /// QXmppTask generateSomething(); + /// + /// void Manager::generate() + /// { + /// generateSomething().then(this, [](QString &&result) { + /// // runs as soon as the result is finished + /// qDebug() << "Generating finished:" << result; + /// }); + /// + /// // The generation could still be in progress here and the lambda might not + /// // have been executed yet. + /// } + /// + /// // Manager is derived from QObject. + /// ``` + /// + /// \param context QObject used for unregistering the handler function when the object is + /// deleted. This way your lambda will never be executed after your object has been deleted. + /// \param continuation A function accepting a result in the form of `T &&`. + /// +#ifndef QXMPP_DOC + template +#endif + void then(QObject *context, Continuation continuation) + { + static_assert( + std::is_void_v && std::is_invocable_v || + !std::is_void_v /* && invocable with T && causes forming ref to void error */, + "Function needs to be invocable with T && or without params for T=void."); + using namespace QXmpp::Private; + + if (d.isFinished()) { + if constexpr (std::is_void_v) { + continuation(); + } else { + // when calling then() after finished value could be empty + if (hasResult()) { + continuation(std::move(*reinterpret_cast(d.result()))); + d.resetResult(); + } + } + } else { + d.setContext(context); + d.setContinuation([f = std::forward(continuation)](TaskPrivate &d, void *result) mutable { + if (d.isContextAlive()) { + if constexpr (std::is_void_v) { + f(); + } else { + f(std::move(*reinterpret_cast(result))); + } + } + + // clear continuation to avoid "deadlocks" in case the user captured this QXmppTask + d.setContinuation({}); + }); + } + } + + /// + /// Whether the asynchronous operation is already finished. + /// + /// This does not mean that the result is still stored, it might have been taken using + /// takeResult() or handled using then(). + /// + [[nodiscard]] bool isFinished() const { return d.isFinished(); } + + /// + /// Returns whether the task is finished and the value has not been taken yet. + /// +#ifndef QXMPP_DOC + template)> * = nullptr> +#endif + [[nodiscard]] bool hasResult() const + { + return d.result() != nullptr; + } + + /// + /// The result of the operation. + /// + /// \warning This can only be used once the operation is finished. + /// +#ifdef QXMPP_DOC + [[nodiscard]] const T &result() const +#else + template)> * = nullptr> + [[nodiscard]] const U &result() const +#endif + { + Q_ASSERT(isFinished()); + Q_ASSERT(hasResult()); + return *reinterpret_cast(d.result()); + } + + /// + /// Moves the result of the operation out of the task. + /// + /// Since this returns by value, you will not be able to cast the result to derived types. Use + /// result() for read-only access or register a handler function using then(). + /// + /// \warning This can only be used once the operation is finished. + /// +#ifdef QXMPP_DOC + [[nodiscard]] T takeResult() +#else + template)> * = nullptr> + [[nodiscard]] U takeResult() +#endif + { + Q_ASSERT(isFinished()); + Q_ASSERT(hasResult()); + U result = std::move(*reinterpret_cast(d.result())); + d.resetResult(); + return result; + } + + /// + /// Converts the Task into a QFuture. Afterwards the QXmppTask object is invalid. + /// + QFuture toFuture(QObject *context) + { + QFutureInterface interface; + + if constexpr (std::is_same_v) { + then(context, [interface]() mutable { + interface.reportFinished(); + }); + } else { + then(context, [interface](T &&val) mutable { + interface.reportResult(val); + interface.reportFinished(); + }); + } + + return interface.future(); + } + +private: + friend class QXmppPromise; + + explicit QXmppTask(QXmpp::Private::TaskPrivate data) + : d(std::move(data)) + { + } + + QXmpp::Private::TaskPrivate d; +}; + +#endif // QXMPPTASK_H -- cgit v1.2.3