aboutsummaryrefslogtreecommitdiff
path: root/room.cpp
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-02-27 19:51:08 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-02-27 19:51:08 +0900
commit375bea5ee67a90c6419acf50c06c05ed0435c772 (patch)
tree76f0a1c830797fe1b17b3bea67ddaf7fb53cbd6a /room.cpp
parente80bfd2fc710d4780a2c22bde9d605a41bd4aaa4 (diff)
parentefeeca097a3c69991683615366f07625855ba2ac (diff)
downloadlibquotient-375bea5ee67a90c6419acf50c06c05ed0435c772.tar.gz
libquotient-375bea5ee67a90c6419acf50c06c05ed0435c772.zip
Merge branch 'master' into kitsune-gtad
Diffstat (limited to 'room.cpp')
-rw-r--r--room.cpp275
1 files changed, 176 insertions, 99 deletions
diff --git a/room.cpp b/room.cpp
index 73591c98..db36a713 100644
--- a/room.cpp
+++ b/room.cpp
@@ -64,7 +64,7 @@ class Room::Private
Private(Connection* c, QString id_, JoinState initialJoinState)
: q(nullptr), connection(c), id(std::move(id_))
- , avatar(c), joinState(initialJoinState)
+ , joinState(initialJoinState)
{ }
Room* q;
@@ -95,6 +95,8 @@ class Room::Private
QString firstDisplayedEventId;
QString lastDisplayedEventId;
QHash<const User*, QString> lastReadEventIds;
+ QHash<QString, TagRecord> tags;
+ QHash<QString, QJsonObject> accountData;
QString prevBatch;
QPointer<RoomMessagesJob> roomMessagesJob;
@@ -140,16 +142,10 @@ class Room::Private
const RoomMessageEvent* getEventWithFile(const QString& eventId) const;
- // Convenience methods to work with the membersMap and usersLeft.
- // addMember() and removeMember() emit respective Room:: signals
- // after a succesful operation.
//void inviteUser(User* u); // We might get it at some point in time.
- void addMember(User* u);
- bool hasMember(User* u) const;
- // You can't identify a single user by displayname, only by id
- User* member(const QString& id) const;
+ void insertMemberIntoMap(User* u);
void renameMember(User* u, QString oldName);
- void removeMember(User* u);
+ void removeMemberFromMap(const QString& username, User* u);
void getPreviousContent(int limit = 10);
@@ -202,12 +198,9 @@ class Room::Private
QString calculateDisplayname() const;
QString roomNameFromMemberNames(const QList<User*>& userlist) const;
- void insertMemberIntoMap(User* u);
- void removeMemberFromMap(const QString& username, User* u);
-
bool isLocalUser(const User* u) const
{
- return u == connection->user();
+ return u == q->localUser();
}
};
@@ -287,7 +280,7 @@ QImage Room::avatar(int dimension)
QImage Room::avatar(int width, int height)
{
if (!d->avatar.url().isEmpty())
- return d->avatar.get(width, height, [=] { emit avatarChanged(); });
+ return d->avatar.get(connection(), width, height, [=] { emit avatarChanged(); });
// Use the other side's avatar for 1:1's
if (d->membersMap.size() == 2)
@@ -295,12 +288,24 @@ QImage Room::avatar(int width, int height)
auto theOtherOneIt = d->membersMap.begin();
if (theOtherOneIt.value() == localUser())
++theOtherOneIt;
- return (*theOtherOneIt)->avatarObject()
- .get(width, height, [=] { emit avatarChanged(); });
+ return (*theOtherOneIt)->avatar(width, height, this,
+ [=] { emit avatarChanged(); });
}
return {};
}
+User* Room::user(const QString& userId) const
+{
+ return connection()->user(userId);
+}
+
+JoinState Room::memberJoinState(User* user) const
+{
+ return
+ d->membersMap.contains(user->name(this), user) ? JoinState::Join :
+ JoinState::Leave;
+}
+
JoinState Room::joinState() const
{
return d->joinState;
@@ -549,6 +554,31 @@ void Room::resetHighlightCount()
emit highlightCountChanged(this);
}
+QStringList Room::tagNames() const
+{
+ return d->tags.keys();
+}
+
+const QHash<QString, TagRecord>& Room::tags() const
+{
+ return d->tags;
+}
+
+TagRecord Room::tag(const QString& name) const
+{
+ return d->tags.value(name);
+}
+
+bool Room::isFavourite() const
+{
+ return d->tags.contains(FavouriteTag);
+}
+
+bool Room::isLowPriority() const
+{
+ return d->tags.contains(LowPriorityTag);
+}
+
const RoomMessageEvent*
Room::Private::getEventWithFile(const QString& eventId) const
{
@@ -700,7 +730,7 @@ QStringList Room::memberNames() const
{
QStringList res;
for (auto u : d->membersMap)
- res.append( this->roomMembername(u) );
+ res.append( roomMembername(u) );
return res;
}
@@ -717,23 +747,50 @@ int Room::timelineSize() const
void Room::Private::insertMemberIntoMap(User *u)
{
- auto namesakes = membersMap.values(u->name());
- membersMap.insert(u->name(), 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.
+ auto namesakes = membersMap.values(userName);
+ if (namesakes.size() == 1)
+ emit q->memberAboutToRename(namesakes.front(),
+ namesakes.front()->fullName(q));
+ membersMap.insert(userName, u);
if (namesakes.size() == 1)
- emit q->memberRenamed(namesakes[0]);
+ emit q->memberRenamed(namesakes.front());
+}
+
+void Room::Private::renameMember(User* u, QString 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))
+ {
+ removeMemberFromMap(oldName, u);
+ insertMemberIntoMap(u);
+ }
+ emit q->memberRenamed(u);
}
void Room::Private::removeMemberFromMap(const QString& username, User* u)
{
+ User* namesake = nullptr;
+ auto namesakes = membersMap.values(username);
+ if (namesakes.size() == 2)
+ {
+ namesake = namesakes.front() == u ? namesakes.back() : namesakes.front();
+ Q_ASSERT_X(namesake != u, __FUNCTION__, "Room members list is broken");
+ emit q->memberAboutToRename(namesake, username);
+ }
membersMap.remove(username, u);
// If there was one namesake besides the removed user, signal member renaming
// for it because it doesn't need to be disambiguated anymore.
// TODO: Think about left users.
- auto formerNamesakes = membersMap.values(username);
- if (formerNamesakes.size() == 1)
- emit q->memberRenamed(formerNamesakes[0]);
+ if (namesake)
+ emit q->memberRenamed(namesake);
}
inline auto makeErrorStr(const Event& e, QByteArray msg)
@@ -772,69 +829,17 @@ Room::Timeline::size_type Room::Private::insertEvents(RoomEventsRange&& events,
return events.size();
}
-void Room::Private::addMember(User *u)
-{
- if (!hasMember(u))
- {
- insertMemberIntoMap(u);
- connect(u, &User::nameChanged, q,
- std::bind(&Private::renameMember, this, u, _2));
- emit q->userAdded(u);
- }
-}
-
-bool Room::Private::hasMember(User* u) const
-{
- return membersMap.values(u->name()).contains(u);
-}
-
-User* Room::Private::member(const QString& id) const
-{
- auto u = connection->user(id);
- return hasMember(u) ? u : nullptr;
-}
-
-void Room::Private::renameMember(User* u, QString oldName)
-{
- if (hasMember(u))
- {
- qCWarning(MAIN) << "Room::Private::renameMember(): the user "
- << u->name()
- << "is already known in the room under a new name.";
- return;
- }
-
- if (membersMap.values(oldName).contains(u))
- {
- removeMemberFromMap(oldName, u);
- insertMemberIntoMap(u);
- emit q->memberRenamed(u);
- }
-}
-
-void Room::Private::removeMember(User* u)
-{
- if (hasMember(u))
- {
- if ( !membersLeft.contains(u) )
- membersLeft.append(u);
- removeMemberFromMap(u->name(), u);
- emit q->userRemoved(u);
- }
-}
-
QString Room::roomMembername(const User* u) const
{
// See the CS spec, section 11.2.2.3
- QString username = u->name();
+ const auto username = u->name(this);
if (username.isEmpty())
return u->id();
// Get the list of users with the same display name. Most likely,
// there'll be one, but there's a chance there are more.
- auto namesakes = d->membersMap.values(username);
- if (namesakes.size() == 1)
+ if (d->membersMap.count(username) == 1)
return username;
// We expect a user to be a member of the room - but technically it is
@@ -851,12 +856,12 @@ QString Room::roomMembername(const User* u) const
// }
// In case of more than one namesake, use the full name to disambiguate
- return u->fullName();
+ return u->fullName(this);
}
QString Room::roomMembername(const QString& userId) const
{
- return roomMembername(connection()->user(userId));
+ return roomMembername(user(userId));
}
void Room::updateData(SyncRoomData&& data)
@@ -895,6 +900,15 @@ void Room::updateData(SyncRoomData&& data)
<< et.elapsed() << "ms";
}
+ if (!data.accountData.empty())
+ {
+ et.restart();
+ for (auto&& event: data.accountData)
+ processAccountDataEvent(move(event));
+ qCDebug(PROFILER) << "*** Room::processAccountData():"
+ << et.elapsed() << "ms";
+ }
+
if( data.highlightCount != d->highlightCount )
{
d->highlightCount = data.highlightCount;
@@ -1250,7 +1264,7 @@ void Room::Private::checkUnreadMessages(timeline_iter_t from)
// read receipts from the server (or, for the local user,
// markMessagesAsRead() invocation) to promote their read markers over
// the new message events.
- auto firstWriter = connection->user((*from)->senderId());
+ auto firstWriter = q->user((*from)->senderId());
if (q->readMarker(firstWriter) != timeline.crend())
{
promoteReadMarker(firstWriter, q->findInTimeline((*from)->id()));
@@ -1349,16 +1363,35 @@ void Room::processStateEvents(const RoomEvents& events)
}
case EventType::RoomMember: {
auto memberEvent = static_cast<RoomMemberEvent*>(event);
- // Can't use d->member() below because the user may be not a member (yet)
- auto u = d->connection->user(memberEvent->userId());
- u->processEvent(event);
+ auto u = user(memberEvent->userId());
+ u->processEvent(memberEvent, this);
if( memberEvent->membership() == MembershipType::Join )
{
- d->addMember(u);
+ if (memberJoinState(u) != JoinState::Join)
+ {
+ d->insertMemberIntoMap(u);
+ connect(u, &User::nameAboutToChange, this,
+ [=] (QString newName, QString, const Room* context) {
+ if (context == this)
+ emit memberAboutToRename(u, newName);
+ });
+ connect(u, &User::nameChanged, this,
+ [=] (QString, QString oldName, const Room* context) {
+ if (context == this)
+ d->renameMember(u, oldName);
+ });
+ emit userAdded(u);
+ }
}
else if( memberEvent->membership() == MembershipType::Leave )
{
- d->removeMember(u);
+ if (memberJoinState(u) == JoinState::Join)
+ {
+ if (!d->membersLeft.contains(u))
+ d->membersLeft.append(u);
+ d->removeMemberFromMap(u->name(this), u);
+ emit userRemoved(u);
+ }
}
break;
}
@@ -1380,8 +1413,9 @@ void Room::processEphemeralEvent(EventPtr event)
d->usersTyping.clear();
for( const QString& userId: typingEvent->users() )
{
- if (auto m = d->member(userId))
- d->usersTyping.append(m);
+ auto u = user(userId);
+ if (memberJoinState(u) == JoinState::Join)
+ d->usersTyping.append(u);
}
emit typingChanged();
break;
@@ -1403,8 +1437,11 @@ void Room::processEphemeralEvent(EventPtr event)
{
const auto newMarker = findInTimeline(p.evtId);
for( const Receipt& r: p.receipts )
- if (auto m = d->member(r.userId))
- d->promoteReadMarker(m, newMarker);
+ {
+ auto u = user(r.userId);
+ if (memberJoinState(u) == JoinState::Join)
+ d->promoteReadMarker(u, newMarker);
+ }
} else
{
qCDebug(EPHEMERAL) << "Event" << p.evtId
@@ -1414,9 +1451,12 @@ void Room::processEphemeralEvent(EventPtr event)
// 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 )
- if (auto m = d->member(r.userId))
- if (readMarker(m) == timelineEdge())
- d->setLastReadEvent(m, p.evtId);
+ {
+ auto u = user(r.userId);
+ if (memberJoinState(u) == JoinState::Join &&
+ readMarker(u) == timelineEdge())
+ d->setLastReadEvent(u, p.evtId);
+ }
}
}
if (receiptEvent->unreadMessages())
@@ -1429,6 +1469,19 @@ void Room::processEphemeralEvent(EventPtr event)
}
}
+void Room::processAccountDataEvent(EventPtr event)
+{
+ switch (event->type())
+ {
+ case EventType::Tag:
+ d->tags = static_cast<TagEvent*>(event.get())->tags();
+ emit tagsChanged();
+ break;
+ default:
+ d->accountData[event->jsonType()] = event->contentJson();
+ }
+}
+
QString Room::Private::roomNameFromMemberNames(const QList<User *> &userlist) const
{
// This is part 3(i,ii,iii) in the room displayname algorithm described
@@ -1448,6 +1501,12 @@ QString Room::Private::roomNameFromMemberNames(const QList<User *> &userlist) co
}
);
+ // Spec extension. A single person in the chat but not the local user
+ // (the local user is apparently invited).
+ if (userlist.size() == 1 && !isLocalUser(first_two.front()))
+ 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]);
@@ -1541,16 +1600,16 @@ QJsonObject Room::Private::toJson() const
appendStateEvent(stateEvents, "m.room.canonical_alias", "alias",
canonicalAlias);
- for (const auto &i : membersMap)
+ for (const auto *m : membersMap)
{
QJsonObject content;
content.insert("membership", QStringLiteral("join"));
- content.insert("displayname", i->name());
- content.insert("avatar_url", i->avatarUrl().toString());
+ content.insert("displayname", m->name(q));
+ content.insert("avatar_url", m->avatarUrl(q).toString());
QJsonObject memberEvent;
memberEvent.insert("type", QStringLiteral("m.room.member"));
- memberEvent.insert("state_key", i->id());
+ memberEvent.insert("state_key", m->id());
memberEvent.insert("content", content);
stateEvents.append(memberEvent);
}
@@ -1591,6 +1650,20 @@ QJsonObject Room::Private::toJson() const
result.insert("ephemeral", ephemeralObj);
}
+ {
+ QJsonObject accountDataObj;
+ if (!tags.empty())
+ {
+ QJsonObject tagsObj;
+ for (auto it = tags.begin(); it != tags.end(); ++it)
+ tagsObj.insert(it.key(), { {"order", it->order} });
+ if (!tagsObj.empty())
+ accountDataObj.insert("m.tag", tagsObj);
+ }
+ if (!accountDataObj.empty())
+ result.insert("account_data", accountDataObj);
+ }
+
QJsonObject unreadNotificationsObj;
if (highlightCount > 0)
unreadNotificationsObj.insert("highlight_count", highlightCount);
@@ -1614,11 +1687,15 @@ MemberSorter Room::memberSorter() const
bool MemberSorter::operator()(User *u1, User *u2) const
{
+ return operator()(u1, room->roomMembername(u2));
+}
+
+bool MemberSorter::operator ()(User* u1, const QString& u2name) const
+{
auto n1 = room->roomMembername(u1);
- auto n2 = room->roomMembername(u2);
if (n1.startsWith('@'))
n1.remove(0, 1);
- if (n2.startsWith('@'))
- n2.remove(0, 1);
+ auto n2 = u2name.midRef(u2name.startsWith('@') ? 1 : 0);
+
return n1.localeAwareCompare(n2) < 0;
}