From b2f9b212c78bc9dd7c69f6a2d1f94376adb488e3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Nov 2021 06:55:16 +0100 Subject: 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. --- lib/eventstats.h | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 lib/eventstats.h (limited to 'lib/eventstats.h') diff --git a/lib/eventstats.h b/lib/eventstats.h new file mode 100644 index 00000000..9be83377 --- /dev/null +++ b/lib/eventstats.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2021 Quotient contributors +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "room.h" + +namespace Quotient { + +//! \brief Counters of unread events and highlights with a precision flag +//! +//! This structure contains a static snapshot with values of unread counters +//! returned by Room::partiallyReadStats and Room::unreadStats (properties +//! or methods). +//! +//! \note It's just a simple grouping of counters and is not automatically +//! updated from the room as subsequent syncs arrive. +//! \sa Room::unreadStats, Room::partiallyReadStats, Room::isEventNotable +struct EventStats { + Q_GADGET + Q_PROPERTY(qsizetype notableCount MEMBER notableCount CONSTANT) + Q_PROPERTY(qsizetype highlightCount MEMBER highlightCount CONSTANT) + Q_PROPERTY(bool isEstimate MEMBER isEstimate CONSTANT) +public: + //! The number of "notable" events in an events range + //! \sa Room::isEventNotable + qsizetype notableCount = 0; + qsizetype highlightCount = 0; + //! \brief Whether the counter values above are exact + //! + //! This is false when the end marker (m.read receipt or m.fully_read) used + //! to collect the stats points to an event loaded locally and the counters + //! can therefore be calculated exactly using the locally available segment + //! of the timeline; true when the marker points to an event outside of + //! the local timeline (in which case the estimation is made basing on + //! the data supplied by the homeserver as well as counters saved from + //! the previous run of the client). + bool isEstimate = true; + + bool operator==(const EventStats& rhs) const& = default; + + //! \brief Check whether the event statistics are empty + //! + //! Empty statistics have notable and highlight counters of zero and + //! isEstimate set to false. + Q_INVOKABLE bool empty() const + { + return notableCount == 0 && !isEstimate && highlightCount == 0; + } + + using marker_t = Room::rev_iter_t; + + //! \brief Build event statistics on a range of events + //! + //! This is a factory that returns an EventStats instance with counts of + //! notable and highlighted events between \p from and \p to reverse + //! timeline iterators; the \p init parameter allows to override + //! the initial statistics object and start from other values. + static EventStats fromRange(const Room* room, const marker_t& from, + const marker_t& to, + const EventStats& init = { 0, 0, false }); + + //! \brief Build event statistics on a range from sync edge to marker + //! + //! This is mainly a shortcut for \code + //! fromRange(room, marker_t(room->syncEdge()), marker) + //! \endcode except that it also sets isEstimate to true if (and only if) + //! to == room->historyEdge(). + static EventStats fromMarker(const Room* room, const marker_t& marker); + + //! \brief Loads a statistics object from the cached counters + //! + //! Sets isEstimate to `true` unless both notableCount and highlightCount + //! are equal to -1. + static EventStats fromCachedCounters(Omittable notableCount, + Omittable highlightCount = none); + + //! \brief Update statistics when a read marker moves down the timeline + //! + //! Removes events between oldMarker and newMarker from statistics + //! calculation if \p oldMarker points to an existing event in the timeline, + //! or recalculates the statistics entirely if \p oldMarker points + //! to room->historyEdge(). Always results in exact statistics + //! (isEstimate == false. + //! \param oldMarker Must point correspond to the _current_ statistics + //! isEstimate state, i.e. it should point to + //! room->historyEdge() if isEstimate == true, or + //! to a valid position within the timeline otherwise + //! \param newMarker Must point to a valid position in the timeline (not to + //! room->historyEdge() that is equal to or closer to + //! the sync edge than \p oldMarker + //! \return true if either notableCount or highlightCount changed, or if + //! the statistics was completely recalculated; false otherwise + bool updateOnMarkerMove(const Room* room, const marker_t& oldMarker, + const marker_t& newMarker); + + //! \brief Validate the statistics object against the given marker + //! + //! Checks whether the statistics object data are valid for a given marker. + //! No stats recalculation takes place, only isEstimate and zero-ness + //! of notableCount are checked. + bool isValidFor(const Room* room, const marker_t& marker) const; +}; + +QDebug operator<<(QDebug dbg, const EventStats& es); + +} -- cgit v1.2.3 From c57d6de40fb790a4920a9c8ff235511860d68f32 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 24 Nov 2021 09:50:34 +0100 Subject: Don't require C++20 from the clients just yet --- lib/eventstats.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib/eventstats.h') diff --git a/lib/eventstats.h b/lib/eventstats.h index 9be83377..77c661a7 100644 --- a/lib/eventstats.h +++ b/lib/eventstats.h @@ -37,7 +37,14 @@ public: //! the previous run of the client). bool isEstimate = true; - bool operator==(const EventStats& rhs) const& = default; + // TODO: replace with = default once C++20 becomes a requirement on clients + bool operator==(const EventStats& rhs) const + { + return notableCount == rhs.notableCount + && highlightCount == rhs.highlightCount + && isEstimate == rhs.isEstimate; + } + bool operator!=(const EventStats& rhs) const { return !operator==(rhs); } //! \brief Check whether the event statistics are empty //! -- cgit v1.2.3