diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-03-28 10:11:56 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2018-03-28 10:11:56 +0900 |
commit | 0f85d696c595fddb47ffa4eaec305d93e2ac8ab0 (patch) | |
tree | b5b648115723ed9a60d6f6ddef095c7900484e84 /room.cpp | |
parent | 93dc89d9353e9f30d42528d8ffaba992f58139aa (diff) | |
parent | f213e02daa6b9e83e8e76d1576e446357c6c3bc7 (diff) | |
download | libquotient-0f85d696c595fddb47ffa4eaec305d93e2ac8ab0.tar.gz libquotient-0f85d696c595fddb47ffa4eaec305d93e2ac8ab0.zip |
Merge branch 'kitsune-unread-messages-count'
Diffstat (limited to 'room.cpp')
-rw-r--r-- | room.cpp | 236 |
1 files changed, 138 insertions, 98 deletions
@@ -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; |