aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2017-09-21 20:55:37 +0900
committerGitHub <noreply@github.com>2017-09-21 20:55:37 +0900
commit4eeecd2cf3c9a33878974b93211b29df891ecc9a (patch)
tree8d2cb44133edc2237278025e2260f88595fa9971
parentf936182135d166e5dea734775e24cabd4b763c64 (diff)
parent9cc3f82026c64862bae6a535d2b883b8cf97fba0 (diff)
downloadlibquotient-4eeecd2cf3c9a33878974b93211b29df891ecc9a.tar.gz
libquotient-4eeecd2cf3c9a33878974b93211b29df891ecc9a.zip
Merge pull request #83 from QMatrixClient/kitsune-invite-kick
Support inviting, kicking and dealing with rooms in Invite state
-rw-r--r--CMakeLists.txt6
-rw-r--r--connection.cpp103
-rw-r--r--connection.h24
-rw-r--r--jobs/converters.h89
-rw-r--r--jobs/generated/inviting.cpp37
-rw-r--r--jobs/generated/inviting.h42
-rw-r--r--jobs/generated/kicking.cpp41
-rw-r--r--jobs/generated/kicking.h45
-rw-r--r--jobs/syncjob.cpp5
-rw-r--r--jobs/syncjob.h1
-rw-r--r--room.cpp23
-rw-r--r--room.h5
12 files changed, 376 insertions, 45 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 )
diff --git a/connection.cpp b/connection.cpp
index efc40fe9..e20f843f 100644
--- a/connection.cpp
+++ b/connection.cpp
@@ -56,7 +56,11 @@ class Connection::Private
Connection* q;
ConnectionData* data;
- QHash<QString, Room*> 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<QPair<QString, bool>, Room*> roomMap;
QHash<QString, User*> userMap;
QString username;
QString password;
@@ -183,7 +187,7 @@ void Connection::onSyncSuccess(SyncData &&data) {
d->data->setLastEvent(data.nextBatch());
for( auto&& roomData: data.takeRoomData() )
{
- if ( auto* r = provideRoom(roomData.roomId) )
+ if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) )
r->updateData(std::move(roomData));
}
@@ -210,20 +214,12 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const
JoinRoomJob* Connection::joinRoom(const QString& roomAlias)
{
- auto job = callApi<JoinRoomJob>(roomAlias);
- connect( job, &BaseJob::success, [=] () {
- if ( Room* r = provideRoom(job->roomId()) )
- emit joinedRoom(r);
- });
- return job;
+ return callApi<JoinRoomJob>(roomAlias);
}
void Connection::leaveRoom(Room* room)
{
- auto job = callApi<LeaveRoomJob>(room->id());
- connect( job, &BaseJob::success, [=] () {
- emit leftRoom(room);
- });
+ callApi<LeaveRoomJob>(room->id());
}
RoomMessagesJob* Connection::getMessages(Room* room, const QString& from) const
@@ -288,9 +284,18 @@ int Connection::millisToReconnect() const
return d->syncJob ? d->syncJob->millisToRetry() : 0;
}
-QHash< QString, Room* > Connection::roomMap() const
+QHash< QPair<QString, bool>, Room* > Connection::roomMap() const
{
- return d->roomMap;
+ // Copy-on-write-and-remove-elements is faster than copying elements one by one.
+ QHash< QPair<QString, bool>, 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
@@ -298,36 +303,80 @@ 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);
+ // 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)
{
- d->roomMap.insert( id, 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)
+ {
+ qCCritical(MAIN) << "Failed to create a room" << id;
+ return nullptr;
+ }
+ d->roomMap.insert(roomKey, room);
+ qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second;
emit newRoom(room);
- } else {
- qCritical() << "Failed to create a room!!!" << id;
+ }
+ 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});
+ if (joinState == JoinState::Join)
+ emit joinedRoom(room, prevInvite);
+ else if (joinState == JoinState::Leave)
+ emit leftRoom(room, prevInvite);
+ if (prevInvite)
+ {
+ qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id();
+ emit aboutToDeleteRoom(prevInvite);
+ delete prevInvite;
+ }
}
return room;
}
-std::function<Room*(Connection*, const QString&)> 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<User*(Connection*, const QString&)> 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 96cfb63d..4ca6fbc5 100644
--- a/connection.h
+++ b/connection.h
@@ -18,6 +18,8 @@
#pragma once
+#include "joinstate.h"
+
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtCore/QSize>
@@ -47,11 +49,16 @@ namespace QMatrixClient
*/
Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
public:
+ using room_factory_t =
+ std::function<Room*(Connection*, const QString&, JoinState joinState)>;
+ using user_factory_t =
+ std::function<User*(Connection*, const QString&)>;
+
explicit Connection(const QUrl& server, QObject* parent = nullptr);
Connection();
virtual ~Connection();
- QHash<QString, Room*> roomMap() const;
+ QHash<QPair<QString, bool>, Room*> roomMap() const;
Q_INVOKABLE virtual void resolveServer(const QString& domain);
Q_INVOKABLE virtual void connectToServer(const QString& user,
@@ -149,7 +156,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 <typename T = User>
@@ -167,8 +175,10 @@ namespace QMatrixClient
void syncDone();
void newRoom(Room* room);
- void joinedRoom(Room* room);
- void leftRoom(Room* room);
+ 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);
void networkError(size_t nextAttempt, int inMilliseconds);
@@ -193,7 +203,7 @@ 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);
/**
@@ -205,7 +215,7 @@ namespace QMatrixClient
class Private;
Private* d;
- static std::function<Room*(Connection*, const QString&)> createRoom;
- static std::function<User*(Connection*, const QString&)> createUser;
+ static room_factory_t createRoom;
+ static user_factory_t createUser;
};
} // namespace QMatrixClient
diff --git a/jobs/converters.h b/jobs/converters.h
new file mode 100644
index 00000000..f9ab0269
--- /dev/null
+++ b/jobs/converters.h
@@ -0,0 +1,89 @@
+/******************************************************************************
+* Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+*
+* 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 <QtCore/QJsonValue>
+#include <QtCore/QJsonArray>
+#include <QtCore/QDate>
+#include <QtCore/QVariant>
+
+namespace QMatrixClient
+{
+ template <typename T>
+ inline QJsonValue toJson(T val)
+ {
+ return QJsonValue(val);
+ }
+
+ template <typename T>
+ inline QJsonValue toJson(const QVector<T>& 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 <typename T>
+ inline T fromJson(const QJsonValue& jv)
+ {
+ return QVariant(jv).value<T>();
+ }
+
+ template <>
+ inline int fromJson<int>(const QJsonValue& jv)
+ {
+ return jv.toInt();
+ }
+
+ template <>
+ inline qint64 fromJson<qint64>(const QJsonValue& jv)
+ {
+ return static_cast<qint64>(jv.toDouble());
+ }
+
+ template <>
+ inline double fromJson<double>(const QJsonValue& jv)
+ {
+ return jv.toDouble();
+ }
+
+ template <>
+ inline QString fromJson<QString>(const QJsonValue& jv)
+ {
+ return jv.toString();
+ }
+
+ template <>
+ inline QDateTime fromJson<QDateTime>(const QJsonValue& jv)
+ {
+ return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), Qt::UTC);
+ }
+
+ template <>
+ inline QDate fromJson<QDate>(const QJsonValue& jv)
+ {
+ return fromJson<QDateTime>(jv).date();
+ }
+} // namespace QMatrixClient
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 <QtCore/QStringBuilder>
+
+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 <QtCore/QString>
+
+
+
+
+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 <QtCore/QStringBuilder>
+
+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 <QtCore/QString>
+
+
+
+
+namespace QMatrixClient
+{
+
+
+ // Operations
+
+ /**
+
+ */
+ class KickJob : public BaseJob
+ {
+ public:
+ KickJob(const ConnectionData* connection
+
+ ,
+ QString roomId
+
+ ,
+ QString user_id
+
+ ,
+ QString reason
+ );
+
+ };
+
+
+} // namespace QMatrixClient
diff --git a/jobs/syncjob.cpp b/jobs/syncjob.cpp
index 78a9e93f..f679e6f4 100644
--- a/jobs/syncjob.cpp
+++ b/jobs/syncjob.cpp
@@ -92,15 +92,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 2ded0df3..6697a265 100644
--- a/jobs/syncjob.h
+++ b/jobs/syncjob.h
@@ -51,7 +51,6 @@ namespace QMatrixClient
Batch<RoomEvent> timeline;
Batch<Event> ephemeral;
Batch<Event> accountData;
- Batch<Event> inviteState;
bool timelineLimited;
QString timelinePrevBatch;
diff --git a/room.cpp b/room.cpp
index 332d6fa7..ae8c7e63 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 <array>
#include <QtCore/QHash>
@@ -48,9 +51,9 @@ class Room::Private
typedef QMultiHash<QString, User*> members_map_t;
typedef std::pair<rev_iter_t, rev_iter_t> 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)
{ }
@@ -137,8 +140,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/
@@ -197,6 +200,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);
}
@@ -612,11 +617,21 @@ void Room::Private::getPreviousContent(int limit)
}
}
+void Room::inviteToRoom(const QString& memberId) const
+{
+ connection()->callApi<InviteUserJob>(id(), memberId);
+}
+
void Room::leaveRoom() const
{
connection()->callApi<LeaveRoomJob>(id());
}
+void Room::kickMember(const QString& memberId, const QString& reason) const
+{
+ connection()->callApi<KickJob>(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 06908e3c..5ea89418 100644
--- a/room.h
+++ b/room.h
@@ -77,7 +77,7 @@ namespace QMatrixClient
using Timeline = std::deque<TimelineItem>;
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;
@@ -156,7 +156,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 */