aboutsummaryrefslogtreecommitdiff
path: root/lib/room.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/room.cpp')
-rw-r--r--lib/room.cpp498
1 files changed, 276 insertions, 222 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index 7c45bf89..8b81bfb2 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -25,7 +25,6 @@
#include "csapi/receipts.h"
#include "csapi/redaction.h"
#include "csapi/account-data.h"
-#include "csapi/message_pagination.h"
#include "csapi/room_state.h"
#include "csapi/room_send.h"
#include "csapi/tags.h"
@@ -46,10 +45,10 @@
#include "connection.h"
#include "user.h"
#include "converters.h"
+#include "syncdata.h"
#include <QtCore/QHash>
#include <QtCore/QStringBuilder> // for efficient string concats (operator%)
-#include <QtCore/QElapsedTimer>
#include <QtCore/QPointer>
#include <QtCore/QDir>
#include <QtCore/QTemporaryFile>
@@ -92,18 +91,19 @@ class Room::Private
void updateDisplayname();
Connection* connection;
+ QString id;
+ JoinState joinState;
+ /// The state of the room at timeline position before-0
+ /// \sa timelineBase
+ std::unordered_map<StateEventKey, StateEventPtr> baseState;
+ /// The state of the room at timeline position after-maxTimelineIndex()
+ /// \sa Room::syncEdge
+ QHash<StateEventKey, const StateEventBase*> currentState;
Timeline timeline;
PendingEvents unsyncedEvents;
QHash<QString, TimelineItem::index_t> eventsIndex;
- QString id;
- QStringList aliases;
- QString canonicalAlias;
- QString name;
QString displayname;
- QString topic;
- QString encryptionAlgorithm;
Avatar avatar;
- JoinState joinState;
int highlightCount = 0;
int notificationCount = 0;
members_map_t membersMap;
@@ -157,8 +157,8 @@ class Room::Private
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
+ /// 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;
@@ -169,8 +169,22 @@ class Room::Private
void renameMember(User* u, QString oldName);
void removeMemberFromMap(const QString& username, User* u);
+ /// A point in the timeline corresponding to baseState
+ rev_iter_t timelineBase() const { return q->findInTimeline(-1); }
+
void getPreviousContent(int limit = 10);
+ template <typename EventT>
+ const EventT* getCurrentState(QString stateKey = {}) const
+ {
+ static const EventT empty;
+ const auto* evt =
+ currentState.value({EventT::matrixTypeId(), stateKey}, &empty);
+ Q_ASSERT(evt->type() == EventT::typeId() &&
+ evt->matrixType() == EventT::matrixTypeId());
+ return static_cast<const EventT*>(evt);
+ }
+
bool isEventNotable(const TimelineItem& ti) const
{
return !ti->isRedacted() &&
@@ -178,7 +192,7 @@ class Room::Private
is<RoomMessageEvent>(*ti);
}
- void addNewMessageEvents(RoomEvents&& events);
+ Changes addNewMessageEvents(RoomEvents&& events);
void addHistoricalMessageEvents(RoomEvents&& events);
/** Move events into the timeline
@@ -215,6 +229,8 @@ class Room::Private
QString doSendEvent(const RoomEvent* pEvent);
PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr);
+ void onEventSendingFailure(const RoomEvent* pEvent,
+ const QString& txnId, BaseJob* call = nullptr);
template <typename EvT>
auto requestSetState(const QString& stateKey, const EvT& event)
@@ -288,17 +304,17 @@ const Room::PendingEvents& Room::pendingEvents() const
QString Room::name() const
{
- return d->name;
+ return d->getCurrentState<RoomNameEvent>()->name();
}
QStringList Room::aliases() const
{
- return d->aliases;
+ return d->getCurrentState<RoomAliasesEvent>()->aliases();
}
QString Room::canonicalAlias() const
{
- return d->canonicalAlias;
+ return d->getCurrentState<RoomCanonicalAliasEvent>()->alias();
}
QString Room::displayName() const
@@ -308,7 +324,7 @@ QString Room::displayName() const
QString Room::topic() const
{
- return d->topic;
+ return d->getCurrentState<RoomTopicEvent>()->topic();
}
QString Room::avatarMediaId() const
@@ -366,6 +382,7 @@ void Room::setJoinState(JoinState state)
d->joinState = state;
qCDebug(MAIN) << "Room" << id() << "changed state: "
<< int(oldState) << "->" << int(state);
+ emit changed(Change::JoinStateChange);
emit joinStateChanged(oldState, state);
}
@@ -384,6 +401,7 @@ void Room::Private::setLastReadEvent(User* u, QString eventId)
if (storedId != serverReadMarker)
connection->callApi<PostReadMarkersJob>(id, storedId);
emit q->readMarkerMoved(eventId, storedId);
+ connection->saveRoomState(q);
}
}
@@ -408,7 +426,7 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to)
QElapsedTimer et; et.start();
const auto newUnreadMessages = count_if(from, to,
std::bind(&Room::Private::isEventNotable, this, _1));
- if (et.nsecsElapsed() > 10000)
+ if (et.nsecsElapsed() > profilerMinNsecs() / 10)
qCDebug(PROFILER) << "Counting gained unread messages took" << et;
if(newUnreadMessages > 0)
@@ -418,7 +436,7 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to)
unreadMessages = 0;
unreadMessages += newUnreadMessages;
- qCDebug(MAIN) << "Room" << displayname << "has gained"
+ qCDebug(MAIN) << "Room" << q->objectName() << "has gained"
<< newUnreadMessages << "unread message(s),"
<< (q->readMarker() == timeline.crend() ?
"in total at least" : "in total")
@@ -450,7 +468,7 @@ void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force)
QElapsedTimer et; et.start();
unreadMessages = count_if(eagerMarker, timeline.cend(),
std::bind(&Room::Private::isEventNotable, this, _1));
- if (et.nsecsElapsed() > 10000)
+ if (et.nsecsElapsed() > profilerMinNsecs() / 10)
qCDebug(PROFILER) << "Recounting unread messages took" << et;
// See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count
@@ -513,11 +531,21 @@ int Room::unreadCount() const
return d->unreadMessages;
}
-Room::rev_iter_t Room::timelineEdge() const
+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();
+}
+
TimelineItem::index_t Room::minTimelineIndex() const
{
return d->timeline.empty() ? 0 : d->timeline.front().index();
@@ -945,7 +973,12 @@ int Room::timelineSize() const
bool Room::usesEncryption() const
{
- return !d->encryptionAlgorithm.isEmpty();
+ return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty();
+}
+
+GetRoomEventsJob* Room::eventsHistoryJob() const
+{
+ return d->eventsHistoryJob;
}
void Room::Private::insertMemberIntoMap(User *u)
@@ -1006,8 +1039,9 @@ Room::Timeline::difference_type Room::Private::moveEventsToTimeline(
{
Q_ASSERT(!events.empty());
// Historical messages arrive in newest-to-oldest order, so the process for
- // them is symmetric to the one for new messages.
- auto index = timeline.empty() ? -int(placement) :
+ // 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 baseIndex = index;
@@ -1028,7 +1062,7 @@ Room::Timeline::difference_type Room::Private::moveEventsToTimeline(
eventsIndex.insert(eId, index);
Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId);
}
- const auto insertedSize = (index - baseIndex) * int(placement);
+ const auto insertedSize = (index - baseIndex) * placement;
Q_ASSERT(insertedSize == int(events.size()));
return insertedSize;
}
@@ -1074,45 +1108,48 @@ QString Room::roomMembername(const QString& userId) const
return roomMembername(user(userId));
}
-void Room::updateData(SyncRoomData&& data)
+void Room::updateData(SyncRoomData&& data, bool fromCache)
{
if( d->prevBatch.isEmpty() )
d->prevBatch = data.timelinePrevBatch;
setJoinState(data.joinState);
+ Changes roomChanges = Change::NoChange;
QElapsedTimer et; et.start();
for (auto&& event: data.accountData)
- processAccountDataEvent(move(event));
+ roomChanges |= processAccountDataEvent(move(event));
- bool emitNamesChanged = false;
if (!data.state.empty())
{
et.restart();
- for (const auto& e: data.state)
- emitNamesChanged |= processStateEvent(*e);
+ for (auto&& eptr: data.state)
+ {
+ const auto& evt = *eptr;
+ Q_ASSERT(evt.isStateEvent());
+ d->baseState[{evt.matrixType(),evt.stateKey()}] = move(eptr);
+ roomChanges |= processStateEvent(evt);
+ }
- qCDebug(PROFILER) << "*** Room::processStateEvents():"
- << data.state.size() << "event(s)," << et;
+ if (data.state.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs())
+ qCDebug(PROFILER) << "*** Room::processStateEvents():"
+ << data.state.size() << "event(s)," << et;
}
if (!data.timeline.empty())
{
et.restart();
- // State changes can arrive in a timeline event; so check those.
- for (const auto& e: data.timeline)
- emitNamesChanged |= processStateEvent(*e);
- qCDebug(PROFILER) << "*** Room::processStateEvents(timeline):"
- << data.timeline.size() << "event(s)," << et;
+ roomChanges |= d->addNewMessageEvents(move(data.timeline));
+ if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs())
+ qCDebug(PROFILER) << "*** Room::addNewMessageEvents():"
+ << data.timeline.size() << "event(s)," << et;
}
- if (emitNamesChanged)
+ if (roomChanges&TopicChange)
+ emit topicChanged();
+
+ if (roomChanges&NameChange)
emit namesChanged(this);
+
d->updateDisplayname();
- if (!data.timeline.empty())
- {
- et.restart();
- d->addNewMessageEvents(move(data.timeline));
- qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et;
- }
for( auto&& ephemeralEvent: data.ephemeral )
processEphemeralEvent(move(ephemeralEvent));
@@ -1134,6 +1171,12 @@ void Room::updateData(SyncRoomData&& data)
d->notificationCount = data.notificationCount;
emit notificationCountChanged(this);
}
+ if (roomChanges != Change::NoChange)
+ {
+ emit changed(roomChanges);
+ if (!fromCache)
+ connection()->saveRoomState(this);
+ }
}
QString Room::Private::sendEvent(RoomEventPtr&& event)
@@ -1150,50 +1193,42 @@ QString Room::Private::sendEvent(RoomEventPtr&& event)
QString Room::Private::doSendEvent(const RoomEvent* pEvent)
{
auto txnId = pEvent->transactionId();
- // TODO: Enqueue the job rather than immediately trigger it
- auto call = connection->callApi<SendMessageJob>(BackgroundRequest,
- id, pEvent->matrixType(), txnId, pEvent->contentJson());
- Room::connect(call, &BaseJob::started, q,
- [this,pEvent,txnId] {
- auto it = findAsPending(pEvent);
- if (it == unsyncedEvents.end())
- {
- qWarning(EVENTS) << "Pending event for transaction" << txnId
- << "not found - got synced so soon?";
- return;
- }
- it->setDeparted();
- emit q->pendingEventChanged(it - unsyncedEvents.begin());
- });
- Room::connect(call, &BaseJob::failure, q,
- [this,pEvent,txnId,call] {
- auto it = findAsPending(pEvent);
- if (it == unsyncedEvents.end())
- {
- qCritical(EVENTS) << "Pending event for transaction" << txnId
- << "got lost without successful sending";
- return;
- }
- it->setSendingFailed(
- call->statusCaption() % ": " % call->errorString());
- emit q->pendingEventChanged(it - unsyncedEvents.begin());
-
- });
- Room::connect(call, &BaseJob::success, q,
- [this,call,pEvent,txnId] {
- // Find an event by the pointer saved in the lambda (the pointer
- // may be dangling by now but we can still search by it).
- auto it = findAsPending(pEvent);
- if (it == unsyncedEvents.end())
- {
- qDebug(EVENTS) << "Pending event for transaction" << txnId
- << "already merged";
- return;
- }
+ // 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,pEvent,txnId] {
+ auto it = findAsPending(pEvent);
+ if (it == unsyncedEvents.end())
+ {
+ qWarning(EVENTS) << "Pending event for transaction" << txnId
+ << "not found - got synced so soon?";
+ return;
+ }
+ it->setDeparted();
+ emit q->pendingEventChanged(it - unsyncedEvents.begin());
+ });
+ Room::connect(call, &BaseJob::failure, q,
+ std::bind(&Room::Private::onEventSendingFailure,
+ this, pEvent, txnId, call));
+ Room::connect(call, &BaseJob::success, q,
+ [this,call,pEvent,txnId] {
+ // Find an event by the pointer saved in the lambda (the pointer
+ // may be dangling by now but we can still search by it).
+ auto it = findAsPending(pEvent);
+ if (it == unsyncedEvents.end())
+ {
+ qDebug(EVENTS) << "Pending event for transaction" << txnId
+ << "already merged";
+ return;
+ }
- it->setReachedServer(call->eventId());
- emit q->pendingEventChanged(it - unsyncedEvents.begin());
- });
+ it->setReachedServer(call->eventId());
+ emit q->pendingEventChanged(it - unsyncedEvents.begin());
+ });
+ } else
+ onEventSendingFailure(pEvent, txnId);
return txnId;
}
@@ -1206,6 +1241,22 @@ Room::PendingEvents::iterator Room::Private::findAsPending(
return std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(), comp);
}
+void Room::Private::onEventSendingFailure(const RoomEvent* pEvent,
+ const QString& txnId, BaseJob* call)
+{
+ auto it = findAsPending(pEvent);
+ 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"));
+ emit q->pendingEventChanged(it - unsyncedEvents.begin());
+}
+
QString Room::retryMessage(const QString& txnId)
{
auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(),
@@ -1285,15 +1336,15 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re)
if (le->type() != re->type())
return false;
- if (!le->id().isEmpty())
+ if (!re->id().isEmpty())
return le->id() == re->id();
- if (!le->transactionId().isEmpty())
+ if (!re->transactionId().isEmpty())
return le->transactionId() == re->transactionId();
// This one is not reliable (there can be two unsynced
// events with the same type, sender and state key) but
// it's the best we have for state events.
- if (le->isStateEvent())
+ if (re->isStateEvent())
return le->stateKey() == re->stateKey();
// Empty id and no state key, hmm... (shrug)
@@ -1349,10 +1400,13 @@ void Room::Private::getPreviousContent(int limit)
{
eventsHistoryJob =
connection->callApi<GetRoomEventsJob>(id, prevBatch, "b", "", limit);
+ emit q->eventsHistoryJobChanged();
connect( eventsHistoryJob, &BaseJob::success, q, [=] {
prevBatch = eventsHistoryJob->end();
addHistoricalMessageEvents(eventsHistoryJob->chunk());
});
+ connect( eventsHistoryJob, &QObject::destroyed,
+ q, &Room::eventsHistoryJobChanged);
}
}
@@ -1587,11 +1641,26 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction)
return true;
}
- // Make a new event from the redacted JSON, exchange events,
- // notify everyone and delete the old event
+ // 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));
- q->onRedaction(*oldEvent, *ti.event());
qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction.id();
+ if (oldEvent->isStateEvent())
+ {
+ const StateEventKey evtKey { oldEvent->matrixType(), oldEvent->stateKey() };
+ Q_ASSERT(currentState.contains(evtKey));
+ if (currentState[evtKey] == oldEvent.get())
+ {
+ Q_ASSERT(ti.index() >= 0); // Historical states can't be in currentState
+ qCDebug(MAIN).nospace() << "Reverting state "
+ << oldEvent->matrixType() << "/" << oldEvent->stateKey();
+ // Retarget the current state to the newly made event.
+ if (q->processStateEvent(*ti))
+ emit q->namesChanged(q);
+ updateDisplayname();
+ }
+ }
+ q->onRedaction(*oldEvent, *ti);
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
return true;
}
@@ -1613,48 +1682,49 @@ inline bool isRedaction(const RoomEventPtr& ep)
return is<RedactionEvent>(*ep);
}
-void Room::Private::addNewMessageEvents(RoomEvents&& events)
+Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
{
dropDuplicateEvents(events);
+ if (events.empty())
+ return Change::NoChange;
// Pre-process redactions so that events that get redacted in the same
// batch landed in the timeline already redacted.
- // XXX: The code below is written (and commented) so that it could be
- // quickly converted to not-saving redaction events in the timeline.
- // See #220 for details.
- auto newEnd = std::find_if(events.begin(), events.end(), isRedaction);
- // Either process the redaction, or shift the non-redaction event
- // overwriting redactions in a remove_if fashion.
- for(const auto& eptr: RoomEventsRange(newEnd, events.end()))
+ // NB: We have to store redaction events to the timeline too - see #220.
+ auto redactionIt = std::find_if(events.begin(), events.end(), isRedaction);
+ for(const auto& eptr: RoomEventsRange(redactionIt, events.end()))
if (auto* r = eventCast<RedactionEvent>(eptr))
{
// Try to find the target in the timeline, then in the batch.
if (processRedaction(*r))
continue;
- auto targetIt = std::find_if(events.begin(), newEnd,
+ auto targetIt = std::find_if(events.begin(), redactionIt,
[id=r->redactedEvent()] (const RoomEventPtr& ep) {
return ep->id() == id;
});
- if (targetIt != newEnd)
+ if (targetIt != redactionIt)
*targetIt = makeRedacted(**targetIt, *r);
else
qCDebug(MAIN) << "Redaction" << r->id()
<< "ignored: target event" << r->redactedEvent()
<< "is not found";
- // If the target events comes later, it comes already redacted.
+ // If the target event comes later, it comes already redacted.
}
-// else // This should be uncommented once we stop adding redactions to the timeline
-// *newEnd++ = std::move(eptr);
- newEnd = events.end(); // This line should go if/when we stop adding redactions to the timeline
- if (events.begin() == newEnd)
- return;
+ // State changes arrive as a part of timeline; the current room state gets
+ // updated before merging events to the timeline because that's what
+ // clients historically expect. This may eventually change though if we
+ // postulate that the current state is only current between syncs but not
+ // within a sync.
+ Changes stateChanges = Change::NoChange;
+ for (const auto& eptr: events)
+ stateChanges |= q->processStateEvent(*eptr);
auto timelineSize = timeline.size();
auto totalInserted = 0;
- for (auto it = events.begin(); it != newEnd;)
+ for (auto it = events.begin(); it != events.end();)
{
- auto nextPendingPair = findFirstOf(it, newEnd,
+ auto nextPendingPair = findFirstOf(it, events.end(),
unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent);
auto nextPending = nextPendingPair.first;
@@ -1668,7 +1738,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events)
q->onAddNewTimelineEvents(firstInserted);
emit q->addedMessages(firstInserted->index(), timeline.back().index());
}
- if (nextPending == newEnd)
+ if (nextPending == events.end())
break;
it = nextPending + 1;
@@ -1696,7 +1766,7 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events)
if (totalInserted > 0)
{
qCDebug(MAIN)
- << "Room" << displayname << "received" << totalInserted
+ << "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`)
@@ -1717,21 +1787,34 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events)
}
Q_ASSERT(timeline.size() == timelineSize + totalInserted);
+ return stateChanges;
}
void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
{
+ QElapsedTimer et; et.start();
const auto timelineSize = timeline.size();
dropDuplicateEvents(events);
- RoomEventsRange normalEvents {
- events.begin(), events.end() //remove_if(events.begin(), events.end(), isRedaction)
- };
- if (normalEvents.empty())
+ if (events.empty())
return;
- emit q->aboutToAddHistoricalMessages(normalEvents);
- const auto insertedSize = moveEventsToTimeline(normalEvents, Older);
+ // In case of lazy-loading new members may be loaded with historical
+ // 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)
+ {
+ const auto& e = *eptr;
+ if (e.isStateEvent() &&
+ !currentState.contains({e.matrixType(), e.stateKey()}))
+ {
+ q->processStateEvent(e);
+ }
+ }
+
+ emit q->aboutToAddHistoricalMessages(events);
+ const auto insertedSize = moveEventsToTimeline(events, Older);
const auto from = timeline.crend() - insertedSize;
qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize
@@ -1743,51 +1826,46 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
updateUnreadCount(from, timeline.crend());
Q_ASSERT(timeline.size() == timelineSize + insertedSize);
+ if (insertedSize > 9 || et.nsecsElapsed() >= profilerMinNsecs())
+ qCDebug(PROFILER) << "*** Room::addHistoricalMessageEvents():"
+ << insertedSize << "event(s)," << et;
}
-bool Room::processStateEvent(const RoomEvent& e)
+Room::Changes Room::processStateEvent(const RoomEvent& e)
{
+ if (!e.isStateEvent())
+ return Change::NoChange;
+
+ d->currentState[{e.matrixType(),e.stateKey()}] =
+ static_cast<const StateEventBase*>(&e);
+ if (!is<RoomMemberEvent>(e))
+ qCDebug(EVENTS) << "Room state event:" << e;
+
return visit(e
- , [this] (const RoomNameEvent& evt) {
- d->name = evt.name();
- qCDebug(MAIN) << "Room name updated:" << d->name;
- return true;
+ , [] (const RoomNameEvent&) {
+ return NameChange;
}
- , [this] (const RoomAliasesEvent& evt) {
- d->aliases = evt.aliases();
- qCDebug(MAIN) << "Room aliases updated:" << d->aliases;
- return true;
+ , [] (const RoomAliasesEvent&) {
+ return OtherChange;
}
, [this] (const RoomCanonicalAliasEvent& evt) {
- d->canonicalAlias = evt.alias();
- if (!d->canonicalAlias.isEmpty())
- setObjectName(d->canonicalAlias);
- qCDebug(MAIN) << "Room canonical alias updated:"
- << d->canonicalAlias;
- return true;
+ setObjectName(evt.alias().isEmpty() ? d->id : evt.alias());
+ return CanonicalAliasChange;
}
- , [this] (const RoomTopicEvent& evt) {
- d->topic = evt.topic();
- qCDebug(MAIN) << "Room topic updated:" << d->topic;
- emit topicChanged();
- return false;
+ , [] (const RoomTopicEvent&) {
+ return TopicChange;
}
, [this] (const RoomAvatarEvent& evt) {
if (d->avatar.updateUrl(evt.url()))
- {
- qCDebug(MAIN) << "Room avatar URL updated:"
- << evt.url().toString();
emit avatarChanged();
- }
- return false;
+ return AvatarChange;
}
, [this] (const RoomMemberEvent& evt) {
auto* u = user(evt.userId());
u->processEvent(evt, this);
if (u == localUser() && memberJoinState(u) == JoinState::Invite
&& evt.isDirect())
- connection()->addToDirectChats(this,
- user(evt.senderId()));
+ connection()->addToDirectChats(this, user(evt.senderId()));
if( evt.membership() == MembershipType::Join )
{
@@ -1807,24 +1885,23 @@ bool Room::processStateEvent(const RoomEvent& e)
emit userAdded(u);
}
}
- else if( evt.membership() == MembershipType::Leave )
+ else if( evt.membership() != MembershipType::Join )
{
if (memberJoinState(u) == JoinState::Join)
{
+ if (evt.membership() == MembershipType::Invite)
+ qCWarning(MAIN) << "Invalid membership change:" << evt;
if (!d->membersLeft.contains(u))
d->membersLeft.append(u);
d->removeMemberFromMap(u->name(this), u);
emit userRemoved(u);
}
}
- return false;
+ return MembersChange;
}
- , [this] (const EncryptionEvent& evt) {
- d->encryptionAlgorithm = evt.algorithm();
- qCDebug(MAIN) << "Encryption switched on in room" << id()
- << "with algorithm" << d->encryptionAlgorithm;
- emit encryption();
- return false;
+ , [this] (const EncryptionEvent&) {
+ emit encryption(); // It can only be done once, so emit it here.
+ return EncryptionOn;
}
);
}
@@ -1841,15 +1918,17 @@ void Room::processEphemeralEvent(EventPtr&& event)
if (memberJoinState(u) == JoinState::Join)
d->usersTyping.append(u);
}
- if (!evt->users().isEmpty())
+ if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs())
qCDebug(PROFILER) << "*** Room::processEphemeralEvent(typing):"
<< evt->users().size() << "users," << et;
emit typingChanged();
}
if (auto* evt = eventCast<ReceiptEvent>(event))
{
+ int totalReceipts = 0;
for( const auto &p: qAsConst(evt->eventsWithReceipts()) )
{
+ totalReceipts += p.receipts.size();
{
if (p.receipts.size() == 1)
qCDebug(EPHEMERAL) << "Marking" << p.evtId
@@ -1888,14 +1967,15 @@ void Room::processEphemeralEvent(EventPtr&& event)
}
}
}
- if (!evt->eventsWithReceipts().isEmpty())
+ if (evt->eventsWithReceipts().size() > 3 || totalReceipts > 10 ||
+ et.nsecsElapsed() >= profilerMinNsecs())
qCDebug(PROFILER) << "*** Room::processEphemeralEvent(receipts):"
<< evt->eventsWithReceipts().size()
- << "events with receipts," << et;
+ << "event(s) with" << totalReceipts << "receipt(s)," << et;
}
}
-void Room::processAccountDataEvent(EventPtr&& event)
+Room::Changes Room::processAccountDataEvent(EventPtr&& event)
{
if (auto* evt = eventCast<TagEvent>(event))
d->setTags(evt->tags());
@@ -1922,7 +2002,9 @@ void Room::processAccountDataEvent(EventPtr&& event)
qCDebug(MAIN) << "Updated account data of type"
<< currentData->matrixType();
emit accountDataChanged(currentData->matrixType());
+ return Change::AccountDataChange;
}
+ return Change::NoChange;
}
QString Room::Private::roomNameFromMemberNames(const QList<User *> &userlist) const
@@ -1963,9 +2045,8 @@ QString Room::Private::roomNameFromMemberNames(const QList<User *> &userlist) co
// iii. More users.
if (userlist.size() > 3)
- return tr("%1 and %L2 others")
- .arg(q->roomMembername(first_two[0]))
- .arg(userlist.size() - 3);
+ return tr("%1 and %Ln other(s)", "", userlist.size() - 3)
+ .arg(q->roomMembername(first_two[0]));
// userlist.size() < 2 - apparently, there's only current user in the room
return QString();
@@ -1977,27 +2058,29 @@ QString Room::Private::calculateDisplayname() const
// Numbers below refer to respective parts in the spec.
// 1. Name (from m.room.name)
- if (!name.isEmpty()) {
- return name;
+ auto dispName = q->name();
+ if (!dispName.isEmpty()) {
+ return dispName;
}
// 2. Canonical alias
- if (!canonicalAlias.isEmpty())
- return canonicalAlias;
+ dispName = q->canonicalAlias();
+ if (!dispName.isEmpty())
+ return dispName;
// Using m.room.aliases in naming is explicitly discouraged by the spec
- //if (!aliases.empty() && !aliases.at(0).isEmpty())
- // return aliases.at(0);
+ //if (!q->aliases().empty() && !q->aliases().at(0).isEmpty())
+ // return q->aliases().at(0);
// 3. Room members
- QString topMemberNames = roomNameFromMemberNames(membersMap.values());
- if (!topMemberNames.isEmpty())
- return topMemberNames;
+ dispName = roomNameFromMemberNames(membersMap.values());
+ if (!dispName.isEmpty())
+ return dispName;
// 4. Users that previously left the room
- topMemberNames = roomNameFromMemberNames(membersLeft);
- if (!topMemberNames.isEmpty())
- return tr("Empty room (was: %1)").arg(topMemberNames);
+ dispName = roomNameFromMemberNames(membersLeft);
+ if (!dispName.isEmpty())
+ return tr("Empty room (was: %1)").arg(dispName);
// 5. Fail miserably
return tr("Empty room (%1)").arg(id);
@@ -2016,34 +2099,6 @@ void Room::Private::updateDisplayname()
}
}
-void appendStateEvent(QJsonArray& events, const QString& type,
- const QJsonObject& content, const QString& stateKey = {})
-{
- if (!content.isEmpty() || !stateKey.isEmpty())
- {
- auto json = basicEventJson(type, content);
- json.insert(QStringLiteral("state_key"), stateKey);
- events.append(json);
- }
-}
-
-#define ADD_STATE_EVENT(events, type, name, content) \
- appendStateEvent((events), QStringLiteral(type), \
- {{ QStringLiteral(name), content }});
-
-void appendEvent(QJsonArray& events, const QString& type,
- const QJsonObject& content)
-{
- if (!content.isEmpty())
- events.append(basicEventJson(type, content));
-}
-
-template <typename EvtT>
-void appendEvent(QJsonArray& events, const EvtT& event)
-{
- appendEvent(events, EvtT::matrixTypeId(), event.toJson());
-}
-
QJsonObject Room::Private::toJson() const
{
QElapsedTimer et; et.start();
@@ -2051,23 +2106,19 @@ QJsonObject Room::Private::toJson() const
{
QJsonArray stateEvents;
- ADD_STATE_EVENT(stateEvents, "m.room.name", "name", name);
- ADD_STATE_EVENT(stateEvents, "m.room.topic", "topic", topic);
- ADD_STATE_EVENT(stateEvents, "m.room.avatar", "url",
- avatar.url().toString());
- ADD_STATE_EVENT(stateEvents, "m.room.aliases", "aliases",
- QJsonArray::fromStringList(aliases));
- ADD_STATE_EVENT(stateEvents, "m.room.canonical_alias", "alias",
- canonicalAlias);
- ADD_STATE_EVENT(stateEvents, "m.room.encryption", "algorithm",
- encryptionAlgorithm);
-
- for (const auto *m : membersMap)
- appendStateEvent(stateEvents, QStringLiteral("m.room.member"),
- { { QStringLiteral("membership"), QStringLiteral("join") }
- , { QStringLiteral("displayname"), m->rawName(q) }
- , { QStringLiteral("avatar_url"), m->avatarUrl(q).toString() }
- }, m->id());
+ for (const auto* evt: currentState)
+ {
+ Q_ASSERT(evt->isStateEvent());
+ if ((evt->isRedacted() && !is<RoomMemberEvent>(*evt)) ||
+ evt->contentJson().isEmpty())
+ continue;
+
+ auto json = evt->fullJson();
+ auto unsignedJson = evt->unsignedJson();
+ unsignedJson.remove(QStringLiteral("prev_content"));
+ json[UnsignedKeyL] = unsignedJson;
+ stateEvents.append(json);
+ }
const auto stateObjName = joinState == JoinState::Invite ?
QStringLiteral("invite_state") : QStringLiteral("state");
@@ -2075,14 +2126,17 @@ QJsonObject Room::Private::toJson() const
QJsonObject {{ QStringLiteral("events"), stateEvents }});
}
- QJsonArray accountDataEvents;
if (!accountData.empty())
{
+ QJsonArray accountDataEvents;
for (const auto& e: accountData)
- appendEvent(accountDataEvents, e.first, e.second->contentJson());
+ {
+ if (!e.second->contentJson().isEmpty())
+ accountDataEvents.append(e.second->fullJson());
+ }
+ result.insert(QStringLiteral("account_data"),
+ QJsonObject {{ QStringLiteral("events"), accountDataEvents }});
}
- result.insert(QStringLiteral("account_data"),
- QJsonObject {{ QStringLiteral("events"), accountDataEvents }});
QJsonObject unreadNotifObj
{ { SyncRoomData::UnreadCountKey, unreadMessages } };
@@ -2094,7 +2148,7 @@ QJsonObject Room::Private::toJson() const
result.insert(QStringLiteral("unread_notifications"), unreadNotifObj);
- if (et.elapsed() > 50)
+ if (et.elapsed() > 30)
qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" << et;
return result;