Branch data Line data Source code
1 : : #pragma once
2 : :
3 : : #include <QList>
4 : : #include <QString>
5 : : #include <memory>
6 : : #include <vector>
7 : :
8 : : #include "data/Models.h"
9 : :
10 : : // A node in the mail thread tree.
11 : : // Children are owned via unique_ptr; root nodes are owned by the caller.
12 : : // T-79.B1/H7: the node owns its MailHeader by value. A pointer into the
13 : : // source QList shared its (implicitly shared) buffer with MailListModel —
14 : : // the first detach made the tree paint stale data and a later reallocation
15 : : // turned every node pointer into a use-after-free.
16 : : struct ThreadNode {
17 : : MailHeader header;
18 : : ThreadNode *parent = nullptr;
19 : : std::vector<std::unique_ptr<ThreadNode>> children;
20 : : int depth = 0;
21 : :
22 : 241 : ~ThreadNode() = default;
23 : :
24 : : // Newest date in this subtree (for sorting root nodes).
25 : 381 : QDateTime newestDate() const {
26 : 381 : QDateTime newest = header.date;
27 [ + + ]: 405 : for (const auto &child : children) {
28 [ + - ]: 24 : QDateTime childDate = child->newestDate();
29 [ + - + + : 24 : if (childDate.isValid() && (!newest.isValid() || childDate > newest))
+ - + + +
- + + +
+ ]
30 : 22 : newest = childDate;
31 : 24 : }
32 : 381 : return newest;
33 : 0 : }
34 : :
35 : : // Total count of descendants (not including self).
36 : 99 : int descendantCount() const {
37 : 99 : int count = 0;
38 [ + + ]: 165 : for (const auto &child : children) {
39 [ + - ]: 66 : count += 1 + child->descendantCount();
40 : : }
41 : 99 : return count;
42 : : }
43 : :
44 : : // T-547: Check if any descendant has unseen mail.
45 : 13 : bool hasUnseenDescendant() const {
46 [ + + ]: 16 : for (const auto &child : children) {
47 [ + + ]: 9 : if (!child->header.isSeen())
48 : 6 : return true;
49 [ + - + + ]: 5 : if (child->hasUnseenDescendant())
50 : 2 : return true;
51 : : }
52 : 7 : return false;
53 : : }
54 : :
55 : : // T-547: Count unseen descendants (not including self).
56 : 102 : int unseenDescendantCount() const {
57 : 102 : int count = 0;
58 [ + + ]: 169 : for (const auto &child : children) {
59 [ + + ]: 67 : if (!child->header.isSeen())
60 : 61 : ++count;
61 [ + - ]: 67 : count += child->unseenDescendantCount();
62 : : }
63 : 102 : return count;
64 : : }
65 : : };
66 : :
67 : : // Builds a thread tree from a flat list of MailHeaders.
68 : : // Uses a simplified JWZ algorithm: In-Reply-To based + subject fallback.
69 : : class ThreadBuilder {
70 : : public:
71 : : // Build thread tree from flat header list.
72 : : // The returned unique_ptrs own the root-level ThreadNodes (and their
73 : : // children). Each node holds its own copy of the MailHeader, so 'headers'
74 : : // may be modified or destroyed after this call.
75 : : static std::vector<std::unique_ptr<ThreadNode>> buildThreads(const QList<MailHeader> &headers);
76 : :
77 : : private:
78 : : // Strip Re:/Fwd:/etc. prefixes for subject-based threading.
79 : : static QString normalizeSubject(const QString &subject);
80 : : };
|