aboutsummaryrefslogtreecommitdiff
path: root/room.cpp
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-03-28 10:11:56 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-03-28 10:11:56 +0900
commit0f85d696c595fddb47ffa4eaec305d93e2ac8ab0 (patch)
treeb5b648115723ed9a60d6f6ddef095c7900484e84 /room.cpp
parent93dc89d9353e9f30d42528d8ffaba992f58139aa (diff)
parentf213e02daa6b9e83e8e76d1576e446357c6c3bc7 (diff)
downloadlibquotient-0f85d696c595fddb47ffa4eaec305d93e2ac8ab0.tar.gz
libquotient-0f85d696c595fddb47ffa4eaec305d93e2ac8ab0.zip
Merge branch 'kitsune-unread-messages-count'
Diffstat (limited to 'room.cpp')
-rw-r--r--room.cpp236
1 files changed, 138 insertions, 98 deletions
diff --git a/room.cpp b/room.cpp
index 01055013..9dec2b4a 100644
--- a/room.cpp
+++ b/room.cpp
@@ -72,7 +72,6 @@ class Room::Private
public:
/** Map of user names to users. User names potentially duplicate, hence a multi-hashmap. */
typedef QMultiHash<QString, User*> members_map_t;
- typedef std::pair<rev_iter_t, rev_iter_t> rev_iter_pair_t;
Private(Connection* c, QString id_, JoinState initialJoinState)
: q(nullptr), connection(c), id(std::move(id_))
@@ -102,7 +101,7 @@ class Room::Private
members_map_t membersMap;
QList<User*> usersTyping;
QList<User*> membersLeft;
- bool unreadMessages = false;
+ int unreadMessages = 0;
bool displayed = false;
QString firstDisplayedEventId;
QString lastDisplayedEventId;
@@ -190,10 +189,10 @@ class Room::Private
* Removes events from the passed container that are already in the timeline
*/
void dropDuplicateEvents(RoomEvents* events) const;
- void checkUnreadMessages(timeline_iter_t from);
void setLastReadEvent(User* u, const QString& eventId);
- rev_iter_pair_t promoteReadMarker(User* u, rev_iter_t newMarker,
+ void updateUnreadCount(rev_iter_t from, rev_iter_t to);
+ void promoteReadMarker(User* u, rev_iter_t newMarker,
bool force = false);
void markMessagesAsRead(rev_iter_t upToMarker);
@@ -359,16 +358,55 @@ void Room::Private::setLastReadEvent(User* u, const QString& eventId)
}
}
-Room::Private::rev_iter_pair_t
-Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker,
- bool force)
+void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to)
+{
+ Q_ASSERT(from >= timeline.crbegin() && from <= timeline.crend());
+ Q_ASSERT(to >= from && to <= timeline.crend());
+
+ // Catch a special case when the last read event id refers to an event
+ // that has just arrived. In this case we should recalculate
+ // unreadMessages and might need to promote the read marker further
+ // over local-origin messages.
+ const auto readMarker = q->readMarker();
+ if (readMarker >= from && readMarker < to)
+ {
+ qCDebug(MAIN) << "Discovered last read event in room" << displayname;
+ 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));
+ if (et.nsecsElapsed() > 10000)
+ qCDebug(PROFILER) << "Counting gained unread messages took" << et;
+
+ if(newUnreadMessages > 0)
+ {
+ // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count
+ if (unreadMessages < 0)
+ unreadMessages = 0;
+
+ unreadMessages += newUnreadMessages;
+ qCDebug(MAIN) << "Room" << displayname << "has gained"
+ << newUnreadMessages << "unread message(s),"
+ << (q->readMarker() == timeline.crend() ?
+ "in total at least" : "in total")
+ << unreadMessages << "unread message(s)";
+ emit q->unreadMessagesChanged(q);
+ }
+}
+
+void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force)
{
Q_ASSERT_X(u, __FUNCTION__, "User* should not be nullptr");
Q_ASSERT(newMarker >= timeline.crbegin() && newMarker <= timeline.crend());
const auto prevMarker = q->readMarker(u);
if (!force && prevMarker <= newMarker) // Remember, we deal with reverse iterators
- return { prevMarker, prevMarker };
+ return;
Q_ASSERT(newMarker < timeline.crend());
@@ -378,41 +416,49 @@ Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker,
[=](const TimelineItem& ti) { return ti->senderId() != u->id(); });
setLastReadEvent(u, (*(eagerMarker - 1))->id());
- if (isLocalUser(u) && unreadMessages)
+ if (isLocalUser(u))
{
- auto stillUnreadMessagesCount = count_if(eagerMarker, timeline.cend(),
- std::bind(&Room::Private::isEventNotable, this, _1));
-
- if (stillUnreadMessagesCount == 0)
+ const auto oldUnreadCount = unreadMessages;
+ QElapsedTimer et; et.start();
+ unreadMessages = count_if(eagerMarker, timeline.cend(),
+ std::bind(&Room::Private::isEventNotable, this, _1));
+ if (et.nsecsElapsed() > 10000)
+ qCDebug(PROFILER) << "Recounting unread messages took" << et;
+
+ // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count
+ if (unreadMessages == 0)
+ unreadMessages = -1;
+
+ if (force || unreadMessages != oldUnreadCount)
{
- unreadMessages = false;
- qCDebug(MAIN) << "Room" << displayname << "has no more unread messages";
+ if (unreadMessages == -1)
+ {
+ qCDebug(MAIN) << "Room" << displayname
+ << "has no more unread messages";
+ } else
+ qCDebug(MAIN) << "Room" << displayname << "still has"
+ << unreadMessages << "unread message(s)";
emit q->unreadMessagesChanged(q);
- } else
- qCDebug(MAIN) << "Room" << displayname << "still has"
- << stillUnreadMessagesCount << "unread message(s)";
+ }
}
-
- // Return newMarker, rather than eagerMarker, to save markMessagesAsRead()
- // (that calls this method) from going back through knowingly-local messages.
- return { prevMarker, newMarker };
}
-void Room::Private::markMessagesAsRead(Room::rev_iter_t upToMarker)
+void Room::Private::markMessagesAsRead(rev_iter_t upToMarker)
{
- rev_iter_pair_t markers = promoteReadMarker(q->localUser(), upToMarker);
- if (markers.first != markers.second)
+ const auto prevMarker = q->readMarker();
+ promoteReadMarker(q->localUser(), upToMarker);
+ if (prevMarker != upToMarker)
qCDebug(MAIN) << "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 (; markers.second < markers.first; ++markers.second)
+ for (; upToMarker < prevMarker; ++upToMarker)
{
- if ((*markers.second)->senderId() != q->localUser()->id())
+ if ((*upToMarker)->senderId() != q->localUser()->id())
{
connection->callApi<PostReceiptJob>(id, "m.read",
- (*markers.second)->id());
+ (*upToMarker)->id());
break;
}
}
@@ -429,7 +475,12 @@ void Room::markAllMessagesAsRead()
d->markMessagesAsRead(d->timeline.crbegin());
}
-bool Room::hasUnreadMessages()
+bool Room::hasUnreadMessages() const
+{
+ return unreadCount() >= 0;
+}
+
+int Room::unreadCount() const
{
return d->unreadMessages;
}
@@ -983,6 +1034,14 @@ void Room::updateData(SyncRoomData&& data)
for( auto&& ephemeralEvent: data.ephemeral )
processEphemeralEvent(move(ephemeralEvent));
+ // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count
+ if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages)
+ {
+ qCDebug(MAIN) << "Setting unread_count to" << data.unreadCount;
+ d->unreadMessages = data.unreadCount;
+ emit unreadMessagesChanged(this);
+ }
+
if( data.highlightCount != d->highlightCount )
{
d->highlightCount = data.highlightCount;
@@ -1303,50 +1362,38 @@ void Room::Private::addNewMessageEvents(RoomEvents&& events)
if (!normalEvents.empty())
emit q->aboutToAddNewMessages(normalEvents);
const auto insertedSize = insertEvents(std::move(normalEvents), Newer);
+ const auto from = timeline.cend() - insertedSize;
if (insertedSize > 0)
{
qCDebug(MAIN)
<< "Room" << displayname << "received" << insertedSize
<< "new events; the last event is now" << timeline.back();
- q->onAddNewTimelineEvents(timeline.cend() - insertedSize);
+ q->onAddNewTimelineEvents(from);
}
for (auto&& r: redactions)
processRedaction(move(r));
if (insertedSize > 0)
{
emit q->addedMessages();
- checkUnreadMessages(timeline.cend() - insertedSize);
- }
-
- Q_ASSERT(timeline.size() == timelineSize + insertedSize);
-}
-void Room::Private::checkUnreadMessages(timeline_iter_t from)
-{
- Q_ASSERT(from < timeline.cend());
- const auto newUnreadMessages = count_if(from, timeline.cend(),
- std::bind(&Room::Private::isEventNotable, this, _1));
+ // The first event in the just-added batch (referred to by `from`)
+ // defines whose read marker can possibly be promoted any further over
+ // the same author's events newly arrived. Others will need explicit
+ // read receipts from the server (or, for the local user,
+ // markMessagesAsRead() invocation) to promote their read markers over
+ // the new message events.
+ auto firstWriter = q->user((*from)->senderId());
+ if (q->readMarker(firstWriter) != timeline.crend())
+ {
+ promoteReadMarker(firstWriter, rev_iter_t(from) - 1);
+ qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id()
+ << "to" << *q->readMarker(firstWriter);
+ }
- // The first event in the just-added batch (referred to by `from`)
- // defines whose read marker can possibly be promoted any further over
- // the same author's events newly arrived. Others will need explicit
- // read receipts from the server (or, for the local user,
- // markMessagesAsRead() invocation) to promote their read markers over
- // the new message events.
- auto firstWriter = q->user((*from)->senderId());
- if (q->readMarker(firstWriter) != timeline.crend())
- {
- promoteReadMarker(firstWriter, q->findInTimeline((*from)->id()));
- qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id()
- << "to" << *q->readMarker(firstWriter);
+ updateUnreadCount(timeline.crbegin(), rev_iter_t(from));
}
- if(!unreadMessages && newUnreadMessages > 0)
- {
- unreadMessages = true;
- emit q->unreadMessagesChanged(q);
- qCDebug(MAIN) << "Room" << displayname << "has unread messages";
- }
+ Q_ASSERT(timeline.size() == timelineSize + insertedSize);
}
void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
@@ -1361,24 +1408,17 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
return;
emit q->aboutToAddHistoricalMessages(normalEvents);
- const bool thereWasNoReadMarker = q->readMarker() == timeline.crend();
const auto insertedSize = insertEvents(std::move(normalEvents), Older);
+ const auto from = timeline.crend() - insertedSize;
- // Catch a special case when the last read event id refers to an event
- // that was outside the loaded timeline and has just arrived. Depending on
- // other messages next to the last read one, we might need to promote
- // the read marker and update unreadMessages flag.
- const auto curReadMarker = q->readMarker();
- if (thereWasNoReadMarker && curReadMarker != timeline.crend())
- {
- qCDebug(MAIN) << "Discovered last read event in a historical batch";
- promoteReadMarker(q->localUser(), curReadMarker, true);
- }
qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize
<< "past events; the oldest event is now" << timeline.front();
- q->onAddHistoricalTimelineEvents(timeline.crend() - insertedSize);
+ q->onAddHistoricalTimelineEvents(from);
emit q->addedMessages();
+ if (from <= q->readMarker())
+ updateUnreadCount(from, timeline.crend());
+
Q_ASSERT(timeline.size() == timelineSize + insertedSize);
}
@@ -1563,7 +1603,7 @@ void Room::processAccountDataEvent(EventPtr event)
if (newTags == d->tags)
break;
d->tags = newTags;
- qCDebug(MAIN) << "Room" << id() << "is tagged with: "
+ qCDebug(MAIN) << "Room" << id() << "is tagged with:"
<< tagNames().join(", ");
emit tagsChanged();
break;
@@ -1572,18 +1612,14 @@ void Room::processAccountDataEvent(EventPtr event)
{
const auto* rmEvent = static_cast<ReadMarkerEvent*>(event.get());
const auto& readEventId = rmEvent->event_id();
- qCDebug(MAIN) << "Server-side read marker at " << readEventId;
- static const auto UnreadMsgsKey =
- QStringLiteral("x-qmatrixclient.unread_messages");
- if (rmEvent->contentJson().contains(UnreadMsgsKey))
- d->unreadMessages =
- rmEvent->contentJson().value(UnreadMsgsKey).toBool();
+ qCDebug(MAIN) << "Server-side read marker at" << readEventId;
d->serverReadMarker = readEventId;
const auto newMarker = findInTimeline(readEventId);
if (newMarker != timelineEdge())
d->markMessagesAsRead(newMarker);
- else
+ else {
d->setLastReadEvent(localUser(), readEventId);
+ }
break;
}
default:
@@ -1692,6 +1728,22 @@ void appendStateEvent(QJsonArray& events, const QString& type,
appendStateEvent((events), QStringLiteral(type), \
{{ QStringLiteral(name), content }});
+void appendEvent(QJsonArray& events, const QString& type,
+ const QJsonObject& content)
+{
+ if (!content.isEmpty())
+ events.append(QJsonObject
+ { { QStringLiteral("type"), type }
+ , { QStringLiteral("content"), content }
+ });
+}
+
+template <typename EvtT>
+void appendEvent(QJsonArray& events, const EvtT& event)
+{
+ appendEvent(events, EvtT::TypeId, event.toJson());
+}
+
QJsonObject Room::Private::toJson() const
{
QElapsedTimer et; et.start();
@@ -1723,40 +1775,28 @@ QJsonObject Room::Private::toJson() const
QJsonArray accountDataEvents;
if (!tags.empty())
- accountDataEvents.append(QJsonObject(
- { { QStringLiteral("type"), TagEvent::typeId() }
- , { QStringLiteral("content"), TagEvent(tags).toJson() }
- }));
+ appendEvent(accountDataEvents, TagEvent(tags));
if (!serverReadMarker.isEmpty())
- {
- auto contentJson = ReadMarkerEvent(serverReadMarker).toJson();
- contentJson.insert(QStringLiteral("x-qmatrixclient.unread_messages"),
- unreadMessages);
- accountDataEvents.append(QJsonObject(
- { { QStringLiteral("type"), ReadMarkerEvent::typeId() }
- , { QStringLiteral("content"), contentJson }
- }));
- }
+ appendEvent(accountDataEvents, ReadMarkerEvent(serverReadMarker));
if (!accountData.empty())
{
for (auto it = accountData.begin(); it != accountData.end(); ++it)
- accountDataEvents.append(QJsonObject {
- {"type", it.key()},
- {"content", QJsonObject::fromVariantHash(it.value())}
- });
+ appendEvent(accountDataEvents, it.key(),
+ QJsonObject::fromVariantHash(it.value()));
}
- QJsonObject accountDataEventsObj;
result.insert("account_data", QJsonObject {{ "events", accountDataEvents }});
QJsonObject unreadNotificationsObj;
+
+ unreadNotificationsObj.insert(SyncRoomData::UnreadCountKey, unreadMessages);
if (highlightCount > 0)
unreadNotificationsObj.insert("highlight_count", highlightCount);
if (notificationCount > 0)
unreadNotificationsObj.insert("notification_count", notificationCount);
- if (!unreadNotificationsObj.isEmpty())
- result.insert("unread_notifications", unreadNotificationsObj);
+
+ result.insert("unread_notifications", unreadNotificationsObj);
if (et.elapsed() > 50)
qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" << et;