diff options
-rw-r--r-- | lib/connection.cpp | 121 | ||||
-rw-r--r-- | lib/connection.h | 62 | ||||
-rw-r--r-- | lib/user.cpp | 25 | ||||
-rw-r--r-- | lib/user.h | 6 |
4 files changed, 182 insertions, 32 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index 339be5b9..c4d09d3e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -19,11 +19,9 @@ #include "connection.h" #include "connectiondata.h" #include "user.h" -#include "events/event.h" #include "events/directchatevent.h" #include "room.h" #include "settings.h" -#include "converters.h" #include "csapi/login.h" #include "csapi/logout.h" #include "csapi/receipts.h" @@ -90,7 +88,7 @@ class Connection::Private QVector<Room*> firstTimeRooms; QMap<QString, User*> userMap; DirectChatsMap directChats; - QHash<QString, AccountDataMap> accountData; + std::unordered_map<QString, EventPtr> accountData; QString userId; SyncJob* syncJob = nullptr; @@ -103,6 +101,30 @@ class Connection::Private const QString& deviceId); void broadcastDirectChatUpdates(const DirectChatsMap& additions, const DirectChatsMap& removals); + + template <typename EventT> + EventT* unpackAccountData() const + { + const auto& eventIt = accountData.find(EventT::matrixTypeId()); + return eventIt == accountData.end() + ? nullptr : weakPtrCast<EventT>(eventIt->second); + } + + void packAndSendAccountData(EventPtr&& event) + { + const auto eventType = event->matrixType(); + q->callApi<SetAccountDataJob>(userId, eventType, + event->contentJson()); + accountData[eventType] = std::move(event); + emit q->accountDataChanged(eventType); + } + + template <typename EventT, typename ContentT> + void packAndSendAccountData(ContentT&& content) + { + packAndSendAccountData( + makeEvent<EventT>(std::forward<ContentT>(content))); + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -325,7 +347,7 @@ void Connection::onSyncSuccess(SyncData &&data) { } for (auto&& accountEvent: data.takeAccountData()) { - if (accountEvent->type() == EventType::DirectChat) + if (is<DirectChatEvent>(*accountEvent)) { const auto usersToDCs = ptrCast<DirectChatEvent>(move(accountEvent)) ->usersToDirectChats(); @@ -355,9 +377,21 @@ void Connection::onSyncSuccess(SyncData &&data) { continue; } - d->accountData[accountEvent->matrixType()] = - fromJson<AccountDataMap>(accountEvent->contentJson()); - emit accountDataChanged(accountEvent->matrixType()); + if (is<IgnoredUsersEvent>(*accountEvent)) + qCDebug(MAIN) << "Users ignored by" << d->userId << "updated:" + << QStringList::fromSet(ignoredUsers()).join(','); + + auto& currentData = d->accountData[accountEvent->matrixType()]; + // A polymorphic event-specific comparison might be a bit more + // efficient; maaybe do it another day + if (!currentData || + currentData->contentJson() != accountEvent->contentJson()) + { + currentData = std::move(accountEvent); + qCDebug(MAIN) << "Updated account data of type" + << currentData->matrixType(); + emit accountDataChanged(currentData->matrixType()); + } } } @@ -705,12 +739,30 @@ QHash< QPair<QString, bool>, Room* > Connection::roomMap() const bool Connection::hasAccountData(const QString& type) const { - return d->accountData.contains(type); + return d->accountData.find(type) != d->accountData.cend(); +} + +const EventPtr& Connection::accountData(const QString& type) const +{ + static EventPtr NoEventPtr {}; + auto it = d->accountData.find(type); + return it == d->accountData.end() ? NoEventPtr : it->second; +} + +QJsonObject Connection::accountDataJson(const QString& type) const +{ + const auto& eventPtr = accountData(type); + return eventPtr ? eventPtr->contentJson() : QJsonObject(); +} + +void Connection::setAccountData(EventPtr&& event) +{ + d->packAndSendAccountData(std::move(event)); } -Connection::AccountDataMap Connection::accountData(const QString& type) const +void Connection::setAccountData(const QString& type, const QJsonObject& content) { - return d->accountData.value(type); + d->packAndSendAccountData(loadEvent<Event>(type, content)); } QHash<QString, QVector<Room*>> Connection::tagsToRooms() const @@ -815,6 +867,42 @@ QList<const User*> Connection::directChatUsers(const Room* room) const return d->directChats.keys(room->id()); } +bool Connection::isIgnored(const User* user) const +{ + return ignoredUsers().contains(user->id()); +} + +Connection::IgnoredUsersList Connection::ignoredUsers() const +{ + const auto* event = d->unpackAccountData<IgnoredUsersEvent>(); + return event ? event->ignored_users() : IgnoredUsersList(); +} + +void Connection::addToIgnoredUsers(const User* user) +{ + Q_ASSERT(user != nullptr); + + auto ignoreList = ignoredUsers(); + if (!ignoreList.contains(user->id())) + { + ignoreList.insert(user->id()); + d->packAndSendAccountData<IgnoredUsersEvent>(ignoreList); + emit ignoredUsersListChanged({{ user->id() }}, {}); + } +} + +void Connection::removeFromIgnoredUsers(const User* user) +{ + Q_ASSERT(user != nullptr); + + auto ignoreList = ignoredUsers(); + if (ignoreList.remove(user->id()) != 0) + { + d->packAndSendAccountData<IgnoredUsersEvent>(ignoreList); + emit ignoredUsersListChanged({}, {{ user->id() }}); + } +} + QMap<QString, User*> Connection::users() const { return d->userMap; @@ -952,17 +1040,12 @@ void Connection::saveState(const QUrl &toFile) const } { QJsonArray accountDataEvents { - QJsonObject { - { QStringLiteral("type"), QStringLiteral("m.direct") }, - { QStringLiteral("content"), toJson(d->directChats) } - } + basicEventJson(QStringLiteral("m.direct"), toJson(d->directChats)) }; + for (const auto &e : d->accountData) + accountDataEvents.append( + basicEventJson(e.first, e.second->contentJson())); - for (auto it = d->accountData.begin(); it != d->accountData.end(); ++it) - accountDataEvents.append(QJsonObject { - {"type", it.key()}, - {"content", QMatrixClient::toJson(it.value())} - }); rootObj.insert("account_data", QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); } diff --git a/lib/connection.h b/lib/connection.h index a1662449..b2bfdce3 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -20,6 +20,7 @@ #include "csapi/create_room.h" #include "joinstate.h" +#include "events/accountdataevents.h" #include <QtCore/QObject> #include <QtCore/QUrl> @@ -32,7 +33,6 @@ namespace QMatrixClient { class Room; class User; - class RoomEvent; class ConnectionData; class SyncJob; @@ -46,7 +46,6 @@ namespace QMatrixClient class GetContentJob; class DownloadFileJob; class SendToDeviceJob; - class Event; /** Enumeration with flags defining the network job running policy * So far only background/foreground flags are available. @@ -74,10 +73,7 @@ namespace QMatrixClient std::function<User*(Connection*, const QString&)>; using DirectChatsMap = QMultiHash<const User*, QString>; - - using AccountDataMap = std::conditional_t< - QT_VERSION >= QT_VERSION_CHECK(5, 5, 0), - QVariantHash, QVariantMap>; + using IgnoredUsersList = IgnoredUsersEvent::content_type; using UsersToDevicesToEvents = std::unordered_map<QString, @@ -101,11 +97,37 @@ namespace QMatrixClient bool hasAccountData(const QString& type) const; /** Get a generic account data event of the given type - * This returns a generic hashmap for any account data event + * This returns an account data event of the given type + * stored on the server. Direct chats map cannot be retrieved + * using this method _yet_; use directChats() instead. + */ + const EventPtr& accountData(const QString& type) const; + + /** Get a generic account data event of the given type + * This returns an account data event of the given type * stored on the server. Direct chats map cannot be retrieved * using this method _yet_; use directChats() instead. */ - AccountDataMap accountData(const QString& type) const; + template <typename EventT> + const typename EventT::content_type accountData() const + { + if (const auto& eventPtr = accountData(EventT::matrixTypeId())) + return eventPtr->content(); + return {}; + } + + /** Get account data as a JSON object + * This returns the content part of the account data event + * of the given type. Direct chats map cannot be retrieved using + * this method _yet_; use directChats() instead. + */ + Q_INVOKABLE QJsonObject accountDataJson(const QString& type) const; + + /** Set a generic account data event of the given type */ + void setAccountData(EventPtr&& event); + + Q_INVOKABLE void setAccountData(const QString& type, + const QJsonObject& content); /** Get all Invited and Joined rooms grouped by tag * \return a hashmap from tag name to a vector of room pointers, @@ -154,6 +176,27 @@ namespace QMatrixClient */ QList<const User*> directChatUsers(const Room* room) const; + /** Check whether a particular user is in the ignore list */ + bool isIgnored(const User* user) const; + + /** Get the whole list of ignored users */ + IgnoredUsersList ignoredUsers() const; + + /** Add the user to the ignore list + * The change signal is emitted synchronously, without waiting + * to complete synchronisation with the server. + * + * \sa ignoredUsersListChanged + */ + void addToIgnoredUsers(const User* user); + + /** Remove the user from the ignore list + * Similar to adding, the change signal is emitted synchronously. + * + * \sa ignoredUsersListChanged + */ + void removeFromIgnoredUsers(const User* user); + /** Get the full list of users known to this account */ QMap<QString, User*> users() const; @@ -516,6 +559,9 @@ namespace QMatrixClient void directChatsListChanged(DirectChatsMap additions, DirectChatsMap removals); + void ignoredUsersListChanged(IgnoredUsersList additions, + IgnoredUsersList removals); + void cacheStateChanged(); protected: diff --git a/lib/user.cpp b/lib/user.cpp index 025d669c..f6611309 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -199,6 +199,12 @@ User::User(QString userId, Connection* connection) setObjectName(userId); } +Connection* User::connection() const +{ + Q_ASSERT(d->connection); + return d->connection; +} + User::~User() = default; QString User::id() const @@ -260,7 +266,7 @@ void User::updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl, void User::rename(const QString& newName) { - auto job = d->connection->callApi<SetDisplayNameJob>(id(), newName); + auto job = connection()->callApi<SetDisplayNameJob>(id(), newName); connect(job, &BaseJob::success, this, [=] { updateName(newName); }); } @@ -283,20 +289,29 @@ void User::rename(const QString& newName, const Room* r) bool User::setAvatar(const QString& fileName) { - return avatarObject().upload(d->connection, fileName, + return avatarObject().upload(connection(), fileName, std::bind(&Private::setAvatarOnServer, d.data(), _1, this)); } bool User::setAvatar(QIODevice* source) { - return avatarObject().upload(d->connection, source, + return avatarObject().upload(connection(), source, std::bind(&Private::setAvatarOnServer, d.data(), _1, this)); } void User::requestDirectChat() { - Q_ASSERT(d->connection); - d->connection->requestDirectChat(d->userId); + connection()->requestDirectChat(d->userId); +} + +void User::ignore() +{ + connection()->addToIgnoredUsers(this); +} + +void User::unmarkIgnore() +{ + connection()->removeFromIgnoredUsers(this); } void User::Private::setAvatarOnServer(QString contentUri, User* q) @@ -43,6 +43,8 @@ namespace QMatrixClient User(QString userId, Connection* connection); ~User() override; + Connection* connection() const; + /** Get unique stable user id * User id is generated by the server and is not changed ever. */ @@ -119,6 +121,10 @@ namespace QMatrixClient * Connection::directChatAvailable() */ void requestDirectChat(); + /** Add the user to the ignore list */ + void ignore(); + /** Remove the user from the ignore list */ + void unmarkIgnore(); signals: void nameAboutToChange(QString newName, QString oldName, |