| 1 | // SPDX-FileCopyrightText: 2021 Quotient contributors |
| 2 | // SPDX-License-Identifier: LGPL-2.1-or-later |
| 3 | |
| 4 | #pragma once |
| 5 | |
| 6 | #include "room.h" |
| 7 | |
| 8 | namespace Quotient { |
| 9 | |
| 10 | //! \brief Counters of unread events and highlights with a precision flag |
| 11 | //! |
| 12 | //! This structure contains a static snapshot with values of unread counters |
| 13 | //! returned by Room::partiallyReadStats and Room::unreadStats (properties |
| 14 | //! or methods). |
| 15 | //! |
| 16 | //! \note It's just a simple grouping of counters and is not automatically |
| 17 | //! updated from the room as subsequent syncs arrive. |
| 18 | //! \sa Room::unreadStats, Room::partiallyReadStats, Room::isEventNotable |
| 19 | struct QUOTIENT_API EventStats { |
| 20 | Q_GADGET |
| 21 | Q_PROPERTY(qsizetype notableCount MEMBER notableCount CONSTANT) |
| 22 | Q_PROPERTY(qsizetype highlightCount MEMBER highlightCount CONSTANT) |
| 23 | Q_PROPERTY(bool isEstimate MEMBER isEstimate CONSTANT) |
| 24 | public: |
| 25 | //! The number of "notable" events in an events range |
| 26 | //! \sa Room::isEventNotable |
| 27 | qsizetype notableCount = 0; |
| 28 | qsizetype highlightCount = 0; |
| 29 | //! \brief Whether the counter values above are exact |
| 30 | //! |
| 31 | //! This is false when the end marker (m.read receipt or m.fully_read) used |
| 32 | //! to collect the stats points to an event loaded locally and the counters |
| 33 | //! can therefore be calculated exactly using the locally available segment |
| 34 | //! of the timeline; true when the marker points to an event outside of |
| 35 | //! the local timeline (in which case the estimation is made basing on |
| 36 | //! the data supplied by the homeserver as well as counters saved from |
| 37 | //! the previous run of the client). |
| 38 | bool isEstimate = true; |
| 39 | |
| 40 | // TODO: replace with = default once C++20 becomes a requirement on clients |
| 41 | bool operator==(const EventStats& rhs) const |
| 42 | { |
| 43 | return notableCount == rhs.notableCount |
| 44 | && highlightCount == rhs.highlightCount |
| 45 | && isEstimate == rhs.isEstimate; |
| 46 | } |
| 47 | bool operator!=(const EventStats& rhs) const { return !operator==(rhs); } |
| 48 | |
| 49 | //! \brief Check whether the event statistics are empty |
| 50 | //! |
| 51 | //! Empty statistics have notable and highlight counters of zero and |
| 52 | //! isEstimate set to false. |
| 53 | Q_INVOKABLE bool empty() const |
| 54 | { |
| 55 | return notableCount == 0 && !isEstimate && highlightCount == 0; |
| 56 | } |
| 57 | |
| 58 | using marker_t = Room::rev_iter_t; |
| 59 | |
| 60 | //! \brief Build event statistics on a range of events |
| 61 | //! |
| 62 | //! This is a factory that returns an EventStats instance with counts of |
| 63 | //! notable and highlighted events between \p from and \p to reverse |
| 64 | //! timeline iterators; the \p init parameter allows to override |
| 65 | //! the initial statistics object and start from other values. |
| 66 | static EventStats fromRange(const Room* room, const marker_t& from, |
| 67 | const marker_t& to, |
| 68 | const EventStats& init = { .notableCount: .notableCount: 0, .highlightCount: .highlightCount: 0, .isEstimate: .isEstimate: false }); |
| 69 | |
| 70 | //! \brief Build event statistics on a range from sync edge to marker |
| 71 | //! |
| 72 | //! This is mainly a shortcut for \code |
| 73 | //! <tt>fromRange(room, marker_t(room->syncEdge()), marker)</tt> |
| 74 | //! \endcode except that it also sets isEstimate to true if (and only if) |
| 75 | //! <tt>to == room->historyEdge()</tt>. |
| 76 | static EventStats fromMarker(const Room* room, const marker_t& marker); |
| 77 | |
| 78 | //! \brief Loads a statistics object from the cached counters |
| 79 | //! |
| 80 | //! Sets isEstimate to `true` unless both notableCount and highlightCount |
| 81 | //! are equal to -1. |
| 82 | static EventStats fromCachedCounters(Omittable<int> notableCount, |
| 83 | Omittable<int> highlightCount = none); |
| 84 | |
| 85 | //! \brief Update statistics when a read marker moves down the timeline |
| 86 | //! |
| 87 | //! Removes events between oldMarker and newMarker from statistics |
| 88 | //! calculation if \p oldMarker points to an existing event in the timeline, |
| 89 | //! or recalculates the statistics entirely if \p oldMarker points |
| 90 | //! to <tt>room->historyEdge()</tt>. Always results in exact statistics |
| 91 | //! (<tt>isEstimate == false</tt>. |
| 92 | //! \param oldMarker Must point correspond to the _current_ statistics |
| 93 | //! isEstimate state, i.e. it should point to |
| 94 | //! <tt>room->historyEdge()</tt> if <tt>isEstimate == true</tt>, or |
| 95 | //! to a valid position within the timeline otherwise |
| 96 | //! \param newMarker Must point to a valid position in the timeline (not to |
| 97 | //! <tt>room->historyEdge()</tt> that is equal to or closer to |
| 98 | //! the sync edge than \p oldMarker |
| 99 | //! \return true if either notableCount or highlightCount changed, or if |
| 100 | //! the statistics was completely recalculated; false otherwise |
| 101 | bool updateOnMarkerMove(const Room* room, const marker_t& oldMarker, |
| 102 | const marker_t& newMarker); |
| 103 | |
| 104 | //! \brief Validate the statistics object against the given marker |
| 105 | //! |
| 106 | //! Checks whether the statistics object data are valid for a given marker. |
| 107 | //! No stats recalculation takes place, only isEstimate and zero-ness |
| 108 | //! of notableCount are checked. |
| 109 | bool isValidFor(const Room* room, const marker_t& marker) const; |
| 110 | }; |
| 111 | |
| 112 | QUOTIENT_API QDebug operator<<(QDebug dbg, const EventStats& es); |
| 113 | |
| 114 | } |
| 115 | |