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