diff options
author | Black Hat <bhat@encom.eu.org> | 2019-09-26 22:22:36 -0700 |
---|---|---|
committer | Black Hat <bhat@encom.eu.org> | 2019-09-26 22:22:36 -0700 |
commit | 363cf452bcdbaf6ff1cf94a83ca66cbb31122346 (patch) | |
tree | c64c8fda885e4e1785130e8ee3e7c47fd18cbf67 /lib/room.cpp | |
parent | 412e2cf19449e73aa7da729e9c5de6502687aade (diff) | |
parent | 944653463fe4134c82d85e2d01e2bc0fa43fd727 (diff) | |
download | libquotient-363cf452bcdbaf6ff1cf94a83ca66cbb31122346.tar.gz libquotient-363cf452bcdbaf6ff1cf94a83ca66cbb31122346.zip |
Merge branch 'master' of https://github.com/quotient-im/libQuotient into
bhat-libqtolm-update
Diffstat (limited to 'lib/room.cpp')
-rw-r--r-- | lib/room.cpp | 1918 |
1 files changed, 886 insertions, 1032 deletions
diff --git a/lib/room.cpp b/lib/room.cpp index 29a6ebe2..2f697589 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -13,65 +13,65 @@ * * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "room.h" -#include "csapi/kicking.h" -#include "csapi/inviting.h" +#include "avatar.h" +#include "connection.h" +#include "converters.h" +#include "e2ee.h" +#include "syncdata.h" +#include "user.h" + +#include "csapi/account-data.h" #include "csapi/banning.h" +#include "csapi/inviting.h" +#include "csapi/kicking.h" #include "csapi/leaving.h" #include "csapi/receipts.h" #include "csapi/redaction.h" -#include "csapi/account-data.h" -#include "csapi/room_state.h" #include "csapi/room_send.h" +#include "csapi/room_state.h" +#include "csapi/room_upgrades.h" #include "csapi/rooms.h" #include "csapi/tags.h" -#include "csapi/room_upgrades.h" -#include "events/simplestateevents.h" + +#include "events/callanswerevent.h" +#include "events/callcandidatesevent.h" +#include "events/callhangupevent.h" +#include "events/callinviteevent.h" #include "events/encryptionevent.h" -#include "events/roomcreateevent.h" -#include "events/roomtombstoneevent.h" +#include "events/reactionevent.h" +#include "events/receiptevent.h" +#include "events/redactionevent.h" #include "events/roomavatarevent.h" +#include "events/roomcreateevent.h" #include "events/roommemberevent.h" +#include "events/roomtombstoneevent.h" +#include "events/simplestateevents.h" #include "events/typingevent.h" -#include "events/receiptevent.h" -#include "events/reactionevent.h" -#include "events/callinviteevent.h" -#include "events/callcandidatesevent.h" -#include "events/callanswerevent.h" -#include "events/callhangupevent.h" -#include "events/redactionevent.h" -#include "jobs/mediathumbnailjob.h" #include "jobs/downloadfilejob.h" +#include "jobs/mediathumbnailjob.h" #include "jobs/postreadmarkersjob.h" -#include "avatar.h" -#include "connection.h" -#include "user.h" -#include "converters.h" -#include "syncdata.h" - -#include "e2ee.h" - -#include <session.h> // QtOlm -#include <groupsession.h> // QtOlm -#include <message.h> // QtOlm +#include <QtCore/QDir> #include <QtCore/QHash> -#include <QtCore/QStringBuilder> // for efficient string concats (operator%) +#include <QtCore/QMimeDatabase> #include <QtCore/QPointer> -#include <QtCore/QDir> -#include <QtCore/QTemporaryFile> #include <QtCore/QRegularExpression> -#include <QtCore/QMimeDatabase> +#include <QtCore/QStringBuilder> // for efficient string concats (operator%) +#include <QtCore/QTemporaryFile> #include <array> -#include <functional> #include <cmath> +#include <functional> +#include <groupsession.h> // QtOlm +#include <message.h> // QtOlm +#include <session.h> // QtOlm -using namespace QMatrixClient; +using namespace Quotient; using namespace std::placeholders; using std::move; #if !(defined __GLIBCXX__ && __GLIBCXX__ <= 20150123) @@ -80,276 +80,269 @@ using std::llround; enum EventsPlacement : int { Older = -1, Newer = 1 }; -class Room::Private -{ - public: - /// Map of user names to users - /** User names potentially duplicate, hence QMultiHash. */ - using members_map_t = QMultiHash<QString, User*>; - - Private(Connection* c, QString id_, JoinState initialJoinState) - : q(nullptr), connection(c), id(move(id_)) - , joinState(initialJoinState) - { } - - Room* q; - - Connection* connection; - QString id; - JoinState joinState; - RoomSummary summary = { none, 0, none }; - /// The state of the room at timeline position before-0 - /// \sa timelineBase - std::unordered_map<StateEventKey, StateEventPtr> baseState; - /// State event stubs - events without content, just type and state key - static decltype(baseState) stubbedState; - /// The state of the room at timeline position after-maxTimelineIndex() - /// \sa Room::syncEdge - QHash<StateEventKey, const StateEventBase*> currentState; - /// Servers with aliases for this room except the one of the local user - /// \sa Room::remoteAliases - QSet<QString> aliasServers; - - Timeline timeline; - PendingEvents unsyncedEvents; - QHash<QString, TimelineItem::index_t> eventsIndex; - // A map from evtId to a map of relation type to a vector of event - // pointers. Not using QMultiHash, because we want to quickly return - // a number of relations for a given event without enumerating them. - QHash<QPair<QString, QString>, RelatedEvents> relations; - QString displayname; - Avatar avatar; - int highlightCount = 0; - int notificationCount = 0; - members_map_t membersMap; - QList<User*> usersTyping; - QMultiHash<QString, User*> eventIdReadUsers; - QList<User*> usersInvited; - QList<User*> membersLeft; - int unreadMessages = 0; - bool displayed = false; - QString firstDisplayedEventId; - QString lastDisplayedEventId; - QHash<const User*, QString> lastReadEventIds; - QString serverReadMarker; - TagsMap tags; - std::unordered_map<QString, EventPtr> accountData; - QString prevBatch; - QPointer<GetRoomEventsJob> eventsHistoryJob; - QPointer<GetMembersByRoomJob> allMembersJob; - - struct FileTransferPrivateInfo +class Room::Private { +public: + /// Map of user names to users + /** User names potentially duplicate, hence QMultiHash. */ + using members_map_t = QMultiHash<QString, User*>; + + Private(Connection* c, QString id_, JoinState initialJoinState) + : q(nullptr), connection(c), id(move(id_)), joinState(initialJoinState) + {} + + Room* q; + + Connection* connection; + QString id; + JoinState joinState; + RoomSummary summary = { none, 0, none }; + /// The state of the room at timeline position before-0 + /// \sa timelineBase + UnorderedMap<StateEventKey, StateEventPtr> baseState; + /// State event stubs - events without content, just type and state key + static decltype(baseState) stubbedState; + /// The state of the room at timeline position after-maxTimelineIndex() + /// \sa Room::syncEdge + QHash<StateEventKey, const StateEventBase*> currentState; + /// Servers with aliases for this room except the one of the local user + /// \sa Room::remoteAliases + QSet<QString> aliasServers; + + Timeline timeline; + PendingEvents unsyncedEvents; + QHash<QString, TimelineItem::index_t> eventsIndex; + // A map from evtId to a map of relation type to a vector of event + // pointers. Not using QMultiHash, because we want to quickly return + // a number of relations for a given event without enumerating them. + QHash<QPair<QString, QString>, RelatedEvents> relations; + QString displayname; + Avatar avatar; + int highlightCount = 0; + int notificationCount = 0; + members_map_t membersMap; + QList<User*> usersTyping; + QMultiHash<QString, User*> eventIdReadUsers; + QList<User*> usersInvited; + QList<User*> membersLeft; + int unreadMessages = 0; + bool displayed = false; + QString firstDisplayedEventId; + QString lastDisplayedEventId; + QHash<const User*, QString> lastReadEventIds; + QString serverReadMarker; + TagsMap tags; + UnorderedMap<QString, EventPtr> accountData; + QString prevBatch; + QPointer<GetRoomEventsJob> eventsHistoryJob; + QPointer<GetMembersByRoomJob> allMembersJob; + + struct FileTransferPrivateInfo { + FileTransferPrivateInfo() = default; + FileTransferPrivateInfo(BaseJob* j, const QString& fileName, + bool isUploading = false) + : status(FileTransferInfo::Started) + , job(j) + , localFileInfo(fileName) + , isUpload(isUploading) + {} + + FileTransferInfo::Status status = FileTransferInfo::None; + QPointer<BaseJob> job = nullptr; + QFileInfo localFileInfo {}; + bool isUpload = false; + qint64 progress = 0; + qint64 total = -1; + + void update(qint64 p, qint64 t) { - FileTransferPrivateInfo() = default; - FileTransferPrivateInfo(BaseJob* j, const QString& fileName, - bool isUploading = false) - : status(FileTransferInfo::Started), job(j) - , localFileInfo(fileName), isUpload(isUploading) - { } - - FileTransferInfo::Status status = FileTransferInfo::None; - QPointer<BaseJob> job = nullptr; - QFileInfo localFileInfo { }; - bool isUpload = false; - qint64 progress = 0; - qint64 total = -1; - - void update(qint64 p, qint64 t) - { - if (t == 0) - { - t = -1; - if (p == 0) - p = -1; - } - if (p != -1) - qCDebug(PROFILER) << "Transfer progress:" << p << "/" << t - << "=" << llround(double(p) / t * 100) << "%"; - progress = p; total = t; + if (t == 0) { + t = -1; + if (p == 0) + p = -1; } - }; - void failedTransfer(const QString& tid, const QString& errorMessage = {}) - { - qCWarning(MAIN) << "File transfer failed for id" << tid; - if (!errorMessage.isEmpty()) - qCWarning(MAIN) << "Message:" << errorMessage; - fileTransfers[tid].status = FileTransferInfo::Failed; - emit q->fileTransferFailed(tid, errorMessage); + if (p != -1) + qCDebug(PROFILER) << "Transfer progress:" << p << "/" << t + << "=" << llround(double(p) / t * 100) << "%"; + progress = p; + total = t; } - /// A map from event/txn ids to information about the long operation; - /// used for both download and upload operations - QHash<QString, FileTransferPrivateInfo> fileTransfers; + }; + void failedTransfer(const QString& tid, const QString& errorMessage = {}) + { + qCWarning(MAIN) << "File transfer failed for id" << tid; + if (!errorMessage.isEmpty()) + qCWarning(MAIN) << "Message:" << errorMessage; + fileTransfers[tid].status = FileTransferInfo::Failed; + emit q->fileTransferFailed(tid, errorMessage); + } + /// A map from event/txn ids to information about the long operation; + /// used for both download and upload operations + QHash<QString, FileTransferPrivateInfo> fileTransfers; - const RoomMessageEvent* getEventWithFile(const QString& eventId) const; - QString fileNameToDownload(const RoomMessageEvent* event) const; + const RoomMessageEvent* getEventWithFile(const QString& eventId) const; + QString fileNameToDownload(const RoomMessageEvent* event) const; - Changes setSummary(RoomSummary&& newSummary); + Changes setSummary(RoomSummary&& newSummary); - //void inviteUser(User* u); // We might get it at some point in time. - void insertMemberIntoMap(User* u); - void renameMember(User* u, const QString& oldName); - void removeMemberFromMap(const QString& username, User* u); + // void inviteUser(User* u); // We might get it at some point in time. + void insertMemberIntoMap(User* u); + void renameMember(User* u, const QString& oldName); + void removeMemberFromMap(const QString& username, User* u); - // This updates the room displayname field (which is the way a room - // should be shown in the room list); called whenever the list of - // members, the room name (m.room.name) or canonical alias change. - void updateDisplayname(); - // This is used by updateDisplayname() but only calculates the new name - // without any updates. - QString calculateDisplayname() const; + // This updates the room displayname field (which is the way a room + // should be shown in the room list); called whenever the list of + // members, the room name (m.room.name) or canonical alias change. + void updateDisplayname(); + // This is used by updateDisplayname() but only calculates the new name + // without any updates. + QString calculateDisplayname() const; - /// A point in the timeline corresponding to baseState - rev_iter_t timelineBase() const { return q->findInTimeline(-1); } + /// A point in the timeline corresponding to baseState + rev_iter_t timelineBase() const { return q->findInTimeline(-1); } - void getPreviousContent(int limit = 10); + void getPreviousContent(int limit = 10); - template <typename EventT> - const EventT* getCurrentState(const QString& stateKey = {}) const - { - const StateEventKey evtKey { EventT::matrixTypeId(), stateKey }; - const auto* evt = currentState.value(evtKey, nullptr); - if (!evt) { - if (stubbedState.find(evtKey) == stubbedState.end()) { - // In the absence of a real event, make a stub as-if an event - // with empty content has been received. Event classes should be - // prepared for empty/invalid/malicious content anyway. - stubbedState.emplace(evtKey, - loadStateEvent(EventT::matrixTypeId(), - {}, stateKey)); - qCDebug(MAIN) << "A new stub event created for key {" - << evtKey.first << evtKey.second << "}"; - } - evt = stubbedState[evtKey].get(); - Q_ASSERT(evt); + template <typename EventT> + const EventT* getCurrentState(const QString& stateKey = {}) const + { + const StateEventKey evtKey { EventT::matrixTypeId(), stateKey }; + const auto* evt = currentState.value(evtKey, nullptr); + if (!evt) { + if (stubbedState.find(evtKey) == stubbedState.end()) { + // In the absence of a real event, make a stub as-if an event + // with empty content has been received. Event classes should be + // prepared for empty/invalid/malicious content anyway. + stubbedState.emplace(evtKey, + loadStateEvent(EventT::matrixTypeId(), {}, + stateKey)); + qCDebug(STATE) << "A new stub event created for key {" + << evtKey.first << evtKey.second << "}"; } - Q_ASSERT(evt->type() == EventT::typeId() && - evt->matrixType() == EventT::matrixTypeId()); - return static_cast<const EventT*>(evt); + evt = stubbedState[evtKey].get(); + Q_ASSERT(evt); } + Q_ASSERT(evt->type() == EventT::typeId() + && evt->matrixType() == EventT::matrixTypeId()); + return static_cast<const EventT*>(evt); + } - bool isEventNotable(const TimelineItem& ti) const - { - return !ti->isRedacted() && - ti->senderId() != connection->userId() && - is<RoomMessageEvent>(*ti); - } + bool isEventNotable(const TimelineItem& ti) const + { + return !ti->isRedacted() && ti->senderId() != connection->userId() + && is<RoomMessageEvent>(*ti); + } - template <typename EventArrayT> - Changes updateStateFrom(EventArrayT&& events) - { - Changes changes = NoChange; - if (!events.empty()) - { - QElapsedTimer et; et.start(); - for (auto&& eptr: events) - { - const auto& evt = *eptr; - Q_ASSERT(evt.isStateEvent()); - // Update baseState afterwards to make sure that the old state - // is valid and usable inside processStateEvent - changes |= q->processStateEvent(evt); - baseState[{evt.matrixType(),evt.stateKey()}] = move(eptr); - } - if (events.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::Private::updateStateFrom():" - << events.size() << "event(s)," << et; + template <typename EventArrayT> + Changes updateStateFrom(EventArrayT&& events) + { + Changes changes = NoChange; + if (!events.empty()) { + QElapsedTimer et; + et.start(); + for (auto&& eptr : events) { + const auto& evt = *eptr; + Q_ASSERT(evt.isStateEvent()); + // Update baseState afterwards to make sure that the old state + // is valid and usable inside processStateEvent + changes |= q->processStateEvent(evt); + baseState[{ evt.matrixType(), evt.stateKey() }] = move(eptr); } - return changes; - } - Changes addNewMessageEvents(RoomEvents&& events); - void addHistoricalMessageEvents(RoomEvents&& events); - - /** Move events into the timeline - * - * Insert events into the timeline, either new or historical. - * Pointers in the original container become empty, the ownership - * is passed to the timeline container. - * @param events - the range of events to be inserted - * @param placement - position and direction of insertion: Older for - * historical messages, Newer for new ones - */ - Timeline::size_type moveEventsToTimeline(RoomEventsRange events, - EventsPlacement placement); - - /** - * Remove events from the passed container that are already in the timeline - */ - void dropDuplicateEvents(RoomEvents& events) const; - - Changes setLastReadEvent(User* u, QString eventId); - void updateUnreadCount(rev_iter_t from, rev_iter_t to); - Changes promoteReadMarker(User* u, rev_iter_t newMarker, - bool force = false); - - Changes markMessagesAsRead(rev_iter_t upToMarker); - - void getAllMembers(); - - QString sendEvent(RoomEventPtr&& event); - - template <typename EventT, typename... ArgTs> - QString sendEvent(ArgTs&&... eventArgs) - { - return sendEvent(makeEvent<EventT>(std::forward<ArgTs>(eventArgs)...)); + if (events.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) + << "*** Room::Private::updateStateFrom():" << events.size() + << "event(s)," << et; } + return changes; + } + Changes addNewMessageEvents(RoomEvents&& events); + void addHistoricalMessageEvents(RoomEvents&& events); + + /** Move events into the timeline + * + * Insert events into the timeline, either new or historical. + * Pointers in the original container become empty, the ownership + * is passed to the timeline container. + * @param events - the range of events to be inserted + * @param placement - position and direction of insertion: Older for + * historical messages, Newer for new ones + */ + Timeline::size_type moveEventsToTimeline(RoomEventsRange events, + EventsPlacement placement); + + /** + * Remove events from the passed container that are already in the timeline + */ + void dropDuplicateEvents(RoomEvents& events) const; + + Changes setLastReadEvent(User* u, QString eventId); + void updateUnreadCount(rev_iter_t from, rev_iter_t to); + Changes promoteReadMarker(User* u, rev_iter_t newMarker, bool force = false); + + Changes markMessagesAsRead(rev_iter_t upToMarker); + + void getAllMembers(); + + QString sendEvent(RoomEventPtr&& event); + + template <typename EventT, typename... ArgTs> + QString sendEvent(ArgTs&&... eventArgs) + { + return sendEvent(makeEvent<EventT>(std::forward<ArgTs>(eventArgs)...)); + } - RoomEvent* addAsPending(RoomEventPtr&& event); + RoomEvent* addAsPending(RoomEventPtr&& event); - QString doSendEvent(const RoomEvent* pEvent); - void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr); + QString doSendEvent(const RoomEvent* pEvent); + void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr); - SetRoomStateWithKeyJob* requestSetState(const StateEventBase& event) - { -// if (event.roomId().isEmpty()) -// event.setRoomId(id); -// if (event.senderId().isEmpty()) -// event.setSender(connection->userId()); - // TODO: Queue up state events sending (see #133). - // TODO: Maybe addAsPending() as well, despite having no txnId - return connection->callApi<SetRoomStateWithKeyJob>( - id, event.matrixType(), - event.stateKey(), event.contentJson()); - } + SetRoomStateWithKeyJob* requestSetState(const StateEventBase& event) + { + // if (event.roomId().isEmpty()) + // event.setRoomId(id); + // if (event.senderId().isEmpty()) + // event.setSender(connection->userId()); + // TODO: Queue up state events sending (see #133). + // TODO: Maybe addAsPending() as well, despite having no txnId + return connection->callApi<SetRoomStateWithKeyJob>( + id, event.matrixType(), event.stateKey(), event.contentJson()); + } - template <typename EvT, typename... ArgTs> - auto requestSetState(ArgTs&&... args) - { - return requestSetState(EvT(std::forward<ArgTs>(args)...)); - } + template <typename EvT, typename... ArgTs> + auto requestSetState(ArgTs&&... args) + { + return requestSetState(EvT(std::forward<ArgTs>(args)...)); + } - /*! Apply redaction to the timeline - * - * Tries to find an event in the timeline and redact it; deletes the - * redaction event whether the redacted event was found or not. - * \return true if the event has been found and redacted; false otherwise - */ - bool processRedaction(const RedactionEvent& redaction); - - /*! Apply a new revision of the event to the timeline - * - * Tries to find an event in the timeline and replace it with the new - * content passed in \p newMessage. - * \return true if the event has been found and replaced; false otherwise - */ - bool processReplacement(const RoomMessageEvent& newMessage); - - void setTags(TagsMap newTags); - - QJsonObject toJson() const; - - private: - using users_shortlist_t = std::array<User*, 3>; - template<typename ContT> - users_shortlist_t buildShortlist(const ContT& users) const; - users_shortlist_t buildShortlist(const QStringList& userIds) const; - - bool isLocalUser(const User* u) const - { - return u == q->localUser(); - } + /*! Apply redaction to the timeline + * + * Tries to find an event in the timeline and redact it; deletes the + * redaction event whether the redacted event was found or not. + * \return true if the event has been found and redacted; false otherwise + */ + bool processRedaction(const RedactionEvent& redaction); + + /*! Apply a new revision of the event to the timeline + * + * Tries to find an event in the timeline and replace it with the new + * content passed in \p newMessage. + * \return true if the event has been found and replaced; false otherwise + */ + bool processReplacement(const RoomMessageEvent& newMessage); + + void setTags(TagsMap newTags); + + QJsonObject toJson() const; + +private: + using users_shortlist_t = std::array<User*, 3>; + template <typename ContT> + users_shortlist_t buildShortlist(const ContT& users) const; + users_shortlist_t buildShortlist(const QStringList& userIds) const; + + bool isLocalUser(const User* u) const { return u == q->localUser(); } }; -decltype(Room::Private::baseState) Room::Private::stubbedState { }; +decltype(Room::Private::baseState) Room::Private::stubbedState {}; Room::Room(Connection* connection, QString id, JoinState initialJoinState) : QObject(connection), d(new Private(connection, id, initialJoinState)) @@ -359,24 +352,17 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; d->displayname = d->calculateDisplayname(); // Set initial "Empty room" name - connectUntil(connection, &Connection::loadedRoomState, this, - [this] (Room* r) { - if (this == r) - emit baseStateLoaded(); - return this == r; // loadedRoomState fires only once per room - }); + connectUntil(connection, &Connection::loadedRoomState, this, [this](Room* r) { + if (this == r) + emit baseStateLoaded(); + return this == r; // loadedRoomState fires only once per room + }); qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } -Room::~Room() -{ - delete d; -} +Room::~Room() { delete d; } -const QString& Room::id() const -{ - return d->id; -} +const QString& Room::id() const { return d->id; } QString Room::version() const { @@ -386,8 +372,8 @@ QString Room::version() const bool Room::isUnstable() const { - return !connection()->loadingCapabilities() && - !connection()->stableRoomVersions().contains(version()); + return !connection()->loadingCapabilities() + && !connection()->stableRoomVersions().contains(version()); } QString Room::predecessorId() const @@ -400,10 +386,7 @@ QString Room::successorId() const return d->getCurrentState<RoomTombstoneEvent>()->successorRoomId(); } -const Room::Timeline& Room::messageEvents() const -{ - return d->timeline; -} +const Room::Timeline& Room::messageEvents() const { return d->timeline; } const Room::PendingEvents& Room::pendingEvents() const { @@ -422,14 +405,16 @@ QString Room::name() const QStringList Room::localAliases() const { - return d->getCurrentState<RoomAliasesEvent>( - connection()->homeserver().authority())->aliases(); + return d + ->getCurrentState<RoomAliasesEvent>( + connection()->homeserver().authority()) + ->aliases(); } QStringList Room::remoteAliases() const { QStringList result; - for (const auto& s: d->aliasServers) + for (const auto& s : d->aliasServers) result += d->getCurrentState<RoomAliasesEvent>(s)->aliases(); return result; } @@ -439,40 +424,22 @@ QString Room::canonicalAlias() const return d->getCurrentState<RoomCanonicalAliasEvent>()->alias(); } -QString Room::displayName() const -{ - return d->displayname; -} +QString Room::displayName() const { return d->displayname; } -void Room::refreshDisplayName() -{ - d->updateDisplayname(); -} +void Room::refreshDisplayName() { d->updateDisplayname(); } QString Room::topic() const { return d->getCurrentState<RoomTopicEvent>()->topic(); } -QString Room::avatarMediaId() const -{ - return d->avatar.mediaId(); -} +QString Room::avatarMediaId() const { return d->avatar.mediaId(); } -QUrl Room::avatarUrl() const -{ - return d->avatar.url(); -} +QUrl Room::avatarUrl() const { return d->avatar.url(); } -const Avatar& Room::avatarObject() const -{ - return d->avatar; -} +const Avatar& Room::avatarObject() const { return d->avatar; } -QImage Room::avatar(int dimension) -{ - return avatar(dimension, dimension); -} +QImage Room::avatar(int dimension) { return avatar(dimension, dimension); } QImage Room::avatar(int width, int height) { @@ -482,7 +449,7 @@ QImage Room::avatar(int width, int height) // Use the first (excluding self) user's avatar for direct chats const auto dcUsers = directChatUsers(); - for (auto* u: dcUsers) + for (auto* u : dcUsers) if (u != localUser()) return u->avatar(width, height, this, [=] { emit avatarChanged(); }); @@ -496,24 +463,20 @@ User* Room::user(const QString& userId) const JoinState Room::memberJoinState(User* user) const { - return - d->membersMap.contains(user->name(this), user) ? JoinState::Join : - JoinState::Leave; + return d->membersMap.contains(user->name(this), user) ? JoinState::Join + : JoinState::Leave; } -JoinState Room::joinState() const -{ - return d->joinState; -} +JoinState Room::joinState() const { return d->joinState; } void Room::setJoinState(JoinState state) { JoinState oldState = d->joinState; - if( state == oldState ) + if (state == oldState) return; d->joinState = state; - qCDebug(MAIN) << "Room" << id() << "changed state: " - << int(oldState) << "->" << int(state); + qCDebug(STATE) << "Room" << id() << "changed state: " << int(oldState) + << "->" << int(state); emit changed(Change::JoinStateChange); emit joinStateChanged(oldState, state); } @@ -528,8 +491,7 @@ Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId) swap(storedId, eventId); emit q->lastReadEventChanged(u); emit q->readMarkerForUserMoved(u, eventId, storedId); - if (isLocalUser(u)) - { + if (isLocalUser(u)) { if (storedId != serverReadMarker) connection->callApi<PostReadMarkersJob>(id, storedId); emit q->readMarkerMoved(eventId, storedId); @@ -548,32 +510,32 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) // unreadMessages and might need to promote the read marker further // over local-origin messages. const auto readMarker = q->readMarker(); - if (readMarker >= from && readMarker < to) - { + if (readMarker >= from && readMarker < to) { promoteReadMarker(q->localUser(), readMarker, true); return; } Q_ASSERT(to <= readMarker); - QElapsedTimer et; et.start(); - const auto newUnreadMessages = count_if(from, to, - std::bind(&Room::Private::isEventNotable, this, _1)); + QElapsedTimer et; + et.start(); + const auto newUnreadMessages = + count_if(from, to, std::bind(&Room::Private::isEventNotable, this, _1)); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Counting gained unread messages took" << et; - if(newUnreadMessages > 0) - { + if (newUnreadMessages > 0) { // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages < 0) unreadMessages = 0; unreadMessages += newUnreadMessages; - qCDebug(MAIN) << "Room" << q->objectName() << "has gained" - << newUnreadMessages << "unread message(s)," - << (q->readMarker() == timeline.crend() ? - "in total at least" : "in total") - << unreadMessages << "unread message(s)"; + qCDebug(MESSAGES) << "Room" << q->objectName() << "has gained" + << newUnreadMessages << "unread message(s)," + << (q->readMarker() == timeline.crend() + ? "in total at least" + : "in total") + << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); } } @@ -585,23 +547,27 @@ Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, Q_ASSERT(newMarker >= timeline.crbegin() && newMarker <= timeline.crend()); const auto prevMarker = q->readMarker(u); - if (!force && prevMarker <= newMarker) // Remember, we deal with reverse iterators + if (!force && prevMarker <= newMarker) // Remember, we deal with reverse + // iterators return Change::NoChange; Q_ASSERT(newMarker < timeline.crend()); // Try to auto-promote the read marker over the user's own messages // (switch to direct iterators for that). - auto eagerMarker = find_if(newMarker.base(), timeline.cend(), - [=](const TimelineItem& ti) { return ti->senderId() != u->id(); }); + auto eagerMarker = + find_if(newMarker.base(), timeline.cend(), [=](const TimelineItem& ti) { + return ti->senderId() != u->id(); + }); auto changes = setLastReadEvent(u, (*(eagerMarker - 1))->id()); - if (isLocalUser(u)) - { + if (isLocalUser(u)) { const auto oldUnreadCount = unreadMessages; - QElapsedTimer et; et.start(); - unreadMessages = int(count_if(eagerMarker, timeline.cend(), - std::bind(&Room::Private::isEventNotable, this, _1))); + QElapsedTimer et; + et.start(); + unreadMessages = + int(count_if(eagerMarker, timeline.cend(), + std::bind(&Room::Private::isEventNotable, this, _1))); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; @@ -609,15 +575,13 @@ Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, if (unreadMessages == 0) unreadMessages = -1; - if (force || unreadMessages != oldUnreadCount) - { - if (unreadMessages == -1) - { - qCDebug(MAIN) << "Room" << displayname - << "has no more unread messages"; + if (force || unreadMessages != oldUnreadCount) { + if (unreadMessages == -1) { + qCDebug(MESSAGES) + << "Room" << displayname << "has no more unread messages"; } else - qCDebug(MAIN) << "Room" << displayname << "still has" - << unreadMessages << "unread message(s)"; + qCDebug(MESSAGES) << "Room" << displayname << "still has" + << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); changes |= Change::UnreadNotifsChange; } @@ -630,17 +594,16 @@ Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) const auto prevMarker = q->readMarker(); auto changes = promoteReadMarker(q->localUser(), upToMarker); if (prevMarker != upToMarker) - qCDebug(MAIN) << "Marked messages as read until" << *q->readMarker(); + qCDebug(MESSAGES) << "Marked messages as read until" << *q->readMarker(); // We shouldn't send read receipts for the local user's own messages - so // search earlier messages for the latest message not from the local user // until the previous last-read message, whichever comes first. - for (; upToMarker < prevMarker; ++upToMarker) - { - if ((*upToMarker)->senderId() != q->localUser()->id()) - { + for (; upToMarker < prevMarker; ++upToMarker) { + if ((*upToMarker)->senderId() != q->localUser()->id()) { connection->callApi<PostReceiptJob>(id, QStringLiteral("m.read"), - QUrl::toPercentEncoding((*upToMarker)->id())); + QUrl::toPercentEncoding( + (*upToMarker)->id())); break; } } @@ -665,46 +628,36 @@ bool Room::canSwitchVersions() const // TODO, #276: m.room.power_levels const auto* plEvt = - d->currentState.value({QStringLiteral("m.room.power_levels"), {}}); + d->currentState.value({ QStringLiteral("m.room.power_levels"), {} }); if (!plEvt) return true; const auto plJson = plEvt->contentJson(); const auto currentUserLevel = - plJson.value("users"_ls).toObject() - .value(localUser()->id()).toInt( - plJson.value("users_default"_ls).toInt()); + plJson.value("users"_ls) + .toObject() + .value(localUser()->id()) + .toInt(plJson.value("users_default"_ls).toInt()); const auto tombstonePowerLevel = - plJson.value("events"_ls).toObject() - .value("m.room.tombstone"_ls).toInt( - plJson.value("state_default"_ls).toInt()); + plJson.value("events"_ls) + .toObject() + .value("m.room.tombstone"_ls) + .toInt(plJson.value("state_default"_ls).toInt()); return currentUserLevel >= tombstonePowerLevel; } -bool Room::hasUnreadMessages() const -{ - return unreadCount() >= 0; -} +bool Room::hasUnreadMessages() const { return unreadCount() >= 0; } -int Room::unreadCount() const -{ - return d->unreadMessages; -} +int Room::unreadCount() const { return d->unreadMessages; } -Room::rev_iter_t Room::historyEdge() const -{ - return d->timeline.crend(); -} +Room::rev_iter_t Room::historyEdge() const { return d->timeline.crend(); } Room::Timeline::const_iterator Room::syncEdge() const { return d->timeline.cend(); } -Room::rev_iter_t Room::timelineEdge() const -{ - return historyEdge(); -} +Room::rev_iter_t Room::timelineEdge() const { return historyEdge(); } TimelineItem::index_t Room::minTimelineIndex() const { @@ -718,21 +671,19 @@ TimelineItem::index_t Room::maxTimelineIndex() const bool Room::isValidIndex(TimelineItem::index_t timelineIndex) const { - return !d->timeline.empty() && - timelineIndex >= minTimelineIndex() && - timelineIndex <= maxTimelineIndex(); + return !d->timeline.empty() && timelineIndex >= minTimelineIndex() + && timelineIndex <= maxTimelineIndex(); } Room::rev_iter_t Room::findInTimeline(TimelineItem::index_t index) const { - return timelineEdge() - - (isValidIndex(index) ? index - minTimelineIndex() + 1 : 0); + return timelineEdge() + - (isValidIndex(index) ? index - minTimelineIndex() + 1 : 0); } Room::rev_iter_t Room::findInTimeline(const QString& evtId) const { - if (!d->timeline.empty() && d->eventsIndex.contains(evtId)) - { + if (!d->timeline.empty() && d->eventsIndex.contains(evtId)) { auto it = findInTimeline(d->eventsIndex.value(evtId)); Q_ASSERT(it != historyEdge() && (*it)->id() == evtId); return it; @@ -743,14 +694,18 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const Room::PendingEvents::iterator Room::findPendingEvent(const QString& txnId) { return std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), - [txnId] (const auto& item) { return item->transactionId() == txnId; }); + [txnId](const auto& item) { + return item->transactionId() == txnId; + }); } Room::PendingEvents::const_iterator Room::findPendingEvent(const QString& txnId) const { return std::find_if(d->unsyncedEvents.cbegin(), d->unsyncedEvents.cend(), - [txnId] (const auto& item) { return item->transactionId() == txnId; }); + [txnId](const auto& item) { + return item->transactionId() == txnId; + }); } const Room::RelatedEvents Room::relatedEvents(const QString& evtId, @@ -772,28 +727,25 @@ void Room::Private::getAllMembers() return; allMembersJob = connection->callApi<GetMembersByRoomJob>( - id, connection->nextBatchToken(), "join"); + id, connection->nextBatchToken(), "join"); auto nextIndex = timeline.empty() ? 0 : timeline.back().index() + 1; - connect( allMembersJob, &BaseJob::success, q, [=] { + connect(allMembersJob, &BaseJob::success, q, [=] { Q_ASSERT(timeline.empty() || nextIndex <= q->maxTimelineIndex() + 1); auto roomChanges = updateStateFrom(allMembersJob->chunk()); // Replay member events that arrived after the point for which // the full members list was requested. - if (!timeline.empty() ) + if (!timeline.empty()) for (auto it = q->findInTimeline(nextIndex).base(); it != timeline.cend(); ++it) if (is<RoomMemberEvent>(**it)) roomChanges |= q->processStateEvent(**it); - if (roomChanges&MembersChange) + if (roomChanges & MembersChange) emit q->memberListChanged(); emit q->allMembersLoaded(); }); } -bool Room::displayed() const -{ - return d->displayed; -} +bool Room::displayed() const { return d->displayed; } void Room::setDisplayed(bool displayed) { @@ -802,18 +754,14 @@ void Room::setDisplayed(bool displayed) d->displayed = displayed; emit displayedChanged(displayed); - if( displayed ) - { + if (displayed) { resetHighlightCount(); resetNotificationCount(); d->getAllMembers(); } } -QString Room::firstDisplayedEventId() const -{ - return d->firstDisplayedEventId; -} +QString Room::firstDisplayedEventId() const { return d->firstDisplayedEventId; } Room::rev_iter_t Room::firstDisplayedMarker() const { @@ -835,10 +783,7 @@ void Room::setFirstDisplayedEvent(TimelineItem::index_t index) setFirstDisplayedEventId(findInTimeline(index)->event()->id()); } -QString Room::lastDisplayedEventId() const -{ - return d->lastDisplayedEventId; -} +QString Room::lastDisplayedEventId() const { return d->lastDisplayedEventId; } Room::rev_iter_t Room::lastDisplayedMarker() const { @@ -866,41 +811,33 @@ Room::rev_iter_t Room::readMarker(const User* user) const return findInTimeline(d->lastReadEventIds.value(user)); } -Room::rev_iter_t Room::readMarker() const -{ - return readMarker(localUser()); -} +Room::rev_iter_t Room::readMarker() const { return readMarker(localUser()); } QString Room::readMarkerEventId() const { return d->lastReadEventIds.value(localUser()); } -QList<User*> Room::usersAtEventId(const QString& eventId) { +QList<User*> Room::usersAtEventId(const QString& eventId) +{ return d->eventIdReadUsers.values(eventId); } -int Room::notificationCount() const -{ - return d->notificationCount; -} +int Room::notificationCount() const { return d->notificationCount; } void Room::resetNotificationCount() { - if( d->notificationCount == 0 ) + if (d->notificationCount == 0) return; d->notificationCount = 0; emit notificationCountChanged(); } -int Room::highlightCount() const -{ - return d->highlightCount; -} +int Room::highlightCount() const { return d->highlightCount; } void Room::resetHighlightCount() { - if( d->highlightCount == 0 ) + if (d->highlightCount == 0) return; d->highlightCount = 0; emit highlightCountChanged(); @@ -908,15 +845,13 @@ void Room::resetHighlightCount() void Room::switchVersion(QString newVersion) { - if (!successorId().isEmpty()) - { + if (!successorId().isEmpty()) { Q_ASSERT(!successorId().isEmpty()); emit upgradeFailed(tr("The room is already upgraded")); } if (auto* job = connection()->callApi<UpgradeRoomJob>(id(), newVersion)) - connect(job, &BaseJob::failure, this, [this,job] { - emit upgradeFailed(job->errorString()); - }); + connect(job, &BaseJob::failure, this, + [this, job] { emit upgradeFailed(job->errorString()); }); else emit upgradeFailed(tr("Couldn't initiate upgrade")); } @@ -933,20 +868,11 @@ const EventPtr& Room::accountData(const QString& type) const return it != d->accountData.end() ? it->second : NoEventPtr; } -QStringList Room::tagNames() const -{ - return d->tags.keys(); -} +QStringList Room::tagNames() const { return d->tags.keys(); } -TagsMap Room::tags() const -{ - return d->tags; -} +TagsMap Room::tags() const { return d->tags; } -TagRecord Room::tag(const QString& name) const -{ - return d->tags.value(name); -} +TagRecord Room::tag(const QString& name) const { return d->tags.value(name); } std::pair<bool, QString> validatedTag(QString name) { @@ -964,8 +890,8 @@ std::pair<bool, QString> validatedTag(QString name) void Room::addTag(const QString& name, const TagRecord& record) { const auto& checkRes = validatedTag(name); - if (d->tags.contains(name) || - (checkRes.first && d->tags.contains(checkRes.second))) + if (d->tags.contains(name) + || (checkRes.first && d->tags.contains(checkRes.second))) return; emit tagsAboutToChange(); @@ -977,13 +903,12 @@ void Room::addTag(const QString& name, const TagRecord& record) void Room::addTag(const QString& name, float order) { - addTag(name, TagRecord{order}); + addTag(name, TagRecord { order }); } void Room::removeTag(const QString& name) { - if (d->tags.contains(name)) - { + if (d->tags.contains(name)) { emit tagsAboutToChange(); d->tags.remove(name); emit tagsChanged(); @@ -999,46 +924,39 @@ void Room::setTags(TagsMap newTags) { d->setTags(move(newTags)); connection()->callApi<SetAccountDataPerRoomJob>( - localUser()->id(), id(), TagEvent::matrixTypeId(), - TagEvent(d->tags).contentJson()); + localUser()->id(), id(), TagEvent::matrixTypeId(), + TagEvent(d->tags).contentJson()); } void Room::Private::setTags(TagsMap newTags) { emit q->tagsAboutToChange(); const auto keys = newTags.keys(); - for (const auto& k: keys) - { - const auto& checkRes = validatedTag(k); - if (checkRes.first) - { + for (const auto& k : keys) + if (const auto& checkRes = validatedTag(k); checkRes.first) { if (newTags.contains(checkRes.second)) newTags.remove(k); else newTags.insert(checkRes.second, newTags.take(k)); } - } + tags = move(newTags); - qCDebug(MAIN) << "Room" << q->objectName() << "is tagged with" - << q->tagNames().join(QStringLiteral(", ")); + qCDebug(STATE) << "Room" << q->objectName() << "is tagged with" + << q->tagNames().join(QStringLiteral(", ")); emit q->tagsChanged(); } -bool Room::isFavourite() const -{ - return d->tags.contains(FavouriteTag); -} +bool Room::isFavourite() const { return d->tags.contains(FavouriteTag); } -bool Room::isLowPriority() const -{ - return d->tags.contains(LowPriorityTag); -} +bool Room::isLowPriority() const { return d->tags.contains(LowPriorityTag); } -bool Room::isDirectChat() const +bool Room::isServerNoticeRoom() const { - return connection()->isDirectChat(id()); + return d->tags.contains(ServerNoticeTag); } +bool Room::isDirectChat() const { return connection()->isDirectChat(id()); } + QList<User*> Room::directChatUsers() const { return connection()->directChatUsers(this); @@ -1048,8 +966,7 @@ const RoomMessageEvent* Room::Private::getEventWithFile(const QString& eventId) const { auto evtIt = q->findInTimeline(eventId); - if (evtIt != timeline.rend() && is<RoomMessageEvent>(**evtIt)) - { + if (evtIt != timeline.rend() && is<RoomMessageEvent>(**evtIt)) { auto* event = evtIt->viewAs<RoomMessageEvent>(); if (event->hasFileContent()) return event; @@ -1064,28 +981,24 @@ QString Room::Private::fileNameToDownload(const RoomMessageEvent* event) const const auto* fileInfo = event->content()->fileInfo(); QString fileName; if (!fileInfo->originalName.isEmpty()) - { fileName = QFileInfo(fileInfo->originalName).fileName(); - } - else if (!event->plainBody().isEmpty()) - { + else if (!event->plainBody().isEmpty()) { // Having no better options, assume that the body has // the original file URL or at least the file name. - QUrl u { event->plainBody() }; - if (u.isValid()) + if (QUrl u { event->plainBody() }; u.isValid()) fileName = QFileInfo(u.path()).fileName(); } // Check the file name for sanity if (fileName.isEmpty() || !QTemporaryFile(fileName).open()) return "file." % fileInfo->mimeType.preferredSuffix(); - if (QSysInfo::productType() == "windows") - { - const auto& suffixes = fileInfo->mimeType.suffixes(); - if (!suffixes.isEmpty() && - std::none_of(suffixes.begin(), suffixes.end(), - [&fileName] (const QString& s) { - return fileName.endsWith(s); })) + if (QSysInfo::productType() == "windows") { + if (const auto& suffixes = fileInfo->mimeType.suffixes(); + suffixes.isEmpty() + && std::none_of(suffixes.begin(), suffixes.end(), + [&fileName](const QString& s) { + return fileName.endsWith(s); + })) return fileName % '.' % fileInfo->mimeType.preferredSuffix(); } return fileName; @@ -1094,12 +1007,12 @@ QString Room::Private::fileNameToDownload(const RoomMessageEvent* event) const QUrl Room::urlToThumbnail(const QString& eventId) const { if (auto* event = d->getEventWithFile(eventId)) - if (event->hasThumbnail()) - { + if (event->hasThumbnail()) { auto* thumbnail = event->content()->thumbnailInfo(); Q_ASSERT(thumbnail != nullptr); return MediaThumbnailJob::makeRequestUrl(connection()->homeserver(), - thumbnail->url, thumbnail->imageSize); + thumbnail->url, + thumbnail->imageSize); } qDebug() << "Event" << eventId << "has no thumbnail"; return {}; @@ -1107,8 +1020,7 @@ QUrl Room::urlToThumbnail(const QString& eventId) const QUrl Room::urlToDownload(const QString& eventId) const { - if (auto* event = d->getEventWithFile(eventId)) - { + if (auto* event = d->getEventWithFile(eventId)) { auto* fileInfo = event->content()->fileInfo(); Q_ASSERT(fileInfo != nullptr); return DownloadFileJob::makeRequestUrl(connection()->homeserver(), @@ -1135,27 +1047,18 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const qint64 progress = infoIt->progress; qint64 total = infoIt->total; - if (total > INT_MAX) - { + if (total > INT_MAX) { // JavaScript doesn't deal with 64-bit integers; scale down if necessary progress = llround(double(progress) / total * INT_MAX); total = INT_MAX; } -#ifdef BROKEN_INITIALIZER_LISTS - FileTransferInfo fti; - fti.status = infoIt->status; - fti.progress = int(progress); - fti.total = int(total); - fti.localDir = QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()); - fti.localPath = QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); - return fti; -#else - return { infoIt->status, infoIt->isUpload, int(progress), int(total), - QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), - QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) - }; -#endif + return { infoIt->status, + infoIt->isUpload, + int(progress), + int(total), + QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), + QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) }; } QUrl Room::fileSource(const QString& id) const @@ -1175,66 +1078,57 @@ QUrl Room::fileSource(const QString& id) const QString Room::prettyPrint(const QString& plainText) const { - return QMatrixClient::prettyPrint(plainText); + return Quotient::prettyPrint(plainText); } -QList< User* > Room::usersTyping() const -{ - return d->usersTyping; -} +QList<User*> Room::usersTyping() const { return d->usersTyping; } -QList< User* > Room::membersLeft() const -{ - return d->membersLeft; -} +QList<User*> Room::membersLeft() const { return d->membersLeft; } -QList< User* > Room::users() const -{ - return d->membersMap.values(); -} +QList<User*> Room::users() const { return d->membersMap.values(); } QStringList Room::memberNames() const { QStringList res; for (auto u : qAsConst(d->membersMap)) - res.append( roomMembername(u) ); + res.append(roomMembername(u)); return res; } -int Room::memberCount() const -{ - return d->membersMap.size(); -} +int Room::memberCount() const { return d->membersMap.size(); } -int Room::timelineSize() const -{ - return int(d->timeline.size()); -} +int Room::timelineSize() const { return int(d->timeline.size()); } bool Room::usesEncryption() const { return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty(); } -const RoomEvent* Room::decryptMessage(EncryptedEvent *encryptedEvent) const +RoomEventPtr Room::decryptMessage(EncryptedEvent* encryptedEvent) { if (encryptedEvent->algorithm() == OlmV1Curve25519AesSha2AlgoKey) { - QString identityKey = connection()->olmAccount()->curve25519IdentityKey(); - QJsonObject personalCipherObject = encryptedEvent->ciphertext(identityKey); + QString identityKey = + connection()->olmAccount()->curve25519IdentityKey(); + QJsonObject personalCipherObject = + encryptedEvent->ciphertext(identityKey); if (personalCipherObject.isEmpty()) { - qCDebug(EVENTS) << "Encrypted event is not for the current device"; - return nullptr; + qCDebug(E2EE) << "Encrypted event is not for the current device"; + return {}; } - return makeEvent<RoomMessageEvent>(decryptMessage(personalCipherObject, encryptedEvent->senderKey().toLatin1())).get(); + return makeEvent<RoomMessageEvent>(decryptMessage( + personalCipherObject, encryptedEvent->senderKey().toLatin1())); } if (encryptedEvent->algorithm() == MegolmV1AesSha2AlgoKey) { - return makeEvent<RoomMessageEvent>(decryptMessage(encryptedEvent->ciphertext(), encryptedEvent->senderKey(), encryptedEvent->deviceId(), encryptedEvent->sessionId())).get(); + return makeEvent<RoomMessageEvent>(decryptMessage( + encryptedEvent->ciphertext(), encryptedEvent->senderKey(), + encryptedEvent->deviceId(), encryptedEvent->sessionId())); } - return nullptr; + return {}; } -const QString Room::decryptMessage(QJsonObject personalCipherObject, QByteArray senderKey) const +QString Room::decryptMessage(QJsonObject personalCipherObject, + QByteArray senderKey) { QString decrypted; @@ -1245,35 +1139,27 @@ const QString Room::decryptMessage(QJsonObject personalCipherObject, QByteArray int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - PreKeyMessage* preKeyMessage = new PreKeyMessage(body); - session = new InboundSession(connection()->olmAccount(), preKeyMessage, senderKey); + PreKeyMessage preKeyMessage { body }; + session = + new InboundSession(connection()->olmAccount(), &preKeyMessage, senderKey, this); if (type == 0) { - if (!session->matches(preKeyMessage, senderKey)) - { + if (!session->matches(&preKeyMessage, senderKey)) { connection()->olmAccount()->removeOneTimeKeys(session); } - try - { - decrypted = session->decrypt(preKeyMessage); - } - catch(std::runtime_error& e) - { + try { + decrypted = session->decrypt(&preKeyMessage); + } catch (std::runtime_error& e) { qWarning(EVENTS) << "Decrypt failed:" << e.what(); } } - else if (type == 1) - { - Message* message = new Message(body); - if (!session->matches(preKeyMessage, senderKey)) - { + else if (type == 1) { + Message message { body }; + if (!session->matches(&preKeyMessage, senderKey)) { qWarning(EVENTS) << "Invalid encrypted message"; } - try - { - decrypted = session->decrypt(message); - } - catch(std::runtime_error& e) - { + try { + decrypted = session->decrypt(&message); + } catch (std::runtime_error& e) { qWarning(EVENTS) << "Decrypt failed:" << e.what(); } } @@ -1281,18 +1167,21 @@ const QString Room::decryptMessage(QJsonObject personalCipherObject, QByteArray return decrypted; } -const QString Room::sessionKey(const QString& senderKey, const QString& deviceId, const QString& sessionId) const +QString Room::sessionKey(const QString& senderKey, const QString& deviceId, + const QString& sessionId) const { // TODO: handling an m.room_key event return ""; } -const QString Room::decryptMessage(QByteArray cipher, const QString& senderKey, const QString& deviceId, const QString& sessionId) const +QString Room::decryptMessage(QByteArray cipher, const QString& senderKey, + const QString& deviceId, const QString& sessionId) { QString decrypted; using namespace QtOlm; InboundGroupSession* groupSession; - groupSession = new InboundGroupSession(sessionKey(senderKey, deviceId, sessionId).toLatin1()); + groupSession = new InboundGroupSession( + sessionKey(senderKey, deviceId, sessionId).toLatin1()); groupSession->decrypt(cipher); // TODO: avoid replay attacks return decrypted; @@ -1301,8 +1190,8 @@ const QString Room::decryptMessage(QByteArray cipher, const QString& senderKey, int Room::joinedCount() const { return d->summary.joinedMemberCount.omitted() - ? d->membersMap.size() - : d->summary.joinedMemberCount.value(); + ? d->membersMap.size() + : d->summary.joinedMemberCount.value(); } int Room::invitedCount() const @@ -1312,15 +1201,9 @@ int Room::invitedCount() const return d->summary.invitedMemberCount.value(); } -int Room::totalMemberCount() const -{ - return joinedCount() + invitedCount(); -} +int Room::totalMemberCount() const { return joinedCount() + invitedCount(); } -GetRoomEventsJob* Room::eventsHistoryJob() const -{ - return d->eventsHistoryJob; -} +GetRoomEventsJob* Room::eventsHistoryJob() const { return d->eventsHistoryJob; } Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) { @@ -1332,11 +1215,11 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) return Change::SummaryChange; } -void Room::Private::insertMemberIntoMap(User *u) +void Room::Private::insertMemberIntoMap(User* u) { const auto userName = u->name(q); - // If there is exactly one namesake of the added user, signal member renaming - // for that other one because the two should be disambiguated now. + // If there is exactly one namesake of the added user, signal member + // renaming for that other one because the two should be disambiguated now. const auto namesakes = membersMap.values(userName); // Callers should check they are not adding an existing user once more. @@ -1352,14 +1235,11 @@ void Room::Private::insertMemberIntoMap(User *u) void Room::Private::renameMember(User* u, const QString& oldName) { - if (u->name(q) == oldName) - { + if (u->name(q) == oldName) { qCWarning(MAIN) << "Room::Private::renameMember(): the user " << u->fullName(q) << "is already known in the room under a new name."; - } - else if (membersMap.contains(oldName, u)) - { + } else if (membersMap.contains(oldName, u)) { removeMemberFromMap(oldName, u); insertMemberIntoMap(u); } @@ -1369,15 +1249,14 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) { User* namesake = nullptr; auto namesakes = membersMap.values(username); - if (namesakes.size() == 2) - { + if (namesakes.size() == 2) { namesake = namesakes.front() == u ? namesakes.back() : namesakes.front(); Q_ASSERT_X(namesake != u, __FUNCTION__, "Room members list is broken"); emit q->memberAboutToRename(namesake, username); } membersMap.remove(username, u); - // If there was one namesake besides the removed user, signal member renaming - // for it because it doesn't need to be disambiguated any more. + // If there was one namesake besides the removed user, signal member + // renaming for it because it doesn't need to be disambiguated any more. if (namesake) emit q->memberRenamed(namesake); } @@ -1387,27 +1266,29 @@ inline auto makeErrorStr(const Event& e, QByteArray msg) return msg.append("; event dump follows:\n").append(e.originalJson()); } -Room::Timeline::size_type Room::Private::moveEventsToTimeline( - RoomEventsRange events, EventsPlacement placement) +Room::Timeline::size_type +Room::Private::moveEventsToTimeline(RoomEventsRange events, + EventsPlacement placement) { Q_ASSERT(!events.empty()); // Historical messages arrive in newest-to-oldest order, so the process for // them is almost symmetric to the one for new messages. New messages get // appended from index 0; old messages go backwards from index -1. - auto index = timeline.empty() ? -((placement+1)/2) /* 1 -> -1; -1 -> 0 */ : - placement == Older ? timeline.front().index() : - timeline.back().index(); + auto index = timeline.empty() + ? -((placement + 1) / 2) /* 1 -> -1; -1 -> 0 */ + : placement == Older ? timeline.front().index() + : timeline.back().index(); auto baseIndex = index; - for (auto&& e: events) - { + for (auto&& e : events) { const auto eId = e->id(); Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); - Q_ASSERT_X(!eId.isEmpty(), __FUNCTION__, - makeErrorStr(*e, - "Event with empty id cannot be in the timeline")); - Q_ASSERT_X(!eventsIndex.contains(eId), __FUNCTION__, - makeErrorStr(*e, "Event is already in the timeline; " - "incoming events were not properly deduplicated")); + Q_ASSERT_X( + !eId.isEmpty(), __FUNCTION__, + makeErrorStr(*e, "Event with empty id cannot be in the timeline")); + Q_ASSERT_X( + !eventsIndex.contains(eId), __FUNCTION__, + makeErrorStr(*e, "Event is already in the timeline; " + "incoming events were not properly deduplicated")); if (placement == Older) timeline.emplace_front(move(e), --index); else @@ -1444,8 +1325,7 @@ QString Room::roomMembername(const User* u) const // (extension to the spec) QVector<QString> bridges; for (; namesakesIt != d->membersMap.cend() && namesakesIt.key() == username; - ++namesakesIt) - { + ++namesakesIt) { const auto bridgeName = (*namesakesIt)->bridged(); if (bridges.contains(bridgeName)) // Two accounts on the same bridge return u->fullName(this); // Disambiguate fully @@ -1463,59 +1343,59 @@ QString Room::roomMembername(const QString& userId) const void Room::updateData(SyncRoomData&& data, bool fromCache) { - if( d->prevBatch.isEmpty() ) + if (d->prevBatch.isEmpty()) d->prevBatch = data.timelinePrevBatch; setJoinState(data.joinState); Changes roomChanges = Change::NoChange; - QElapsedTimer et; et.start(); - for (auto&& event: data.accountData) + QElapsedTimer et; + et.start(); + for (auto&& event : data.accountData) roomChanges |= processAccountDataEvent(move(event)); roomChanges |= d->updateStateFrom(data.state); - if (!data.timeline.empty()) - { + if (!data.timeline.empty()) { et.restart(); roomChanges |= d->addNewMessageEvents(move(data.timeline)); if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" - << data.timeline.size() << "event(s)," << et; + qCDebug(PROFILER) + << "*** Room::addNewMessageEvents():" << data.timeline.size() + << "event(s)," << et; } - if (roomChanges&TopicChange) + if (roomChanges & TopicChange) emit topicChanged(); - if (roomChanges&NameChange) + if (roomChanges & NameChange) emit namesChanged(this); - if (roomChanges&MembersChange) + if (roomChanges & MembersChange) emit memberListChanged(); roomChanges |= d->setSummary(move(data.summary)); - for( auto&& ephemeralEvent: data.ephemeral ) + for (auto&& ephemeralEvent : data.ephemeral) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); // See https://github.com/quotient-im/libQuotient/wiki/unread_count - if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) - { - qCDebug(MAIN) << "Setting unread_count to" << data.unreadCount; + if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) { + qCDebug(MESSAGES) << "Setting unread_count to" << data.unreadCount; d->unreadMessages = data.unreadCount; + roomChanges |= Change::UnreadNotifsChange; emit unreadMessagesChanged(this); } - if( data.highlightCount != d->highlightCount ) - { + if (data.highlightCount != d->highlightCount) { d->highlightCount = data.highlightCount; + roomChanges |= Change::UnreadNotifsChange; emit highlightCountChanged(); } - if( data.notificationCount != d->notificationCount ) - { + if (data.notificationCount != d->notificationCount) { d->notificationCount = data.notificationCount; + roomChanges |= Change::UnreadNotifsChange; emit notificationCountChanged(); } - if (roomChanges != Change::NoChange) - { + if (roomChanges != Change::NoChange) { d->updateDisplayname(); emit changed(roomChanges); if (!fromCache) @@ -1540,10 +1420,10 @@ RoomEvent* Room::Private::addAsPending(RoomEventPtr&& event) QString Room::Private::sendEvent(RoomEventPtr&& event) { - if (q->usesEncryption()) - { + if (q->usesEncryption()) { qCCritical(MAIN) << "Room" << q->objectName() - << "enforces encryption; sending encrypted messages is not supported yet"; + << "enforces encryption; sending encrypted messages " + "is not supported yet"; } if (q->successorId().isEmpty()) return doSendEvent(addAsPending(std::move(event))); @@ -1556,37 +1436,37 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) { const auto txnId = pEvent->transactionId(); // TODO, #133: Enqueue the job rather than immediately trigger it. - if (auto call = connection->callApi<SendMessageJob>(BackgroundRequest, - id, pEvent->matrixType(), txnId, pEvent->contentJson())) - { - Room::connect(call, &BaseJob::started, q, - [this,txnId] { - auto it = q->findPendingEvent(txnId); - if (it == unsyncedEvents.end()) - { - qWarning(EVENTS) << "Pending event for transaction" << txnId - << "not found - got synced so soon?"; - return; - } - it->setDeparted(); - emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); - }); + if (auto call = + connection->callApi<SendMessageJob>(BackgroundRequest, id, + pEvent->matrixType(), txnId, + pEvent->contentJson())) { + Room::connect(call, &BaseJob::sentRequest, q, [this, txnId] { + auto it = q->findPendingEvent(txnId); + if (it == unsyncedEvents.end()) { + qWarning(EVENTS) << "Pending event for transaction" << txnId + << "not found - got synced so soon?"; + return; + } + it->setDeparted(); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); + }); Room::connect(call, &BaseJob::failure, q, - std::bind(&Room::Private::onEventSendingFailure, this, txnId, call)); - Room::connect(call, &BaseJob::success, q, - [this,call,txnId] { - emit q->messageSent(txnId, call->eventId()); - auto it = q->findPendingEvent(txnId); - if (it == unsyncedEvents.end()) - { - qDebug(EVENTS) << "Pending event for transaction" << txnId - << "already merged"; - return; - } + std::bind(&Room::Private::onEventSendingFailure, this, + txnId, call)); + Room::connect(call, &BaseJob::success, q, [this, call, txnId] { + emit q->messageSent(txnId, call->eventId()); + auto it = q->findPendingEvent(txnId); + if (it == unsyncedEvents.end()) { + qDebug(EVENTS) << "Pending event for transaction" << txnId + << "already merged"; + return; + } + if (it->deliveryStatus() != EventStatus::ReachedServer) { it->setReachedServer(call->eventId()); emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); - }); + } + }); } else onEventSendingFailure(txnId); return txnId; @@ -1595,15 +1475,13 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) void Room::Private::onEventSendingFailure(const QString& txnId, BaseJob* call) { auto it = q->findPendingEvent(txnId); - if (it == unsyncedEvents.end()) - { + if (it == unsyncedEvents.end()) { qCritical(EVENTS) << "Pending event for transaction" << txnId << "could not be sent"; return; } - it->setSendingFailed(call - ? call->statusCaption() % ": " % call->errorString() - : tr("The call could not be started")); + it->setSendingFailed(call ? call->statusCaption() % ": " % call->errorString() + : tr("The call could not be started")); emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); } @@ -1613,54 +1491,54 @@ QString Room::retryMessage(const QString& txnId) Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Retrying transaction" << txnId; const auto& transferIt = d->fileTransfers.find(txnId); - if (transferIt != d->fileTransfers.end()) - { + if (transferIt != d->fileTransfers.end()) { Q_ASSERT(transferIt->isUpload); - if (transferIt->status == FileTransferInfo::Completed) - { - qCDebug(MAIN) << "File for transaction" << txnId - << "has already been uploaded, bypassing re-upload"; + if (transferIt->status == FileTransferInfo::Completed) { + qCDebug(MESSAGES) + << "File for transaction" << txnId + << "has already been uploaded, bypassing re-upload"; } else { - if (isJobRunning(transferIt->job)) - { - qCDebug(MAIN) << "Abandoning the upload job for transaction" - << txnId << "and starting again"; + if (isJobRunning(transferIt->job)) { + qCDebug(MESSAGES) << "Abandoning the upload job for transaction" + << txnId << "and starting again"; transferIt->job->abandon(); - emit fileTransferFailed(txnId, tr("File upload will be retried")); + emit fileTransferFailed(txnId, + tr("File upload will be retried")); } - uploadFile(txnId, - QUrl::fromLocalFile(transferIt->localFileInfo.absoluteFilePath())); + uploadFile(txnId, QUrl::fromLocalFile( + transferIt->localFileInfo.absoluteFilePath())); // FIXME: Content type is no more passed here but it should } } - if (it->deliveryStatus() == EventStatus::ReachedServer) - { - qCWarning(MAIN) << "The previous attempt has reached the server; two" - " events are likely to be in the timeline after retry"; + if (it->deliveryStatus() == EventStatus::ReachedServer) { + qCWarning(MAIN) + << "The previous attempt has reached the server; two" + " events are likely to be in the timeline after retry"; } it->resetStatus(); + emit pendingEventChanged(int(it - d->unsyncedEvents.begin())); return d->doSendEvent(it->event()); } void Room::discardMessage(const QString& txnId) { auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), - [txnId] (const auto& evt) { return evt->transactionId() == txnId; }); + [txnId](const auto& evt) { + return evt->transactionId() == txnId; + }); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Discarding transaction" << txnId; const auto& transferIt = d->fileTransfers.find(txnId); - if (transferIt != d->fileTransfers.end()) - { + if (transferIt != d->fileTransfers.end()) { Q_ASSERT(transferIt->isUpload); - if (isJobRunning(transferIt->job)) - { + if (isJobRunning(transferIt->job)) { transferIt->status = FileTransferInfo::Cancelled; transferIt->job->abandon(); emit fileTransferFailed(txnId, tr("File upload cancelled")); - } else if (transferIt->status == FileTransferInfo::Completed) - { - qCWarning(MAIN) << "File for transaction" << txnId - << "has been uploaded but the message was discarded"; + } else if (transferIt->status == FileTransferInfo::Completed) { + qCWarning(MAIN) + << "File for transaction" << txnId + << "has been uploaded but the message was discarded"; } } emit pendingEventAboutToDiscard(int(it - d->unsyncedEvents.begin())); @@ -1681,8 +1559,9 @@ QString Room::postPlainText(const QString& plainText) QString Room::postHtmlMessage(const QString& plainText, const QString& html, MessageEventType type) { - return d->sendEvent<RoomMessageEvent>(plainText, type, - new EventContent::TextContent(html, QStringLiteral("text/html"))); + return d->sendEvent<RoomMessageEvent>( + plainText, type, + new EventContent::TextContent(html, QStringLiteral("text/html"))); } QString Room::postHtmlText(const QString& plainText, const QString& html) @@ -1713,42 +1592,39 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath, } auto* context = new QObject(this); connect(this, &Room::fileTransferCompleted, context, - [context,this,txnId] (const QString& id, QUrl, const QUrl& mxcUri) { - if (id == txnId) - { - auto it = findPendingEvent(txnId); - if (it != d->unsyncedEvents.end()) - { - it->setFileUploaded(mxcUri); - emit pendingEventChanged( - int(it - d->unsyncedEvents.begin())); - d->doSendEvent(it->get()); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was cancelled"; + [context, this, txnId](const QString& id, QUrl, const QUrl& mxcUri) { + if (id == txnId) { + auto it = findPendingEvent(txnId); + if (it != d->unsyncedEvents.end()) { + it->setFileUploaded(mxcUri); + emit pendingEventChanged( + int(it - d->unsyncedEvents.begin())); + d->doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) << "File uploaded to" << mxcUri + << "but the event referring to it was " + "cancelled"; + } + context->deleteLater(); } - context->deleteLater(); - } - }); + }); connect(this, &Room::fileTransferCancelled, this, - [context,this,txnId] (const QString& id) { - if (id == txnId) - { - auto it = findPendingEvent(txnId); - if (it != d->unsyncedEvents.end()) - { - const auto idx = int(it - d->unsyncedEvents.begin()); - emit pendingEventAboutToDiscard(idx); - // See #286 on why iterator may not be valid here. - d->unsyncedEvents.erase(d->unsyncedEvents.begin() + idx); - emit pendingEventDiscarded(); + [context, this, txnId](const QString& id) { + if (id == txnId) { + auto it = findPendingEvent(txnId); + if (it != d->unsyncedEvents.end()) { + const auto idx = int(it - d->unsyncedEvents.begin()); + emit pendingEventAboutToDiscard(idx); + // See #286 on why iterator may not be valid here. + d->unsyncedEvents.erase(d->unsyncedEvents.begin() + idx); + emit pendingEventDiscarded(); + } + context->deleteLater(); } - context->deleteLater(); - } - }); + }); return txnId; } @@ -1764,7 +1640,8 @@ QString Room::postJson(const QString& matrixType, return d->sendEvent(loadEvent<RoomEvent>(matrixType, eventContent)); } -SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt) const { +SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt) const +{ return d->requestSetState(evt); } @@ -1780,8 +1657,8 @@ void Room::setCanonicalAlias(const QString& newAlias) void Room::setLocalAliases(const QStringList& aliases) { - d->requestSetState<RoomAliasesEvent>( - connection()->homeserver().authority(), aliases); + d->requestSetState<RoomAliasesEvent>(connection()->homeserver().authority(), + aliases); } void Room::setTopic(const QString& newTopic) @@ -1809,10 +1686,7 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) return le->contentJson() == re->contentJson(); } -bool Room::supportsCalls() const -{ - return joinedCount() == 2; -} +bool Room::supportsCalls() const { return joinedCount() == 2; } void Room::checkVersion() { @@ -1822,8 +1696,7 @@ void Room::checkVersion() // This method is only called after the base state has been loaded // or the server capabilities have been loaded. emit stabilityUpdated(defaultVersion, stableVersions); - if (!stableVersions.contains(version())) - { + if (!stableVersions.contains(version())) { qCDebug(MAIN) << this << "version is" << version() << "which the server doesn't count as stable"; if (canSwitchVersions()) @@ -1864,10 +1737,7 @@ void Room::hangupCall(const QString& callId) d->sendEvent<CallHangupEvent>(callId); } -void Room::getPreviousContent(int limit) -{ - d->getPreviousContent(limit); -} +void Room::getPreviousContent(int limit) { d->getPreviousContent(limit); } void Room::Private::getPreviousContent(int limit) { @@ -1877,12 +1747,12 @@ void Room::Private::getPreviousContent(int limit) eventsHistoryJob = connection->callApi<GetRoomEventsJob>(id, prevBatch, "b", "", limit); emit q->eventsHistoryJobChanged(); - connect( eventsHistoryJob, &BaseJob::success, q, [=] { + connect(eventsHistoryJob, &BaseJob::success, q, [=] { prevBatch = eventsHistoryJob->end(); addHistoricalMessageEvents(eventsHistoryJob->chunk()); }); - connect( eventsHistoryJob, &QObject::destroyed, - q, &Room::eventsHistoryJobChanged); + connect(eventsHistoryJob, &QObject::destroyed, q, + &Room::eventsHistoryJobChanged); } void Room::inviteToRoom(const QString& memberId) @@ -1896,8 +1766,8 @@ LeaveRoomJob* Room::leaveRoom() return connection()->leaveRoom(this); } -SetRoomStateWithKeyJob* Room::setMemberState( - const QString& memberId, const RoomMemberEvent& event) const +SetRoomStateWithKeyJob* Room::setMemberState(const QString& memberId, + const RoomMemberEvent& event) const { return d->requestSetState<RoomMemberEvent>(memberId, event.content()); } @@ -1919,8 +1789,8 @@ void Room::unban(const QString& userId) void Room::redactEvent(const QString& eventId, const QString& reason) { - connection()->callApi<RedactEventJob>(id(), - QUrl::toPercentEncoding(eventId), connection()->generateTxnId(), reason); + connection()->callApi<RedactEventJob>(id(), QUrl::toPercentEncoding(eventId), + connection()->generateTxnId(), reason); } void Room::uploadFile(const QString& id, const QUrl& localFilename, @@ -1930,18 +1800,17 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, "localFilename should point at a local file"); auto fileName = localFilename.toLocalFile(); auto job = connection()->uploadFile(fileName, overrideContentType); - if (isJobRunning(job)) - { + if (isJobRunning(job)) { d->fileTransfers.insert(id, { job, fileName, true }); connect(job, &BaseJob::uploadProgress, this, - [this,id] (qint64 sent, qint64 total) { + [this, id](qint64 sent, qint64 total) { d->fileTransfers[id].update(sent, total); emit fileTransferProgress(id, sent, total); }); - connect(job, &BaseJob::success, this, [this,id,localFilename,job] { - d->fileTransfers[id].status = FileTransferInfo::Completed; - emit fileTransferCompleted(id, localFilename, job->contentUri()); - }); + connect(job, &BaseJob::success, this, [this, id, localFilename, job] { + d->fileTransfers[id].status = FileTransferInfo::Completed; + emit fileTransferCompleted(id, localFilename, job->contentUri()); + }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); emit newFileTransfer(id, localFilename); @@ -1952,9 +1821,8 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, void Room::downloadFile(const QString& eventId, const QUrl& localFilename) { auto ongoingTransfer = d->fileTransfers.find(eventId); - if (ongoingTransfer != d->fileTransfers.end() && - ongoingTransfer->status == FileTransferInfo::Started) - { + if (ongoingTransfer != d->fileTransfers.end() + && ongoingTransfer->status == FileTransferInfo::Started) { qCWarning(MAIN) << "Transfer for" << eventId << "is ongoing; download won't start"; return; @@ -1963,49 +1831,45 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) Q_ASSERT_X(localFilename.isEmpty() || localFilename.isLocalFile(), __FUNCTION__, "localFilename should point at a local file"); const auto* event = d->getEventWithFile(eventId); - if (!event) - { + if (!event) { qCCritical(MAIN) << eventId << "is not in the local timeline or has no file content"; Q_ASSERT(false); return; } const auto* const fileInfo = event->content()->fileInfo(); - if (!fileInfo->isValid()) - { + if (!fileInfo->isValid()) { qCWarning(MAIN) << "Event" << eventId << "has an empty or malformed mxc URL; won't download"; return; } const auto fileUrl = fileInfo->url; auto filePath = localFilename.toLocalFile(); - if (filePath.isEmpty()) - { + if (filePath.isEmpty()) { // Build our own file path, starting with temp directory and eventId. filePath = eventId; - filePath = QDir::tempPath() % '/' % - filePath.replace(QRegularExpression("[/\\<>|\"*?:]"), "_") % - '#' % d->fileNameToDownload(event); + filePath = QDir::tempPath() % '/' + % filePath.replace(QRegularExpression("[/\\<>|\"*?:]"), "_") + % '#' % d->fileNameToDownload(event); } auto job = connection()->downloadFile(fileUrl, filePath); - if (isJobRunning(job)) - { + if (isJobRunning(job)) { // If there was a previous transfer (completed or failed), remove it. d->fileTransfers.remove(eventId); d->fileTransfers.insert(eventId, { job, job->targetFileName() }); connect(job, &BaseJob::downloadProgress, this, - [this,eventId] (qint64 received, qint64 total) { - d->fileTransfers[eventId].update(received, total); - emit fileTransferProgress(eventId, received, total); - }); - connect(job, &BaseJob::success, this, [this,eventId,fileUrl,job] { - d->fileTransfers[eventId].status = FileTransferInfo::Completed; - emit fileTransferCompleted(eventId, fileUrl, - QUrl::fromLocalFile(job->targetFileName())); - }); + [this, eventId](qint64 received, qint64 total) { + d->fileTransfers[eventId].update(received, total); + emit fileTransferProgress(eventId, received, total); + }); + connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { + d->fileTransfers[eventId].status = FileTransferInfo::Completed; + emit fileTransferCompleted( + eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); + }); connect(job, &BaseJob::failure, this, - std::bind(&Private::failedTransfer, d, - eventId, job->errorString())); + std::bind(&Private::failedTransfer, d, eventId, + job->errorString())); } else d->failedTransfer(eventId); } @@ -2013,10 +1877,9 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) void Room::cancelFileTransfer(const QString& id) { auto it = d->fileTransfers.find(id); - if (it == d->fileTransfers.end()) - { - qCWarning(MAIN) << "No information on file transfer" << id - << "in room" << d->id; + if (it == d->fileTransfers.end()) { + qCWarning(MAIN) << "No information on file transfer" << id << "in room" + << d->id; return; } if (isJobRunning(it->job)) @@ -2032,15 +1895,16 @@ void Room::Private::dropDuplicateEvents(RoomEvents& events) const // Multiple-remove (by different criteria), single-erase // 1. Check for duplicates against the timeline. - auto dupsBegin = remove_if(events.begin(), events.end(), - [&] (const RoomEventPtr& e) - { return eventsIndex.contains(e->id()); }); + auto dupsBegin = + remove_if(events.begin(), events.end(), [&](const RoomEventPtr& e) { + return eventsIndex.contains(e->id()); + }); // 2. Check for duplicates within the batch if there are still events. for (auto eIt = events.begin(); distance(eIt, dupsBegin) > 1; ++eIt) - dupsBegin = remove_if(eIt + 1, dupsBegin, - [&] (const RoomEventPtr& e) - { return e->id() == (*eIt)->id(); }); + dupsBegin = remove_if(eIt + 1, dupsBegin, [&](const RoomEventPtr& e) { + return e->id() == (*eIt)->id(); + }); if (dupsBegin == events.end()) return; @@ -2059,48 +1923,53 @@ RoomEventPtr makeRedacted(const RoomEvent& target, const RedactionEvent& redaction) { auto originalJson = target.originalJsonObject(); - static const QStringList keepKeys { - EventIdKey, TypeKey, QStringLiteral("room_id"), - QStringLiteral("sender"), StateKeyKey, - QStringLiteral("prev_content"), ContentKey, - QStringLiteral("hashes"), QStringLiteral("signatures"), - QStringLiteral("depth"), QStringLiteral("prev_events"), - QStringLiteral("prev_state"), QStringLiteral("auth_events"), - QStringLiteral("origin"), QStringLiteral("origin_server_ts"), - QStringLiteral("membership") + static const QStringList keepKeys { EventIdKey, + TypeKey, + QStringLiteral("room_id"), + QStringLiteral("sender"), + StateKeyKey, + QStringLiteral("prev_content"), + ContentKey, + QStringLiteral("hashes"), + QStringLiteral("signatures"), + QStringLiteral("depth"), + QStringLiteral("prev_events"), + QStringLiteral("prev_state"), + QStringLiteral("auth_events"), + QStringLiteral("origin"), + QStringLiteral("origin_server_ts"), + QStringLiteral("membership") }; + + std::vector<std::pair<Event::Type, QStringList>> keepContentKeysMap { + { RoomMemberEvent::typeId(), { QStringLiteral("membership") } }, + { RoomCreateEvent::typeId(), { QStringLiteral("creator") } } + // , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } } + // , { RoomPowerLevels::typeId(), + // { QStringLiteral("ban"), QStringLiteral("events"), + // QStringLiteral("events_default"), + // QStringLiteral("kick"), QStringLiteral("redact"), + // QStringLiteral("state_default"), QStringLiteral("users"), + // QStringLiteral("users_default") } } + , + { RoomAliasesEvent::typeId(), { QStringLiteral("aliases") } } + // , { RoomHistoryVisibility::typeId(), + // { QStringLiteral("history_visibility") } } }; - - std::vector<std::pair<Event::Type, QStringList>> keepContentKeysMap - { { RoomMemberEvent::typeId(), { QStringLiteral("membership") } } - , { RoomCreateEvent::typeId(), { QStringLiteral("creator") } } -// , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } } -// , { RoomPowerLevels::typeId(), -// { QStringLiteral("ban"), QStringLiteral("events"), -// QStringLiteral("events_default"), QStringLiteral("kick"), -// QStringLiteral("redact"), QStringLiteral("state_default"), -// QStringLiteral("users"), QStringLiteral("users_default") } } - , { RoomAliasesEvent::typeId(), { QStringLiteral("aliases") } } -// , { RoomHistoryVisibility::typeId(), -// { QStringLiteral("history_visibility") } } - }; - for (auto it = originalJson.begin(); it != originalJson.end();) - { + for (auto it = originalJson.begin(); it != originalJson.end();) { if (!keepKeys.contains(it.key())) it = originalJson.erase(it); // TODO: shred the value else ++it; } auto keepContentKeys = - find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), - [&target](const auto& t) { return target.type() == t.first; } ); - if (keepContentKeys == keepContentKeysMap.end()) - { + find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), + [&target](const auto& t) { return target.type() == t.first; }); + if (keepContentKeys == keepContentKeysMap.end()) { originalJson.remove(ContentKeyL); originalJson.remove(PrevContentKeyL); } else { auto content = originalJson.take(ContentKeyL).toObject(); - for (auto it = content.begin(); it != content.end(); ) - { + for (auto it = content.begin(); it != content.end();) { if (!keepContentKeys->second.contains(it.key())) it = content.erase(it); else @@ -2126,26 +1995,26 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) Q_ASSERT(q->isValidIndex(*pIdx)); auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())]; - if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) - { - qCDebug(MAIN) << "Redaction" << redaction.id() - << "of event" << ti->id() << "already done, skipping"; + if (ti->isRedacted() && ti->redactedBecause()->id() == redaction.id()) { + qCDebug(EVENTS) << "Redaction" << redaction.id() << "of event" + << ti->id() << "already done, skipping"; return true; } // Make a new event from the redacted JSON and put it in the timeline // instead of the redacted one. oldEvent will be deleted on return. auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction)); - qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction.id(); - if (oldEvent->isStateEvent()) - { - const StateEventKey evtKey { oldEvent->matrixType(), oldEvent->stateKey() }; + qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with" << redaction.id(); + if (oldEvent->isStateEvent()) { + const StateEventKey evtKey { oldEvent->matrixType(), + oldEvent->stateKey() }; Q_ASSERT(currentState.contains(evtKey)); - if (currentState.value(evtKey) == oldEvent.get()) - { - Q_ASSERT(ti.index() >= 0); // Historical states can't be in currentState - qCDebug(MAIN).nospace() << "Redacting state " - << oldEvent->matrixType() << "/" << oldEvent->stateKey(); + if (currentState.value(evtKey) == oldEvent.get()) { + Q_ASSERT(ti.index() >= 0); // Historical states can't be in + // currentState + qCDebug(EVENTS).nospace() + << "Redacting state " << oldEvent->matrixType() << "/" + << oldEvent->stateKey(); // Retarget the current state to the newly made event. if (q->processStateEvent(*ti)) emit q->namesChanged(q); @@ -2154,8 +2023,8 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) } if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) { const auto& targetEvtId = reaction->relation().eventId; - const auto lookupKey = qMakePair(targetEvtId, - EventRelation::Annotation()); + const auto lookupKey = + qMakePair(targetEvtId, EventRelation::Annotation()); if (relations.contains(lookupKey)) { relations[lookupKey].removeOne(reaction); } @@ -2196,9 +2065,8 @@ bool Room::Private::processReplacement(const RoomMessageEvent& newEvent) Q_ASSERT(q->isValidIndex(*pIdx)); auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())]; - if (ti->replacedBy() == newEvent.id()) - { - qCDebug(MAIN) << "Event" << ti->id() << "is already replaced with" + if (ti->replacedBy() == newEvent.id()) { + qCDebug(EVENTS) << "Event" << ti->id() << "is already replaced with" << newEvent.id(); return true; } @@ -2206,7 +2074,7 @@ bool Room::Private::processReplacement(const RoomMessageEvent& newEvent) // Make a new event from the redacted JSON and put it in the timeline // instead of the redacted one. oldEvent will be deleted on return. auto oldEvent = ti.replaceEvent(makeReplaced(*ti, newEvent)); - qCDebug(MAIN) << "Replaced" << oldEvent->id() << "with" << newEvent.id(); + qCDebug(EVENTS) << "Replaced" << oldEvent->id() << "with" << newEvent.id(); emit q->replacedEvent(ti.event(), rawPtr(oldEvent)); return true; } @@ -2217,10 +2085,7 @@ Connection* Room::connection() const return d->connection; } -User* Room::localUser() const -{ - return connection()->user(); -} +User* Room::localUser() const { return connection()->user(); } /// Whether the event is a redaction or a replacement inline bool isEditing(const RoomEventPtr& ep) @@ -2260,7 +2125,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (targetIt != it) *targetIt = makeRedacted(**targetIt, *r); else - qCDebug(MAIN) + qCDebug(EVENTS) << "Redaction" << r->id() << "ignored: target event" << r->redactedEvent() << "is not found"; // If the target event comes later, it comes already redacted. @@ -2277,9 +2142,10 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (targetIt != it) *targetIt = makeReplaced(**targetIt, *msg); else // FIXME: don't ignore, just show it wherever it arrived - qCDebug(MAIN) << "Replacing event" << msg->id() - << "ignored: replaced event" - << msg->replacedEvent() << "is not found"; + qCDebug(EVENTS) + << "Replacing event" << msg->id() + << "ignored: replaced event" << msg->replacedEvent() + << "is not found"; // Same as with redactions above, the replaced event coming // later will come already with the new content. } @@ -2293,49 +2159,52 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // postulate that the current state is only current between syncs but not // within a sync. Changes roomChanges = Change::NoChange; - for (const auto& eptr: events) + for (const auto& eptr : events) roomChanges |= q->processStateEvent(*eptr); auto timelineSize = timeline.size(); size_t totalInserted = 0; - for (auto it = events.begin(); it != events.end();) - { - auto nextPendingPair = findFirstOf(it, events.end(), - unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent); - auto nextPending = nextPendingPair.first; - - if (it != nextPending) - { - RoomEventsRange eventsSpan { it, nextPending }; + for (auto it = events.begin(); it != events.end();) { + auto nextPendingPair = + findFirstOf(it, events.end(), unsyncedEvents.begin(), + unsyncedEvents.end(), isEchoEvent); + const auto& remoteEcho = nextPendingPair.first; + const auto& localEcho = nextPendingPair.second; + + if (it != remoteEcho) { + RoomEventsRange eventsSpan { it, remoteEcho }; emit q->aboutToAddNewMessages(eventsSpan); auto insertedSize = moveEventsToTimeline(eventsSpan, Newer); totalInserted += insertedSize; auto firstInserted = timeline.cend() - insertedSize; q->onAddNewTimelineEvents(firstInserted); - emit q->addedMessages(firstInserted->index(), timeline.back().index()); + emit q->addedMessages(firstInserted->index(), + timeline.back().index()); } - if (nextPending == events.end()) + if (remoteEcho == events.end()) break; - it = nextPending + 1; - auto* nextPendingEvt = nextPending->get(); - const auto pendingEvtIdx = - int(nextPendingPair.second - unsyncedEvents.begin()); + it = remoteEcho + 1; + auto* nextPendingEvt = remoteEcho->get(); + const auto pendingEvtIdx = int(localEcho - unsyncedEvents.begin()); + if (localEcho->deliveryStatus() != EventStatus::ReachedServer) { + localEcho->setReachedServer(nextPendingEvt->id()); + emit q->pendingEventChanged(pendingEvtIdx); + } emit q->pendingEventAboutToMerge(nextPendingEvt, pendingEvtIdx); - qDebug(EVENTS) << "Merging pending event from transaction" - << nextPendingEvt->transactionId() << "into" - << nextPendingEvt->id(); + qDebug(MESSAGES) << "Merging pending event from transaction" + << nextPendingEvt->transactionId() << "into" + << nextPendingEvt->id(); auto transfer = fileTransfers.take(nextPendingEvt->transactionId()); if (transfer.status != FileTransferInfo::None) fileTransfers.insert(nextPendingEvt->id(), transfer); // After emitting pendingEventAboutToMerge() above we cannot rely - // on the previously obtained nextPendingPair.second staying valid + // on the previously obtained localEcho staying valid // because a signal handler may send another message, thereby altering // unsyncedEvents (see #286). Fortunately, unsyncedEvents only grows at // its back so we can rely on the index staying valid at least. unsyncedEvents.erase(unsyncedEvents.begin() + pendingEvtIdx); - if (auto insertedSize = moveEventsToTimeline({nextPending, it}, Newer)) - { + if (auto insertedSize = moveEventsToTimeline({ remoteEcho, it }, Newer)) { totalInserted += insertedSize; q->onAddNewTimelineEvents(timeline.cend() - insertedSize); } @@ -2349,8 +2218,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (auto* evt = it->viewAs<CallEventBase>()) emit q->callEvent(q, evt); - if (totalInserted > 0) - { + if (totalInserted > 0) { for (auto it = from; it != timeline.cend(); ++it) { if (const auto* reaction = it->viewAs<ReactionEvent>()) { const auto& relation = reaction->relation(); @@ -2359,9 +2227,9 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) } } - qCDebug(MAIN) - << "Room" << q->objectName() << "received" << totalInserted - << "new events; the last event is now" << timeline.back(); + qCDebug(MESSAGES) << "Room" << q->objectName() << "received" + << totalInserted << "new events; the last event is now" + << timeline.back(); // The first event in the just-added batch (referred to by `from`) // defines whose read marker can possibly be promoted any further over @@ -2370,11 +2238,11 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // markMessagesAsRead() invocation) to promote their read markers over // the new message events. auto firstWriter = q->user((*from)->senderId()); - if (q->readMarker(firstWriter) != timeline.crend()) - { + if (q->readMarker(firstWriter) != timeline.crend()) { roomChanges |= promoteReadMarker(firstWriter, rev_iter_t(from) - 1); - qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id() - << "to" << *q->readMarker(firstWriter); + qCDebug(MESSAGES) + << "Auto-promoted read marker for" << firstWriter->id() << "to" + << *q->readMarker(firstWriter); } updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); @@ -2387,7 +2255,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) { - QElapsedTimer et; et.start(); + QElapsedTimer et; + et.start(); const auto timelineSize = timeline.size(); dropDuplicateEvents(events); @@ -2398,12 +2267,10 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) // messages. Also, the cache doesn't store events with empty content; // so when such events show up in the timeline they should be properly // incorporated. - for (const auto& eptr: events) - { + for (const auto& eptr : events) { const auto& e = *eptr; - if (e.isStateEvent() && - !currentState.contains({e.matrixType(), e.stateKey()})) - { + if (e.isStateEvent() + && !currentState.contains({ e.matrixType(), e.stateKey() })) { q->processStateEvent(e); } } @@ -2412,8 +2279,9 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) const auto insertedSize = moveEventsToTimeline(events, Older); const auto from = timeline.crend() - insertedSize; - qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize - << "past events; the oldest event is now" << timeline.front(); + qCDebug(MESSAGES) << "Room" << displayname << "received" << insertedSize + << "past events; the oldest event is now" + << timeline.front(); q->onAddHistoricalTimelineEvents(from); emit q->addedMessages(timeline.front().index(), from->index()); @@ -2438,12 +2306,12 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) if (!e.isStateEvent()) return Change::NoChange; - const auto* oldStateEvent = std::exchange( - d->currentState[{e.matrixType(),e.stateKey()}], - static_cast<const StateEventBase*>(&e)); - Q_ASSERT(!oldStateEvent || - (oldStateEvent->matrixType() == e.matrixType() && - oldStateEvent->stateKey() == e.stateKey())); + const auto* oldStateEvent = + std::exchange(d->currentState[{ e.matrixType(), e.stateKey() }], + static_cast<const StateEventBase*>(&e)); + Q_ASSERT(!oldStateEvent + || (oldStateEvent->matrixType() == e.matrixType() + && oldStateEvent->stateKey() == e.stateKey())); if (!is<RoomMemberEvent>(e)) // Room member events are too numerous qCDebug(EVENTS) << "Room state event:" << e; @@ -2455,18 +2323,20 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) , [this,oldStateEvent] (const RoomAliasesEvent& ae) { // clang-format on if (ae.aliases().isEmpty()) { - qDebug(MAIN).noquote() << ae.stateKey() - << "no more has aliases for room" << objectName(); + qDebug(STATE).noquote() + << ae.stateKey() << "no more has aliases for room" + << objectName(); d->aliasServers.remove(ae.stateKey()); } else { d->aliasServers.insert(ae.stateKey()); - qDebug(MAIN).nospace().noquote() + qDebug(STATE).nospace().noquote() << "New server with aliases for room " << objectName() << ": " << ae.stateKey(); } - const auto previousAliases = oldStateEvent - ? static_cast<const RoomAliasesEvent*>(oldStateEvent)->aliases() - : QStringList(); + const auto previousAliases = + oldStateEvent + ? static_cast<const RoomAliasesEvent*>(oldStateEvent)->aliases() + : QStringList(); connection()->updateRoomAliases(id(), ae.stateKey(), previousAliases, ae.aliases()); return OtherChange; @@ -2488,30 +2358,28 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // clang-format on auto* u = user(evt.userId()); const auto* oldMemberEvent = - static_cast<const RoomMemberEvent*>(oldStateEvent); + static_cast<const RoomMemberEvent*>(oldStateEvent); u->processEvent(evt, this, oldMemberEvent == nullptr); const auto prevMembership = oldMemberEvent - ? oldMemberEvent->membership() : MembershipType::Leave; + ? oldMemberEvent->membership() + : MembershipType::Leave; if (u == localUser() && evt.membership() == MembershipType::Invite - && evt.isDirect()) + && evt.isDirect()) connection()->addToDirectChats(this, user(evt.senderId())); - switch (prevMembership) - { + switch (prevMembership) { case MembershipType::Invite: - if (evt.membership() != prevMembership) - { + if (evt.membership() != prevMembership) { d->usersInvited.removeOne(u); Q_ASSERT(!d->usersInvited.contains(u)); } break; case MembershipType::Join: if (evt.membership() == MembershipType::Invite) - qCWarning(MAIN) - << "Invalid membership change from Join to Invite:" - << evt; - if (evt.membership() != prevMembership) - { + qCWarning(STATE) << "Invalid membership change from " + "Join to Invite:" + << evt; + if (evt.membership() != prevMembership) { disconnect(u, &User::nameAboutToChange, this, nullptr); disconnect(u, &User::nameChanged, this, nullptr); d->removeMemberFromMap(u->name(this), u); @@ -2520,32 +2388,28 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) break; default: if (evt.membership() == MembershipType::Invite - || evt.membership() == MembershipType::Join) - { + || evt.membership() == MembershipType::Join) { d->membersLeft.removeOne(u); Q_ASSERT(!d->membersLeft.contains(u)); } } - switch(evt.membership()) - { + switch (evt.membership()) { case MembershipType::Join: - if (prevMembership != MembershipType::Join) - { + if (prevMembership != MembershipType::Join) { d->insertMemberIntoMap(u); connect(u, &User::nameAboutToChange, this, - [=] (QString newName, QString, const Room* context) { - if (context == this) - emit memberAboutToRename(u, newName); - }); + [=](QString newName, QString, const Room* context) { + if (context == this) + emit memberAboutToRename(u, newName); + }); connect(u, &User::nameChanged, this, - [=] (QString, QString oldName, const Room* context) { - if (context == this) - { - d->renameMember(u, oldName); - emit memberRenamed(u); - } - }); + [=](QString, QString oldName, const Room* context) { + if (context == this) { + d->renameMember(u, oldName); + emit memberRenamed(u); + } + }); emit userAdded(u); } break; @@ -2587,70 +2451,64 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) Room::Changes Room::processEphemeralEvent(EventPtr&& event) { Changes changes = NoChange; - QElapsedTimer et; et.start(); - if (auto* evt = eventCast<TypingEvent>(event)) - { + QElapsedTimer et; + et.start(); + if (auto* evt = eventCast<TypingEvent>(event)) { d->usersTyping.clear(); - for( const QString& userId: qAsConst(evt->users()) ) - { + for (const QString& userId : qAsConst(evt->users())) { auto u = user(userId); if (memberJoinState(u) == JoinState::Join) d->usersTyping.append(u); } if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "*** Room::processEphemeralEvent(typing):" - << evt->users().size() << "users," << et; + << evt->users().size() << "users," << et; emit typingChanged(); } - if (auto* evt = eventCast<ReceiptEvent>(event)) - { + if (auto* evt = eventCast<ReceiptEvent>(event)) { int totalReceipts = 0; - for( const auto &p: qAsConst(evt->eventsWithReceipts()) ) - { + for (const auto& p : qAsConst(evt->eventsWithReceipts())) { totalReceipts += p.receipts.size(); { if (p.receipts.size() == 1) - qCDebug(EPHEMERAL) << "Marking" << p.evtId - << "as read for" << p.receipts[0].userId; + qCDebug(EPHEMERAL) << "Marking" << p.evtId << "as read for" + << p.receipts[0].userId; else - qCDebug(EPHEMERAL) << "Marking" << p.evtId - << "as read for" << p.receipts.size() << "users"; + qCDebug(EPHEMERAL) << "Marking" << p.evtId << "as read for" + << p.receipts.size() << "users"; } const auto newMarker = findInTimeline(p.evtId); - if (newMarker != timelineEdge()) - { - for( const Receipt& r: p.receipts ) - { + if (newMarker != timelineEdge()) { + for (const Receipt& r : p.receipts) { if (r.userId == connection()->userId()) continue; // FIXME, #185 auto u = user(r.userId); if (memberJoinState(u) == JoinState::Join) changes |= d->promoteReadMarker(u, newMarker); } - } else - { + } else { qCDebug(EPHEMERAL) << "Event" << p.evtId - << "not found; saving read receipts anyway"; + << "not found; saving read receipts anyway"; // If the event is not found (most likely, because it's too old // and hasn't been fetched from the server yet), but there is // a previous marker for a user, keep the previous marker. // Otherwise, blindly store the event id for this user. - for( const Receipt& r: p.receipts ) - { + for (const Receipt& r : p.receipts) { if (r.userId == connection()->userId()) continue; // FIXME, #185 auto u = user(r.userId); - if (memberJoinState(u) == JoinState::Join && - readMarker(u) == timelineEdge()) + if (memberJoinState(u) == JoinState::Join + && readMarker(u) == timelineEdge()) changes |= d->setLastReadEvent(u, p.evtId); } } } - if (evt->eventsWithReceipts().size() > 3 || totalReceipts > 10 || - et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::processEphemeralEvent(receipts):" - << evt->eventsWithReceipts().size() - << "event(s) with" << totalReceipts << "receipt(s)," << et; + if (evt->eventsWithReceipts().size() > 3 || totalReceipts > 10 + || et.nsecsElapsed() >= profilerMinNsecs()) + qCDebug(PROFILER) + << "*** Room::processEphemeralEvent(receipts):" + << evt->eventsWithReceipts().size() << "event(s) with" + << totalReceipts << "receipt(s)," << et; } return changes; } @@ -2658,31 +2516,28 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) Room::Changes Room::processAccountDataEvent(EventPtr&& event) { Changes changes = NoChange; - if (auto* evt = eventCast<TagEvent>(event)) - { + if (auto* evt = eventCast<TagEvent>(event)) { d->setTags(evt->tags()); changes |= Change::TagsChange; } - if (auto* evt = eventCast<ReadMarkerEvent>(event)) - { + if (auto* evt = eventCast<ReadMarkerEvent>(event)) { auto readEventId = evt->event_id(); - qCDebug(MAIN) << "Server-side read marker at" << readEventId; + qCDebug(STATE) << "Server-side read marker at" << readEventId; d->serverReadMarker = readEventId; const auto newMarker = findInTimeline(readEventId); changes |= newMarker != timelineEdge() - ? d->markMessagesAsRead(newMarker) - : d->setLastReadEvent(localUser(), readEventId); + ? d->markMessagesAsRead(newMarker) + : d->setLastReadEvent(localUser(), readEventId); } // For all account data events auto& currentData = d->accountData[event->matrixType()]; // A polymorphic event-specific comparison might be a bit more // efficient; maaybe do it another day - if (!currentData || currentData->contentJson() != event->contentJson()) - { + if (!currentData || currentData->contentJson() != event->contentJson()) { emit accountDataAboutToChange(event->matrixType()); currentData = move(event); - qCDebug(MAIN) << "Updated account data of type" + qCDebug(STATE) << "Updated account data of type" << currentData->matrixType(); emit accountDataChanged(currentData->matrixType()); return Change::AccountDataChange; @@ -2699,15 +2554,14 @@ Room::Private::buildShortlist(const ContT& users) const // display names of two topmost users excluding the current one to render // the name of the room. The below code selects 3 topmost users, // slightly extending the spec. - users_shortlist_t shortlist { }; // Prefill with nullptrs + users_shortlist_t shortlist {}; // Prefill with nullptrs std::partial_sort_copy( - users.begin(), users.end(), - shortlist.begin(), shortlist.end(), - [this] (const User* u1, const User* u2) { - // localUser(), if it's in the list, is sorted below all others + users.begin(), users.end(), shortlist.begin(), shortlist.end(), + [this](const User* u1, const User* u2) { + // localUser(), if it's in the list, is sorted + // below all others return isLocalUser(u2) || (!isLocalUser(u1) && u1->id() < u2->id()); - } - ); + }); return shortlist; } @@ -2716,14 +2570,14 @@ Room::Private::buildShortlist(const QStringList& userIds) const { QList<User*> users; users.reserve(userIds.size()); - for (const auto& h: userIds) + for (const auto& h : userIds) users.push_back(q->user(h)); return buildShortlist(users); } QString Room::Private::calculateDisplayname() const { - // CS spec, section 11.2.2.5 Calculating the display name for a room + // CS spec, section 13.2.2.5 Calculating the display name for a room // Numbers below refer to respective parts in the spec. // 1. Name (from m.room.name) @@ -2737,35 +2591,39 @@ QString Room::Private::calculateDisplayname() const if (!dispName.isEmpty()) return dispName; - // Using m.room.aliases in naming is explicitly discouraged by the spec + // 3. m.room.aliases - only local aliases, subject for further removal + const auto aliases = q->localAliases(); + if (!aliases.isEmpty()) + return aliases.front(); + + // 4. m.heroes and m.room.member + // From here on, we use a more general algorithm than the spec describes + // in order to provide back-compatibility with pre-MSC688 servers. - // Supplementary code for 3 and 4: build the shortlist of users whose names + // Supplementary code: build the shortlist of users whose names // will be used to construct the room name. Takes into account MSC688's // "heroes" if available. - const bool localUserIsIn = joinState == JoinState::Join; - const bool emptyRoom = membersMap.isEmpty() || - (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); + const bool emptyRoom = + membersMap.isEmpty() + || (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); const bool nonEmptySummary = - !summary.heroes.omitted() && !summary.heroes->empty(); - auto shortlist = nonEmptySummary ? buildShortlist(summary.heroes.value()) : - !emptyRoom ? buildShortlist(membersMap) : - users_shortlist_t { }; - - // When lazy-loading is on, we can rely on the heroes list. - // If it's off, the below code gathers invited and left members. - // NB: including invitations, if any, into naming is a spec extension. - // This kicks in when there's no lazy loading and it's a room with - // the local user as the only member, with more users invited. + !summary.heroes.omitted() && !summary.heroes->empty(); + auto shortlist = nonEmptySummary ? buildShortlist(summary.heroes.value()) + : !emptyRoom ? buildShortlist(membersMap) + : users_shortlist_t {}; + + // When the heroes list is there, we can rely on it. If the heroes list is + // missing, the below code gathers invited, or, if there are no invitees, + // left members. if (!shortlist.front() && localUserIsIn) shortlist = buildShortlist(usersInvited); - if (!shortlist.front()) // Still empty shortlist; use left members + if (!shortlist.front()) shortlist = buildShortlist(membersLeft); QStringList names; - for (auto u: shortlist) - { + for (auto u : shortlist) { if (u == nullptr || isLocalUser(u)) break; // Only disambiguate if the room is not empty @@ -2773,17 +2631,19 @@ QString Room::Private::calculateDisplayname() const } const auto usersCountExceptLocal = - !emptyRoom ? q->joinedCount() - int(joinState == JoinState::Join) : - !usersInvited.empty() ? usersInvited.count() : - membersLeft.size() - int(joinState == JoinState::Leave); + !emptyRoom + ? q->joinedCount() - int(joinState == JoinState::Join) + : !usersInvited.empty() + ? usersInvited.count() + : membersLeft.size() - int(joinState == JoinState::Leave); if (usersCountExceptLocal > int(shortlist.size())) - names << - tr("%Ln other(s)", - "Used to make a room name from user names: A, B and _N others_", - usersCountExceptLocal - int(shortlist.size())); + names << tr( + "%Ln other(s)", + "Used to make a room name from user names: A, B and _N others_", + usersCountExceptLocal - int(shortlist.size())); const auto namesList = QLocale().createSeparatedList(names); - // 3. Room members + // Room members if (!emptyRoom) return namesList; @@ -2791,19 +2651,18 @@ QString Room::Private::calculateDisplayname() const if (!usersInvited.empty()) return tr("Empty room (invited: %1)").arg(namesList); - // 4. Users that previously left the room - if (membersLeft.size() > 0) + // Users that previously left the room + if (!membersLeft.isEmpty()) return tr("Empty room (was: %1)").arg(namesList); - // 5. Fail miserably + // Fail miserably return tr("Empty room (%1)").arg(id); } void Room::Private::updateDisplayname() { auto swappedName = calculateDisplayname(); - if (swappedName != displayname) - { + if (swappedName != displayname) { emit q->displaynameAboutToChange(q); swap(displayname, swappedName); qDebug(MAIN) << q->objectName() << "has changed display name from" @@ -2814,17 +2673,17 @@ void Room::Private::updateDisplayname() QJsonObject Room::Private::toJson() const { - QElapsedTimer et; et.start(); + QElapsedTimer et; + et.start(); QJsonObject result; addParam<IfNotEmpty>(result, QStringLiteral("summary"), summary); { QJsonArray stateEvents; - for (const auto* evt: currentState) - { + for (const auto* evt : currentState) { Q_ASSERT(evt->isStateEvent()); - if ((evt->isRedacted() && !is<RoomMemberEvent>(*evt)) || - evt->contentJson().isEmpty()) + if ((evt->isRedacted() && !is<RoomMemberEvent>(*evt)) + || evt->contentJson().isEmpty()) continue; auto json = evt->fullJson(); @@ -2834,31 +2693,32 @@ QJsonObject Room::Private::toJson() const stateEvents.append(json); } - const auto stateObjName = joinState == JoinState::Invite ? - QStringLiteral("invite_state") : QStringLiteral("state"); + const auto stateObjName = joinState == JoinState::Invite + ? QStringLiteral("invite_state") + : QStringLiteral("state"); result.insert(stateObjName, - QJsonObject {{ QStringLiteral("events"), stateEvents }}); + QJsonObject { { QStringLiteral("events"), stateEvents } }); } - if (!accountData.empty()) - { + if (!accountData.empty()) { QJsonArray accountDataEvents; - for (const auto& e: accountData) - { + for (const auto& e : accountData) { if (!e.second->contentJson().isEmpty()) accountDataEvents.append(e.second->fullJson()); } result.insert(QStringLiteral("account_data"), - QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); + QJsonObject { + { QStringLiteral("events"), accountDataEvents } }); } - QJsonObject unreadNotifObj - { { SyncRoomData::UnreadCountKey, unreadMessages } }; + QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, + unreadMessages } }; if (highlightCount > 0) unreadNotifObj.insert(QStringLiteral("highlight_count"), highlightCount); if (notificationCount > 0) - unreadNotifObj.insert(QStringLiteral("notification_count"), notificationCount); + unreadNotifObj.insert(QStringLiteral("notification_count"), + notificationCount); result.insert(QStringLiteral("unread_notifications"), unreadNotifObj); @@ -2868,22 +2728,16 @@ QJsonObject Room::Private::toJson() const return result; } -QJsonObject Room::toJson() const -{ - return d->toJson(); -} +QJsonObject Room::toJson() const { return d->toJson(); } -MemberSorter Room::memberSorter() const -{ - return MemberSorter(this); -} +MemberSorter Room::memberSorter() const { return MemberSorter(this); } -bool MemberSorter::operator()(User *u1, User *u2) const +bool MemberSorter::operator()(User* u1, User* u2) const { return operator()(u1, room->roomMembername(u2)); } -bool MemberSorter::operator ()(User* u1, const QString& u2name) const +bool MemberSorter::operator()(User* u1, const QString& u2name) const { auto n1 = room->roomMembername(u1); if (n1.startsWith('@')) |