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.4% 84 / 0 / 89
Functions: 100.0% 14 / 0 / 14
Branches: 72.2% 91 / 0 / 126

presentation/QuickContactHistoryStore.cpp
Line Branch Exec Source
1 // QuickContactMailHistory: the worker's kept-open connections, the indexed
2 // mail read, the history cache and the address-index backfill (Sprint 105
3 // Phase 5, D-7).
4
5 #include "QuickContactMailHistory.h"
6
7 #include "data/CalendarStore.h"
8 #include "data/MailAddressHistory.h"
9
10 #include <QDate>
11 #include <QDateTime>
12 #include <QLocale>
13 #include <QLoggingCategory>
14 #include <QSqlDatabase>
15 #include <QSqlQuery>
16 #include <QThread>
17 #include <QTimer>
18 #include <QUuid>
19 #include <memory>
20
21 Q_DECLARE_LOGGING_CATEGORY(lcContactMailHistory)
22
23 namespace {
24 constexpr int kBackfillPauseMs = 200; // idle pace between chunks
25
26 // The worker thread's connections (S105.5.3): opened on first use, kept
27 // until the thread ends; a different path reopens.
28 struct WorkerConnections {
29 QString mailPath;
30 QString mailName;
31 QString calendarPath;
32 std::unique_ptr<CalendarStore> calendar;
33 87 ~WorkerConnections() {
34 87 calendar.reset();
35
2/2
✓ Branch 4 → 5 taken 86 times.
✓ Branch 4 → 6 taken 1 time.
87 if (!mailName.isEmpty()) QSqlDatabase::removeDatabase(mailName);
36 87 }
37 };
38 thread_local WorkerConnections connections;
39
40 472 QSqlDatabase mailConnection(const QString &path) {
41
5/6
✓ Branch 4 → 5 taken 381 times.
✓ Branch 4 → 8 taken 91 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 381 times.
✓ Branch 10 → 11 taken 91 times.
✓ Branch 10 → 65 taken 381 times.
472 if (connections.mailPath != path || connections.mailName.isEmpty()) {
42
2/2
✓ Branch 13 → 14 taken 5 times.
✓ Branch 13 → 20 taken 86 times.
91 if (!connections.mailName.isEmpty()) {
43
2/2
✓ Branch 15 → 16 taken 5 times.
✓ Branch 16 → 17 taken 5 times.
5 QSqlDatabase::database(connections.mailName, false).close();
44
1/1
✓ Branch 19 → 20 taken 5 times.
5 QSqlDatabase::removeDatabase(connections.mailName);
45 }
46 91 connections.mailPath = path;
47
3/3
✓ Branch 22 → 23 taken 91 times.
✓ Branch 23 → 24 taken 91 times.
✓ Branch 27 → 28 taken 91 times.
182 connections.mailName = QStringLiteral("contact-history-") + QUuid::createUuid().toString(QUuid::WithoutBraces);
48
1/1
✓ Branch 38 → 39 taken 91 times.
182 QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), connections.mailName);
49
1/1
✓ Branch 41 → 42 taken 91 times.
91 db.setDatabaseName(path);
50
2/8
✓ Branch 42 → 43 taken 91 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 55 taken 91 times.
✗ Branch 44 → 45 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 53 → 46 not taken.
✗ Branch 53 → 54 not taken.
91 if (!db.open()) qCWarning(lcContactMailHistory) << "Cannot open the mail cache for the history";
51
1/1
✓ Branch 55 → 56 taken 91 times.
91 QSqlQuery pragma(db);
52
1/1
✓ Branch 59 → 60 taken 91 times.
91 pragma.exec(QStringLiteral("PRAGMA busy_timeout = 5000"));
53 91 }
54 472 return QSqlDatabase::database(connections.mailName, false);
55 }
56
57 189 QString dateText(const QDateTime &when) {
58
1/1
✓ Branch 2 → 3 taken 189 times.
189 const QDateTime local = when.toLocalTime();
59
1/1
✓ Branch 3 → 4 taken 189 times.
189 const QDate today = QDate::currentDate();
60
2/6
✓ Branch 4 → 5 taken 189 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 13 taken 189 times.
✗ Branch 7 → 8 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 9 → 10 not taken.
189 if (local.date() == today) return QLocale().toString(local.time(), QLocale::ShortFormat);
61
3/5
✓ Branch 13 → 14 taken 189 times.
✓ Branch 14 → 15 taken 189 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 19 taken 189 times.
✗ Branch 17 → 18 not taken.
189 if (local.date() == today.addDays(-1)) return QuickContactMailHistory::tr("Yesterday");
62
3/3
✓ Branch 19 → 20 taken 189 times.
✓ Branch 20 → 21 taken 189 times.
✓ Branch 21 → 22 taken 189 times.
189 return QLocale().toString(local.date(), QLocale::ShortFormat);
63 189 }
64
65 189 QString folderName(const QString &path) {
66
2/2
✓ Branch 5 → 6 taken 189 times.
✓ Branch 9 → 10 taken 189 times.
378 const QString name = path.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), -1);
67
3/3
✓ Branch 13 → 14 taken 85 times.
✓ Branch 13 → 15 taken 104 times.
✓ Branch 14 → 16 taken 85 times.
378 return name.compare(QLatin1String("INBOX"), Qt::CaseInsensitive) == 0 ? QuickContactMailHistory::tr("Inbox") : name;
68 189 }
69 } // namespace
70
71 // Used by readHistory(): the worker's calendar store, kept open.
72 556 CalendarStore *contactHistoryCalendar(const QString &path) {
73
2/2
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 553 times.
556 if (path.isEmpty()) return nullptr;
74
5/6
✓ Branch 7 → 8 taken 473 times.
✓ Branch 7 → 11 taken 80 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 473 times.
✓ Branch 13 → 14 taken 80 times.
✓ Branch 13 → 28 taken 473 times.
553 if (!connections.calendar || connections.calendarPath != path) {
75
1/1
✓ Branch 14 → 15 taken 80 times.
80 connections.calendar = std::make_unique<CalendarStore>();
76 80 connections.calendarPath = path;
77
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 28 taken 80 times.
80 if (!connections.calendar->open(path)) {
78 ✗ connections.calendar.reset();
79 ✗ connections.calendarPath.clear();
80 }
81 }
82 553 return connections.calendar.get();
83 }
84
85 143 QVariantList QuickContactMailHistory::read(const QString &databasePath, const QStringList &addresses, int limit) {
86
3/3
✓ Branch 2 → 3 taken 143 times.
✓ Branch 3 → 4 taken 143 times.
✓ Branch 4 → 5 taken 143 times.
143 const auto indexed = MailAddressHistory::read(mailConnection(databasePath), addresses.mid(0, MaxAddresses), limit);
87
3/3
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 11 taken 142 times.
✓ Branch 9 → 10 taken 1 time.
143 if (!indexed) return readByLike(databasePath, addresses, limit); // backfill still running
88 142 QVariantList result;
89
2/2
✓ Branch 71 → 14 taken 189 times.
✓ Branch 71 → 72 taken 142 times.
331 for (const MailAddressHistory::Mail &mail : *indexed)
90
1/1
✓ Branch 47 → 48 taken 189 times.
567 result.append(QVariantMap{{QStringLiteral("subject"), mail.subject},
91 378 {QStringLiteral("from"), mail.from},
92
1/1
✓ Branch 26 → 27 taken 189 times.
378 {QStringLiteral("dateText"), dateText(mail.date)},
93
1/1
✓ Branch 31 → 32 taken 189 times.
378 {QStringLiteral("folder"), folderName(mail.folderPath)},
94 378 {QStringLiteral("messageId"), mail.messageId},
95 378 {QStringLiteral("date"), mail.date},
96
2/4
✓ Branch 51 → 52 taken 1323 times.
✓ Branch 51 → 53 taken 189 times.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
1890 {QStringLiteral("sent"), mail.sent}});
97 142 return result;
98 1655 }
99
100 320 QPair<qint64, qint64> QuickContactMailHistory::stamps(const QString &mailDatabase, const QString &calendarDatabase) {
101 320 qint64 mail = -1;
102
2/2
✓ Branch 3 → 4 taken 318 times.
✓ Branch 3 → 27 taken 2 times.
320 if (!mailDatabase.isEmpty()) {
103
2/2
✓ Branch 4 → 5 taken 318 times.
✓ Branch 5 → 6 taken 318 times.
318 QSqlQuery query(mailConnection(mailDatabase));
104
9/18
✓ Branch 10 → 11 taken 318 times.
✓ Branch 11 → 12 taken 318 times.
✗ Branch 11 → 15 not taken.
✓ Branch 12 → 13 taken 318 times.
✓ Branch 13 → 14 taken 318 times.
✗ Branch 13 → 15 not taken.
✓ Branch 16 → 17 taken 318 times.
✗ Branch 16 → 18 not taken.
✓ Branch 18 → 19 taken 318 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 318 times.
✗ Branch 20 → 25 not taken.
✓ Branch 21 → 22 taken 318 times.
✓ Branch 22 → 23 taken 318 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
636 if (query.exec(QStringLiteral("PRAGMA data_version")) && query.next()) mail = query.value(0).toLongLong();
105 318 }
106
1/1
✓ Branch 27 → 28 taken 320 times.
320 CalendarStore *calendar = contactHistoryCalendar(calendarDatabase);
107
3/3
✓ Branch 28 → 29 taken 318 times.
✓ Branch 28 → 31 taken 2 times.
✓ Branch 29 → 30 taken 318 times.
320 return {mail, calendar ? calendar->dataVersion() : -1};
108 }
109
110 11 bool QuickContactMailHistory::backfillAddressIndex(const QString &mailDatabase, int chunk) {
111
2/2
✓ Branch 2 → 3 taken 11 times.
✓ Branch 3 → 4 taken 11 times.
11 const auto result = MailAddressHistory::backfill(mailConnection(mailDatabase), chunk);
112
3/4
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 8 taken 8 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 3 times.
22 return result.complete || !result.error.isEmpty(); // an error stops the loop (the old path stays)
113 11 }
114
115 7 void QuickContactMailHistory::startAddressBackfill() {
116
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 7 times.
7 if (m_backfillRunning) return;
117 7 m_backfillRunning = true;
118 7 scheduleBackfill(0);
119 }
120
121 7 void QuickContactMailHistory::scheduleBackfill(int delayMs) {
122
1/1
✓ Branch 2 → 3 taken 7 times.
7 QTimer::singleShot(delayMs, this, [this] {
123
2/3
✓ Branch 3 → 4 taken 7 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 6 taken 7 times.
7 const QString path = m_databasePath ? m_databasePath() : QString();
124
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 7 times.
7 if (path.isEmpty()) {
125 ✗ m_backfillRunning = false;
126 ✗ return;
127 }
128
1/1
✓ Branch 10 → 11 taken 7 times.
7 m_pool.start([this, path] {
129 7 const bool done = backfillAddressIndex(path);
130
1/1
✓ Branch 3 → 4 taken 7 times.
7 QMetaObject::invokeMethod(this, [this, done] {
131
1/2
✓ Branch 2 → 3 taken 7 times.
✗ Branch 2 → 4 not taken.
7 if (done) m_backfillRunning = false;
132 ✗ else scheduleBackfill(kBackfillPauseMs);
133 7 }, Qt::QueuedConnection);
134 7 });
135
1/2
✓ Branch 14 → 15 taken 7 times.
✗ Branch 14 → 17 not taken.
7 });
136 7 }
137
138 312 void QuickContactMailHistory::remember(const QString &key, const History &history) {
139
1/1
✓ Branch 3 → 4 taken 312 times.
312 m_cache.insert(key, {history});
140 312 m_cacheOrder.removeOne(key);
141 312 m_cacheOrder.append(key);
142
1/4
✗ Branch 8 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 13 → 8 not taken.
✓ Branch 13 → 14 taken 312 times.
312 while (m_cacheOrder.size() > CachedHistories) m_cache.remove(m_cacheOrder.takeFirst());
143 624 }
144