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% 17 / 0 / 17
Functions: 100.0% 7 / 0 / 7
Branches: 100.0% 4 / 0 / 4

controller/ServerSearchSession.h
Line Branch Exec Source
1 #pragma once
2
3 #include "data/MailCache.h"
4 #include <QObject>
5 #include <QStringList>
6 #include <functional>
7
8 class ImapService;
9
10 // Multi-folder server-side IMAP SEARCH on the dedicated search connection
11 // (Sprint 91 S91.1.3, extracted from MailController). Callers reach it as
12 // `controller.search()`.
13 //
14 // Owner: MailController (value member). GUI thread only. The controller
15 // keeps the search connection's lifecycle (creation, health monitor,
16 // reconnect, polling STATUS on the same socket); the session obtains the
17 // connection through Deps::ensureConnection and is told about reconnects
18 // and connection loss by the controller's stateChanged handler
19 // (resumeAfterReconnect / dropPendingHandlers). Its own connections to the
20 // search ImapService are one-shot waits per folder (state, SELECT, SELECT
21 // failure, SEARCH result).
22 class ServerSearchSession final : public QObject {
23 Q_OBJECT
24 public:
25 struct Deps {
26 // Creates/configures (and connects) the search connection if needed.
27 std::function<void()> ensureConnection;
28 std::function<qint64(const QString &)> resolveFolderId;
29 // S101.1.7/SF-04 (D-12): the server hits of a folder without a cached
30 // header, and where the fetched headers go. Without them hits are
31 // reported as they are (receivers drop uncached ones).
32 std::function<QList<qint64>(qint64 folderId, const QList<qint64> &uids)> uncachedUids = {};
33 // S106-103: `uidValidity` is the epoch of the search connection's
34 // SELECT; the cache stores the headers only under that epoch.
35 std::function<void(qint64 folderId, quint32 uidValidity, const QList<MailHeader> &headers)>
36 storeHeaders = {};
37 };
38 // D-12: at most this many uncached headers are fetched per folder (the
39 // newest UIDs first); the rest stays unfetched.
40 static constexpr int MaximumHeaderFetch = 200;
41 // Progress of the running search. Public so the recovery contract
42 // (resume after reconnect, cancellation) is observable and can be set up
43 // without private access.
44 struct Progress {
45 QString query;
46 QStringList pendingFolders;
47 QString currentFolder;
48 qint64 currentFolderId = -1;
49 };
50 // Which one-shot waits are connected (recovery contract).
51 struct Waits {
52 bool connection = false;
53 bool folderSelected = false;
54 bool folderSelectFailed = false;
55 bool searchResult = false;
56 bool headerFetch = false;
57 };
58 // searchImap: the controller's (lazily created) search connection
59 // pointer; the session reads it, the controller owns and creates it.
60 ServerSearchSession(ImapService *const &searchImap,
61 const QStringList &subscribedFolders, Deps deps,
62 QObject *parent = nullptr);
63
64 // Server-side IMAP SEARCH on dedicated second connection.
65 // folderFilter (optional): if non-empty, only subscribed folders whose path
66 // contains this substring (case-insensitive) are searched — mirrors the
67 // "folder:" prefix of the local search so the server does not waste work
68 // scanning every folder.
69 // Sprint 59 (S1): the free-text term plus the structured filter. The folder
70 // scope is taken from filter.folderPatterns (Sprint 60: OR over folders).
71 // Server-mappable facets (from/to/
72 // subject/date/is:/tags) are translated to a composite IMAP SEARCH; purely
73 // local facets (e.g. has:attachment) are ignored here — the local FTS/cache
74 // covers them. When nothing is server-mappable, no server scan is started.
75 // Sprint 101 S101.1.7: `owner` names the search tab whose session started
76 // the scan (empty for owner-less callers); results carry no owner, so the
77 // receiver checks owner() before merging them.
78 void serverSearch(const QString &freeText,
79 const MailCache::SearchFilter &filter, const QString &owner = {});
80 void cancelServerSearch();
81 // S106-161: ends a running (or connection-waiting) scan because the
82 // account changed — its owner is told the scan finished, nothing of it
83 // resumes on the new account's connection.
84 void abandon();
85 // D-12: stop after the folder in flight, keep the rest; resume() goes on.
86 void pause();
87 void resume();
88 2 bool paused() const { return m_paused; }
89
4/4
✓ Branch 3 → 4 taken 1046 times.
✓ Branch 3 → 6 taken 2 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1045 times.
1048 bool running() const { return !m_searchCurrentFolder.isEmpty() || !m_searchPendingFolders.isEmpty(); }
90 194 QString owner() const { return m_owner; }
91 int totalFolders() const { return m_totalFolders; }
92 int doneFolders() const;
93 void setServerSearchExcludedFolders(const QStringList &folders);
94 // T-720: called by the controller's search-connection state handler.
95 void resumeAfterReconnect();
96 void dropPendingHandlers();
97 // True while a search waits for the connection to authenticate.
98 1046 bool awaitingConnection() const { return bool(m_searchStateConn); }
99
100 19 Progress progress() const {
101 57 return {m_searchQuery, m_searchPendingFolders, m_searchCurrentFolder,
102 38 m_searchCurrentFolderId};
103 19 }
104 4 void setProgress(const Progress &progress) {
105 4 m_searchQuery = progress.query;
106 4 m_searchPendingFolders = progress.pendingFolders;
107 4 m_searchCurrentFolder = progress.currentFolder;
108 4 m_searchCurrentFolderId = progress.currentFolderId;
109 4 }
110 16 Waits waits() const {
111 16 return {bool(m_searchStateConn), bool(m_searchFolderConn),
112 16 bool(m_searchFailConn), bool(m_searchResultConn), bool(m_headerDoneConn)};
113 }
114
115 signals:
116 // Server-side search results from dedicated search connection (per folder)
117 void serverSearchResultReceived(const QList<qint64> &uids,
118 qint64 folderId, const QString &folderPath);
119 void serverSearchComplete();
120 // S106-024: the server refused the search in this folder (its hits are
121 // unknown, not empty).
122 void serverSearchFolderFailed(const QString &folderPath);
123 // Folders finished (searched or skipped) of the running scan.
124 void serverSearchProgress(int done, int total);
125
126 private:
127 // Queues the folders of a scan on the ready connection; keeps m_paused.
128 void beginScan(const QString &freeText, const MailCache::SearchFilter &filter);
129 void searchNextFolder(); // Pop next folder and issue SEARCH
130 // SF-04: fetch the uncached hits' headers, then finishFolder().
131 void fetchUncachedThenFinish(const QList<qint64> &uids);
132 void finishFolder(const QList<qint64> &uids);
133 void dropHeaderFetch();
134 const QStringList &m_subscribedFolders; // owned by MailController
135 Deps m_deps;
136 ImapService *const &m_searchImap; // owned by MailController
137 QStringList m_serverSearchExcludedFolders;
138 QMetaObject::Connection m_searchStateConn; // T-195: auth wait
139 QMetaObject::Connection m_searchFolderConn; // T-195: SELECT wait
140 QMetaObject::Connection m_searchFailConn; // SELECT-failure skip
141 QMetaObject::Connection m_searchResultConn; // multi-folder search result
142 QMetaObject::Connection m_searchRefusedConn; // S106-024: SEARCH NO/BAD
143 bool m_currentFolderRefused = false;
144 QMetaObject::Connection m_headerConn; // SF-04: header batches
145 QMetaObject::Connection m_headerDoneConn; // SF-04: header fetch done
146 QMetaObject::Connection m_headerFailConn; // SF-04: header fetch failed
147 QList<MailHeader> m_fetchedHeaders;
148
149 // Multi-folder server search state
150 QStringList m_searchPendingFolders; // Queue of folders still to search
151 QString m_searchQuery; // Current search free-text term
152 MailCache::SearchFilter m_searchFilter; // Sprint 59 (S1): facets for SEARCH
153 QString m_searchCurrentFolder; // Folder currently being searched
154 qint64 m_searchCurrentFolderId = -1;// Its folderId
155 quint32 m_searchCurrentUidValidity = 0; // epoch of its SELECT (S106-103)
156 QString m_owner; // S101.1.7: the search tab that started it
157 bool m_paused = false;
158 int m_totalFolders = 0;
159 };
160