diff options
author | Marc Deop <marc@marcdeop.com> | 2019-03-02 12:26:57 +0100 |
---|---|---|
committer | Marc Deop <marc@marcdeop.com> | 2019-03-02 12:26:57 +0100 |
commit | aacc4bcb4a487871daae6717f77605aaba444341 (patch) | |
tree | 4f50b6874821667ccb6b91c017e5d041e3846112 /lib/room.cpp | |
parent | c971b924cd62822ed6fb1a0291c483ae73a3ecda (diff) | |
download | libquotient-aacc4bcb4a487871daae6717f77605aaba444341.tar.gz libquotient-aacc4bcb4a487871daae6717f77605aaba444341.zip |
style: apply .clang-format to all .cpp and .h files
Diffstat (limited to 'lib/room.cpp')
-rw-r--r-- | lib/room.cpp | 1734 |
1 files changed, 798 insertions, 936 deletions
diff --git a/lib/room.cpp b/lib/room.cpp index 5da9373e..c7c94fe5 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -13,55 +13,55 @@ * * 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 "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/roomcreateevent.h" -#include "events/roomtombstoneevent.h" -#include "events/roomavatarevent.h" -#include "events/roommemberevent.h" -#include "events/typingevent.h" -#include "events/receiptevent.h" -#include "events/callinviteevent.h" -#include "events/callcandidatesevent.h" #include "events/callanswerevent.h" +#include "events/callcandidatesevent.h" #include "events/callhangupevent.h" +#include "events/callinviteevent.h" +#include "events/receiptevent.h" #include "events/redactionevent.h" -#include "jobs/mediathumbnailjob.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 "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 "user.h" +#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> using namespace QMatrixClient; using namespace std::placeholders; @@ -75,239 +75,235 @@ enum EventsPlacement : int { Older = -1, Newer = 1 }; class Room::Private { public: - /** Map of user names to users. User names potentially duplicate, hence a multi-hashmap. */ - 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; - /// The state of the room at timeline position after-maxTimelineIndex() - /// \sa Room::syncEdge - QHash<StateEventKey, const StateEventBase*> currentState; - Timeline timeline; - PendingEvents unsyncedEvents; - QHash<QString, TimelineItem::index_t> eventsIndex; - QString displayname; - Avatar avatar; - int highlightCount = 0; - int notificationCount = 0; - members_map_t membersMap; - QList<User*> usersTyping; - QMultiHash<QString, User*> eventIdReadUsers; - 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 + /** Map of user names to users. User names potentially duplicate, hence a + * multi-hashmap. */ + 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; + /// The state of the room at timeline position after-maxTimelineIndex() + /// \sa Room::syncEdge + QHash<StateEventKey, const StateEventBase*> currentState; + Timeline timeline; + PendingEvents unsyncedEvents; + QHash<QString, TimelineItem::index_t> eventsIndex; + QString displayname; + Avatar avatar; + int highlightCount = 0; + int notificationCount = 0; + members_map_t membersMap; + QList<User*> usersTyping; + QMultiHash<QString, User*> eventIdReadUsers; + 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 { + FileTransferPrivateInfo() = default; + FileTransferPrivateInfo(BaseJob* j, const QString& fileName, + bool isUploading = false) + : status(FileTransferInfo::Started), + job(j), + localFileInfo(fileName), + isUpload(isUploading) { - 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; - } - }; - void failedTransfer(const QString& tid, const QString& errorMessage = {}) + } + + 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) { - 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 (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; } - /// 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, 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, 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(QString stateKey = {}) const - { - static const EventT empty; - const auto* evt = - currentState.value({EventT::matrixTypeId(), stateKey}, &empty); - Q_ASSERT(evt->type() == EventT::typeId() && - evt->matrixType() == EventT::matrixTypeId()); - return static_cast<const EventT*>(evt); - } + template <typename EventT> + const EventT* getCurrentState(QString stateKey = {}) const + { + static const EventT empty; + const auto* evt = currentState.value( + { EventT::matrixTypeId(), stateKey }, &empty); + 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::difference_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::difference_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); - template <typename EvT> - SetRoomStateWithKeyJob* requestSetState(const QString& stateKey, - const EvT& event) - { - if (q->successorId().isEmpty()) - { - // TODO: Queue up state events sending (see #133). - return connection->callApi<SetRoomStateWithKeyJob>( - id, EvT::matrixTypeId(), stateKey, event.contentJson()); - } - qCWarning(MAIN) << q << "has been upgraded, state won't be set"; - return nullptr; + template <typename EvT> + SetRoomStateWithKeyJob* requestSetState(const QString& stateKey, + const EvT& event) + { + if (q->successorId().isEmpty()) { + // TODO: Queue up state events sending (see #133). + return connection->callApi<SetRoomStateWithKeyJob>( + id, EvT::matrixTypeId(), stateKey, event.contentJson()); } + qCWarning(MAIN) << q << "has been upgraded, state won't be set"; + return nullptr; + } - template <typename EvT> - auto requestSetState(const EvT& event) - { - return connection->callApi<SetRoomStateJob>( - id, EvT::matrixTypeId(), event.contentJson()); - } + template <typename EvT> auto requestSetState(const EvT& event) + { + return connection->callApi<SetRoomStateJob>(id, EvT::matrixTypeId(), + event.contentJson()); + } - /** - * @brief 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. - */ - bool processRedaction(const RedactionEvent& redaction); + /** + * @brief 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. + */ + bool processRedaction(const RedactionEvent& redaction); - void setTags(TagsMap newTags); + void setTags(TagsMap newTags); - QJsonObject toJson() const; + 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; + 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(); - } + bool isLocalUser(const User* u) const { return u == q->localUser(); } }; Room::Room(Connection* connection, QString id, JoinState initialJoinState) @@ -318,24 +314,18 @@ 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 { @@ -345,8 +335,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 @@ -359,10 +349,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 { @@ -384,35 +371,20 @@ QString Room::canonicalAlias() const return d->getCurrentState<RoomCanonicalAliasEvent>()->alias(); } -QString Room::displayName() const -{ - return d->displayname; -} +QString Room::displayName() const { return d->displayname; } 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) { @@ -422,9 +394,10 @@ 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(); }); + return u->avatar(width, height, this, + [=] { emit avatarChanged(); }); return {}; } @@ -436,24 +409,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(MAIN) << "Room" << id() << "changed state: " << int(oldState) + << "->" << int(state); emit changed(Change::JoinStateChange); emit joinStateChanged(oldState, state); } @@ -468,8 +437,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); @@ -488,32 +456,33 @@ 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) - { - // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + if (newUnreadMessages > 0) { + // See + // https://github.com/QMatrixClient/libqmatrixclient/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)"; + << newUnreadMessages << "unread message(s)," + << (q->readMarker() == timeline.crend() + ? "in total at least" + : "in total") + << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); } } @@ -525,34 +494,36 @@ 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 = count_if(eagerMarker, timeline.cend(), - std::bind(&Room::Private::isEventNotable, this, _1)); + QElapsedTimer et; + et.start(); + unreadMessages = + count_if(eagerMarker, timeline.cend(), + std::bind(&Room::Private::isEventNotable, this, _1)); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; - // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See + // https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count if (unreadMessages == 0) unreadMessages = -1; - if (force || unreadMessages != oldUnreadCount) - { - if (unreadMessages == -1) - { + if (force || unreadMessages != oldUnreadCount) { + if (unreadMessages == -1) { qCDebug(MAIN) << "Room" << displayname << "has no more unread messages"; } else @@ -575,10 +546,8 @@ Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) // 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, "m.read", (*upToMarker)->id()); break; @@ -601,47 +570,36 @@ void Room::markAllMessagesAsRead() bool Room::canSwitchVersions() const { // TODO, #276: m.room.power_levels - const auto* plEvt = - d->currentState.value({"m.room.power_levels", ""}); + const auto* plEvt = d->currentState.value({ "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").toObject() - .value("m.room.tombstone"_ls).toInt( - plJson.value("state_default"_ls).toInt()); + plJson.value("events") + .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 { @@ -655,21 +613,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)->id() == evtId); return it; @@ -680,14 +636,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; + }); } void Room::Private::getAllMembers() @@ -697,28 +657,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) { @@ -727,18 +684,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 { @@ -760,10 +713,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 { @@ -791,41 +741,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(this); } -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(this); @@ -834,9 +776,8 @@ void Room::resetHighlightCount() void Room::switchVersion(QString newVersion) { 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()); }); } bool Room::hasAccountData(const QString& type) const @@ -851,20 +792,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) { @@ -882,8 +814,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(); @@ -895,13 +827,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(); @@ -918,18 +849,16 @@ void Room::setTags(TagsMap newTags) d->setTags(move(newTags)); connection()->callApi<SetAccountDataPerRoomJob>( localUser()->id(), id(), TagEvent::matrixTypeId(), - TagEvent(d->tags).contentJson()); + TagEvent(d->tags).contentJson()); } void Room::Private::setTags(TagsMap newTags) { emit q->tagsAboutToChange(); const auto keys = newTags.keys(); - for (const auto& k: keys) - { + for (const auto& k : keys) { const auto& checkRes = validatedTag(k); - if (checkRes.first) - { + if (checkRes.first) { if (newTags.contains(checkRes.second)) newTags.remove(k); else @@ -942,20 +871,11 @@ void Room::Private::setTags(TagsMap newTags) 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 -{ - return connection()->isDirectChat(id()); -} +bool Room::isDirectChat() const { return connection()->isDirectChat(id()); } QList<User*> Room::directChatUsers() const { @@ -966,8 +886,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; @@ -981,12 +900,9 @@ QString Room::Private::fileNameToDownload(const RoomMessageEvent* event) const Q_ASSERT(event->hasFileContent()); const auto* fileInfo = event->content()->fileInfo(); QString fileName; - if (!fileInfo->originalName.isEmpty()) - { + 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() }; @@ -997,13 +913,13 @@ QString Room::Private::fileNameToDownload(const RoomMessageEvent* event) const if (fileName.isEmpty() || !QTemporaryFile(fileName).open()) return "file." % fileInfo->mimeType.preferredSuffix(); - if (QSysInfo::productType() == "windows") - { + 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 (!suffixes.isEmpty() + && std::none_of(suffixes.begin(), suffixes.end(), + [&fileName](const QString& s) { + return fileName.endsWith(s); + })) return fileName % '.' % fileInfo->mimeType.preferredSuffix(); } return fileName; @@ -1012,12 +928,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 {}; @@ -1025,8 +941,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(), @@ -1053,8 +968,7 @@ 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; @@ -1066,13 +980,16 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const fti.progress = int(progress); fti.total = int(total); fti.localDir = QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()); - fti.localPath = QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); + 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()) - }; + return { infoIt->status, + infoIt->isUpload, + int(progress), + int(total), + QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()), + QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()) }; #endif } @@ -1096,39 +1013,24 @@ QString Room::prettyPrint(const QString& plainText) const return QMatrixClient::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 { @@ -1149,31 +1051,25 @@ 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) { if (!summary.merge(newSummary)) return Change::NoChange; - qCDebug(MAIN).nospace().noquote() - << "Updated room summary for " << q->objectName() << ": " << summary; + qCDebug(MAIN).nospace().noquote() << "Updated room summary for " + << q->objectName() << ": " << summary; emit q->memberListChanged(); 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. auto namesakes = membersMap.values(userName); if (namesakes.size() == 1) emit q->memberAboutToRename(namesakes.front(), @@ -1185,14 +1081,11 @@ void Room::Private::insertMemberIntoMap(User *u) void Room::Private::renameMember(User* u, 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); } @@ -1203,15 +1096,15 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) { User* namesake = nullptr; auto namesakes = membersMap.values(username); - if (namesakes.size() == 2) - { - namesake = namesakes.front() == u ? namesakes.back() : namesakes.front(); + 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 anymore. + // If there was one namesake besides the removed user, signal member + // renaming for it because it doesn't need to be disambiguated anymore. // TODO: Think about left users. if (namesake) emit q->memberRenamed(namesake); @@ -1222,27 +1115,31 @@ inline auto makeErrorStr(const Event& e, QByteArray msg) return msg.append("; event dump follows:\n").append(e.originalJson()); } -Room::Timeline::difference_type Room::Private::moveEventsToTimeline( - RoomEventsRange events, EventsPlacement placement) +Room::Timeline::difference_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 @@ -1279,8 +1176,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 @@ -1298,60 +1194,56 @@ 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; } - 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)); d->updateDisplayname(); - for( auto&& ephemeralEvent: data.ephemeral ) + for (auto&& ephemeralEvent : data.ephemeral) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count - if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) - { + if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) { qCDebug(MAIN) << "Setting unread_count to" << data.unreadCount; d->unreadMessages = data.unreadCount; emit unreadMessagesChanged(this); } - if( data.highlightCount != d->highlightCount ) - { + if (data.highlightCount != d->highlightCount) { d->highlightCount = data.highlightCount; emit highlightCountChanged(this); } - if( data.notificationCount != d->notificationCount ) - { + if (data.notificationCount != d->notificationCount) { d->notificationCount = data.notificationCount; emit notificationCountChanged(this); } - if (roomChanges != Change::NoChange) - { + if (roomChanges != Change::NoChange) { emit changed(roomChanges); if (!fromCache) connection()->saveRoomState(this); @@ -1382,37 +1274,34 @@ 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(it - unsyncedEvents.begin()); - }); + 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(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; + } - it->setReachedServer(call->eventId()); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); - }); + it->setReachedServer(call->eventId()); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); + }); } else onEventSendingFailure(txnId); return txnId; @@ -1421,15 +1310,14 @@ 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(it - unsyncedEvents.begin()); } @@ -1439,30 +1327,29 @@ 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) - { + if (transferIt->status == FileTransferInfo::Completed) { qCDebug(MAIN) << "File for transaction" << txnId << "has already been uploaded, bypassing re-upload"; } else { - if (isJobRunning(transferIt->job)) - { + if (isJobRunning(transferIt->job)) { qCDebug(MAIN) << "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())); + 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(); return d->doSendEvent(it->event()); @@ -1470,23 +1357,22 @@ QString Room::retryMessage(const QString& txnId) 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; }); + auto it = std::find_if( + d->unsyncedEvents.begin(), d->unsyncedEvents.end(), + [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())); @@ -1507,8 +1393,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) @@ -1523,58 +1410,58 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath, Q_ASSERT(localFile.isFile()); // Remote URL will only be known after upload; fill in the local path // to enable the preview while the event is pending. - const auto txnId = d->addAsPending(makeEvent<RoomMessageEvent>( - plainText, localFile, asGenericFile) - )->transactionId(); + const auto txnId = + d->addAsPending(makeEvent<RoomMessageEvent>(plainText, localFile, + asGenericFile)) + ->transactionId(); uploadFile(txnId, 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( + [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"; + 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; } QString Room::postEvent(RoomEvent* event) { - if (usesEncryption()) - { + if (usesEncryption()) { qCCritical(MAIN) << "Room" << displayName() - << "enforces encryption; sending encrypted messages is not supported yet"; + << "enforces encryption; sending encrypted messages " + "is not supported yet"; } return d->sendEvent(RoomEventPtr(event)); } @@ -1582,7 +1469,8 @@ QString Room::postEvent(RoomEvent* event) QString Room::postJson(const QString& matrixType, const QJsonObject& eventContent) { - return d->sendEvent(loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent))); + return d->sendEvent( + loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent))); } void Room::setName(const QString& newName) @@ -1625,10 +1513,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() { @@ -1638,8 +1523,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()) @@ -1680,25 +1564,22 @@ void Room::hangupCall(const QString& callId) postEvent(new CallHangupEvent(callId)); } -void Room::getPreviousContent(int limit) -{ - d->getPreviousContent(limit); -} +void Room::getPreviousContent(int limit) { d->getPreviousContent(limit); } void Room::Private::getPreviousContent(int limit) { if (isJobRunning(eventsHistoryJob)) return; - eventsHistoryJob = - connection->callApi<GetRoomEventsJob>(id, prevBatch, "b", "", 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) @@ -1712,7 +1593,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(memberId, event); } @@ -1735,7 +1617,7 @@ void Room::unban(const QString& userId) void Room::redactEvent(const QString& eventId, const QString& reason) { connection()->callApi<RedactEventJob>( - id(), eventId, connection()->generateTxnId(), reason); + id(), eventId, connection()->generateTxnId(), reason); } void Room::uploadFile(const QString& id, const QUrl& localFilename, @@ -1745,18 +1627,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); @@ -1767,9 +1648,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; @@ -1778,42 +1658,41 @@ 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"; + << eventId + << "is not in the local timeline or has no file content"; Q_ASSERT(false); return; } const auto fileUrl = event->content()->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); } @@ -1821,10 +1700,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)) @@ -1840,15 +1718,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; @@ -1867,48 +1746,54 @@ RoomEventPtr makeRedacted(const RoomEvent& target, const RedactionEvent& redaction) { auto originalJson = target.originalJsonObject(); - static const QStringList keepKeys { - EventIdKey, TypeKey, QStringLiteral("room_id"), - QStringLiteral("sender"), QStringLiteral("state_key"), - 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"), + QStringLiteral("state_key"), + 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()) - { + auto keepContentKeys = 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 @@ -1934,10 +1819,9 @@ 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(MAIN) << "Redaction" << redaction.id() << "of event" << ti->id() + << "already done, skipping"; return true; } @@ -1945,15 +1829,16 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) // 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() }; + if (oldEvent->isStateEvent()) { + const StateEventKey evtKey { oldEvent->matrixType(), + oldEvent->stateKey() }; Q_ASSERT(currentState.contains(evtKey)); - if (currentState[evtKey] == oldEvent.get()) - { - Q_ASSERT(ti.index() >= 0); // Historical states can't be in currentState - qCDebug(MAIN).nospace() << "Reverting state " - << oldEvent->matrixType() << "/" << oldEvent->stateKey(); + if (currentState[evtKey] == oldEvent.get()) { + Q_ASSERT(ti.index() + >= 0); // Historical states can't be in currentState + qCDebug(MAIN).nospace() + << "Reverting state " << oldEvent->matrixType() << "/" + << oldEvent->stateKey(); // Retarget the current state to the newly made event. if (q->processStateEvent(*ti)) emit q->namesChanged(q); @@ -1971,10 +1856,7 @@ Connection* Room::connection() const return d->connection; } -User* Room::localUser() const -{ - return connection()->user(); -} +User* Room::localUser() const { return connection()->user(); } inline bool isRedaction(const RoomEventPtr& ep) { @@ -1992,22 +1874,22 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // batch landed in the timeline already redacted. // NB: We have to store redaction events to the timeline too - see #220. auto redactionIt = std::find_if(events.begin(), events.end(), isRedaction); - for(const auto& eptr: RoomEventsRange(redactionIt, events.end())) - if (auto* r = eventCast<RedactionEvent>(eptr)) - { + for (const auto& eptr : RoomEventsRange(redactionIt, events.end())) + if (auto* r = eventCast<RedactionEvent>(eptr)) { // Try to find the target in the timeline, then in the batch. if (processRedaction(*r)) continue; - auto targetIt = std::find_if(events.begin(), redactionIt, - [id=r->redactedEvent()] (const RoomEventPtr& ep) { - return ep->id() == id; - }); + auto targetIt = std::find_if( + events.begin(), redactionIt, + [id = r->redactedEvent()](const RoomEventPtr& ep) { + return ep->id() == id; + }); if (targetIt != redactionIt) *targetIt = makeRedacted(**targetIt, *r); else - qCDebug(MAIN) << "Redaction" << r->id() - << "ignored: target event" << r->redactedEvent() - << "is not found"; + qCDebug(MAIN) + << "Redaction" << r->id() << "ignored: target event" + << r->redactedEvent() << "is not found"; // If the target event comes later, it comes already redacted. } @@ -2017,26 +1899,26 @@ 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(); auto totalInserted = 0; - for (auto it = events.begin(); it != events.end();) - { - auto nextPendingPair = findFirstOf(it, events.end(), - unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent); + 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) - { + if (it != nextPending) { RoomEventsRange eventsSpan { it, nextPending }; 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()) break; @@ -2058,8 +1940,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // 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({ nextPending, it }, Newer)) { totalInserted += insertedSize; q->onAddNewTimelineEvents(timeline.cend() - insertedSize); } @@ -2073,11 +1955,10 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (auto* evt = it->viewAs<CallEventBase>()) emit q->callEvent(q, evt); - if (totalInserted > 0) - { - qCDebug(MAIN) - << "Room" << q->objectName() << "received" << totalInserted - << "new events; the last event is now" << timeline.back(); + if (totalInserted > 0) { + qCDebug(MAIN) << "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 @@ -2086,11 +1967,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(MAIN) << "Auto-promoted read marker for" + << firstWriter->id() << "to" + << *q->readMarker(firstWriter); } updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); @@ -2103,7 +1984,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); @@ -2114,12 +1996,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); } } @@ -2147,167 +2027,157 @@ 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)) qCDebug(EVENTS) << "Room state event:" << e; - return visit(e - , [] (const RoomNameEvent&) { - return NameChange; - } - , [this,oldStateEvent] (const RoomAliasesEvent& ae) { - const auto previousAliases = oldStateEvent - ? static_cast<const RoomAliasesEvent*>(oldStateEvent)->aliases() - : QStringList(); - connection()->updateRoomAliases(id(), previousAliases, ae.aliases()); - return OtherChange; - } - , [this] (const RoomCanonicalAliasEvent& evt) { - setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); - return CanonicalAliasChange; - } - , [] (const RoomTopicEvent&) { - return TopicChange; - } - , [this] (const RoomAvatarEvent& evt) { - if (d->avatar.updateUrl(evt.url())) - emit avatarChanged(); - return AvatarChange; - } - , [this] (const RoomMemberEvent& evt) { - auto* u = user(evt.userId()); - u->processEvent(evt, this); - if (u == localUser() && memberJoinState(u) == JoinState::Invite + return visit( + e, [](const RoomNameEvent&) { return NameChange; }, + [this, oldStateEvent](const RoomAliasesEvent& ae) { + const auto previousAliases = oldStateEvent + ? static_cast<const RoomAliasesEvent*>(oldStateEvent) + ->aliases() + : QStringList(); + connection()->updateRoomAliases(id(), previousAliases, + ae.aliases()); + return OtherChange; + }, + [this](const RoomCanonicalAliasEvent& evt) { + setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); + return CanonicalAliasChange; + }, + [](const RoomTopicEvent&) { return TopicChange; }, + [this](const RoomAvatarEvent& evt) { + if (d->avatar.updateUrl(evt.url())) + emit avatarChanged(); + return AvatarChange; + }, + [this](const RoomMemberEvent& evt) { + auto* u = user(evt.userId()); + u->processEvent(evt, this); + if (u == localUser() && memberJoinState(u) == JoinState::Invite && evt.isDirect()) - connection()->addToDirectChats(this, user(evt.senderId())); - - if( evt.membership() == MembershipType::Join ) - { - if (memberJoinState(u) != JoinState::Join) - { - d->insertMemberIntoMap(u); - connect(u, &User::nameAboutToChange, this, - [=] (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 userAdded(u); - } - } - else if( evt.membership() != MembershipType::Join ) - { - if (memberJoinState(u) == JoinState::Join) - { - if (evt.membership() == MembershipType::Invite) - qCWarning(MAIN) << "Invalid membership change:" << evt; - if (!d->membersLeft.contains(u)) - d->membersLeft.append(u); - d->removeMemberFromMap(u->name(this), u); - emit userRemoved(u); + connection()->addToDirectChats(this, user(evt.senderId())); + + if (evt.membership() == MembershipType::Join) { + if (memberJoinState(u) != JoinState::Join) { + d->insertMemberIntoMap(u); + connect(u, &User::nameAboutToChange, this, + [=](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 userAdded(u); + } + } else if (evt.membership() != MembershipType::Join) { + if (memberJoinState(u) == JoinState::Join) { + if (evt.membership() == MembershipType::Invite) + qCWarning(MAIN) + << "Invalid membership change:" << evt; + if (!d->membersLeft.contains(u)) + d->membersLeft.append(u); + d->removeMemberFromMap(u->name(this), u); + emit userRemoved(u); + } } - } - return MembersChange; - } - , [this] (const EncryptionEvent&) { - emit encryption(); // It can only be done once, so emit it here. - return OtherChange; - } - , [this] (const RoomTombstoneEvent& evt) { - const auto successorId = evt.successorRoomId(); - if (auto* successor = connection()->room(successorId)) - emit upgraded(evt.serverMessage(), successor); - else - connectUntil(connection(), &Connection::loadedRoomState, this, - [this,successorId,serverMsg=evt.serverMessage()] - (Room* newRoom) { - if (newRoom->id() != successorId) - return false; - emit upgraded(serverMsg, newRoom); - return true; - }); - - return OtherChange; - } - ); + return MembersChange; + }, + [this](const EncryptionEvent&) { + emit encryption(); // It can only be done once, so emit it here. + return OtherChange; + }, + [this](const RoomTombstoneEvent& evt) { + const auto successorId = evt.successorRoomId(); + if (auto* successor = connection()->room(successorId)) + emit upgraded(evt.serverMessage(), successor); + else + connectUntil( + connection(), &Connection::loadedRoomState, this, + [this, successorId, + serverMsg = evt.serverMessage()](Room* newRoom) { + if (newRoom->id() != successorId) + return false; + emit upgraded(serverMsg, newRoom); + return true; + }); + + return OtherChange; + }); } 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; } @@ -2315,28 +2185,25 @@ 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; 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" @@ -2356,15 +2223,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 - return isLocalUser(u2) || (!isLocalUser(u1) && u1->id() < u2->id()); - } - ); + 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; } @@ -2373,7 +2239,7 @@ 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); } @@ -2395,23 +2261,22 @@ QString Room::Private::calculateDisplayname() const return dispName; // Using m.room.aliases in naming is explicitly discouraged by the spec - //if (!q->aliases().empty() && !q->aliases().at(0).isEmpty()) + // if (!q->aliases().empty() && !q->aliases().at(0).isEmpty()) // return q->aliases().at(0); // Supplementary code for 3 and 4: 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 emptyRoom = membersMap.isEmpty() || - (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); - const auto shortlist = - !summary.heroes.omitted() ? buildShortlist(summary.heroes.value()) : - !emptyRoom ? buildShortlist(membersMap) : - buildShortlist(membersLeft); + const bool emptyRoom = membersMap.isEmpty() + || (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); + const auto shortlist = !summary.heroes.omitted() + ? buildShortlist(summary.heroes.value()) + : !emptyRoom ? buildShortlist(membersMap) + : buildShortlist(membersLeft); QStringList names; - for (auto u: shortlist) - { + for (auto u : shortlist) { if (u == nullptr || isLocalUser(u)) break; names.push_back(q->roomMembername(u)); @@ -2421,10 +2286,10 @@ QString Room::Private::calculateDisplayname() const ? membersLeft.size() - int(joinState == JoinState::Leave) : q->joinedCount() - int(joinState == JoinState::Join); 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); + names << tr( + "%Ln other(s)", + "Used to make a room name from user names: A, B and _N others_", + usersCountExceptLocal); auto namesList = QLocale().createSeparatedList(names); // 3. Room members @@ -2442,8 +2307,7 @@ QString Room::Private::calculateDisplayname() const 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" @@ -2454,17 +2318,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(); @@ -2474,56 +2338,54 @@ QJsonObject Room::Private::toJson() const stateEvents.append(json); } - const auto stateObjName = joinState == JoinState::Invite ? - QStringLiteral("invite_state") : QStringLiteral("state"); - result.insert(stateObjName, - QJsonObject {{ QStringLiteral("events"), stateEvents }}); + const auto stateObjName = joinState == JoinState::Invite + ? QStringLiteral("invite_state") + : QStringLiteral("state"); + result.insert( + stateObjName, + 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); + 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); if (et.elapsed() > 30) - qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" << et; + qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" + << et; 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('@')) |