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: 94.9% 166 / 0 / 175
Functions: 91.3% 21 / 0 / 23
Branches: 71.1% 239 / 0 / 336

service/FolderPredictionService.cpp
Line Branch Exec Source
1 #include "FolderPredictionService.h"
2 #include "data/FolderPredictor.h"
3 #include "data/FolderSuggestionStore.h"
4 #include "data/MailCache.h"
5 #include <QCoreApplication>
6 #include <QElapsedTimer>
7 #include <QFileInfo>
8 #include <QFutureWatcher>
9 #include <QLoggingCategory>
10 #include <QSet>
11 #include <QtConcurrent>
12 #include <algorithm>
13
14
4/7
✓ Branch 2 → 3 taken 13 times.
✓ Branch 2 → 8 taken 376 times.
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 13 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
389 Q_LOGGING_CATEGORY(lcFolderPrediction, "mailjd.predictor.service")
15
16
2/2
✓ Branch 3 → 4 taken 35 times.
✓ Branch 8 → 9 taken 35 times.
35 FolderPredictionService::FolderPredictionService(QObject *parent) : QObject(parent) {
17
1/1
✓ Branch 9 → 10 taken 35 times.
35 m_pool.setMaxThreadCount(1);
18
1/1
✓ Branch 10 → 11 taken 35 times.
35 m_pool.setExpiryTimeout(1000);
19
1/1
✓ Branch 12 → 13 taken 35 times.
35 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
20 36 this, [this] { stop(); });
21 35 }
22 57 FolderPredictionService::~FolderPredictionService() { stop(); }
23 10348 FolderPredictionService *FolderPredictionService::shared() {
24
3/4
✓ Branch 2 → 3 taken 21 times.
✓ Branch 2 → 7 taken 10327 times.
✓ Branch 4 → 5 taken 21 times.
✗ Branch 4 → 7 not taken.
10348 static QPointer<FolderPredictionService> service;
25
5/8
✓ Branch 8 → 9 taken 21 times.
✓ Branch 8 → 16 taken 10327 times.
✓ Branch 11 → 12 taken 21 times.
✓ Branch 12 → 13 taken 21 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 21 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
10348 if (!service) service = new FolderPredictionService(QCoreApplication::instance());
26 10348 return service;
27 }
28 36 void FolderPredictionService::stop() {
29 36 m_stopping = true;
30
5/5
✓ Branch 2 → 3 taken 36 times.
✓ Branch 3 → 4 taken 36 times.
✓ Branch 6 → 7 taken 6 times.
✓ Branch 9 → 5 taken 6 times.
✓ Branch 9 → 10 taken 36 times.
42 for (const auto &request : m_pending) disconnect(request.destroyed);
31 36 m_pending.clear();
32
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 18 taken 35 times.
36 if (m_active) {
33 1 m_active->cancelled->store(true);
34 1 disconnect(m_active->destroyed);
35 }
36
1/1
✓ Branch 19 → 20 taken 36 times.
36 m_pool.waitForDone();
37 36 }
38 10396 void FolderPredictionService::cancel(QObject *owner) {
39
6/6
✓ Branch 3 → 4 taken 618 times.
✓ Branch 3 → 8 taken 9778 times.
✓ Branch 6 → 7 taken 343 times.
✓ Branch 6 → 8 taken 275 times.
✓ Branch 9 → 10 taken 343 times.
✓ Branch 9 → 13 taken 10053 times.
10396 if (m_active && m_active->owner == owner) m_active->cancelled->store(true);
40 10396 m_pending.removeIf([this, owner](const Request &request) {
41
6/6
✓ Branch 3 → 4 taken 506 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 5 → 6 taken 486 times.
✓ Branch 5 → 7 taken 20 times.
✓ Branch 8 → 9 taken 486 times.
✓ Branch 8 → 10 taken 21 times.
507 if (request.owner && request.owner != owner) return false;
42 21 disconnect(request.destroyed);
43 21 return true;
44 });
45 10396 }
46 468 void FolderPredictionService::predict(QObject *owner, FolderPredictionRequest input,
47 Callback callback) {
48
1/1
✓ Branch 2 → 3 taken 468 times.
468 cancel(owner);
49
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 468 times.
474 if (!owner) return;
50 2322 const auto invalidText = [](const QString &text) {
51
5/5
✓ Branch 3 → 4 taken 2321 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 5 → 6 taken 2321 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 2320 times.
2322 return text.size() > 4096 || text.contains(QChar::Null);
52 };
53
4/6
✓ Branch 7 → 8 taken 467 times.
✓ Branch 7 → 35 taken 1 time.
✓ Branch 8 → 9 taken 467 times.
✗ Branch 8 → 35 not taken.
✓ Branch 10 → 11 taken 467 times.
✗ Branch 10 → 35 not taken.
935 if (m_stopping || m_pending.size() >= MaxPending || input.folderId <= 0 ||
54
2/4
✓ Branch 12 → 13 taken 467 times.
✗ Branch 12 → 35 not taken.
✓ Branch 14 → 15 taken 467 times.
✗ Branch 14 → 35 not taken.
1401 input.cachePath.isEmpty() || input.predictorPath.isEmpty() ||
55
3/4
✓ Branch 16 → 17 taken 467 times.
✗ Branch 16 → 35 not taken.
✓ Branch 18 → 19 taken 466 times.
✓ Branch 18 → 35 taken 1 time.
1401 input.account.isEmpty() || input.folder.isEmpty() ||
56
2/2
✓ Branch 20 → 21 taken 465 times.
✓ Branch 20 → 35 taken 1 time.
933 input.uids.isEmpty() || input.uids.size() > MaxRows ||
57
4/6
✓ Branch 21 → 22 taken 465 times.
✓ Branch 22 → 23 taken 465 times.
✗ Branch 22 → 35 not taken.
✓ Branch 23 → 24 taken 465 times.
✓ Branch 24 → 25 taken 465 times.
✗ Branch 24 → 35 not taken.
465 invalidText(input.cachePath) || invalidText(input.predictorPath) ||
58
11/13
✓ Branch 5 → 6 taken 468 times.
✗ Branch 5 → 35 not taken.
✓ Branch 25 → 26 taken 465 times.
✓ Branch 26 → 27 taken 464 times.
✓ Branch 26 → 35 taken 1 time.
✓ Branch 27 → 28 taken 464 times.
✓ Branch 28 → 29 taken 463 times.
✓ Branch 28 → 35 taken 1 time.
✓ Branch 29 → 30 taken 463 times.
✓ Branch 30 → 31 taken 463 times.
✗ Branch 30 → 35 not taken.
✓ Branch 37 → 38 taken 6 times.
✓ Branch 37 → 45 taken 462 times.
1399 invalidText(input.account) || invalidText(input.folder) || invalidText(input.junkFolder) ||
59
3/3
✓ Branch 33 → 34 taken 463 times.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 462 times.
1280 std::any_of(input.uids.cbegin(), input.uids.cend(), [](qint64 uid) { return uid <= 0; })) {
60 6 callback({{}, QStringLiteral("Folder prediction is unavailable or exceeds its identity budget"), input.uidValidity});
61 6 return;
62 }
63
3/11
✓ Branch 45 → 46 taken 462 times.
✓ Branch 50 → 51 taken 462 times.
✓ Branch 51 → 52 taken 462 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
462 Request request{owner, std::move(input), std::move(callback)};
64 462 const auto cancelled = request.cancelled;
65
1/1
✓ Branch 54 → 55 taken 462 times.
536 request.destroyed = connect(owner, &QObject::destroyed, this, [cancelled] { cancelled->store(true); });
66
1/1
✓ Branch 59 → 60 taken 462 times.
462 m_pending.append(std::move(request));
67
1/1
✓ Branch 60 → 61 taken 462 times.
462 startNext();
68 474 }
69 896 void FolderPredictionService::startNext() {
70
5/6
✓ Branch 2 → 3 taken 896 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 65 times.
✓ Branch 4 → 6 taken 831 times.
✓ Branch 7 → 8 taken 65 times.
✓ Branch 7 → 9 taken 831 times.
1292 if (m_stopping || m_active) return;
71
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 831 times.
831 if (m_warming) return;
72
1/1
✓ Branch 11 → 12 taken 831 times.
831 cancel(nullptr);
73
3/3
✓ Branch 13 → 14 taken 396 times.
✓ Branch 13 → 16 taken 435 times.
✓ Branch 14 → 15 taken 396 times.
831 if (m_pending.isEmpty()) { startWarm(); return; }
74
1/1
✓ Branch 16 → 17 taken 435 times.
435 m_active = m_pending.takeFirst();
75
3/6
✓ Branch 19 → 20 taken 435 times.
✓ Branch 20 → 21 taken 435 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 435 times.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
435 auto *watcher = new QFutureWatcher<FolderPredictionResult>(this);
76
1/1
✓ Branch 23 → 24 taken 435 times.
435 connect(watcher, &QFutureWatcher<FolderPredictionResult>::finished, this, [this, watcher] {
77 434 auto request = std::move(*m_active);
78 434 m_active.reset();
79
1/1
✓ Branch 6 → 7 taken 434 times.
434 auto result = watcher->result();
80
3/4
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 14 taken 433 times.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 14 not taken.
434 if (result.provisional && !m_stopping)
81 1 m_warm.append({request.input.cachePath, request.input.account});
82
1/1
✓ Branch 14 → 15 taken 434 times.
434 watcher->deleteLater();
83
1/1
✓ Branch 15 → 16 taken 434 times.
434 disconnect(request.destroyed);
84
1/1
✓ Branch 16 → 17 taken 434 times.
434 QPointer<FolderPredictionService> guard(this);
85
7/8
✓ Branch 17 → 18 taken 434 times.
✗ Branch 17 → 24 not taken.
✓ Branch 19 → 20 taken 368 times.
✓ Branch 19 → 24 taken 66 times.
✓ Branch 22 → 23 taken 282 times.
✓ Branch 22 → 24 taken 86 times.
✓ Branch 25 → 26 taken 282 times.
✓ Branch 25 → 31 taken 152 times.
434 if (!m_stopping && request.owner && !request.cancelled->load())
86
1/1
✓ Branch 28 → 29 taken 282 times.
282 request.callback(std::move(result));
87
3/3
✓ Branch 32 → 33 taken 433 times.
✓ Branch 32 → 34 taken 1 time.
✓ Branch 33 → 34 taken 433 times.
434 if (guard) startNext();
88
1/3
✓ Branch 11 → 12 taken 1 time.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
435 });
89 435 const auto input = m_active->input;
90 435 const auto cancelled = m_active->cancelled;
91 435 const auto statistics = m_statistics;
92
1/1
✓ Branch 30 → 31 taken 435 times.
435 const auto model = modelFor(input.cachePath, input.account);
93
2/8
✓ Branch 35 → 36 taken 435 times.
✓ Branch 36 → 37 taken 435 times.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
435 watcher->setFuture(QtConcurrent::run(&m_pool, [input, cancelled, statistics, model] {
94 435 return classify(input, *cancelled, statistics, *model);
95 }));
96 435 }
97
98 // Runs on the worker. Evidence first (S105.3.1); the Naive Bayes word model
99 // answers only for mails the evidence knows nothing about (an empty or not
100 // yet synchronised filing history), as before.
101 435 FolderPredictionResult FolderPredictionService::classify(const FolderPredictionRequest &input,
102 const std::atomic_bool &cancelled,
103 const std::shared_ptr<FolderPredictor::StatisticsCache> &statistics,
104 FolderEvidenceModel &model) {
105 435 FolderPredictionResult result;
106 435 result.uidValidity = input.uidValidity;
107
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 434 times.
435 if (cancelled.load()) return result;
108 434 QElapsedTimer timer;
109 434 timer.start();
110 // The training session owns the predictor file; without it there is
111 // no model to answer from (and nothing is created here).
112
4/4
✓ Branch 7 → 8 taken 434 times.
✓ Branch 8 → 9 taken 434 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 19 taken 433 times.
434 if (!QFileInfo(input.predictorPath).isFile()) {
113 1 result.error = QStringLiteral("Folder prediction database is unavailable");
114 1 return result;
115 }
116
1/1
✓ Branch 19 → 20 taken 433 times.
433 FolderSuggestionStore store;
117 433 QString storeError;
118
1/1
✓ Branch 20 → 21 taken 433 times.
433 const bool stored = store.open(input.predictorPath, &storeError);
119
1/8
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 35 taken 433 times.
✗ Branch 22 → 23 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 33 → 24 not taken.
✗ Branch 33 → 34 not taken.
433 if (!stored) qCWarning(lcFolderPrediction).noquote() << "suggestion store unavailable:" << storeError;
120 // S105.3.5: until the model is built, stored suggestions answer at once.
121
6/7
✓ Branch 35 → 36 taken 433 times.
✓ Branch 36 → 37 taken 264 times.
✓ Branch 36 → 39 taken 169 times.
✓ Branch 37 → 38 taken 264 times.
✗ Branch 37 → 39 not taken.
✓ Branch 40 → 41 taken 264 times.
✓ Branch 40 → 100 taken 169 times.
433 if (!model.built(input.account) && stored) {
122
1/1
✓ Branch 41 → 42 taken 264 times.
264 const auto known = store.read(input.account, input.folderId, input.uidValidity, input.uids, &storeError);
123
1/1
✓ Branch 44 → 45 taken 264 times.
264 QSet<qint64> wanted(input.uids.cbegin(), input.uids.cend());
124
5/6
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 51 taken 263 times.
✓ Branch 49 → 50 taken 1 time.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 1 time.
✓ Branch 52 → 90 taken 263 times.
264 if (!known.isEmpty() && known.size() == wanted.size()) {
125
2/2
✓ Branch 63 → 54 taken 1 time.
✓ Branch 63 → 64 taken 1 time.
2 for (auto it = known.cbegin(); it != known.cend(); ++it)
126 1 result.items.append({{input.account, input.folderId, it.key()}, it.value()});
127 1 result.provisional = true;
128
4/4
✓ Branch 64 → 65 taken 1 time.
✓ Branch 68 → 69 taken 1 time.
✓ Branch 87 → 66 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
2 qCInfo(lcFolderPrediction).noquote()
129
3/3
✓ Branch 75 → 76 taken 1 time.
✓ Branch 78 → 79 taken 1 time.
✓ Branch 79 → 80 taken 1 time.
3 << QStringLiteral("stored suggestions answered mails=%1 ms=%2").arg(known.size()).arg(timer.elapsed());
130 1 return result;
131 }
132 265 }
133
1/1
✓ Branch 100 → 101 taken 432 times.
432 MailCache cache;
134
2/4
✓ Branch 101 → 102 taken 432 times.
✗ Branch 102 → 103 not taken.
✓ Branch 102 → 108 taken 432 times.
✗ Branch 103 → 104 not taken.
432 if (!cache.openReadOnly(input.cachePath)) { result.error = cache.lastError(); return result; }
135 432 QString modelError;
136
1/1
✓ Branch 108 → 109 taken 432 times.
432 const bool modelReady = model.built(input.account)
137
3/3
✓ Branch 109 → 110 taken 169 times.
✓ Branch 109 → 113 taken 263 times.
✓ Branch 111 → 112 taken 169 times.
432 ? model.update(cache.database(), &modelError)
138
1/1
✓ Branch 114 → 115 taken 263 times.
263 : model.build(cache.database(), input.account, &modelError);
139
1/8
✗ Branch 116 → 117 not taken.
✓ Branch 116 → 130 taken 432 times.
✗ Branch 117 → 118 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 123 → 124 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 128 → 119 not taken.
✗ Branch 128 → 129 not taken.
432 if (!modelReady) qCWarning(lcFolderPrediction).noquote() << "evidence model unavailable:" << modelError;
140 432 const auto page = cache.training().trainingHeaders(input.account, input.folderId, input.folder,
141
1/1
✓ Branch 131 → 132 taken 432 times.
432 input.uidValidity, input.uids);
142
2/2
✓ Branch 133 → 134 taken 9 times.
✓ Branch 133 → 137 taken 423 times.
432 if (!page.error.isEmpty()) { result.error = page.error; return result; }
143
2/2
✓ Branch 138 → 139 taken 33 times.
✓ Branch 138 → 141 taken 390 times.
423 if (cancelled.load()) return result;
144 390 std::optional<FolderPredictor> predictor;
145 390 QList<QPair<qint64, FolderSuggestion>> computed;
146
2/2
✓ Branch 193 → 143 taken 617 times.
✓ Branch 193 → 194 taken 388 times.
1005 for (const auto &header : page.headers) {
147
2/2
✓ Branch 145 → 146 taken 1 time.
✓ Branch 145 → 147 taken 616 times.
618 if (cancelled.load()) return FolderPredictionResult{};
148 616 FolderSuggestion suggestion;
149
2/3
✓ Branch 147 → 148 taken 616 times.
✗ Branch 147 → 152 not taken.
✓ Branch 148 → 149 taken 616 times.
616 if (modelReady) suggestion = model.suggest(header, input.junkFolder);
150
2/2
✓ Branch 153 → 154 taken 282 times.
✓ Branch 153 → 179 taken 334 times.
616 if (suggestion.primary.first.isEmpty()) {
151
2/2
✓ Branch 155 → 156 taken 278 times.
✓ Branch 155 → 170 taken 4 times.
282 if (!predictor) {
152
1/1
✓ Branch 156 → 157 taken 278 times.
278 predictor.emplace();
153
2/3
✓ Branch 159 → 160 taken 278 times.
✗ Branch 161 → 162 not taken.
✓ Branch 161 → 170 taken 278 times.
278 if (!predictor->openReadOnly(input.predictorPath, statistics)) {
154 ✗ result.error = QStringLiteral("Folder prediction database is unavailable");
155 ✗ return result;
156 }
157 }
158
1/1
✓ Branch 171 → 172 taken 282 times.
282 suggestion = predictor->suggestions(header, input.junkFolder, &result.error);
159
3/3
✓ Branch 175 → 176 taken 1 time.
✓ Branch 175 → 179 taken 281 times.
✓ Branch 176 → 177 taken 1 time.
282 if (!result.error.isEmpty()) { result.items.clear(); return result; }
160 }
161 615 result.items.append({{input.account, input.folderId, header.uid}, suggestion});
162
1/1
✓ Branch 184 → 185 taken 615 times.
615 computed.append({header.uid, suggestion});
163 616 }
164
2/3
✓ Branch 195 → 196 taken 388 times.
✗ Branch 196 → 197 not taken.
✓ Branch 196 → 205 taken 388 times.
388 if (!cache.training().trainingIdentityCurrent(input.account, input.folderId, input.folder, input.uidValidity)) {
165 ✗ result.items.clear();
166 ✗ result.error = QStringLiteral("Folder prediction mailbox identity changed");
167 ✗ return result;
168 }
169
4/7
✓ Branch 205 → 206 taken 388 times.
✗ Branch 205 → 209 not taken.
✓ Branch 206 → 207 taken 388 times.
✗ Branch 207 → 208 not taken.
✓ Branch 207 → 209 taken 388 times.
✗ Branch 210 → 211 not taken.
✓ Branch 210 → 224 taken 388 times.
388 if (stored && !store.write(input.account, input.folderId, input.uidValidity, computed, &storeError))
170 ✗ qCWarning(lcFolderPrediction).noquote() << "suggestions not stored:" << storeError;
171
4/4
✓ Branch 224 → 225 taken 388 times.
✓ Branch 228 → 229 taken 388 times.
✓ Branch 247 → 226 taken 388 times.
✓ Branch 247 → 248 taken 388 times.
776 qCDebug(lcFolderPrediction).noquote()
172
3/3
✓ Branch 235 → 236 taken 388 times.
✓ Branch 238 → 239 taken 388 times.
✓ Branch 239 → 240 taken 388 times.
1164 << QStringLiteral("classified mails=%1 ms=%2").arg(computed.size()).arg(timer.elapsed());
173 388 return result;
174 1051 }
175
176 436 std::shared_ptr<FolderEvidenceModel> FolderPredictionService::modelFor(const QString &cachePath,
177 const QString &account) {
178
3/3
✓ Branch 4 → 5 taken 436 times.
✓ Branch 5 → 6 taken 436 times.
✓ Branch 6 → 7 taken 436 times.
436 auto &model = m_models[cachePath + QLatin1Char('\n') + account];
179
3/3
✓ Branch 10 → 11 taken 265 times.
✓ Branch 10 → 15 taken 171 times.
✓ Branch 11 → 12 taken 265 times.
436 if (!model) model = std::make_shared<FolderEvidenceModel>();
180 436 return model;
181 }
182
183 40 void FolderPredictionService::recordFiling(const QString &cachePath, const QString &account,
184 const MailHeader &header, const QString &folder) {
185
2/2
✓ Branch 4 → 5 taken 40 times.
✓ Branch 5 → 6 taken 40 times.
40 const auto model = m_models.value(cachePath + QLatin1Char('\n') + account);
186 // An unbuilt model reads the filing from the cache when it is built.
187
7/8
✓ Branch 10 → 11 taken 37 times.
✓ Branch 10 → 15 taken 3 times.
✓ Branch 12 → 13 taken 37 times.
✓ Branch 13 → 14 taken 37 times.
✗ Branch 13 → 15 not taken.
✓ Branch 16 → 17 taken 37 times.
✓ Branch 16 → 19 taken 3 times.
✓ Branch 18 → 19 taken 37 times.
40 if (model && model->built(account)) model->correct(header, folder);
188 40 }
189
190 ✗ void FolderPredictionService::forgetAccount(const QString &account) {
191 ✗ m_models.removeIf([&account](const auto &entry) { return entry.key().endsWith(QLatin1Char('\n') + account); });
192 ✗ }
193
194 // S105.3.5: after a provisional answer the model is built in the
195 // background, then consumers are told to ask again.
196 396 void FolderPredictionService::startWarm() {
197
8/12
✓ Branch 2 → 3 taken 396 times.
✗ Branch 2 → 10 not taken.
✓ Branch 4 → 5 taken 396 times.
✗ Branch 4 → 10 not taken.
✓ Branch 5 → 6 taken 396 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 395 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 12 → 13 taken 395 times.
✓ Branch 12 → 14 taken 1 time.
396 if (m_stopping || m_active || m_warming || m_warm.isEmpty() || !m_pending.isEmpty()) return;
198
1/1
✓ Branch 14 → 15 taken 1 time.
1 const Warm warm = m_warm.takeFirst();
199
1/1
✓ Branch 15 → 16 taken 1 time.
1 const auto model = modelFor(warm.cachePath, warm.account);
200
2/4
✓ Branch 17 → 18 taken 1 time.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 21 taken 1 time.
✗ Branch 19 → 20 not taken.
1 if (model->built(warm.account)) { startWarm(); return; }
201 1 m_warming = true;
202
3/6
✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1 time.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
1 auto *watcher = new QFutureWatcher<bool>(this);
203
1/1
✓ Branch 26 → 27 taken 1 time.
1 connect(watcher, &QFutureWatcher<bool>::finished, this, [this, watcher, account = warm.account] {
204
1/1
✓ Branch 2 → 3 taken 1 time.
1 const bool built = watcher->result();
205
1/1
✓ Branch 3 → 4 taken 1 time.
1 watcher->deleteLater();
206 1 m_warming = false;
207
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
1 if (m_stopping) return;
208
1/1
✓ Branch 6 → 7 taken 1 time.
1 QPointer<FolderPredictionService> guard(this);
209
2/3
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
✓ Branch 8 → 9 taken 1 time.
1 if (built) emit evidenceRebuilt(account);
210
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
1 if (!guard) return;
211
1/1
✓ Branch 12 → 13 taken 1 time.
1 startNext();
212
1/2
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 18 not taken.
1 });
213
2/4
✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
1 watcher->setFuture(QtConcurrent::run(&m_pool, [warm, model] {
214
1/1
✓ Branch 2 → 3 taken 1 time.
1 MailCache cache;
215
2/3
✓ Branch 3 → 4 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
1 if (!cache.openReadOnly(warm.cachePath)) return false;
216 1 QString error;
217
1/1
✓ Branch 8 → 9 taken 1 time.
1 const bool built = model->build(cache.database(), warm.account, &error);
218
1/8
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 23 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 21 → 12 not taken.
✗ Branch 21 → 22 not taken.
1 if (!built) qCWarning(lcFolderPrediction).noquote() << "evidence model not built:" << error;
219 1 return built;
220 1 }));
221 1 }
222