aboutsummaryrefslogtreecommitdiff
path: root/lib/room.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/room.cpp')
-rw-r--r--lib/room.cpp205
1 files changed, 145 insertions, 60 deletions
diff --git a/lib/room.cpp b/lib/room.cpp
index cdc7572a..7232741a 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -27,6 +27,7 @@
#include "csapi/account-data.h"
#include "csapi/room_state.h"
#include "csapi/room_send.h"
+#include "csapi/rooms.h"
#include "csapi/tags.h"
#include "events/simplestateevents.h"
#include "events/roomavatarevent.h"
@@ -93,6 +94,7 @@ class Room::Private
Connection* connection;
QString id;
JoinState joinState;
+ RoomSummary summary;
/// The state of the room at timeline position before-0
/// \sa timelineBase
std::unordered_map<StateEventKey, StateEventPtr> baseState;
@@ -120,6 +122,7 @@ class Room::Private
std::unordered_map<QString, EventPtr> accountData;
QString prevBatch;
QPointer<GetRoomEventsJob> eventsHistoryJob;
+ QPointer<GetMembersByRoomJob> allMembersJob;
struct FileTransferPrivateInfo
{
@@ -164,6 +167,8 @@ class Room::Private
const RoomMessageEvent* getEventWithFile(const QString& eventId) const;
QString fileNameToDownload(const RoomMessageEvent* event) const;
+ Changes setSummary(RoomSummary&& newSummary);
+
//void inviteUser(User* u); // We might get it at some point in time.
void insertMemberIntoMap(User* u);
void renameMember(User* u, QString oldName);
@@ -219,6 +224,8 @@ class Room::Private
Changes markMessagesAsRead(rev_iter_t upToMarker);
+ void getAllMembers();
+
QString sendEvent(RoomEventPtr&& event);
template <typename EventT, typename... ArgTs>
@@ -260,8 +267,11 @@ class Room::Private
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;
QString calculateDisplayname() const;
- QString roomNameFromMemberNames(const QList<User*>& userlist) const;
bool isLocalUser(const User* u) const
{
@@ -276,9 +286,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState)
// See "Accessing the Public Class" section in
// https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/
d->q = this;
- connect(this, &Room::userAdded, this, &Room::memberListChanged);
- connect(this, &Room::userRemoved, this, &Room::memberListChanged);
- connect(this, &Room::memberRenamed, this, &Room::memberListChanged);
qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id;
}
@@ -585,6 +592,37 @@ Room::rev_iter_t Room::findInTimeline(const QString& evtId) const
return timelineEdge();
}
+void Room::Private::getAllMembers()
+{
+ // If already loaded or already loading, there's nothing to do here.
+ if (q->joinedCount() <= membersMap.size() || isJobRunning(allMembersJob))
+ return;
+
+ allMembersJob = connection->callApi<GetMembersByRoomJob>(
+ id, connection->nextBatchToken(), "join");
+ auto nextIndex = timeline.empty() ? 0 : timeline.back().index() + 1;
+ connect( allMembersJob, &BaseJob::success, q, [=] {
+ Q_ASSERT(timeline.empty() || nextIndex <= q->maxTimelineIndex() + 1);
+ Changes roomChanges = NoChange;
+ for (auto&& e: allMembersJob->chunk())
+ {
+ const auto& evt = *e;
+ baseState[{evt.matrixType(),evt.stateKey()}] = move(e);
+ roomChanges |= q->processStateEvent(evt);
+ }
+ // Replay member events that arrived after the point for which
+ // the full members list was requested.
+ 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)
+ emit q->memberListChanged();
+ emit q->allMembersLoaded();
+ });
+}
+
bool Room::displayed() const
{
return d->displayed;
@@ -601,6 +639,7 @@ void Room::setDisplayed(bool displayed)
{
resetHighlightCount();
resetNotificationCount();
+ d->getAllMembers();
}
}
@@ -981,11 +1020,39 @@ bool Room::usesEncryption() const
return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty();
}
+int Room::joinedCount() const
+{
+ return d->summary.joinedMemberCount.omitted()
+ ? d->membersMap.size()
+ : d->summary.joinedMemberCount.value();
+}
+
+int Room::invitedCount() const
+{
+ // TODO: Store invited users in Room too
+ return d->summary.invitedMemberCount;
+}
+
+int Room::totalMemberCount() const
+{
+ return joinedCount() + invitedCount();
+}
+
GetRoomEventsJob* Room::eventsHistoryJob() const
{
return d->eventsHistoryJob;
}
+Room::Changes Room::Private::setSummary(RoomSummary&& newSummary)
+{
+ if (!summary.merge(newSummary))
+ return Change::NoChange;
+ qCDebug(MAIN).nospace().noquote()
+ << "Updated room summary for " << q->objectName() << ": " << summary;
+ emit q->memberListChanged();
+ return Change::SummaryChange;
+}
+
void Room::Private::insertMemberIntoMap(User *u)
{
const auto userName = u->name(q);
@@ -1153,6 +1220,10 @@ void Room::updateData(SyncRoomData&& data, bool fromCache)
if (roomChanges&NameChange)
emit namesChanged(this);
+ if (roomChanges&MembersChange)
+ emit memberListChanged();
+
+ roomChanges |= d->setSummary(move(data.summary));
d->updateDisplayname();
for( auto&& ephemeralEvent: data.ephemeral )
@@ -1358,7 +1429,7 @@ bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re)
bool Room::supportsCalls() const
{
- return d->membersMap.size() == 2;
+ return joinedCount() == 2;
}
void Room::inviteCall(const QString& callId, const int lifetime,
@@ -1401,18 +1472,18 @@ void Room::getPreviousContent(int limit)
void Room::Private::getPreviousContent(int limit)
{
- if( !isJobRunning(eventsHistoryJob) )
- {
- 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);
- }
+ if (isJobRunning(eventsHistoryJob))
+ return;
+
+ 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);
}
void Room::inviteToRoom(const QString& memberId)
@@ -2018,49 +2089,35 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event)
return Change::NoChange;
}
-QString Room::Private::roomNameFromMemberNames(const QList<User *> &userlist) const
+template <typename ContT>
+Room::Private::users_shortlist_t
+Room::Private::buildShortlist(const ContT& users) const
{
- // This is part 3(i,ii,iii) in the room displayname algorithm described
- // in the CS spec (see also Room::Private::updateDisplayname() ).
- // The spec requires to sort users lexicographically by state_key (user id)
- // and use disambiguated display names of two topmost users excluding
- // the current one to render the name of the room.
-
- // std::array is the leanest C++ container
- std::array<User*, 2> first_two = { {nullptr, nullptr} };
+ // To calculate room display name the spec requires to sort users
+ // lexicographically by state_key (user id) and use disambiguated
+ // 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
std::partial_sort_copy(
- userlist.begin(), userlist.end(),
- first_two.begin(), first_two.end(),
- [this](const User* u1, const User* u2) {
- // Filter out the "me" user so that it never hits the room name
+ 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;
+}
- // Spec extension. A single person in the chat but not the local user
- // (the local user is invited).
- if (userlist.size() == 1 && !isLocalUser(first_two.front()) &&
- joinState == JoinState::Invite)
- return tr("Invitation from %1")
- .arg(q->roomMembername(first_two.front()));
-
- // i. One-on-one chat. first_two[1] == localUser() in this case.
- if (userlist.size() == 2)
- return q->roomMembername(first_two[0]);
-
- // ii. Two users besides the current one.
- if (userlist.size() == 3)
- return tr("%1 and %2")
- .arg(q->roomMembername(first_two[0]),
- q->roomMembername(first_two[1]));
-
- // iii. More users.
- if (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();
+Room::Private::users_shortlist_t
+Room::Private::buildShortlist(const QStringList& userIds) const
+{
+ QList<User*> users;
+ users.reserve(userIds.size());
+ for (const auto& h: userIds)
+ users.push_back(q->user(h));
+ return buildShortlist(users);
}
QString Room::Private::calculateDisplayname() const
@@ -2083,15 +2140,42 @@ QString Room::Private::calculateDisplayname() const
//if (!q->aliases().empty() && !q->aliases().at(0).isEmpty())
// return q->aliases().at(0);
+ // Supplementary code for 3 and 4: build the shortlist of users whose names
+ // will be used to construct the room name. Takes into account MSC688's
+ // "heroes" if available.
+
+ const bool emptyRoom = membersMap.isEmpty() ||
+ (membersMap.size() == 1 && isLocalUser(*membersMap.begin()));
+ const auto shortlist =
+ !summary.heroes.omitted() ? buildShortlist(summary.heroes.value()) :
+ !emptyRoom ? buildShortlist(membersMap) :
+ buildShortlist(membersLeft);
+
+ QStringList names;
+ for (auto u: shortlist)
+ {
+ if (u == nullptr || isLocalUser(u))
+ break;
+ names.push_back(q->roomMembername(u));
+ }
+
+ auto usersCountExceptLocal = emptyRoom
+ ? membersLeft.size() - int(joinState == JoinState::Leave)
+ : q->joinedCount() - int(joinState == JoinState::Join);
+ if (usersCountExceptLocal > int(shortlist.size()))
+ names <<
+ tr("%Ln other(s)",
+ "Used to make a room name from user names: A, B and _N others_",
+ usersCountExceptLocal);
+ auto namesList = QLocale().createSeparatedList(names);
+
// 3. Room members
- dispName = roomNameFromMemberNames(membersMap.values());
- if (!dispName.isEmpty())
- return dispName;
+ if (!emptyRoom)
+ return namesList;
// 4. Users that previously left the room
- dispName = roomNameFromMemberNames(membersLeft);
- if (!dispName.isEmpty())
- return tr("Empty room (was: %1)").arg(dispName);
+ if (membersLeft.size() > 0)
+ return tr("Empty room (was: %1)").arg(namesList);
// 5. Fail miserably
return tr("Empty room (%1)").arg(id);
@@ -2114,6 +2198,7 @@ QJsonObject Room::Private::toJson() const
{
QElapsedTimer et; et.start();
QJsonObject result;
+ addParam<IfNotEmpty>(result, QStringLiteral("summary"), summary);
{
QJsonArray stateEvents;