MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 5 / 0 / 5
Functions: 100.0% 4 / 0 / 4
Branches: -% 0 / 0 / 0

service/MailBodyWriteService.h
Line Branch Exec Source
1 #pragma once
2
3 #include "data/MailRequest.h"
4 #include "data/Models.h"
5 #include "service/MimeParser.h"
6 #include <QObject>
7 #include <QThreadPool>
8 #include <functional>
9 #include <atomic>
10 #include <memory>
11 #include <optional>
12 #include <QHash>
13 #include <QMap>
14 #include <QMutex>
15 #include <QSet>
16
17 class MailCache;
18 struct MailHeaderStreamIdentity {
19 QString databasePath;
20 QString account;
21 QString folder;
22 qint64 folderId = -1;
23 quint32 uidValidity = 0;
24 };
25
26 // Changes observed after a FETCH starts take precedence over its older payload.
27 // This state is bounded and lives only as long as the stream or its queued writes.
28 class MailHeaderStateFence {
29 public:
30 ~MailHeaderStateFence();
31 QString apply(QList<MailHeader> &headers) const;
32 521 void cancel() { m_cancelled.store(true); }
33
34 private:
35 friend class MailBodyWriteService;
36 struct Change {
37 std::optional<quint32> flags;
38 std::optional<bool> spam;
39 std::optional<QStringList> labels;
40 QMap<QString, bool> labelChanges;
41 bool removed = false;
42 };
43 void amend(qint64 uid, const std::function<void(Change &)> &change);
44 MailHeaderStreamIdentity identity;
45 QString writeDomain;
46 QSet<qint64> uids;
47 mutable QMutex mutex;
48 QHash<qint64, Change> changes;
49 qint64 bytes = 0;
50 QString error;
51 std::atomic_bool m_cancelled{false};
52 QMetaObject::Connection destroyed;
53 };
54
55 struct MailHeaderWriteResult {
56 bool stored = false;
57 QString error;
58 QList<MailHeader> headers;
59 bool projectionChanged = true;
60 int totalCount = -1; // Aggregate from the committed worker snapshot
61 QSet<qint64> pendingMoveUids; // Journal view after the header commit
62 quint32 uidValidity = 0; // Confirmed epoch for downstream readers
63 };
64
65 struct MailBodyWriteResult {
66 enum class Outcome { Stored, Failed, Busy, Limited };
67 Outcome outcome = Outcome::Failed;
68 QString error;
69 MailBody body;
70 std::optional<MailHeader> header;
71 bool hasVisibleAttachment = false;
72 int inlineImageCount = 0;
73 };
74
75 // One application-owned payload writer. Accepted tasks survive consumer
76 // destruction; each task validates mailbox identity inside its transaction.
77 class MailBodyWriteService : public QObject {
78 public:
79 // Header batches retried after SQLITE_BUSY (each waits busy_timeout).
80 static constexpr int MaximumBusyAttempts = 3;
81 using Callback = std::function<void(MailBodyWriteResult)>;
82 enum class Priority { Speculative = -1, Normal = 0, Foreground = 1 };
83 static constexpr qint64 MaximumRequestBytes = 128LL * 1024 * 1024;
84 static constexpr qint64 MaximumQueuedBytes = 192LL * 1024 * 1024;
85 static constexpr int MaximumRequests = 8;
86 explicit MailBodyWriteService(QObject *parent = nullptr);
87 ~MailBodyWriteService() override;
88 static MailBodyWriteService *shared();
89 void write(QObject *owner, const QString &databasePath, const MailId &mailId,
90 quint32 uidValidity, QByteArray raw, MimeMessage parsed,
91 Callback callback, Priority priority = Priority::Normal);
92 // Advances on the worker immediately after a successful body commit, before
93 // its GUI completion callback can race a display snapshot callback.
94 1218 quint64 bodyStoreRevision() const {
95 2436 return m_bodyStoreRevision->load(std::memory_order_acquire);
96 }
97 using HeaderCallback = std::function<void(MailHeaderWriteResult)>;
98 void observeCache(MailCache *cache);
99 std::shared_ptr<MailHeaderStateFence> beginHeaderStream(
100 QObject *owner, const MailHeaderStreamIdentity &identity,
101 const QList<qint64> &uids = {});
102 void preserveHeaderFlags(const std::shared_ptr<MailHeaderStateFence> &fence,
103 qint64 uid, quint32 flags);
104 // A second header FETCH of the same folder starts while the stream of the
105 // first is still open: accept the new UIDs too instead of replacing the
106 // stream (the first FETCH's batches would then fail its identity check).
107 // Empty `uids` (a UID range) makes the stream accept every UID. False when
108 // the stream is no longer active or belongs to another folder/epoch.
109 bool widenHeaderStream(const std::shared_ptr<MailHeaderStateFence> &fence,
110 const MailHeaderStreamIdentity &identity,
111 const QList<qint64> &uids);
112 void writeHeaders(QObject *owner, QList<MailHeader> headers,
113 std::shared_ptr<MailHeaderStateFence> fence, HeaderCallback callback);
114
115 37 int pendingCount() const { return m_pendingCount; }
116 13 qint64 pendingBytes() const { return m_pendingBytes; }
117
118 private:
119 static void stopSharedBeforeTeardown(); // S106-120, see shared()
120 friend class TestMailBodyWrite;
121 friend class TestSprint55Controller;
122 static constexpr qint64 ForegroundReservedBytes = 64LL * 1024 * 1024;
123 void stop();
124 void amendHeaderState(const QString &databasePath, qint64 folderId, qint64 uid,
125 const std::function<void(MailHeaderStateFence::Change &)> &change);
126 QMutex m_fencesMutex;
127 QList<std::weak_ptr<MailHeaderStateFence>> m_fences;
128 QThreadPool m_pool;
129 bool m_stopping = false;
130 int m_pendingCount = 0;
131 qint64 m_pendingBytes = 0;
132 std::shared_ptr<std::atomic<quint64>> m_bodyStoreRevision =
133 std::make_shared<std::atomic<quint64>>(0);
134 };
135