diff --git a/Quotient/avatar.cpp b/Quotient/avatar.cpp index d481f56d2..d86e30230 100644 --- a/Quotient/avatar.cpp +++ b/Quotient/avatar.cpp @@ -63,13 +63,13 @@ QImage Avatar::get(int width, int height, get_callback_t callback) const QFuture Avatar::upload(const QString& fileName) const { d->uploadRequest = d->connection->uploadFile(fileName); - return d->uploadRequest.responseFuture(); + return d->uploadRequest.toFuture(); } QFuture Avatar::upload(QIODevice* source) const { d->uploadRequest = d->connection->uploadContent(source); - return d->uploadRequest.responseFuture(); + return d->uploadRequest.toFuture(); } bool Avatar::isEmpty() const { return d->_url.isEmpty(); } diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index f9a3b7043..7a68e478b 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -672,10 +672,11 @@ JobHandle Connection::joinRoom(const QString& roomAlias, const QStr .then([this](const QString& roomId) { provideRoom(roomId); }); } -QFuture Connection::joinAndGetRoom(const QString& roomAlias, const QStringList& serverNames) +QFuture> Connection::joinAndGetRoom(const QString &roomAlias, + const QStringList &serverNames) { return callApi(roomAlias, serverNames, serverNames) - .then([this](const QString& roomId) { return provideRoom(roomId); }); + .then([this](const QString &roomId) { return provideRoom(roomId); }, &BaseJob::status); } QFuture Connection::waitForNewRoom(const QString &roomId) @@ -839,10 +840,20 @@ JobHandle Connection::createRoom( void Connection::requestDirectChat(const QString& userId) { - getDirectChat(userId).then([this](Room* r) { emit directChatAvailable(r); }); + tryGetDirectChat(userId).then([this](const JobResult &expRoom) { + if (expRoom) + emit directChatAvailable(*expRoom); + }); +} + +QFuture Connection::getDirectChat(const QString &otherUserId) +{ + return tryGetDirectChat(otherUserId).then([](JobResult expRoom) { + return expRoom.value_or(nullptr); + }); } -QFuture Connection::getDirectChat(const QString& otherUserId) +QFuture> Connection::tryGetDirectChat(const QString& otherUserId) { auto* u = user(otherUserId); if (QUO_ALARM_X(!u, u"Couldn't get a user object for" % otherUserId)) @@ -861,7 +872,7 @@ QFuture Connection::getDirectChat(const QString& otherUserId) continue; qCDebug(MAIN) << "Requested direct chat with" << otherUserId << "is already available as" << r->id(); - return QtFuture::makeReadyValueFuture(r); + return QtFuture::makeReadyValueFuture>(r); } if (auto ir = invitation(roomId)) { Q_ASSERT(ir->id() == roomId); @@ -889,9 +900,9 @@ QFuture Connection::getDirectChat(const QString& otherUserId) emit directChatsListChanged({}, removals); } - return createDirectChat(otherUserId).then([this](const QString& roomId) { + return createDirectChat(otherUserId).then([this](const QString &roomId) { return room(roomId, JoinState::Join); - }); + }, &BaseJob::status); } JobHandle Connection::createDirectChat(const QString& userId, const QString& topic, @@ -1463,7 +1474,7 @@ QFuture> Connection::setHomeserver(const QUrl& baseUrl) d->loginFlows.clear(); emit loginFlowsChanged(); }); - return d->loginFlowsJob.responseFuture(); + return d->loginFlowsJob.toFuture(); } void Connection::saveRoomState(Room* r) const diff --git a/Quotient/connection.h b/Quotient/connection.h index 56d7e7cf8..5e3542355 100644 --- a/Quotient/connection.h +++ b/Quotient/connection.h @@ -615,9 +615,12 @@ class QUOTIENT_API Connection : public QObject { //! \sa LoginFlowsJob, loginFlows, loginFlowsChanged, homeserverChanged Q_INVOKABLE QFuture > setHomeserver(const QUrl& baseUrl); - //! \brief Get a future to a direct chat with the user + [[deprecated("Use tryGetDirectChat() instead")]] Q_INVOKABLE QFuture getDirectChat(const QString& otherUserId); + //! \brief Get a future to a direct chat with the user + Q_INVOKABLE QFuture> tryGetDirectChat(const QString &otherUserId); + //! Create a direct chat with a single user, optional name and topic //! //! A room will always be created, unlike in requestDirectChat. @@ -631,8 +634,8 @@ class QUOTIENT_API Connection : public QObject { Q_INVOKABLE JobHandle joinRoom(const QString& roomAlias, const QStringList& serverNames = {}); - Q_INVOKABLE QFuture joinAndGetRoom(const QString& roomAlias, - const QStringList& serverNames = {}); + Q_INVOKABLE QFuture> joinAndGetRoom(const QString &roomAlias, + const QStringList &serverNames = {}); Q_INVOKABLE QFuture waitForNewRoom(const QString &roomId); diff --git a/Quotient/jobs/jobhandle.h b/Quotient/jobs/jobhandle.h index 5cd0da5e8..a39cd2b66 100644 --- a/Quotient/jobs/jobhandle.h +++ b/Quotient/jobs/jobhandle.h @@ -5,6 +5,8 @@ #include #include +#include + namespace Quotient { template @@ -191,9 +193,19 @@ class QUOTIENT_API JobHandle : public QPointer, public QFuture { } //! Get a QFuture for the value returned by `collectResponse()` called on the underlying job - auto responseFuture() + auto toFuture() + { + return future_type::then([](future_type ft) mutable { + auto *const job = ft.result(); + if (!job->status().good()) + ft.cancel(); + return collectResponse(job); + }); + } + + auto toFutureExpected() { - return future_type::then([](auto* j) { return collectResponse(j); }); + return then([] (JobT* j) { return collectResponse(j); }, &BaseJob::status); } //! \brief Abandon the underlying job, if there's one pending @@ -222,17 +234,17 @@ class QUOTIENT_API JobHandle : public QPointer, public QFuture { auto callFn(future_value_type job) { if constexpr (std::invocable) { - return std::forward(fn)(); + return std::invoke(std::forward(fn)); } else { static_assert(AllowJobArg, "onCanceled continuations should not accept arguments"); - if constexpr (requires { fn(job); }) - return fn(job); + if constexpr (std::invocable) + return std::invoke(std::forward(fn), job); else if constexpr (requires { collectResponse(job); }) { static_assert( - requires { fn(collectResponse(job)); }, + std::invocable, "The continuation function must accept either of: 1) no arguments; " "2) the job pointer itself; 3) the value returned by collectResponse(job)"); - return fn(collectResponse(job)); + return std::invoke(std::forward(fn), collectResponse(job)); } } } @@ -291,8 +303,12 @@ class QUOTIENT_API JobHandle : public QPointer, public QFuture { else if constexpr (std::is_same_v) { // Still call fFn to suppress unused lambda warning return job->status().good() ? sFn(job) : (fFn(job), sType{}); - } else + } else if constexpr (std::is_same_v) return job->status().good() ? sFn(job) : fFn(job); + else { + using result_t = std::expected; + return job->status().good() ? result_t(sFn(job)) : std::unexpected(fFn(job)); + } }; } @@ -339,6 +355,9 @@ class QUOTIENT_API JobHandle : public QPointer, public QFuture { template JobT> JobHandle(JobT*) -> JobHandle; +template +using JobResult = std::expected; + } // namespace Quotient Q_DECLARE_SMART_POINTER_METATYPE(Quotient::JobHandle) diff --git a/Quotient/room.cpp b/Quotient/room.cpp index ed8340f72..c7d6d1509 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -1325,14 +1325,13 @@ inline namespace v16 { } } -QFuture> Room::upgrade(QString newVersion, - const QStringList &additionalCreators) +QFuture> Room::upgrade(QString newVersion, const QStringList &additionalCreators) { if (!successorId().isEmpty()) { Q_ASSERT(!successorId().isEmpty()); emit upgradeFailed(tr("The room is already upgraded")); } - using future_t = std::expected; + using future_t = JobResult; return connection() ->callApi(id(), newVersion, additionalCreators) .then( diff --git a/Quotient/room.h b/Quotient/room.h index 01e38ef4d..fd3b3068e 100644 --- a/Quotient/room.h +++ b/Quotient/room.h @@ -820,9 +820,10 @@ class QUOTIENT_API Room : public QObject { //! a new room of the specified version. It is possible to specify \p additionalCreators for //! room versions that support those (unfortunately it is only possible to find out whether //! a given room version supports additional creators by attempting to upgrade a room). - //! \return a future eventually holding a new room once it arrives via sync - QFuture> upgrade( - QString newVersion, const QStringList &additionalCreators = {}); + //! \return a future eventually holding either a new room once it arrives via sync, + //! or the failed upgrade job status if the upgrade wasn't successful + QFuture> upgrade(QString newVersion, + const QStringList &additionalCreators = {}); public Q_SLOTS: /** Check whether the room should be upgraded */ diff --git a/Quotient/util.h b/Quotient/util.h index cd85d8c89..b0e7f3778 100644 --- a/Quotient/util.h +++ b/Quotient/util.h @@ -384,4 +384,12 @@ struct QUOTIENT_API HomeserverData { bool checkMatrixSpecVersion(QStringView targetVersion) const; }; + +//! Basic concept for all specialisations of std::expected +template +concept Expected_Class = requires(T exp) { + exp.value(); + exp.error(); +}; + } // namespace Quotient diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index f5e8f8982..4fa1b195b 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -313,15 +313,18 @@ void TestManager::setupAndRun(const QString& targetRoomAlias) c->setLazyLoading(true); qInfo() << "Joining" << targetRoomAlias; - c->joinAndGetRoom(targetRoomAlias).then(this, [this](Room* room) { - if (!room) { - qCritical() << "Failed to join the test room"; + c->joinAndGetRoom(targetRoomAlias) + .then(this, [this](const JobResult &expectedRoom) { + if (!expectedRoom) { + auto logLine = qCritical(); + logLine << "Failed to join the test room: "; + expectedRoom.error().dumpToLog(logLine); finalize(); return; } // Ensure that the room has been joined and filled with some events // so that other tests could use that - testSuite = new TestSuite(room, origin, this); + testSuite = new TestSuite(*expectedRoom, origin, this); // Only start the sync after joining, to make sure the room just // joined is in it c->syncLoop();