aboutsummaryrefslogtreecommitdiff
path: root/lib/room.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/room.cpp')
-rw-r--r--lib/room.cpp270
1 files changed, 156 insertions, 114 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index 155f5cd9..89ac17de 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -24,6 +24,7 @@
#include "csapi/room_state.h"
#include "csapi/room_upgrades.h"
#include "csapi/rooms.h"
+#include "csapi/read_markers.h"
#include "csapi/tags.h"
#include "events/callanswerevent.h"
@@ -43,7 +44,6 @@
#include "events/roompowerlevelsevent.h"
#include "jobs/downloadfilejob.h"
#include "jobs/mediathumbnailjob.h"
-#include "jobs/postreadmarkersjob.h"
#include "events/roomcanonicalaliasevent.h"
#include <QtCore/QDir>
@@ -251,10 +251,11 @@ public:
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);
+ auto change = q->processStateEvent(evt);
+ if (change != NoChange) {
+ changes |= change;
+ baseState[{ evt.matrixType(), evt.stateKey() }] = move(eptr);
+ }
}
if (events.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs())
qCDebug(PROFILER)
@@ -620,8 +621,8 @@ Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId)
emit q->readMarkerForUserMoved(u, eventId, storedId);
if (isLocalUser(u)) {
if (storedId != serverReadMarker)
- connection->callApi<PostReadMarkersJob>(BackgroundRequest, id,
- storedId);
+ connection->callApi<SetReadMarkerJob>(BackgroundRequest, id,
+ storedId);
emit q->readMarkerMoved(eventId, storedId);
return Change::ReadMarkerChange;
}
@@ -845,7 +846,7 @@ const Room::RelatedEvents Room::relatedEvents(const RoomEvent& evt,
void Room::Private::getAllMembers()
{
// If already loaded or already loading, there's nothing to do here.
- if (q->joinedCount() <= membersMap.size() || isJobRunning(allMembersJob))
+ if (q->joinedCount() <= membersMap.size() || isJobPending(allMembersJob))
return;
allMembersJob = connection->callApi<GetMembersByRoomJob>(
@@ -1341,23 +1342,27 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary)
void Room::Private::insertMemberIntoMap(User* u)
{
- const auto userName =
- getCurrentState<RoomMemberEvent>(u->id())->displayName();
- qDebug(STATE) << "insertMemberIntoMap(), user" << u->id() << "with name"
- << userName;
- // 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 maybeUserName =
+ getCurrentState<RoomMemberEvent>(u->id())->newDisplayName();
+ if (!maybeUserName)
+ qCWarning(MEMBERS) << "insertMemberIntoMap():" << u->id()
+ << "has no name (even empty)";
+ const auto userName = maybeUserName.value_or(QString());
const auto namesakes = membersMap.values(userName);
- qDebug(STATE) << namesakes.size() << "namesake(s) found";
+ qCDebug(MEMBERS) << "insertMemberIntoMap(), user" << u->id()
+ << "with name" << userName << '-'
+ << namesakes.size() << "namesake(s) found";
- // Callers should check they are not adding an existing user once more.
+ // Callers should make sure they are not adding an existing user once more
Q_ASSERT(!namesakes.contains(u));
if (namesakes.contains(u)) { // Release version whines but continues
- qCCritical(STATE) << "Trying to add a user" << u->id() << "to room"
- << q->objectName() << "but that's already in it";
+ qCCritical(MEMBERS) << "Trying to add a user" << u->id() << "to room"
+ << q->objectName() << "but that's already in it";
return;
}
+ // 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 (namesakes.size() == 1)
emit q->memberAboutToRename(namesakes.front(),
namesakes.front()->fullName(q));
@@ -1369,21 +1374,40 @@ void Room::Private::insertMemberIntoMap(User* u)
void Room::Private::removeMemberFromMap(User* u)
{
const auto userName =
- getCurrentState<RoomMemberEvent>(u->id())->displayName();
+ getCurrentState<RoomMemberEvent>(
+ u->id())->newDisplayName().value_or(QString());
- qDebug(STATE) << "removeMemberFromMap(), username" << userName << "for user"
- << u->id();
+ qCDebug(MEMBERS) << "removeMemberFromMap(), username" << userName
+ << "for user" << u->id();
User* namesake = nullptr;
auto namesakes = membersMap.values(userName);
+ // 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 (namesakes.size() == 2) {
- namesake = namesakes.front() == u ? namesakes.back() : namesakes.front();
+ namesake =
+ namesakes.front() == u ? namesakes.back() : namesakes.front();
Q_ASSERT_X(namesake != u, __FUNCTION__, "Room members list is broken");
emit q->memberAboutToRename(namesake, userName);
}
- const auto removed = membersMap.remove(userName, u);
- qDebug(STATE) << "Removed" << removed << "entries";
- // 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 (membersMap.remove(userName, u) == 0) {
+ qCDebug(MEMBERS) << "No entries removed; checking the whole list";
+ // Unless at the stage of initial filling, this no removed entries
+ // is suspicious; double-check that this user is not found in
+ // the whole map, and stop (for debug builds) or shout in the logs
+ // (for release builds) if there's one. That search is O(n), which
+ // may come rather expensive for larger rooms.
+ QElapsedTimer et;
+ auto it = std::find(membersMap.cbegin(), membersMap.cend(), u);
+ if (et.nsecsElapsed() > profilerMinNsecs() / 10)
+ qCDebug(MEMBERS) << "...done in" << et;
+ if (it != membersMap.cend()) {
+ Q_ASSERT_X(false, __FUNCTION__,
+ "Mismatched name in the room members list");
+ qCCritical(MEMBERS) << "Mismatched name in the room members list;"
+ " avoiding the list corruption";
+ membersMap.remove(it.key(), u);
+ }
+ }
if (namesake)
emit q->memberRenamed(namesake);
}
@@ -1649,7 +1673,7 @@ QString Room::retryMessage(const QString& txnId)
<< "File for transaction" << txnId
<< "has already been uploaded, bypassing re-upload";
} else {
- if (isJobRunning(transferIt->job)) {
+ if (isJobPending(transferIt->job)) {
qCDebug(MESSAGES) << "Abandoning the upload job for transaction"
<< txnId << "and starting again";
transferIt->job->abandon();
@@ -1682,7 +1706,7 @@ void Room::discardMessage(const QString& txnId)
const auto& transferIt = d->fileTransfers.find(txnId);
if (transferIt != d->fileTransfers.end()) {
Q_ASSERT(transferIt->isUpload);
- if (isJobRunning(transferIt->job)) {
+ if (isJobPending(transferIt->job)) {
transferIt->status = FileTransferInfo::Cancelled;
transferIt->job->abandon();
emit fileTransferFailed(txnId, tr("File upload cancelled"));
@@ -1888,7 +1912,7 @@ void Room::getPreviousContent(int limit, const QString &filter) { d->getPrevious
void Room::Private::getPreviousContent(int limit, const QString &filter)
{
- if (isJobRunning(eventsHistoryJob))
+ if (isJobPending(eventsHistoryJob))
return;
eventsHistoryJob =
@@ -1941,7 +1965,7 @@ 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 (isJobPending(job)) {
d->fileTransfers[id] = { job, fileName, true };
connect(job, &BaseJob::uploadProgress, this,
[this, id](qint64 sent, qint64 total) {
@@ -1997,7 +2021,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename)
qDebug(MAIN) << "File path:" << filePath;
}
auto job = connection()->downloadFile(fileUrl, filePath);
- if (isJobRunning(job)) {
+ if (isJobPending(job)) {
// If there was a previous transfer (completed or failed), overwrite it.
d->fileTransfers[eventId] = { job, job->targetFileName() };
connect(job, &BaseJob::downloadProgress, this,
@@ -2025,7 +2049,7 @@ void Room::cancelFileTransfer(const QString& id)
<< d->id;
return;
}
- if (isJobRunning(it->job))
+ if (isJobPending(it->job))
it->job->abandon();
d->fileTransfers.remove(id);
emit fileTransferCancelled(id);
@@ -2437,7 +2461,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
Room::Changes Room::processStateEvent(const RoomEvent& e)
{
if (!e.isStateEvent())
- return Change::NoChange;
+ return NoChange;
// Find a value (create an empty one if necessary) and get a reference
// to it. Can't use getCurrentState<>() because it (creates and) returns
@@ -2445,48 +2469,87 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
// or nullptr.
auto& curStateEvent = d->currentState[{ e.matrixType(), e.stateKey() }];
// Prepare for the state change
- visit(e, [this, oldRme = static_cast<const RoomMemberEvent*>(curStateEvent)](
- const RoomMemberEvent& rme) {
- auto* u = user(rme.userId());
- if (!u) { // ???
- qCCritical(MAIN)
- << "Could not get a user object for" << rme.userId();
- return;
- }
- const auto prevMembership = oldRme ? oldRme->membership()
- : MembershipType::Leave;
- switch (prevMembership) {
- case MembershipType::Invite:
- if (rme.membership() != prevMembership) {
- d->usersInvited.removeOne(u);
- Q_ASSERT(!d->usersInvited.contains(u));
+ // clang-format off
+ const bool proceed = visit(e
+ , [this, curStateEvent](const RoomMemberEvent& rme) {
+ // clang-format on
+ auto* oldRme = static_cast<const RoomMemberEvent*>(curStateEvent);
+ auto* u = user(rme.userId());
+ if (!u) { // Some terribly malformed user id?
+ qCCritical(MAIN) << "Could not get a user object for"
+ << rme.userId();
+ return false; // Stay low and hope for the best...
}
- break;
- case MembershipType::Join:
- switch (rme.membership()) {
- case MembershipType::Join: // rename/avatar change or no-op
- if (rme.newDisplayName()) {
- emit memberAboutToRename(u, *rme.newDisplayName());
+ const auto prevMembership = oldRme ? oldRme->membership()
+ : MembershipType::Leave;
+ switch (prevMembership) {
+ case MembershipType::Invite:
+ if (rme.membership() != prevMembership) {
+ d->usersInvited.removeOne(u);
+ Q_ASSERT(!d->usersInvited.contains(u));
+ }
+ break;
+ case MembershipType::Join:
+ if (rme.membership() == MembershipType::Join) {
+ // rename/avatar change or no-op
+ if (rme.newDisplayName()) {
+ emit memberAboutToRename(u, *rme.newDisplayName());
+ d->removeMemberFromMap(u);
+ }
+ if (!rme.newDisplayName() && !rme.newAvatarUrl()) {
+ qCWarning(MEMBERS)
+ << "No-op membership event for" << rme.userId()
+ << "- retaining the state";
+ qCWarning(MEMBERS) << "The event dump:" << rme;
+ return false;
+ }
+ } else {
+ if (rme.membership() == MembershipType::Invite)
+ qCWarning(MAIN)
+ << "Membership change from Join to Invite:" << rme;
+ // whatever the new membership, it's no more Join
d->removeMemberFromMap(u);
+ emit userRemoved(u);
}
break;
- case MembershipType::Invite:
- qCWarning(MAIN) << "Membership change from Join to Invite:"
- << rme;
- [[fallthrough]];
- default: // whatever the new membership, it's no more Join
- d->removeMemberFromMap(u);
- emit userRemoved(u);
+ case MembershipType::Ban:
+ case MembershipType::Knock:
+ case MembershipType::Leave:
+ if (rme.membership() == MembershipType::Invite
+ || rme.membership() == MembershipType::Join) {
+ d->membersLeft.removeOne(u);
+ Q_ASSERT(!d->membersLeft.contains(u));
+ }
+ break;
+ case MembershipType::Undefined:
+ ; // A warning will be dropped in the post-processing block below
}
- break;
- default:
- if (rme.membership() == MembershipType::Invite
- || rme.membership() == MembershipType::Join) {
- d->membersLeft.removeOne(u);
- Q_ASSERT(!d->membersLeft.contains(u));
+ return true;
+ // clang-format off
+ }
+ , [this, curStateEvent]( const EncryptionEvent& ee) {
+ // clang-format on
+ auto* oldEncEvt =
+ static_cast<const EncryptionEvent*>(curStateEvent);
+ if (ee.algorithm().isEmpty()) {
+ qWarning(STATE)
+ << "The encryption event for room" << objectName()
+ << "doesn't have 'algorithm' specified - ignoring";
+ return false;
+ }
+ if (oldEncEvt
+ && oldEncEvt->encryption() != EncryptionEventContent::Undefined) {
+ qCWarning(STATE) << "The room is already encrypted but a new"
+ " room encryption event arrived - ignoring";
+ return false;
}
+ return true;
+ // clang-format off
}
- });
+ , true); // By default, go forward with the state change
+ // clang-format on
+ if (!proceed)
+ return NoChange;
// Change the state
const auto* const oldStateEvent =
@@ -2494,47 +2557,35 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
Q_ASSERT(!oldStateEvent
|| (oldStateEvent->matrixType() == e.matrixType()
&& oldStateEvent->stateKey() == e.stateKey()));
- if (!is<RoomMemberEvent>(e)) // Room member events are too numerous
+ if (is<RoomMemberEvent>(e))
+ qCDebug(MEMBERS) << "Updated room member state:" << e;
+ else
qCDebug(STATE) << "Updated room state:" << e;
// Update internal structures as per the change and work out the return value
// clang-format off
- return visit(e
+ const auto result = visit(e
, [] (const RoomNameEvent&) {
return NameChange;
}
- , [] (const RoomAliasesEvent&) {
- return NoChange; // This event has been removed by MSC2432
- }
, [this, oldStateEvent] (const RoomCanonicalAliasEvent& cae) {
// clang-format on
setObjectName(cae.alias().isEmpty() ? d->id : cae.alias());
- QString previousCanonicalAlias =
- oldStateEvent
- ? static_cast<const RoomCanonicalAliasEvent*>(oldStateEvent)
- ->alias()
- : QString();
-
- auto previousAltAliases =
- oldStateEvent
- ? static_cast<const RoomCanonicalAliasEvent*>(oldStateEvent)
- ->altAliases()
- : QStringList();
-
- if (!previousCanonicalAlias.isEmpty()) {
- previousAltAliases.push_back(previousCanonicalAlias);
+ const auto* oldCae =
+ static_cast<const RoomCanonicalAliasEvent*>(oldStateEvent);
+ QStringList previousAltAliases {};
+ if (oldCae) {
+ previousAltAliases = oldCae->altAliases();
+ if (!oldCae->alias().isEmpty())
+ previousAltAliases.push_back(oldCae->alias());
}
- const auto previousAliases = std::move(previousAltAliases);
-
auto newAliases = cae.altAliases();
-
- if (!cae.alias().isEmpty()) {
+ if (!cae.alias().isEmpty())
newAliases.push_front(cae.alias());
- }
- connection()->updateRoomAliases(id(), previousAliases, newAliases);
+ connection()->updateRoomAliases(id(), previousAltAliases, newAliases);
return AliasesChange;
// clang-format off
}
@@ -2557,13 +2608,11 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
switch (evt.membership()) {
case MembershipType::Join:
if (prevMembership != MembershipType::Join) {
- qDebug(STATE) << "!Join -> Join";
d->insertMemberIntoMap(u);
emit userAdded(u);
} else {
if (evt.newDisplayName()) {
- qDebug(STATE) << "After renaming";
- d->insertMemberIntoMap(u);
+ d->insertMemberIntoMap(u);
emit memberRenamed(u);
}
if (evt.newAvatarUrl())
@@ -2576,33 +2625,23 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
if (u == localUser() && evt.isDirect())
connection()->addToDirectChats(this, user(evt.senderId()));
break;
- default:
+ case MembershipType::Knock:
+ case MembershipType::Ban:
+ case MembershipType::Leave:
if (!d->membersLeft.contains(u))
d->membersLeft.append(u);
+ break;
+ case MembershipType::Undefined:
+ qCWarning(MEMBERS) << "Ignored undefined membership type";
}
return MembersChange;
// clang-format off
}
- , [this, oldEncEvt = static_cast<const EncryptionEvent*>(oldStateEvent)](
- const EncryptionEvent& ee) {
- // clang-format on
- if (ee.algorithm().isEmpty()) {
- qWarning(STATE)
- << "The encryption event for room" << objectName()
- << "doesn't have 'algorithm' specified - ignoring";
- return NoChange;
- }
- if (oldEncEvt
- && oldEncEvt->encryption() != EncryptionEventContent::Undefined) {
- qCWarning(STATE) << "The room is already encrypted but a new"
- " room encryption event arrived - ignoring";
- return NoChange;
- }
+ , [this] (const EncryptionEvent&) {
// As encryption can only be switched on once, emit the signal here
// instead of aggregating and emitting in updateData()
emit encryption();
return OtherChange;
- // clang-format off
}
, [this] (const RoomTombstoneEvent& evt) {
const auto successorId = evt.successorRoomId();
@@ -2619,9 +2658,12 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
});
return OtherChange;
+ // clang-format off
}
- );
+ , OtherChange);
// clang-format on
+ Q_ASSERT(result != NoChange);
+ return result;
}
Room::Changes Room::processEphemeralEvent(EventPtr&& event)