From d57485bb3a7d980642e7f68edd5785c144acc742 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 8 Sep 2017 18:36:44 +0900 Subject: converters.h: Facility methods for generated jobs A cherry-pick from the kitsune-apigen branch; a family of toJson() and fromJson<>() functions to unify conversion of data back and forth. --- jobs/converters.h | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 jobs/converters.h diff --git a/jobs/converters.h b/jobs/converters.h new file mode 100644 index 00000000..376dfeab --- /dev/null +++ b/jobs/converters.h @@ -0,0 +1,89 @@ +/****************************************************************************** +* Copyright (C) 2017 Kitsune Ral +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include +#include +#include + +namespace QMatrixClient +{ + template + inline QJsonValue toJson(T val) + { + return QJsonValue(val); + } + + template + inline QJsonValue toJson(const QVector& vals) + { + QJsonArray ar; + for (const auto& v: vals) + ar.push_back(toJson(v)); + return ar; + } + + inline QJsonValue toJson(const QStringList& strings) + { + return QJsonArray::fromStringList(strings); + } + + template + inline T fromJson(const QJsonValue& jv) + { + return QVariant(jv).value(); + } + + template <> + inline int fromJson(const QJsonValue& jv) + { + return jv.toInt(); + } + + template <> + inline qint64 fromJson(const QJsonValue& jv) + { + return static_cast(jv.toDouble()); + } + + template <> + inline double fromJson(const QJsonValue& jv) + { + return jv.toDouble(); + } + + template <> + inline QString fromJson(const QJsonValue& jv) + { + return jv.toString(); + } + + template <> + inline QDateTime fromJson(const QJsonValue& jv) + { + return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); + } + + template <> + inline QDate fromJson(const QJsonValue& jv) + { + return QDateTime::fromMSecsSinceEpoch( + fromJson(jv), Qt::UTC).date(); + } +} // namespace QMatrixClient \ No newline at end of file -- cgit v1.2.3 From 8ae1da50f8474a698ce83f61233b3a8975faeaee Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Sep 2017 20:14:08 +0900 Subject: First files made by api-generator Actual usage will come with the next commit. --- jobs/generated/inviting.cpp | 37 +++++++++++++++++++++++++++++++++++++ jobs/generated/inviting.h | 42 ++++++++++++++++++++++++++++++++++++++++++ jobs/generated/kicking.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ jobs/generated/kicking.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 jobs/generated/inviting.cpp create mode 100644 jobs/generated/inviting.h create mode 100644 jobs/generated/kicking.cpp create mode 100644 jobs/generated/kicking.h diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp new file mode 100644 index 00000000..e5e7f410 --- /dev/null +++ b/jobs/generated/inviting.cpp @@ -0,0 +1,37 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "inviting.h" + + +#include "../converters.h" + +#include + +using namespace QMatrixClient; + + + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + + +InviteUserJob::InviteUserJob(const ConnectionData* connection, + QString roomId + , + QString user_id + ) + : BaseJob(connection, HttpVerb::Post, "InviteUserJob" + , basePath % "/rooms/" % roomId % "/invite" + , Query { } + , Data { + { "user_id", toJson(user_id) } + } + + ) +{ } + + + + diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h new file mode 100644 index 00000000..af5a426d --- /dev/null +++ b/jobs/generated/inviting.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + + +#include "../basejob.h" + + + +#include + + + + +namespace QMatrixClient +{ + + + // Operations + + /** + + */ + class InviteUserJob : public BaseJob + { + public: + InviteUserJob(const ConnectionData* connection + + , + QString roomId + + , + QString user_id + ); + + }; + + +} // namespace QMatrixClient diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp new file mode 100644 index 00000000..726f6fb0 --- /dev/null +++ b/jobs/generated/kicking.cpp @@ -0,0 +1,41 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "kicking.h" + + +#include "../converters.h" + +#include + +using namespace QMatrixClient; + + + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + + +KickJob::KickJob(const ConnectionData* connection, + QString roomId + , + QString user_id + , + QString reason + ) + : BaseJob(connection, HttpVerb::Post, "KickJob" + , basePath % "/rooms/" % roomId % "/kick" + , Query { } + , Data { + { "user_id", toJson(user_id) }, + + { "reason", toJson(reason) } + } + + ) +{ } + + + + diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h new file mode 100644 index 00000000..7b183b08 --- /dev/null +++ b/jobs/generated/kicking.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + + +#include "../basejob.h" + + + +#include + + + + +namespace QMatrixClient +{ + + + // Operations + + /** + + */ + class KickJob : public BaseJob + { + public: + KickJob(const ConnectionData* connection + + , + QString roomId + + , + QString user_id + + , + QString reason + ); + + }; + + +} // namespace QMatrixClient -- cgit v1.2.3 From 24e3d967d7c9029147202e10385b0b8e8881e4d9 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Sep 2017 20:17:42 +0900 Subject: Kicking, inviting, exposing rooms in Invite state Kicking and inviting use generated job classes. Rooms in Invite state are stored separately in the hash from those in Join/Leave state because The Spec says so. For clients, this means that the same room may appear twice in the rooms map if it's been left and then the user was again invited to it. The code in Quaternion that properly processes this will arrive shortly. --- connection.cpp | 69 ++++++++++++++++++++++++++++++++++---------------------- connection.h | 18 +++++++++++---- jobs/syncjob.cpp | 5 ++-- jobs/syncjob.h | 1 - room.cpp | 23 +++++++++++++++---- room.h | 5 +++- 6 files changed, 80 insertions(+), 41 deletions(-) diff --git a/connection.cpp b/connection.cpp index 2c9ee88a..5d8a42e3 100644 --- a/connection.cpp +++ b/connection.cpp @@ -50,7 +50,11 @@ class Connection::Private Connection* q; ConnectionData* data; - QHash roomMap; + // A complex key below is a pair of room name and whether its + // state is Invited. The spec mandates to keep Invited room state + // separately so we should, e.g., keep objects for Invite and + // Leave state of the same room. + QHash, Room*> roomMap; QHash userMap; QString username; QString password; @@ -160,7 +164,7 @@ void Connection::sync(int timeout) d->data->setLastEvent(job->nextBatch()); for( auto&& roomData: job->takeRoomData() ) { - if ( auto* r = provideRoom(roomData.roomId) ) + if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) r->updateData(std::move(roomData)); } d->syncJob = nullptr; @@ -197,20 +201,12 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const JoinRoomJob* Connection::joinRoom(const QString& roomAlias) { - auto job = callApi(roomAlias); - connect( job, &BaseJob::success, [=] () { - if ( Room* r = provideRoom(job->roomId()) ) - emit joinedRoom(r); - }); - return job; + return callApi(roomAlias); } void Connection::leaveRoom(Room* room) { - auto job = callApi(room->id()); - connect( job, &BaseJob::success, [=] () { - emit leftRoom(room); - }); + callApi(room->id()); } RoomMessagesJob* Connection::getMessages(Room* room, const QString& from) const @@ -275,7 +271,7 @@ int Connection::millisToReconnect() const return d->syncJob ? d->syncJob->millisToRetry() : 0; } -QHash< QString, Room* > Connection::roomMap() const +const QHash< QPair, Room* >& Connection::roomMap() const { return d->roomMap; } @@ -285,36 +281,55 @@ const ConnectionData* Connection::connectionData() const return d->data; } -Room* Connection::provideRoom(const QString& id) +Room* Connection::provideRoom(const QString& id, JoinState joinState) { + // TODO: This whole function is a strong case for a RoomManager class. if (id.isEmpty()) { qCDebug(MAIN) << "Connection::provideRoom() with empty id, doing nothing"; return nullptr; } - if (d->roomMap.contains(id)) - return d->roomMap.value(id); - - // Not yet in the map, create a new one. - auto* room = createRoom(this, id); - if (room) + const auto roomKey = qMakePair(id, joinState == JoinState::Invite); + auto* room = d->roomMap.value(roomKey, nullptr); + if (!room) { - d->roomMap.insert( id, room ); + room = createRoom(this, id, joinState); + if (!room) + { + qCritical() << "Failed to create a room!!!" << id; + return nullptr; + } + qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; + + d->roomMap.insert(roomKey, room); emit newRoom(room); - } else { - qCritical() << "Failed to create a room!!!" << id; + } + else if (room->joinState() != joinState) + { + room->setJoinState(joinState); + if (joinState == JoinState::Leave) + emit leftRoom(room); + else if (joinState == JoinState::Join) + emit joinedRoom(room); + } + + if (joinState != JoinState::Invite && d->roomMap.contains({id, true})) + { + // Preempt the Invite room after it's been acted upon (joined or left). + qCDebug(MAIN) << "Deleting invited state"; + delete d->roomMap.take({id, true}); } return room; } -std::function Connection::createRoom = - [](Connection* c, const QString& id) { return new Room(c, id); }; +Connection::room_factory_t Connection::createRoom = + [](Connection* c, const QString& id, JoinState joinState) + { return new Room(c, id, joinState); }; -std::function Connection::createUser = +Connection::user_factory_t Connection::createUser = [](Connection* c, const QString& id) { return new User(id, c); }; - QByteArray Connection::generateTxnId() { return d->data->generateTxnId(); diff --git a/connection.h b/connection.h index 4b0413e3..b118ffb0 100644 --- a/connection.h +++ b/connection.h @@ -18,6 +18,8 @@ #pragma once +#include "joinstate.h" + #include #include #include @@ -41,11 +43,16 @@ namespace QMatrixClient class Connection: public QObject { Q_OBJECT public: + using room_factory_t = + std::function; + using user_factory_t = + std::function; + explicit Connection(const QUrl& server, QObject* parent = nullptr); Connection(); virtual ~Connection(); - QHash roomMap() const; + const QHash, Room*>& roomMap() const; Q_INVOKABLE virtual void resolveServer(const QString& domain); Q_INVOKABLE virtual void connectToServer(const QString& user, @@ -102,7 +109,8 @@ namespace QMatrixClient static void setRoomType() { createRoom = - [](Connection* c, const QString& id) { return new T(c, id); }; + [](Connection* c, const QString& id, JoinState joinState) + { return new T(c, id, joinState); }; } template @@ -144,13 +152,13 @@ namespace QMatrixClient * @return a pointer to a Room object with the specified id; nullptr * if roomId is empty if createRoom() failed to create a Room object. */ - Room* provideRoom(const QString& roomId); + Room* provideRoom(const QString& roomId, JoinState joinState); private: class Private; Private* d; - static std::function createRoom; - static std::function createUser; + static room_factory_t createRoom; + static user_factory_t createUser; }; } // namespace QMatrixClient diff --git a/jobs/syncjob.cpp b/jobs/syncjob.cpp index 29ddc2e6..38cfcb2a 100644 --- a/jobs/syncjob.cpp +++ b/jobs/syncjob.cpp @@ -99,15 +99,14 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, const QJsonObject& room_) : roomId(roomId_) , joinState(joinState_) - , state("state") + , state(joinState == JoinState::Invite ? "invite_state" : "state") , timeline("timeline") , ephemeral("ephemeral") , accountData("account_data") - , inviteState("invite_state") { switch (joinState) { case JoinState::Invite: - inviteState.fromJson(room_); + state.fromJson(room_); break; case JoinState::Join: state.fromJson(room_); diff --git a/jobs/syncjob.h b/jobs/syncjob.h index 07824e23..57a87c9f 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -51,7 +51,6 @@ namespace QMatrixClient Batch timeline; Batch ephemeral; Batch accountData; - Batch inviteState; bool timelineLimited; QString timelinePrevBatch; diff --git a/room.cpp b/room.cpp index 547b74c4..78e5b80d 100644 --- a/room.cpp +++ b/room.cpp @@ -18,6 +18,9 @@ #include "room.h" +#include "jobs/generated/kicking.h" +#include "jobs/generated/inviting.h" + #include #include @@ -48,9 +51,9 @@ class Room::Private typedef QMultiHash members_map_t; typedef std::pair rev_iter_pair_t; - Private(Connection* c, QString id_) + Private(Connection* c, QString id_, JoinState initialJoinState) : q(nullptr), connection(c), id(std::move(id_)) - , joinState(JoinState::Join), unreadMessages(false) + , joinState(initialJoinState), unreadMessages(false) , highlightCount(0), notificationCount(0), roomMessagesJob(nullptr) { } @@ -134,8 +137,8 @@ class Room::Private } }; -Room::Room(Connection* connection, QString id) - : QObject(connection), d(new Private(connection, id)) +Room::Room(Connection* connection, QString id, JoinState initialJoinState) + : QObject(connection), d(new Private(connection, id, initialJoinState)) { // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ @@ -194,6 +197,8 @@ void Room::setJoinState(JoinState state) if( state == oldState ) return; d->joinState = state; + qCDebug(MAIN) << "Room" << id() << "changed state: " + << int(oldState) << "->" << int(state); emit joinStateChanged(oldState, state); } @@ -601,11 +606,21 @@ void Room::Private::getPreviousContent(int limit) } } +void Room::inviteToRoom(const QString& memberId) const +{ + connection()->callApi(id(), memberId); +} + void Room::leaveRoom() const { connection()->callApi(id()); } +void Room::kickMember(const QString& memberId, const QString& reason) const +{ + connection()->callApi(id(), memberId, reason); +} + void Room::Private::dropDuplicateEvents(RoomEvents* events) const { // Collect all duplicate events at the end of the container diff --git a/room.h b/room.h index 23a1412d..9465a960 100644 --- a/room.h +++ b/room.h @@ -77,7 +77,7 @@ namespace QMatrixClient using Timeline = std::deque; using rev_iter_t = Timeline::const_reverse_iterator; - Room(Connection* connection, QString id); + Room(Connection* connection, QString id, JoinState initialJoinState); virtual ~Room(); Connection* connection() const; @@ -154,7 +154,10 @@ namespace QMatrixClient void getPreviousContent(int limit = 10); + void inviteToRoom(const QString& memberId) const; void leaveRoom() const; + void kickMember(const QString& memberId, const QString& reason) const; + void userRenamed(User* user, QString oldName); /** Mark all messages in the room as read */ -- cgit v1.2.3 From 6300d53663ea118a1f80ceed24648d8553766e17 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Sep 2017 21:34:18 +0900 Subject: Collect files from jobs/generated into the list of built sources --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a345e06..9e3abce1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,11 +81,13 @@ set(libqmatrixclient_SRCS jobs/syncjob.cpp jobs/mediathumbnailjob.cpp jobs/logoutjob.cpp - ) +) + +aux_source_directory(jobs/generated libqmatrixclient_job_SRCS) set(example_SRCS examples/qmc-example.cpp) -add_library(qmatrixclient ${libqmatrixclient_SRCS}) +add_library(qmatrixclient ${libqmatrixclient_SRCS} ${libqmatrixclient_job_SRCS}) set_property(TARGET qmatrixclient PROPERTY VERSION "0.0.0") set_property(TARGET qmatrixclient PROPERTY SOVERSION 0 ) -- cgit v1.2.3 From 5ddfbf25f2657f232649a9fdd2ad481127f6c349 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 13 Sep 2017 21:09:20 +0900 Subject: Add a missing #include --- jobs/converters.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jobs/converters.h b/jobs/converters.h index 376dfeab..f9ab0269 100644 --- a/jobs/converters.h +++ b/jobs/converters.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace QMatrixClient { @@ -83,7 +84,6 @@ namespace QMatrixClient template <> inline QDate fromJson(const QJsonValue& jv) { - return QDateTime::fromMSecsSinceEpoch( - fromJson(jv), Qt::UTC).date(); + return fromJson(jv).date(); } -} // namespace QMatrixClient \ No newline at end of file +} // namespace QMatrixClient -- cgit v1.2.3 From 6b40c313f8e4a0964e857973d8f9636a9f833f9d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Sep 2017 20:19:41 +0900 Subject: Better API for clients to catch up on room list changes joinedRoom() and leftRoom() now pass the preempted Invite state of the room as well; roomMap() only returns Invite and Join rooms, not Leave. --- connection.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++--------------- connection.h | 7 ++++--- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/connection.cpp b/connection.cpp index 5d8a42e3..fdf58e9e 100644 --- a/connection.cpp +++ b/connection.cpp @@ -271,9 +271,18 @@ int Connection::millisToReconnect() const return d->syncJob ? d->syncJob->millisToRetry() : 0; } -const QHash< QPair, Room* >& Connection::roomMap() const +QHash< QPair, Room* > Connection::roomMap() const { - return d->roomMap; + // Copy-on-write-and-remove-elements is faster than copying elements one by one. + QHash< QPair, Room* > roomMap = d->roomMap; + for (auto it = roomMap.begin(); it != roomMap.end(); ) + { + if (it.value()->joinState() == JoinState::Leave) + it = roomMap.erase(it); + else + ++it; + } + return roomMap; } const ConnectionData* Connection::connectionData() const @@ -290,6 +299,23 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) return nullptr; } + // Room transitions from the Connection standpoint: + // - none -> (new) Invite + // - none -> (new) Join + // - none -> (new) Leave + // - Invite -> (new) Join replaces Invite (deleted) + // - Invite -> (new) Leave (archived) replaces Invite (deleted) + // - Join -> (moves to) Leave + // - Leave -> (new) Invite, Leave + // - Leave -> (moves to) Join + // Room transitions from the user's standpoint (what's seen in signals): + // - none -> Invite: newRoom(Invite) + // - none -> Join: newRoom(Join) or Room::joinStateChanged(Join); joinedRoom + // - Invite -> Invite replaced with Join: + // newRoom(Join); joinedRoom; aboutToDeleteRoom(Invite) + // - Invite -> Invite replaced with Leave (none): + // newRoom(Leave); leftRoom; aboutToDeleteRoom(Invite) + // - Join -> Leave (none): leftRoom const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); if (!room) @@ -297,7 +323,7 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) room = createRoom(this, id, joinState); if (!room) { - qCritical() << "Failed to create a room!!!" << id; + qCCritical(MAIN) << "Failed to create a room" << id; return nullptr; } qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; @@ -305,20 +331,21 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) d->roomMap.insert(roomKey, room); emit newRoom(room); } - else if (room->joinState() != joinState) - { - room->setJoinState(joinState); - if (joinState == JoinState::Leave) - emit leftRoom(room); - else if (joinState == JoinState::Join) - emit joinedRoom(room); - } - if (joinState != JoinState::Invite && d->roomMap.contains({id, true})) + if (joinState != JoinState::Invite) { - // Preempt the Invite room after it's been acted upon (joined or left). - qCDebug(MAIN) << "Deleting invited state"; - delete d->roomMap.take({id, true}); + // Preempt the Invite room (if any) with a room in Join/Leave state. + auto prevInvite = d->roomMap.take({id, true}); + if (joinState == JoinState::Join) + joinedRoom(room, prevInvite); + else if (joinState == JoinState::Leave) + leftRoom(room, prevInvite); + if (prevInvite) + { + qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); + emit aboutToDeleteRoom(prevInvite); + delete prevInvite; + } } return room; diff --git a/connection.h b/connection.h index b118ffb0..361e0870 100644 --- a/connection.h +++ b/connection.h @@ -52,7 +52,7 @@ namespace QMatrixClient Connection(); virtual ~Connection(); - const QHash, Room*>& roomMap() const; + QHash, Room*> roomMap() const; Q_INVOKABLE virtual void resolveServer(const QString& domain); Q_INVOKABLE virtual void connectToServer(const QString& user, @@ -128,8 +128,9 @@ namespace QMatrixClient void syncDone(); void newRoom(Room* room); - void joinedRoom(Room* room); - void leftRoom(Room* room); + void joinedRoom(Room* room, Room* prevInvite); + void leftRoom(Room* room, Room* prevInvite); + void aboutToDeleteRoom(Room* room); void loginError(QString error); void networkError(size_t nextAttempt, int inMilliseconds); -- cgit v1.2.3 From 726f8d464f4b29f6fd3dc92fa5493e239970b209 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Sep 2017 20:39:36 +0900 Subject: provideRoom: Added invitedRoom() signal; fixed issues with some transitions Notably: * setJoinState() invocation has been missing from the previous code * processing invites did not take into account that a Leave state may already exist, thereby forcing clients that display left rooms to look through their records just in case they have to replace a Leave with Invite. * joinedRoom() was emitted even when the room is not newly joined. --- connection.cpp | 57 ++++++++++++++++++++++++++++++++------------------------- connection.h | 5 +++-- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/connection.cpp b/connection.cpp index fdf58e9e..513d9242 100644 --- a/connection.cpp +++ b/connection.cpp @@ -299,26 +299,28 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) return nullptr; } - // Room transitions from the Connection standpoint: - // - none -> (new) Invite - // - none -> (new) Join - // - none -> (new) Leave - // - Invite -> (new) Join replaces Invite (deleted) - // - Invite -> (new) Leave (archived) replaces Invite (deleted) - // - Join -> (moves to) Leave - // - Leave -> (new) Invite, Leave - // - Leave -> (moves to) Join - // Room transitions from the user's standpoint (what's seen in signals): - // - none -> Invite: newRoom(Invite) - // - none -> Join: newRoom(Join) or Room::joinStateChanged(Join); joinedRoom - // - Invite -> Invite replaced with Join: - // newRoom(Join); joinedRoom; aboutToDeleteRoom(Invite) - // - Invite -> Invite replaced with Leave (none): - // newRoom(Leave); leftRoom; aboutToDeleteRoom(Invite) - // - Join -> Leave (none): leftRoom + // Room transitions: + // 1. none -> Invite: r=createRoom, emit invitedRoom(r,null) + // 2. none -> Join: r=createRoom, emit joinedRoom(r,null) + // 3. none -> Leave: r=createRoom, emit leftRoom(r,null) + // 4. inv=Invite -> Join: r=createRoom, emit joinedRoom(r,inv), delete Invite + // 4a. Leave, inv=Invite -> Join: change state, emit joinedRoom(r,inv), delete Invite + // 5. inv=Invite -> Leave: r=createRoom, emit leftRoom(r,inv), delete Invite + // 5a. r=Leave, inv=Invite -> Leave: emit leftRoom(r,inv), delete Invite + // 6. Join -> Leave: change state + // 7. r=Leave -> Invite: inv=createRoom, emit invitedRoom(inv,r) + // 8. Leave -> (changes to) Join const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); - if (!room) + if (room) + { + // Leave is a special case because in transition (5a) above + // joinState == room->joinState but we still have to preempt the Invite + // and emit a signal. For Invite and Join, there's no such problem. + if (room->joinState() == joinState && joinState != JoinState::Leave) + return room; + } + else { room = createRoom(this, id, joinState); if (!room) @@ -326,20 +328,25 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) qCCritical(MAIN) << "Failed to create a room" << id; return nullptr; } - qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; - d->roomMap.insert(roomKey, room); + qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; emit newRoom(room); } - - if (joinState != JoinState::Invite) + if (joinState == JoinState::Invite) + { + // prev is either Leave or nullptr + auto* prev = d->roomMap.value({id, false}, nullptr); + emit invitedRoom(room, prev); + } + else { + room->setJoinState(joinState); // Preempt the Invite room (if any) with a room in Join/Leave state. - auto prevInvite = d->roomMap.take({id, true}); + auto* prevInvite = d->roomMap.take({id, true}); if (joinState == JoinState::Join) - joinedRoom(room, prevInvite); + emit joinedRoom(room, prevInvite); else if (joinState == JoinState::Leave) - leftRoom(room, prevInvite); + emit leftRoom(room, prevInvite); if (prevInvite) { qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); diff --git a/connection.h b/connection.h index 361e0870..0dcb8c3f 100644 --- a/connection.h +++ b/connection.h @@ -128,8 +128,9 @@ namespace QMatrixClient void syncDone(); void newRoom(Room* room); - void joinedRoom(Room* room, Room* prevInvite); - void leftRoom(Room* room, Room* prevInvite); + void invitedRoom(Room* room, Room* prev); + void joinedRoom(Room* room, Room* prev); + void leftRoom(Room* room, Room* prev); void aboutToDeleteRoom(Room* room); void loginError(QString error); -- cgit v1.2.3