aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--connection.cpp156
-rw-r--r--connection.h49
-rw-r--r--room.cpp5
-rw-r--r--user.cpp6
-rw-r--r--user.h1
5 files changed, 204 insertions, 13 deletions
diff --git a/connection.cpp b/connection.cpp
index b3f1ceb8..fb946392 100644
--- a/connection.cpp
+++ b/connection.cpp
@@ -20,12 +20,14 @@
#include "connectiondata.h"
#include "user.h"
#include "events/event.h"
+#include "events/directchatevent.h"
#include "room.h"
#include "settings.h"
#include "jobs/generated/login.h"
#include "jobs/generated/logout.h"
#include "jobs/generated/receipts.h"
#include "jobs/generated/leaving.h"
+#include "jobs/generated/account-data.h"
#include "jobs/sendeventjob.h"
#include "jobs/joinroomjob.h"
#include "jobs/roommessagesjob.h"
@@ -45,6 +47,8 @@
using namespace QMatrixClient;
+using DirectChatsMap = QMultiHash<const User*, QString>;
+
class Connection::Private
{
public:
@@ -64,6 +68,8 @@ class Connection::Private
QHash<QPair<QString, bool>, Room*> roomMap;
QVector<QString> roomIdsToForget;
QMap<QString, User*> userMap;
+ DirectChatsMap directChats;
+ QHash<QString, QVariantHash> accountData;
QString userId;
SyncJob* syncJob = nullptr;
@@ -74,6 +80,7 @@ class Connection::Private
void connectWithToken(const QString& user, const QString& accessToken,
const QString& deviceId);
+ void applyDirectChatUpdates(const DirectChatsMap& newMap);
};
Connection::Connection(const QUrl& server, QObject* parent)
@@ -258,7 +265,7 @@ void Connection::sync(int timeout)
void Connection::onSyncSuccess(SyncData &&data) {
d->data->setLastEvent(data.nextBatch());
- for( auto&& roomData: data.takeRoomData() )
+ for (auto&& roomData: data.takeRoomData())
{
const auto forgetIdx = d->roomIdsToForget.indexOf(roomData.roomId);
if (forgetIdx != -1)
@@ -279,7 +286,29 @@ void Connection::onSyncSuccess(SyncData &&data) {
r->updateData(std::move(roomData));
QCoreApplication::processEvents();
}
-
+ for (auto&& accountEvent: data.takeAccountData())
+ {
+ if (accountEvent->type() == EventType::DirectChat)
+ {
+ DirectChatsMap newDirectChats;
+ const auto* event = static_cast<DirectChatEvent*>(accountEvent.get());
+ auto usersToDCs = event->usersToDirectChats();
+ for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it)
+ {
+ newDirectChats.insert(user(it.key()), it.value());
+ qCDebug(MAIN) << "Marked room" << it.value()
+ << "as a direct chat with" << it.key();
+ }
+ if (newDirectChats != d->directChats)
+ {
+ d->directChats = newDirectChats;
+ emit directChatsListChanged();
+ }
+ continue;
+ }
+ d->accountData[accountEvent->jsonType()] =
+ accountEvent->contentJson().toVariantHash();
+ }
}
void Connection::stopSync()
@@ -405,6 +434,42 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility,
return job;
}
+void Connection::requestDirectChat(const QString& userId)
+{
+ auto roomId = d->directChats.value(user(userId));
+ if (roomId.isEmpty())
+ {
+ auto j = createDirectChat(userId);
+ connect(j, &BaseJob::success, this, [this,j,userId,roomId] {
+ qCDebug(MAIN) << "Direct chat with" << userId
+ << "has been created as" << roomId;
+ emit directChatAvailable(roomMap().value({j->roomId(), false}));
+ });
+ return;
+ }
+
+ auto room = roomMap().value({roomId, false}, nullptr);
+ if (room)
+ {
+ Q_ASSERT(room->id() == roomId);
+ qCDebug(MAIN) << "Requested direct chat with" << userId
+ << "is already available as" << room->id();
+ emit directChatAvailable(room);
+ return;
+ }
+ room = roomMap().value({roomId, true}, nullptr);
+ if (room)
+ {
+ Q_ASSERT(room->id() == roomId);
+ auto j = joinRoom(room->id());
+ connect(j, &BaseJob::success, this, [this,j,roomId,userId] {
+ qCDebug(MAIN) << "Joined the already invited direct chat with"
+ << userId << "as" << roomId;
+ emit directChatAvailable(roomMap().value({roomId, false}));
+ });
+ }
+}
+
CreateRoomJob* Connection::createDirectChat(const QString& userId,
const QString& topic, const QString& name)
{
@@ -470,11 +535,14 @@ User* Connection::user(const QString& userId)
return user;
}
-User *Connection::user()
+const User* Connection::user() const
{
- if( d->userId.isEmpty() )
- return nullptr;
- return user(d->userId);
+ return d->userId.isEmpty() ? nullptr : d->userMap.value(d->userId, nullptr);
+}
+
+User* Connection::user()
+{
+ return d->userId.isEmpty() ? nullptr : user(d->userId);
}
QString Connection::userId() const
@@ -556,6 +624,56 @@ QVector<Room*> Connection::roomsWithTag(const QString& tagName) const
return rooms;
}
+QJsonObject toJson(const DirectChatsMap& directChats)
+{
+ QJsonObject json;
+ for (auto it = directChats.keyBegin(); it != directChats.keyEnd(); ++it)
+ json.insert((*it)->id(), toJson(directChats.values(*it)));
+ return json;
+}
+
+void Connection::Private::applyDirectChatUpdates(const DirectChatsMap& newMap)
+{
+ auto j = q->callApi<SetAccountDataJob>(userId, "m.direct", toJson(newMap));
+ connect(j, &BaseJob::success, q, [this, newMap] {
+ if (directChats != newMap)
+ {
+ directChats = newMap;
+ emit q->directChatsListChanged();
+ }
+ });
+}
+
+void Connection::addToDirectChats(const Room* room, const User* user)
+{
+ Q_ASSERT(room != nullptr && user != nullptr);
+ if (d->directChats.contains(user, room->id()))
+ return;
+ auto newMap = d->directChats;
+ newMap.insert(user, room->id());
+ d->applyDirectChatUpdates(newMap);
+}
+
+void Connection::removeFromDirectChats(const Room* room, const User* user)
+{
+ Q_ASSERT(room != nullptr);
+ if ((user != nullptr && !d->directChats.contains(user, room->id())) ||
+ d->directChats.key(room->id()) == nullptr)
+ return;
+ DirectChatsMap newMap;
+ for (auto it = d->directChats.begin(); it != d->directChats.end(); ++it)
+ {
+ if (it.value() != room->id() || (user != nullptr && it.key() != user))
+ newMap.insert(it.key(), it.value());
+ }
+ d->applyDirectChatUpdates(newMap);
+}
+
+bool Connection::isDirectChat(const Room* room) const
+{
+ return d->directChats.key(room->id()) != nullptr;
+}
+
QMap<QString, User*> Connection::users() const
{
return d->userMap;
@@ -639,7 +757,7 @@ void Connection::setHomeserver(const QUrl& url)
emit homeserverChanged(homeserver());
}
-static constexpr int CACHE_VERSION_MAJOR = 5;
+static constexpr int CACHE_VERSION_MAJOR = 6;
static constexpr int CACHE_VERSION_MINOR = 0;
void Connection::saveState(const QUrl &toFile) const
@@ -665,7 +783,7 @@ void Connection::saveState(const QUrl &toFile) const
return;
}
- QJsonObject roomObj;
+ QJsonObject rootObj;
{
QJsonObject rooms;
QJsonObject inviteRooms;
@@ -681,15 +799,31 @@ void Connection::saveState(const QUrl &toFile) const
qCDebug(PROFILER) << "processEvents() borrowed" << et1;
}
+ QJsonObject roomObj;
if (!rooms.isEmpty())
roomObj.insert("join", rooms);
if (!inviteRooms.isEmpty())
roomObj.insert("invite", inviteRooms);
+
+ rootObj.insert("next_batch", d->data->lastEvent());
+ rootObj.insert("rooms", roomObj);
}
+ {
+ QJsonArray accountDataEvents {
+ QJsonObject {
+ { QStringLiteral("type"), QStringLiteral("m.direct") },
+ { QStringLiteral("content"), toJson(d->directChats) }
+ }
+ };
- QJsonObject rootObj;
- rootObj.insert("next_batch", d->data->lastEvent());
- rootObj.insert("rooms", roomObj);
+ for (auto it = d->accountData.begin(); it != d->accountData.end(); ++it)
+ accountDataEvents.append(QJsonObject {
+ {"type", it.key()},
+ {"content", QJsonObject::fromVariantHash(it.value())}
+ });
+ rootObj.insert("account_data",
+ QJsonObject {{ QStringLiteral("events"), accountDataEvents }});
+ }
QJsonObject versionObj;
versionObj.insert("major", CACHE_VERSION_MAJOR);
diff --git a/connection.h b/connection.h
index 1e9df5e2..e046d4a0 100644
--- a/connection.h
+++ b/connection.h
@@ -89,12 +89,26 @@ namespace QMatrixClient
/** Get the list of rooms with the specified tag */
QVector<Room*> roomsWithTag(const QString& tagName) const;
+ /** Mark the room as a direct chat with the user */
+ void addToDirectChats(const Room* room, const User* user);
+
+ /** Unmark the room from direct chats
+ * This function removes the room from direct chats either for
+ * a specific \p user or for all users if \p user in nullptr.
+ */
+ void removeFromDirectChats(const Room* room,
+ const User* user = nullptr);
+
+ /** Check whether the room is a direct chat */
+ bool isDirectChat(const Room* room) const;
+
QMap<QString, User*> users() const;
// FIXME: Convert Q_INVOKABLEs to Q_PROPERTIES
// (breaks back-compatibility)
QUrl homeserver() const;
Q_INVOKABLE User* user(const QString& userId);
+ const User* user() const;
User* user();
QString userId() const;
QString deviceId() const;
@@ -223,7 +237,21 @@ namespace QMatrixClient
const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
const QJsonObject creationContent = {});
- /** Create a direct chat with a single user, optional name and topic */
+ /** Get a direct chat with a single user
+ * This method may return synchronously or asynchoronously depending
+ * on whether a direct chat room with the respective person exists
+ * already.
+ *
+ * \sa directChatAvailable
+ */
+ Q_INVOKABLE void requestDirectChat(const QString& userId);
+
+ /** Create a direct chat with a single user, optional name and topic
+ * A room will always be created, unlike in requestDirectChat.
+ * It is advised to use requestDirectChat as a default way of getting
+ * one-on-one with a person, and only use createDirectChat when
+ * a new creation is explicitly desired.
+ */
CreateRoomJob* createDirectChat(const QString& userId,
const QString& topic = {}, const QString& name = {});
@@ -341,13 +369,30 @@ namespace QMatrixClient
/** The room object is about to be deleted */
void aboutToDeleteRoom(Room* room);
- /** The room has just been created by createRoom or createDirectChat
+ /** The room has just been created by createRoom or requestDirectChat
+ *
* This signal is not emitted in usual room state transitions,
* only as an outcome of room creation operations invoked by
* the client.
+ * \note requestDirectChat doesn't necessarily create a new chat;
+ * use directChatAvailable signal if you just need to obtain
+ * a direct chat room.
*/
void createdRoom(Room* room);
+ /** The direct chat room is ready for using
+ * This signal is emitted upon any successful outcome from
+ * requestDirectChat.
+ */
+ void directChatAvailable(Room* directChat);
+
+ /** The list of direct chats has changed
+ * This signal is emitted every time when the mapping of users
+ * to direct chat rooms is changed (because of either local updates
+ * or a different list arrived from the server).
+ */
+ void directChatsListChanged();
+
void cacheStateChanged();
protected:
diff --git a/room.cpp b/room.cpp
index 6c8d762b..48c27ba0 100644
--- a/room.cpp
+++ b/room.cpp
@@ -1424,6 +1424,11 @@ void Room::processStateEvents(const RoomEvents& events)
auto memberEvent = static_cast<RoomMemberEvent*>(event);
auto u = user(memberEvent->userId());
u->processEvent(memberEvent, this);
+ if (u == localUser() && memberJoinState(u) == JoinState::Invite
+ && memberEvent->isDirect())
+ connection()->addToDirectChats(this,
+ user(memberEvent->senderId()));
+
if( memberEvent->membership() == MembershipType::Join )
{
if (memberJoinState(u) != JoinState::Join)
diff --git a/user.cpp b/user.cpp
index cfcb2f4d..7a6dbc73 100644
--- a/user.cpp
+++ b/user.cpp
@@ -287,6 +287,12 @@ bool User::setAvatar(QIODevice* source)
std::bind(&Private::setAvatarOnServer, d.data(), _1, this));
}
+void User::requestDirectChat()
+{
+ Q_ASSERT(d->connection);
+ d->connection->requestDirectChat(d->userId);
+}
+
void User::Private::setAvatarOnServer(QString contentUri, User* q)
{
auto* j = connection->callApi<SetAvatarUrlJob>(userId, contentUri);
diff --git a/user.h b/user.h
index d19fa8f4..f76f9e0a 100644
--- a/user.h
+++ b/user.h
@@ -101,6 +101,7 @@ namespace QMatrixClient
void rename(const QString& newName, const Room* r);
bool setAvatar(const QString& fileName);
bool setAvatar(QIODevice* source);
+ void requestDirectChat();
signals:
void nameAboutToChange(QString newName, QString oldName,