aboutsummaryrefslogtreecommitdiff
path: root/lib/eventstats.cpp
diff options
context:
space:
mode:
authorAlexey Rusakov <Kitsune-Ral@users.sf.net>2021-11-21 06:55:16 +0100
committerAlexey Rusakov <Kitsune-Ral@users.sf.net>2021-11-21 07:07:00 +0100
commitb2f9b212c78bc9dd7c69f6a2d1f94376adb488e3 (patch)
tree4a282de17e2b6157b1ce3dc97b2b245853adc304 /lib/eventstats.cpp
parente7babe6715672a358f7cc8b90d5df27e21a1b3e8 (diff)
downloadlibquotient-b2f9b212c78bc9dd7c69f6a2d1f94376adb488e3.tar.gz
libquotient-b2f9b212c78bc9dd7c69f6a2d1f94376adb488e3.zip
EventStats and Room::partiallyRead/unreadStats()
This introduces a new API to count unread events that would allow to obtain those unread and highlight counts since either the fully read marker (Room::partiallyReadStats) or the last read receipt (Room::unreadStats). Element uses the read receipt as the anchor to count unread numbers, while Quaternion historically used the fully read marker for that (with the pre-0.7 library sticking the two markers to each other). From now on the meaning of "unread" in Quotient is aligned with that of the spec and Element, and "partially read" means events between the fully read marker and the local read receipt; the design allows client authors to use either or both counting strategies as they see fit. Respectively, Room::P::setFullyReadMarker() updates partially-read statistics, while Room::P::setLastReadReceipt(), when called on a local user, updates unread statistics. Room::notificationCount() and Room::highlightCount() maintain their previous meaning as the counters since the last read receipt; Room::notificationCount() counts unread events locally, falling back to the value from the above-mentioned key defined by MSC2654, and if that is not there, further to `unread_notifications/notification_count` defined in the current spec. Room::highlightCount(), however, is still taken from the homeserver, not from Room::unreadStats().highlightCount.
Diffstat (limited to 'lib/eventstats.cpp')
-rw-r--r--lib/eventstats.cpp98
1 files changed, 98 insertions, 0 deletions
diff --git a/lib/eventstats.cpp b/lib/eventstats.cpp
new file mode 100644
index 00000000..9fa7f5ff
--- /dev/null
+++ b/lib/eventstats.cpp
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2021 Quotient contributors
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "eventstats.h"
+
+using namespace Quotient;
+
+EventStats EventStats::fromRange(const Room* room, const Room::rev_iter_t& from,
+ const Room::rev_iter_t& to,
+ const EventStats& init)
+{
+ Q_ASSERT(to <= room->historyEdge());
+ Q_ASSERT(from >= Room::rev_iter_t(room->syncEdge()));
+ Q_ASSERT(from <= to);
+ QElapsedTimer et;
+ et.start();
+ const auto result =
+ accumulate(from, to, init,
+ [room](EventStats acc, const TimelineItem& ti) {
+ acc.notableCount += room->isEventNotable(ti);
+ acc.highlightCount += room->notificationFor(ti).type
+ == Notification::Highlight;
+ return acc;
+ });
+ if (et.nsecsElapsed() > profilerMinNsecs() / 10)
+ qCDebug(PROFILER).nospace()
+ << "Event statistics collection over index range [" << from->index()
+ << "," << (to - 1)->index() << "] took " << et;
+ return result;
+}
+
+EventStats EventStats::fromMarker(const Room* room,
+ const EventStats::marker_t& marker)
+{
+ const auto s = fromRange(room, marker_t(room->syncEdge()), marker,
+ { 0, 0, marker == room->historyEdge() });
+ Q_ASSERT(s.isValidFor(room, marker));
+ return s;
+}
+
+EventStats EventStats::fromCachedCounters(Omittable<int> notableCount,
+ Omittable<int> highlightCount)
+{
+ const auto hCount = std::max(0, highlightCount.value_or(0));
+ if (!notableCount.has_value())
+ return { 0, hCount, true };
+ auto nCount = notableCount.value_or(0);
+ return { std::max(0, nCount), hCount, nCount != -1 };
+}
+
+bool EventStats::updateOnMarkerMove(const Room* room, const marker_t& oldMarker,
+ const marker_t& newMarker)
+{
+ if (newMarker == oldMarker)
+ return false;
+
+ // Double-check consistency between the old marker and the old stats
+ Q_ASSERT(isValidFor(room, oldMarker));
+ Q_ASSERT(oldMarker > newMarker);
+
+ // A bit of optimisation: only calculate the difference if the marker moved
+ // less than half the remaining timeline ahead; otherwise, recalculation
+ // over the remaining timeline will very likely be faster.
+ if (oldMarker != room->historyEdge()
+ && oldMarker - newMarker < newMarker - marker_t(room->syncEdge())) {
+ const auto removedStats = fromRange(room, newMarker, oldMarker);
+ Q_ASSERT(notableCount >= removedStats.notableCount
+ && highlightCount >= removedStats.highlightCount);
+ notableCount -= removedStats.notableCount;
+ highlightCount -= removedStats.highlightCount;
+ return removedStats.notableCount > 0 || removedStats.highlightCount > 0;
+ }
+
+ const auto newStats = EventStats::fromMarker(room, newMarker);
+ if (!isEstimate && newStats == *this)
+ return false;
+ *this = newStats;
+ return true;
+}
+
+bool EventStats::isValidFor(const Room* room, const marker_t& marker) const
+{
+ const auto markerAtHistoryEdge = marker == room->historyEdge();
+ // Either markerAtHistoryEdge and isEstimate are in the same state, or it's
+ // a special case of no notable events and the marker at history edge
+ // (then isEstimate can assume any value).
+ return markerAtHistoryEdge == isEstimate
+ || (markerAtHistoryEdge && notableCount == 0);
+}
+
+QDebug Quotient::operator<<(QDebug dbg, const EventStats& es)
+{
+ QDebugStateSaver _(dbg);
+ dbg.nospace() << es.notableCount << '/' << es.highlightCount;
+ if (es.isEstimate)
+ dbg << " (estimated)";
+ return dbg;
+}