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: 95.2% 20 / 0 / 21
Functions: 94.4% 17 / 0 / 18
Branches: 100.0% 1 / 0 / 1

controller/MailBodyTransport.h
Line Branch Exec Source
1 #pragma once
2
3 #include "controller/BodyRequestRegistry.h"
4 #include "controller/FolderContext.h"
5 #include "data/Models.h"
6 #include <QHash>
7 #include <QList>
8 #include <QObject>
9 #include <QSet>
10 #include <QThreadPool>
11 #include <functional>
12 #include <optional>
13
14 class ImapService;
15 class MailCache;
16 class MailDisplayPort;
17 class MailListModel;
18 class QTimer;
19 struct MailBodyWriteResult;
20 struct MimeMessage;
21
22 // Body transport of the mail reader (Sprint 91 S91.4.1, extracted from
23 // MailController): body requests per consumer (display, tabs, prefetch),
24 // demand-first dispatch on the body connection, the bounded parse/persist
25 // pipeline (P90.9), reconnect retries, speculative prefetch policy
26 // (T-87.G2) and attachment downloads. Callers reach it as
27 // `controller.bodies()`.
28 //
29 // Owner: MailController (value member). GUI thread; payload preparation
30 // runs on the unit's two-thread pool. The body connection's lifecycle stays
31 // in the controller (ensureBodyConnection); it calls connectBodyTransport()
32 // at the former position, so the connection's stateChanged receivers keep
33 // their order (lifecycle log, body transport, mutations, health). The
34 // display port, the loading placeholder timer, the folder context, network
35 // and fetch flags stay owned by the controller and are read by reference;
36 // display/selection code of the controller uses requests() and the small
37 // state methods below. Controller signals keep their emitter (notifiers).
38 class MailBodyTransport final : public QObject {
39 8 Q_OBJECT
40 public:
41 struct Deps {
42 std::function<void(const MailId &)> displayMailId;
43 std::function<void()> ensureBodyConnection;
44 std::function<void(qint64 uid, qint64 folderId, std::optional<bool> visibleSeen)>
45 markMailAsSeenInFolder;
46 std::function<void(const MailHeader &, const QString &, bool)> presentError;
47 std::function<void(const MailHeader &, const MailBody &)> presentMail;
48 // MailController signals (the emitter stays the controller).
49 std::function<void(const QString &)> statusMessage;
50 std::function<void(qint64 uid, qint64 folderId)> bodyLoaded;
51 std::function<void(const MailId &, BodyOutcome, const QString &)> bodyRequestFinished;
52 };
53 MailBodyTransport(MailCache *cache, MailListModel *model, MailDisplayPort *const &view,
54 const FolderContext &folder, ImapService *const &bodyImap,
55 QString &bodyImapSelectedFolder, QTimer *const &loadingPlaceholderTimer,
56 const bool &networkEnabled, const bool &fetchInProgress,
57 const QSet<QString> &unavailableFolders, Deps deps,
58 QObject *parent = nullptr);
59 // Body-transport consumers of the body connection (S91.0.6).
60 void connectBodyTransport(ImapService *bodyImap);
61
62 // workerConfirmedMissing: the display read worker just observed no cached
63 // body and no intervening body store, so the GUI-side recheck is skipped.
64 quint64 requestBodyForConsumer(const MailId &mailId, BodyPurpose purpose,
65 quint64 consumerId = 0,
66 bool workerConfirmedMissing = false);
67 void removeBodyConsumer(BodyPurpose purpose, quint64 consumerId);
68 bool downloadAttachment(qint64 attachmentId, const QString &savePath,
69 bool overwriteExisting = true);
70 // T-87.G2 speculative body prefetch policy. Suspended drops queued
71 // speculation immediately; conservative keeps only the adjacent messages
72 // (the deterministic default while metered/platform signals are absent).
73 void setPrefetchSuspended(bool suspended);
74 void setPrefetchConservative(bool conservative);
75 795 void setIndexedAdjacentHeadersProvider(
76 std::function<QList<MailHeader>(const MailId &)> provider) {
77 795 m_indexedAdjacentHeaders = std::move(provider);
78 795 }
79 void prefetchAdjacent(int currentRow);
80 void prefetchIndexedAdjacent(const MailId &mailId);
81 bool rejectMessageAboveDownloadLimit(const MailHeader &header,
82 bool showFeedback);
83 void dispatchNextBodyRequest();
84
85 // Shared with the controller's display/selection code.
86 13309 BodyRequestRegistry &requests() { return m_bodyRequests; }
87 ✗ bool prefetchSuspended() const { return m_prefetchSuspended; }
88 // P90.9: navigation cadence anchors the prefetch idle window.
89 void noteDemandActivity();
90 633 qint64 lastDemandBodyActivityMs() const { return m_lastDemandBodyActivityMs; }
91 // A direct selection or Retry click starts a fresh user-visible attempt.
92 1216 void clearAutoRetry(const MailId &mailId) { m_bodyAutoRetried.remove(mailId); }
93 // Drop queued reconnect retries of a folder of the current account.
94 void dropReconnectRetries(qint64 folderId);
95
1/1
✓ Branch 3 → 4 taken 1050 times.
1050 void waitForPreparations() { m_bodyPreparationPool.waitForDone(); }
96 // P90.9: prefetches only start in idle windows — an in-flight speculative
97 // transport cannot be aborted protocol-safely, so navigation bursts hold
98 // queued prefetches until the reader settles.
99 static constexpr qint64 PrefetchIdleWindowMs = 1500;
100
101 // Transport/pipeline state contract (tests drive the pipeline without a
102 // live body connection; S91.4.1 replaced friend access, sprint rule 8).
103 struct PendingBodyRetry {
104 BodyRequest request;
105 QList<BodyConsumer> consumers;
106 };
107 bool demandBodyIdle() const;
108 void retryBodiesAfterReconnect();
109 void requeueUndispatchedBody(const BodyResult &result);
110 2 bool transportBusy() const { return m_bodyTransportBusy; }
111 31 void setTransportBusy(bool busy) { m_bodyTransportBusy = busy; }
112 2 quint64 activeTransportRequestId() const { return m_activeBodyTransportRequestId; }
113 29 void setActiveTransportRequestId(quint64 requestId) { m_activeBodyTransportRequestId = requestId; }
114 28 int preparationCount() const { return m_bodyPreparations; }
115 1 qint64 preparationBytes() const { return m_bodyPreparationBytes; }
116 1 const QHash<quint64, qint64> &preparationBytesByRequest() const {
117 1 return m_bodyPreparationBytesByRequest;
118 }
119 2 QThreadPool &preparationPool() { return m_bodyPreparationPool; }
120 14 QList<PendingBodyRetry> &reconnectRetries() { return m_bodyReconnectRetries; }
121 2 bool autoRetried(const MailId &mailId) const { return m_bodyAutoRetried.contains(mailId); }
122 1 void setLastDemandBodyActivityMs(qint64 ms) { m_lastDemandBodyActivityMs = ms; }
123
124 public slots:
125 void onBodyRequestCompleted(const BodyResult &result);
126
127 private:
128 void finishPreparedBody(BodyResult result, MimeMessage *prepared = nullptr,
129 const MailBodyWriteResult *persisted = nullptr);
130 void persistPreparedBody(BodyResult result, MimeMessage *prepared,
131 const MailId &pendingId);
132 void finishFailedBody(const BodyRequestResolution &resolution, bool persisted);
133 void showMessageAboveDownloadLimit(const MailHeader &header,
134 qint64 maxBytes);
135 // P90.9: release one admitted payload's pipeline accounting (idempotent).
136 void releaseBodyPreparation(quint64 requestId);
137
138 MailCache *m_cache;
139 MailListModel *m_model;
140 MailDisplayPort *const &m_view; // MailController (setDisplayPort)
141 const FolderContext &m_folder; // MailController::m_folder
142 ImapService *const &m_bodyImap; // MailController (lazy body connection)
143 QString &m_bodyImapSelectedFolder; // connection state, MailController
144 QTimer *const &m_loadingPlaceholderTimer; // MailController (display)
145 const bool &m_networkEnabled;
146 const bool &m_fetchInProgress;
147 const QSet<QString> &m_unavailableFolders;
148 Deps m_deps;
149 bool m_prefetchSuspended = false;
150 bool m_prefetchConservative = true; // conservative default without signals
151 int m_lastNavigationRow = -1;
152 static constexpr qint64 PrefetchMaximumBytes = 4LL * 1024 * 1024;
153 qint64 m_lastDemandBodyActivityMs = 0;
154 bool m_prefetchRetryQueued = false;
155 BodyRequestRegistry m_bodyRequests;
156 bool m_bodyTransportBusy = false;
157 // P90.9: bounded parse/persist pipeline instead of a single busy flag —
158 // a finishing speculative payload must not block the next demand
159 // transport or MIME parse. At most two payloads prepare concurrently,
160 // with at most one speculative payload under a shared byte budget; each
161 // admitted payload is accounted once.
162 static constexpr int MaximumBodyPreparations = 2;
163 static constexpr qint64 MaximumBodyPreparationBytes = 96LL * 1024 * 1024;
164 int m_bodyPreparations = 0;
165 qint64 m_bodyPreparationBytes = 0;
166 QHash<quint64, qint64> m_bodyPreparationBytesByRequest;
167 QThreadPool m_bodyPreparationPool;
168 quint64 m_activeBodyTransportRequestId = 0;
169 QList<PendingBodyRetry> m_bodyReconnectRetries;
170 QSet<MailId> m_bodyAutoRetried;
171 std::function<QList<MailHeader>(const MailId &)> m_indexedAdjacentHeaders;
172 };
173