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% 390 / 0 / 413
Functions: 90.0% 45 / 0 / 50
Branches: 68.2% 649 / 0 / 952

presentation/DavCollectionDiscovery.cpp
Line Branch Exec Source
1 #include "DavCollectionDiscovery.h"
2 #include "service/CalDavClient.h"
3 #include "service/CardDavClient.h"
4 #include "util/SecureUtil.h"
5 #include <QJsonArray>
6 #include <QJsonDocument>
7 #include <QJsonObject>
8 #include <QSet>
9 #include <QSettings>
10 #include <QTimer>
11 #include <QUrl>
12
13 1622 DavCollectionDiscovery::DavCollectionDiscovery(QObject *parent) : QAbstractListModel(parent) {
14 816 m_resolver = [](QObject *owner, const Account &account, DavCredentials::PasswordCallback callback) {
15
1/1
✓ Branch 5 → 6 taken 5 times.
5 DavCredentials::readPasswordAsync(owner, account.id, account.serverUrl, account.username, std::move(callback));
16 816 };
17 811 m_calendarFactory = [](const Account &a, const QByteArray &password) {
18 ✗ return new CalDavClient(a.serverUrl, a.username, QString::fromUtf8(password));
19 811 };
20 811 m_bookFactory = [](const Account &a, const QByteArray &password) {
21 ✗ return new CardDavClient(a.serverUrl, a.username, QString::fromUtf8(password));
22 811 };
23
1/1
✓ Branch 24 → 25 taken 811 times.
811 connect(this, &QAbstractItemModel::modelReset, this, &DavCollectionDiscovery::countChanged);
24
1/1
✓ Branch 26 → 27 taken 811 times.
811 connect(this, &QAbstractItemModel::rowsInserted, this, &DavCollectionDiscovery::countChanged);
25
1/1
✓ Branch 28 → 29 taken 811 times.
811 connect(this, &QAbstractItemModel::rowsRemoved, this, &DavCollectionDiscovery::countChanged);
26 811 }
27
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 810 times.
811 DavCollectionDiscovery::~DavCollectionDiscovery() { ++m_generation; delete m_request.data(); }
28
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 246 times.
246 int DavCollectionDiscovery::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_rows.size(); }
29 18 QHash<int, QByteArray> DavCollectionDiscovery::roleNames() const {
30 ✗ return {{Name, "collectionName"}, {Path, "collectionPath"}, {Kind, "collectionKind"},
31 ✗ {Selected, "collectionSelected"}, {Available, "collectionAvailable"}, {Color, "collectionColor"},
32 ✗ {Content, "collectionContent"}, {ReadOnly, "collectionReadOnly"}, {Push, "collectionPush"},
33
2/4
✓ Branch 15 → 16 taken 198 times.
✓ Branch 15 → 17 taken 18 times.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
216 {Reason, "collectionReason"}, {Key, "collectionKey"}};
34 18 }
35 129 QVariant DavCollectionDiscovery::data(const QModelIndex &index, int role) const {
36
4/8
✓ Branch 3 → 4 taken 129 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 129 times.
✗ Branch 5 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 129 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 129 times.
129 if (!index.isValid() || index.row() < 0 || index.row() >= m_rows.size()) return {};
37 129 const auto &row = m_rows.at(index.row());
38
10/12
✓ Branch 15 → 16 taken 12 times.
✓ Branch 15 → 17 taken 15 times.
✗ Branch 15 → 18 not taken.
✓ Branch 15 → 32 taken 15 times.
✓ Branch 15 → 33 taken 13 times.
✓ Branch 15 → 34 taken 12 times.
✓ Branch 15 → 35 taken 12 times.
✓ Branch 15 → 39 taken 12 times.
✓ Branch 15 → 40 taken 12 times.
✓ Branch 15 → 41 taken 13 times.
✓ Branch 15 → 45 taken 13 times.
✗ Branch 15 → 49 not taken.
129 switch (role) {
39 12 case Name: return row.name;
40 15 case Path: return row.path;
41 ✗ case Kind: return row.calendar ? QStringLiteral("calendar") : QStringLiteral("book");
42 15 case Selected: return row.selected;
43 13 case Available: return row.available;
44 12 case Color: return row.color;
45
1/1
✓ Branch 35 → 36 taken 12 times.
12 case Content: return contentKind(row);
46 12 case ReadOnly: return row.readOnly;
47 12 case Push: return row.push;
48
1/1
✓ Branch 41 → 42 taken 13 times.
13 case Reason: return defaultOffReason(row.path, row.calendar);
49
1/1
✓ Branch 45 → 46 taken 13 times.
13 case Key: return collectionKey(row.path);
50 ✗ default: return {};
51 }
52 }
53 51 void DavCollectionDiscovery::stopRequest() {
54 51 ++m_generation;
55
2/2
✓ Branch 3 → 4 taken 24 times.
✓ Branch 3 → 6 taken 27 times.
51 if (m_request) m_request->deleteLater();
56 51 m_request = nullptr;
57 51 m_busy = false;
58 51 }
59 27 void DavCollectionDiscovery::cancel() {
60 27 stopRequest();
61 27 beginResetModel(); m_rows.clear(); endResetModel();
62 27 m_dirty = false; m_calendarsOk = false; m_booksOk = false;
63 54 m_error.clear(); m_state = QStringLiteral("cancelled");
64 27 emit stateChanged();
65 27 }
66 7 void DavCollectionDiscovery::fail(const QString &error) {
67 7 stopRequest();
68 14 m_error = error; m_state = QStringLiteral("error");
69 7 m_dirty = false; m_calendarsOk = false; m_booksOk = false;
70 7 emit stateChanged();
71 7 }
72 74 bool DavCollectionDiscovery::currentConfiguration() const {
73 74 int found = 0;
74
5/5
✓ Branch 2 → 3 taken 74 times.
✓ Branch 3 → 4 taken 74 times.
✓ Branch 4 → 5 taken 74 times.
✓ Branch 15 → 6 taken 75 times.
✓ Branch 15 → 16 taken 73 times.
148 for (const auto &account : DavAccountSettings::accounts())
75
7/7
✓ Branch 8 → 9 taken 68 times.
✓ Branch 8 → 13 taken 7 times.
✓ Branch 9 → 10 taken 68 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 67 times.
✓ Branch 18 → 19 taken 73 times.
✓ Branch 18 → 21 taken 1 time.
148 if (account.id == m_account.id) { if (account != m_account) return false; ++found; }
76
2/2
✓ Branch 20 → 22 taken 6 times.
✓ Branch 20 → 23 taken 67 times.
73 if (found != 1) return false;
77 67 auto current = DavAccountSettings::CalendarConfig{m_account.id, {}, {}};
78 67 found = 0;
79
5/5
✓ Branch 24 → 25 taken 67 times.
✓ Branch 25 → 26 taken 67 times.
✓ Branch 26 → 27 taken 67 times.
✓ Branch 35 → 28 taken 13 times.
✓ Branch 35 → 36 taken 67 times.
80 for (const auto &config : DavAccountSettings::calendarConfigs())
80
1/2
✓ Branch 30 → 31 taken 13 times.
✗ Branch 30 → 33 not taken.
80 if (config.accountId == m_account.id) { current = config; ++found; }
81
3/5
✓ Branch 37 → 38 taken 67 times.
✗ Branch 37 → 41 not taken.
✓ Branch 38 → 39 taken 67 times.
✓ Branch 39 → 40 taken 67 times.
✗ Branch 39 → 41 not taken.
67 return found <= 1 && current == m_calendarConfig;
82 67 }
83 17 void DavCollectionDiscovery::invalidateConfiguration() {
84
6/6
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 7 taken 9 times.
✓ Branch 5 → 6 taken 5 times.
✓ Branch 5 → 7 taken 3 times.
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 13 taken 12 times.
17 if (!m_account.id.isEmpty() && !currentConfiguration())
85
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 10 → 11 taken 5 times.
5 fail(tr("The account settings changed. Discover its collections again."));
86 17 }
87 179 QString DavCollectionDiscovery::collectionKey(const QString &path) const {
88 179 return keyFor(m_account.serverUrl, path);
89 }
90 1297 QString DavCollectionDiscovery::keyFor(const QString &serverUrl, const QString &path) {
91
3/6
✓ Branch 3 → 4 taken 1297 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1297 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1297 times.
1297 if (path.isEmpty() || path.size() > 4096) return {};
92
5/8
✓ Branch 13 → 14 taken 16724 times.
✗ Branch 13 → 16 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 16724 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 16724 times.
✓ Branch 21 → 12 taken 16724 times.
✓ Branch 21 → 22 taken 1297 times.
18021 for (const auto ch : path) if (ch.isNull() || ch.category() == QChar::Other_Control) return {};
93
2/2
✓ Branch 24 → 26 taken 1297 times.
✓ Branch 26 → 27 taken 1297 times.
1297 QUrl base(serverUrl + QLatin1Char('/'));
94
1/1
✓ Branch 28 → 29 taken 1297 times.
1297 const QUrl candidate(path, QUrl::StrictMode);
95
4/4
✓ Branch 29 → 30 taken 1297 times.
✓ Branch 30 → 31 taken 1296 times.
✓ Branch 30 → 32 taken 1 time.
✓ Branch 31 → 33 taken 1296 times.
1297 const QUrl resolved = candidate.isRelative() ? base.resolved(candidate) : candidate;
96
6/9
✓ Branch 33 → 34 taken 1297 times.
✓ Branch 34 → 35 taken 1297 times.
✗ Branch 34 → 45 not taken.
✓ Branch 35 → 36 taken 1297 times.
✓ Branch 36 → 37 taken 1297 times.
✗ Branch 36 → 45 not taken.
✓ Branch 37 → 38 taken 1297 times.
✓ Branch 38 → 39 taken 1297 times.
✗ Branch 38 → 45 not taken.
2594 if (!resolved.isValid() || resolved.hasQuery() || resolved.hasFragment() ||
97
10/19
✓ Branch 39 → 40 taken 1297 times.
✓ Branch 41 → 42 taken 1297 times.
✓ Branch 42 → 43 taken 1297 times.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 46 taken 1296 times.
✓ Branch 47 → 48 taken 1297 times.
✗ Branch 47 → 49 not taken.
✓ Branch 49 → 50 taken 1297 times.
✗ Branch 49 → 51 not taken.
✓ Branch 51 → 52 taken 1297 times.
✗ Branch 51 → 53 not taken.
✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 55 taken 1296 times.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
2594 DavCredentials::canonicalOrigin(resolved.toString()) != DavCredentials::canonicalOrigin(serverUrl)) return {};
98
1/1
✓ Branch 56 → 57 taken 1296 times.
1296 return resolved.toString(QUrl::FullyEncoded);
99 1297 }
100 26 bool DavCollectionDiscovery::discover(const QString &id) {
101
1/1
✓ Branch 2 → 3 taken 26 times.
26 QPointer<DavCollectionDiscovery> guard(this);
102 26 const auto nextGeneration = m_generation + 1;
103
1/1
✓ Branch 3 → 4 taken 26 times.
26 cancel();
104
3/6
✓ Branch 5 → 6 taken 26 times.
✗ Branch 5 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 26 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 26 times.
26 if (!guard || m_generation != nextGeneration) return false;
105 26 m_account = {};
106 26 int found = 0;
107
5/5
✓ Branch 13 → 14 taken 26 times.
✓ Branch 14 → 15 taken 26 times.
✓ Branch 15 → 16 taken 26 times.
✓ Branch 24 → 17 taken 27 times.
✓ Branch 24 → 25 taken 26 times.
53 for (const auto &account : DavAccountSettings::accounts())
108
2/2
✓ Branch 19 → 20 taken 26 times.
✓ Branch 19 → 22 taken 1 time.
53 if (account.id == id) { m_account = account; ++found; }
109 26 m_calendarConfig = {id, {}, {}};
110 26 int configs = 0;
111
5/5
✓ Branch 29 → 30 taken 26 times.
✓ Branch 30 → 31 taken 26 times.
✓ Branch 31 → 32 taken 26 times.
✓ Branch 40 → 33 taken 4 times.
✓ Branch 40 → 41 taken 26 times.
30 for (const auto &config : DavAccountSettings::calendarConfigs())
112
1/2
✓ Branch 35 → 36 taken 4 times.
✗ Branch 35 → 38 not taken.
30 if (config.accountId == id) { m_calendarConfig = config; ++configs; }
113
2/6
✓ Branch 42 → 43 taken 26 times.
✗ Branch 42 → 44 not taken.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 48 taken 26 times.
✗ Branch 44 → 45 not taken.
✗ Branch 45 → 46 not taken.
26 if (found != 1 || configs > 1) { fail(tr("This DAV account is no longer available.")); return false; }
114
4/4
✓ Branch 48 → 49 taken 1 time.
✓ Branch 48 → 53 taken 25 times.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 50 → 51 taken 1 time.
26 if (m_account.requiresLocalAuthorization) { fail(tr("Authorize this account locally before discovering collections.")); return false; }
115
2/2
✓ Branch 53 → 54 taken 25 times.
✓ Branch 54 → 55 taken 25 times.
25 m_calendars.clear(); m_books.clear(); m_calendarError.clear(); m_bookError.clear();
116 25 m_calendarsDone = m_booksDone = false;
117
4/7
✓ Branch 57 → 58 taken 25 times.
✓ Branch 58 → 59 taken 25 times.
✓ Branch 59 → 60 taken 25 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 25 times.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
25 m_request = new QObject(this);
118 25 const auto generation = ++m_generation;
119 50 m_busy = true; m_state = QStringLiteral("credentials");
120
1/3
✓ Branch 70 → 71 taken 25 times.
✗ Branch 107 → 108 not taken.
✗ Branch 107 → 109 not taken.
25 QTimer::singleShot(30000, m_request, [guard, generation] {
121 ✗ if (guard && guard->m_generation == generation) guard->fail(tr("Collection discovery timed out. Try again."));
122 ✗ });
123
1/1
✓ Branch 72 → 73 taken 25 times.
25 emit stateChanged();
124
3/6
✓ Branch 74 → 75 taken 25 times.
✗ Branch 74 → 76 not taken.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 77 taken 25 times.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 25 times.
25 if (!guard || generation != m_generation) return false;
125
2/4
✓ Branch 81 → 82 taken 25 times.
✓ Branch 83 → 84 taken 25 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
25 m_resolver(m_request, m_account, [guard, generation](QByteArray password, QString error) {
126
6/7
✓ Branch 3 → 4 taken 20 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 18 times.
✓ Branch 5 → 7 taken 2 times.
✓ Branch 8 → 9 taken 18 times.
✓ Branch 8 → 15 taken 2 times.
✓ Branch 12 → 13 taken 18 times.
20 if (guard && guard->m_generation == generation) guard->beginNetwork(generation, std::move(password), error);
127 20 });
128 25 return true;
129 52 }
130 18 void DavCollectionDiscovery::beginNetwork(quint64 generation, QByteArray password, const QString &error) {
131
5/5
✓ Branch 2 → 3 taken 18 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 8 taken 17 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
18 if (!currentConfiguration()) { fail(tr("The account settings changed. Discover its collections again.")); return; }
132
1/4
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 26 taken 17 times.
✗ Branch 17 → 18 not taken.
✗ Branch 19 → 20 not taken.
17 if (password.isEmpty()) { fail(tr("No password is stored for this account. Enter it with Edit, save, then discover again.") +
133 ✗ (error.isEmpty() ? QString{} : QLatin1Char('\n') + error.left(1024))); return; }
134
1/1
✓ Branch 26 → 27 taken 17 times.
17 QPointer<DavCollectionDiscovery> guard(this);
135 17 m_state = QStringLiteral("discovering");
136
1/1
✓ Branch 33 → 34 taken 17 times.
17 auto *calendar = m_calendarFactory(m_account, password);
137
1/1
✓ Branch 34 → 35 taken 17 times.
17 auto *books = m_bookFactory(m_account, password);
138
1/1
✓ Branch 35 → 36 taken 17 times.
17 SecureUtil::zeroMemory(password);
139
2/10
✓ Branch 36 → 37 taken 17 times.
✗ Branch 36 → 38 not taken.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 46 taken 17 times.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 43 → 44 not taken.
17 if (!calendar || !books) { delete calendar; delete books; fail(tr("Collection discovery could not start.")); return; }
140
2/2
✓ Branch 47 → 48 taken 17 times.
✓ Branch 49 → 50 taken 17 times.
17 calendar->setParent(m_request); books->setParent(m_request);
141
1/3
✓ Branch 52 → 53 taken 17 times.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
17 connect(calendar, &CalDavClient::calendarsDiscovered, m_request, [guard, generation](const QList<CalendarInfo> &items) {
142
3/6
✓ Branch 3 → 4 taken 17 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 17 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 17 times.
17 if (!guard || guard->m_generation != generation) return;
143 17 QList<Item> values;
144
1/2
✓ Branch 11 → 12 taken 17 times.
✗ Branch 11 → 25 not taken.
17 if (items.size() <= 256)
145
2/2
✓ Branch 23 → 14 taken 34 times.
✓ Branch 23 → 24 taken 17 times.
51 for (const auto &item : items)
146 34 values.append({item.path, item.displayName, item.color, item.supportedComponents, item.readOnly,
147 34 item.push.capability == DavPushCapability::Supported});
148
2/4
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 17 times.
✗ Branch 28 → 30 not taken.
✓ Branch 30 → 31 taken 17 times.
17 guard->received(true, values, items.size() > 256 ? tr("Too many collections were returned.") : QString{});
149
1/9
✓ Branch 19 → 20 taken 34 times.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
51 });
150
1/3
✓ Branch 57 → 58 taken 17 times.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
17 connect(books, &CardDavClient::addressBooksDiscovered, m_request, [guard, generation](const QList<AddressBookInfo> &items) {
151
3/6
✓ Branch 3 → 4 taken 16 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 16 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 16 times.
16 if (!guard || guard->m_generation != generation) return;
152 16 QList<Item> values;
153
1/2
✓ Branch 11 → 12 taken 16 times.
✗ Branch 11 → 23 not taken.
16 if (items.size() <= 256)
154
2/2
✓ Branch 21 → 14 taken 16 times.
✓ Branch 21 → 22 taken 16 times.
32 for (const auto &item : items)
155 16 values.append({item.path, item.displayName, QString(), {}, item.readOnly,
156 16 item.push.capability == DavPushCapability::Supported});
157
2/4
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 16 times.
✗ Branch 26 → 28 not taken.
✓ Branch 28 → 29 taken 16 times.
16 guard->received(false, values, items.size() > 256 ? tr("Too many collections were returned.") : QString{});
158
1/9
✓ Branch 17 → 18 taken 16 times.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
32 });
159
1/3
✓ Branch 62 → 63 taken 17 times.
✗ Branch 132 → 133 not taken.
✗ Branch 132 → 134 not taken.
17 connect(calendar, &CalDavClient::syncFailed, m_request, [guard, generation](const QString &message) {
160 ✗ if (guard && guard->m_generation == generation) guard->received(true, {}, message.left(1024));
161 ✗ });
162
1/3
✓ Branch 67 → 68 taken 17 times.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
17 connect(books, &CardDavClient::syncFailed, m_request, [guard, generation](const QString &message) {
163
5/8
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 16 not taken.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
1 if (guard && guard->m_generation == generation) guard->received(false, {}, message.left(1024));
164 1 });
165
1/1
✓ Branch 70 → 71 taken 17 times.
17 emit stateChanged();
166
3/6
✓ Branch 72 → 73 taken 17 times.
✗ Branch 72 → 75 not taken.
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 76 taken 17 times.
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 79 taken 17 times.
17 if (!guard || guard->m_generation != generation) return;
167
1/1
✓ Branch 79 → 80 taken 17 times.
17 calendar->discoverCalendars();
168
4/7
✓ Branch 81 → 82 taken 17 times.
✗ Branch 81 → 85 not taken.
✓ Branch 83 → 84 taken 17 times.
✗ Branch 83 → 85 not taken.
✓ Branch 86 → 87 taken 17 times.
✗ Branch 86 → 88 not taken.
✓ Branch 87 → 88 taken 17 times.
17 if (guard && guard->m_generation == generation) books->discoverAddressBooks();
169 17 }
170 34 void DavCollectionDiscovery::received(bool calendars, const QList<Item> &values, const QString &error) {
171
6/8
✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 4 taken 17 times.
✓ Branch 3 → 4 taken 17 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 17 times.
✓ Branch 4 → 7 taken 17 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 17 times.
34 if ((calendars && m_calendarsDone) || (!calendars && m_booksDone)) return;
172
2/5
✓ Branch 7 → 8 taken 34 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 13 taken 34 times.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
34 if (!currentConfiguration()) { fail(tr("The account settings changed. Discover its collections again.")); return; }
173
2/2
✓ Branch 13 → 14 taken 17 times.
✓ Branch 13 → 15 taken 17 times.
34 auto &target = calendars ? m_calendars : m_books;
174
2/2
✓ Branch 16 → 17 taken 17 times.
✓ Branch 16 → 18 taken 17 times.
34 auto &failure = calendars ? m_calendarError : m_bookError;
175 34 failure = error;
176 34 QSet<QString> keys;
177
4/4
✓ Branch 22 → 23 taken 33 times.
✓ Branch 22 → 59 taken 1 time.
✓ Branch 57 → 25 taken 50 times.
✓ Branch 57 → 58 taken 32 times.
83 if (failure.isEmpty()) for (const auto &item : values) {
178
2/2
✓ Branch 26 → 27 taken 50 times.
✓ Branch 27 → 28 taken 50 times.
50 const QString key = collectionKey(item.path.trimmed());
179
3/3
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 35 taken 49 times.
✓ Branch 31 → 32 taken 1 time.
50 if (key.isEmpty()) { failure = tr("The server returned an invalid collection address."); break; }
180
1/2
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 49 times.
49 if (keys.contains(key)) continue;
181
1/1
✓ Branch 38 → 39 taken 49 times.
49 keys.insert(key);
182
3/7
✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 49 times.
✗ Branch 41 → 43 not taken.
✓ Branch 42 → 43 taken 49 times.
✓ Branch 43 → 44 taken 49 times.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
49 Row row{item.name.isEmpty() ? item.path.left(512) : item.name.left(512), item.path.trimmed(), calendars, false, true, false};
183 49 row.color = item.color;
184 49 row.components = item.components;
185 49 row.readOnly = item.readOnly;
186 49 row.push = item.push;
187
1/1
✓ Branch 46 → 47 taken 49 times.
49 target.append(row);
188 50 }
189
3/3
✓ Branch 60 → 61 taken 2 times.
✓ Branch 60 → 62 taken 32 times.
✓ Branch 61 → 62 taken 2 times.
34 if (!failure.isEmpty()) target.clear();
190
2/2
✓ Branch 62 → 63 taken 17 times.
✓ Branch 62 → 65 taken 17 times.
34 if (calendars) { m_calendarsDone = true; m_calendarsOk = failure.isEmpty(); }
191 17 else { m_booksDone = true; m_booksOk = failure.isEmpty(); }
192
4/5
✓ Branch 67 → 68 taken 34 times.
✗ Branch 67 → 70 not taken.
✓ Branch 68 → 69 taken 17 times.
✓ Branch 68 → 70 taken 17 times.
✓ Branch 69 → 70 taken 17 times.
34 if (m_calendarsDone && m_booksDone) completeEnumeration();
193 34 }
194 17 void DavCollectionDiscovery::completeEnumeration() {
195
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 20 taken 17 times.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 20 not taken.
17 if (!m_calendarsOk && !m_booksOk) {
196 ✗ fail(tr("No collections could be discovered.") + QLatin1Char('\n') + m_calendarError + QLatin1Char('\n') + m_bookError);
197 ✗ return;
198 }
199 32 const auto retainSelections = [this](QList<Row> &rows, const QStringList &selected, bool calendar) {
200
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 32 times.
32 if (selected.size() > 256) return false;
201 32 QSet<QString> found;
202
4/4
✓ Branch 6 → 7 taken 32 times.
✓ Branch 7 → 8 taken 32 times.
✓ Branch 26 → 9 taken 49 times.
✓ Branch 26 → 27 taken 32 times.
81 for (auto &row : rows) {
203
6/6
✓ Branch 13 → 14 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 21 taken 1 time.
✓ Branch 23 → 12 taken 2 times.
✓ Branch 23 → 24 taken 49 times.
51 for (const auto &path : selected) if (collectionKey(path) == collectionKey(row.path)) {
204
1/1
✓ Branch 19 → 20 taken 1 time.
1 row.selected = row.initiallySelected = true; found.insert(path);
205 }
206 }
207
4/4
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 37 taken 1 time.
✓ Branch 39 → 29 taken 2 times.
✓ Branch 39 → 40 taken 32 times.
34 for (const auto &path : selected) if (!found.contains(path))
208 1 rows.append({path.left(512), path, calendar, true, false, true});
209 32 return rows.size() <= 256;
210
2/6
✓ Branch 32 → 33 taken 1 time.
✓ Branch 34 → 35 taken 1 time.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
33 };
211
4/7
✓ Branch 20 → 21 taken 17 times.
✗ Branch 20 → 23 not taken.
✓ Branch 21 → 22 taken 17 times.
✓ Branch 22 → 23 taken 17 times.
✗ Branch 22 → 26 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 33 taken 17 times.
34 if ((m_calendarsOk && !retainSelections(m_calendars, m_calendarConfig.selectedCalendars, true)) ||
212
4/5
✓ Branch 23 → 24 taken 15 times.
✓ Branch 23 → 27 taken 2 times.
✓ Branch 24 → 25 taken 15 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 15 times.
17 (m_booksOk && !retainSelections(m_books, m_account.selectedBooks, false))) {
213 ✗ fail(tr("Too many collections were returned.")); return;
214 }
215
1/1
✓ Branch 33 → 34 taken 17 times.
17 stopRequest();
216
1/1
✓ Branch 34 → 35 taken 17 times.
17 beginResetModel();
217
1/1
✓ Branch 35 → 36 taken 17 times.
17 m_rows = m_calendars + m_books;
218
1/1
✓ Branch 38 → 39 taken 17 times.
17 endResetModel();
219 17 m_state = QStringLiteral("ready"); m_dirty = false;
220
1/1
✓ Branch 45 → 46 taken 17 times.
17 writeCache();
221
1/1
✓ Branch 47 → 48 taken 17 times.
17 rememberKnown({}); // the first discovery of an account offers nothing as new
222 17 QStringList errors;
223
1/5
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 58 taken 17 times.
✗ Branch 51 → 52 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 54 → 55 not taken.
17 if (!m_calendarError.isEmpty()) errors.append(tr("Calendars: %1").arg(m_calendarError));
224
5/5
✓ Branch 59 → 60 taken 2 times.
✓ Branch 59 → 67 taken 15 times.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 62 → 63 taken 2 times.
✓ Branch 63 → 64 taken 2 times.
19 if (!m_bookError.isEmpty()) errors.append(tr("Address books: %1").arg(m_bookError));
225
1/1
✓ Branch 69 → 70 taken 17 times.
17 m_error = errors.join(QLatin1Char('\n'));
226
1/1
✓ Branch 72 → 73 taken 17 times.
17 emit stateChanged();
227 17 }
228 19 bool DavCollectionDiscovery::setSelected(int row, bool selected) {
229
4/8
✓ Branch 2 → 3 taken 19 times.
✗ Branch 2 → 11 not taken.
✓ Branch 3 → 4 taken 19 times.
✗ Branch 3 → 11 not taken.
✓ Branch 5 → 6 taken 19 times.
✗ Branch 5 → 11 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 19 times.
38 if (m_busy || row < 0 || row >= m_rows.size() ||
230
7/8
✓ Branch 6 → 7 taken 19 times.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 12 taken 16 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 12 taken 2 times.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
19 (!m_rows[row].available && selected && !m_rows[row].initiallySelected)) return false;
231
1/1
✓ Branch 15 → 16 taken 19 times.
19 m_rows[row].selected = selected;
232 19 m_dirty = false;
233
4/4
✓ Branch 16 → 17 taken 19 times.
✓ Branch 17 → 18 taken 19 times.
✓ Branch 22 → 19 taken 58 times.
✓ Branch 22 → 23 taken 19 times.
77 for (const auto &item : m_rows) m_dirty |= item.selected != item.initiallySelected;
234
1/1
✓ Branch 23 → 24 taken 19 times.
19 QPointer<DavCollectionDiscovery> guard(this);
235
4/4
✓ Branch 24 → 25 taken 19 times.
✓ Branch 26 → 27 taken 19 times.
✓ Branch 28 → 29 taken 19 times.
✓ Branch 29 → 30 taken 19 times.
19 emit dataChanged(index(row), index(row), {Selected});
236
2/3
✓ Branch 32 → 33 taken 19 times.
✗ Branch 32 → 34 not taken.
✓ Branch 33 → 34 taken 19 times.
19 if (guard) emit stateChanged();
237 19 return true;
238 19 }
239 15 bool DavCollectionDiscovery::apply() {
240
8/8
✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 4 → 5 taken 14 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 13 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 11 taken 13 times.
✓ Branch 9 → 10 taken 2 times.
15 if (!canApply() || !currentConfiguration()) { invalidateConfiguration(); return false; }
241 13 QStringList books, calendars;
242
6/6
✓ Branch 11 → 12 taken 13 times.
✓ Branch 12 → 13 taken 13 times.
✓ Branch 15 → 16 taken 13 times.
✓ Branch 15 → 29 taken 25 times.
✓ Branch 31 → 14 taken 38 times.
✓ Branch 31 → 32 taken 13 times.
51 for (const auto &row : m_rows) if (row.selected) {
243
2/5
✓ Branch 16 → 17 taken 13 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 25 taken 13 times.
✗ Branch 20 → 21 not taken.
✗ Branch 23 → 24 not taken.
13 if (collectionKey(row.path).isEmpty()) { m_error = tr("Deselect unavailable collections before saving."); emit stateChanged(); return false; }
244
3/3
✓ Branch 25 → 26 taken 10 times.
✓ Branch 25 → 27 taken 3 times.
✓ Branch 28 → 29 taken 13 times.
13 (row.calendar ? calendars : books).append(row.path);
245 }
246
1/1
✓ Branch 39 → 40 taken 13 times.
13 if (!DavAccountSettings::storeSelections(m_account, m_calendarConfig,
247
2/2
✓ Branch 36 → 37 taken 11 times.
✓ Branch 36 → 38 taken 2 times.
26 m_booksOk ? std::optional<QStringList>(books) : std::nullopt,
248
2/4
✓ Branch 32 → 34 taken 13 times.
✗ Branch 32 → 35 not taken.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 48 taken 13 times.
26 m_calendarsOk ? std::optional<QStringList>(calendars) : std::nullopt)) {
249 ✗ m_error = tr("The collection selection could not be saved. Discover again if the account changed.");
250 ✗ emit stateChanged(); return false;
251 }
252
2/2
✓ Branch 48 → 49 taken 11 times.
✓ Branch 48 → 50 taken 2 times.
13 if (m_booksOk) m_account.selectedBooks = books;
253
1/2
✓ Branch 50 → 51 taken 13 times.
✗ Branch 50 → 52 not taken.
13 if (m_calendarsOk) m_calendarConfig.selectedCalendars = calendars;
254
4/4
✓ Branch 52 → 53 taken 13 times.
✓ Branch 53 → 54 taken 13 times.
✓ Branch 58 → 55 taken 38 times.
✓ Branch 58 → 59 taken 13 times.
51 for (auto &row : m_rows) row.initiallySelected = row.selected;
255 26 m_dirty = false; m_state = QStringLiteral("saved");
256 13 const auto id = m_account.id;
257 13 const auto generation = m_generation;
258
1/1
✓ Branch 66 → 67 taken 13 times.
13 QPointer<DavCollectionDiscovery> guard(this);
259
1/1
✓ Branch 67 → 68 taken 13 times.
13 emit stateChanged();
260
4/7
✓ Branch 69 → 70 taken 13 times.
✗ Branch 69 → 72 not taken.
✓ Branch 70 → 71 taken 13 times.
✗ Branch 70 → 72 not taken.
✓ Branch 73 → 74 taken 13 times.
✗ Branch 73 → 75 not taken.
✓ Branch 74 → 75 taken 13 times.
13 if (guard && generation == m_generation) emit applied(id);
261 13 return true;
262 13 }
263
264 // ─── Sprint 100 S100.1.12/S100.1.13: what a collection is, instant toggles,
265 // the last known list and what is new on the server ───
266
267 15 QString DavCollectionDiscovery::contentKind(const Row &row) const {
268 15 return kindOf(row.calendar, row.components);
269 }
270 612 QString DavCollectionDiscovery::kindOf(bool calendar, const QStringList &components) {
271
2/2
✓ Branch 2 → 3 taken 125 times.
✓ Branch 2 → 8 taken 487 times.
737 if (!calendar) return QStringLiteral("book");
272
8/8
✓ Branch 9 → 10 taken 240 times.
✓ Branch 9 → 15 taken 247 times.
✓ Branch 14 → 15 taken 226 times.
✓ Branch 14 → 16 taken 14 times.
✓ Branch 17 → 18 taken 240 times.
✓ Branch 17 → 19 taken 247 times.
✓ Branch 19 → 20 taken 240 times.
✓ Branch 19 → 21 taken 247 times.
727 const bool events = components.isEmpty() || components.contains(QStringLiteral("VEVENT"));
273 487 const bool tasks = components.contains(QStringLiteral("VTODO"));
274
3/4
✓ Branch 27 → 28 taken 473 times.
✓ Branch 27 → 34 taken 14 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 34 taken 473 times.
487 if (events && tasks) return QStringLiteral("both");
275
6/6
✓ Branch 34 → 35 taken 14 times.
✓ Branch 34 → 38 taken 473 times.
✓ Branch 42 → 43 taken 473 times.
✓ Branch 42 → 44 taken 14 times.
✓ Branch 44 → 45 taken 14 times.
✓ Branch 44 → 46 taken 473 times.
1461 return tasks ? QStringLiteral("tasks") : QStringLiteral("calendar");
276 }
277
278 22 QString DavCollectionDiscovery::defaultOffReason(const QString &path, bool calendar) {
279 // Nextcloud's address book of every user of the instance, and server-made
280 // birthday calendars (MailJD shows birthdays itself, S97.5.10).
281
7/7
✓ Branch 2 → 3 taken 6 times.
✓ Branch 2 → 7 taken 16 times.
✓ Branch 4 → 5 taken 6 times.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 3 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 19 times.
22 if (!calendar && path.contains(QLatin1String("z-server-generated--system")))
282 3 return tr("All users of the server; off by default.");
283
7/7
✓ Branch 10 → 11 taken 16 times.
✓ Branch 10 → 15 taken 3 times.
✓ Branch 12 → 13 taken 16 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 14 times.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 17 times.
19 if (calendar && path.contains(QLatin1String("contact_birthdays")))
284 2 return tr("Birthdays from the server; MailJD shows them from your contacts.");
285 17 return {};
286 }
287
288 10 QVariantMap DavCollectionDiscovery::toggle(int row, bool selected) {
289
3/10
✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 10 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 19 taken 10 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 152 → 153 not taken.
✗ Branch 152 → 154 not taken.
10 if (row < 0 || row >= m_rows.size()) return {{QStringLiteral("ok"), false}};
290 10 const Row item = m_rows.at(row);
291
7/8
✓ Branch 21 → 22 taken 3 times.
✓ Branch 21 → 26 taken 7 times.
✓ Branch 22 → 23 taken 3 times.
✗ Branch 22 → 26 not taken.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 1 time.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 54 taken 8 times.
10 if (!selected && item.selected && m_pendingCounter) {
292
1/1
✓ Branch 28 → 29 taken 2 times.
2 const int pending = m_pendingCounter(m_account.id, item.path, item.calendar);
293
2/2
✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 53 taken 1 time.
2 if (pending > 0)
294
2/4
✓ Branch 44 → 45 taken 3 times.
✓ Branch 44 → 46 taken 1 time.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
8 return {{QStringLiteral("ok"), false}, {QStringLiteral("pending"), pending}, {QStringLiteral("name"), item.name}};
295 }
296
4/6
✓ Branch 56 → 57 taken 1 time.
✓ Branch 56 → 74 taken 8 times.
✓ Branch 67 → 68 taken 2 times.
✓ Branch 67 → 69 taken 1 time.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
15 if (m_state == QLatin1String("cached")) return {{QStringLiteral("ok"), false}, {QStringLiteral("name"), item.name}};
297
2/7
✓ Branch 74 → 75 taken 8 times.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 93 taken 8 times.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
✗ Branch 222 → 223 not taken.
✗ Branch 222 → 224 not taken.
8 if (!setSelected(row, selected)) return {{QStringLiteral("ok"), false}, {QStringLiteral("name"), item.name}};
298
4/11
✓ Branch 93 → 94 taken 8 times.
✗ Branch 93 → 97 not taken.
✓ Branch 94 → 95 taken 8 times.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 8 times.
✗ Branch 98 → 99 not taken.
✓ Branch 98 → 116 taken 8 times.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
✗ Branch 245 → 246 not taken.
✗ Branch 245 → 247 not taken.
8 if (m_dirty && !apply()) return {{QStringLiteral("ok"), false}, {QStringLiteral("name"), item.name}};
299
5/7
✓ Branch 116 → 117 taken 6 times.
✓ Branch 116 → 125 taken 2 times.
✓ Branch 119 → 120 taken 6 times.
✓ Branch 122 → 123 taken 6 times.
✓ Branch 122 → 124 taken 6 times.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 272 not taken.
20 if (selected) rememberKnown({collectionKey(item.path)});
300
2/4
✓ Branch 139 → 140 taken 24 times.
✓ Branch 139 → 141 taken 8 times.
✗ Branch 281 → 282 not taken.
✗ Branch 281 → 283 not taken.
64 return {{QStringLiteral("ok"), true}, {QStringLiteral("name"), item.name}, {QStringLiteral("selected"), selected}};
301 65 }
302
303 1 int DavCollectionDiscovery::selectDefaults() {
304 1 int selected = 0;
305
2/2
✓ Branch 20 → 3 taken 3 times.
✓ Branch 20 → 21 taken 1 time.
4 for (int row = 0; row < m_rows.size(); ++row) {
306
5/9
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 11 not taken.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 14 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
3 const bool on = m_rows.at(row).available && defaultOffReason(m_rows.at(row).path, m_rows.at(row).calendar).isEmpty();
307 3 setSelected(row, on);
308
2/2
✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 17 taken 1 time.
3 selected += on ? 1 : 0;
309 }
310
1/2
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 23 not taken.
1 if (m_dirty) apply();
311 1 return selected;
312 }
313
314 namespace {
315
1/1
✓ Branch 5 → 6 taken 1450 times.
2900 QString knownName(const QString &accountId) { return QStringLiteral("carddav/collectionKnown/") + accountId; }
316
1/1
✓ Branch 5 → 6 taken 69 times.
138 QString seededName(const QString &accountId) { return QStringLiteral("carddav/collectionSeeded/") + accountId; }
317
1/1
✓ Branch 5 → 6 taken 1424 times.
2848 QString cacheName(const QString &accountId) { return QStringLiteral("carddav/collectionCache/") + accountId; }
318 // The kinds ("calendars", "books") whose first list is already in the known
319 // set. A known set from before the per-kind record came from a complete
320 // discovery, so it covers both.
321 32 QStringList seededKinds(const QString &accountId) {
322
1/1
✓ Branch 2 → 3 taken 32 times.
32 QSettings settings;
323
7/7
✓ Branch 3 → 4 taken 32 times.
✓ Branch 5 → 6 taken 32 times.
✓ Branch 7 → 8 taken 18 times.
✓ Branch 7 → 16 taken 14 times.
✓ Branch 8 → 9 taken 18 times.
✓ Branch 10 → 11 taken 18 times.
✓ Branch 11 → 12 taken 18 times.
32 if (settings.contains(seededName(accountId))) return settings.value(seededName(accountId)).toStringList();
324
3/8
✓ Branch 16 → 17 taken 14 times.
✓ Branch 18 → 19 taken 14 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 14 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
28 return settings.contains(knownName(accountId)) ? QStringList{QStringLiteral("calendars"), QStringLiteral("books")}
325
3/12
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 27 taken 14 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 33 taken 14 times.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 33 not taken.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 14 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 58 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 58 not taken.
14 : QStringList{};
326 32 }
327 // Adds the keys of the kinds not seeded yet (the first list of a kind
328 // offers nothing as new) and `keys` (added or dismissed) to the known set.
329 32 QStringList storeKnown(const QString &accountId, const QHash<QString, QStringList> &firstLists, const QStringList &keys) {
330
1/1
✓ Branch 2 → 3 taken 32 times.
32 QSettings settings;
331
3/3
✓ Branch 3 → 4 taken 32 times.
✓ Branch 5 → 6 taken 32 times.
✓ Branch 6 → 7 taken 32 times.
32 QStringList known = settings.value(knownName(accountId)).toStringList();
332
1/1
✓ Branch 9 → 10 taken 32 times.
32 QStringList seeded = seededKinds(accountId);
333 32 bool changed = false;
334
2/2
✓ Branch 36 → 11 taken 38 times.
✓ Branch 36 → 37 taken 32 times.
70 for (auto it = firstLists.cbegin(); it != firstLists.cend(); ++it) {
335
2/2
✓ Branch 13 → 14 taken 13 times.
✓ Branch 13 → 15 taken 25 times.
38 if (seeded.contains(it.key())) continue;
336
1/1
✓ Branch 16 → 17 taken 25 times.
25 seeded << it.key();
337
6/9
✓ Branch 22 → 23 taken 38 times.
✗ Branch 22 → 26 not taken.
✓ Branch 24 → 25 taken 38 times.
✗ Branch 24 → 26 not taken.
✓ Branch 27 → 28 taken 38 times.
✗ Branch 27 → 29 not taken.
✓ Branch 28 → 29 taken 38 times.
✓ Branch 31 → 20 taken 38 times.
✓ Branch 31 → 32 taken 25 times.
63 for (const auto &key : it.value()) if (!key.isEmpty() && !known.contains(key)) known << key;
338 25 changed = true;
339 }
340
2/2
✓ Branch 51 → 39 taken 9 times.
✓ Branch 51 → 52 taken 32 times.
41 for (const auto &key : keys)
341
6/7
✓ Branch 41 → 42 taken 9 times.
✗ Branch 41 → 45 not taken.
✓ Branch 43 → 44 taken 4 times.
✓ Branch 43 → 45 taken 5 times.
✓ Branch 46 → 47 taken 4 times.
✓ Branch 46 → 49 taken 5 times.
✓ Branch 47 → 48 taken 4 times.
9 if (!key.isEmpty() && !known.contains(key)) { known << key; changed = true; }
342
2/2
✓ Branch 52 → 53 taken 19 times.
✓ Branch 52 → 66 taken 13 times.
32 if (changed) {
343
2/2
✓ Branch 54 → 55 taken 19 times.
✓ Branch 56 → 57 taken 19 times.
19 settings.setValue(knownName(accountId), known);
344
2/2
✓ Branch 60 → 61 taken 19 times.
✓ Branch 62 → 63 taken 19 times.
19 settings.setValue(seededName(accountId), seeded);
345 }
346 32 return known;
347 32 }
348 } // namespace
349
350 17 void DavCollectionDiscovery::writeCache() const {
351
1/1
✓ Branch 2 → 3 taken 17 times.
17 QJsonArray rows;
352
2/2
✓ Branch 28 → 5 taken 50 times.
✓ Branch 28 → 29 taken 17 times.
67 for (const auto &row : m_rows) {
353
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 49 times.
50 if (!row.available) continue;
354
1/1
✓ Branch 18 → 19 taken 49 times.
49 rows.append(QJsonObject{{"name", row.name}, {"path", row.path}, {"calendar", row.calendar}, {"color", row.color},
355 49 {"components", QJsonArray::fromStringList(row.components)}, {"readOnly", row.readOnly},
356
2/4
✓ Branch 22 → 23 taken 343 times.
✓ Branch 22 → 24 taken 49 times.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
441 {"push", row.push}});
357 }
358
3/3
✓ Branch 29 → 30 taken 17 times.
✓ Branch 34 → 35 taken 17 times.
✓ Branch 36 → 37 taken 17 times.
34 QSettings().setValue(cacheName(m_account.id),
359
3/3
✓ Branch 30 → 31 taken 17 times.
✓ Branch 31 → 32 taken 17 times.
✓ Branch 32 → 33 taken 17 times.
34 QString::fromUtf8(QJsonDocument(rows).toJson(QJsonDocument::Compact)));
360 66 }
361
362 8 bool DavCollectionDiscovery::readCache() {
363
4/4
✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 5 → 6 taken 8 times.
✓ Branch 6 → 7 taken 8 times.
8 const QString text = QSettings().value(cacheName(m_account.id)).toString();
364
3/3
✓ Branch 10 → 11 taken 8 times.
✓ Branch 11 → 12 taken 8 times.
✓ Branch 12 → 13 taken 8 times.
8 const QJsonArray rows = QJsonDocument::fromJson(text.toUtf8()).array();
365
3/3
✓ Branch 15 → 16 taken 8 times.
✓ Branch 16 → 17 taken 5 times.
✓ Branch 16 → 18 taken 3 times.
8 if (rows.isEmpty()) return false;
366 3 QList<Row> list;
367
3/3
✓ Branch 19 → 20 taken 3 times.
✓ Branch 94 → 21 taken 9 times.
✓ Branch 94 → 95 taken 3 times.
12 for (const auto &value : rows) {
368
1/1
✓ Branch 22 → 23 taken 9 times.
9 const auto object = value.toObject();
369
5/5
✓ Branch 23 → 24 taken 9 times.
✓ Branch 24 → 25 taken 9 times.
✓ Branch 27 → 28 taken 9 times.
✓ Branch 30 → 31 taken 9 times.
✓ Branch 31 → 32 taken 9 times.
36 Row row{object.value("name").toString(), object.value("path").toString(), object.value("calendar").toBool(),
370
4/8
✓ Branch 25 → 26 taken 9 times.
✓ Branch 26 → 27 taken 9 times.
✓ Branch 28 → 29 taken 9 times.
✓ Branch 29 → 30 taken 9 times.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
✗ Branch 134 → 135 not taken.
✗ Branch 134 → 136 not taken.
27 false, true, false};
371
3/3
✓ Branch 38 → 39 taken 9 times.
✓ Branch 39 → 40 taken 9 times.
✓ Branch 40 → 41 taken 9 times.
9 row.color = object.value("color").toString();
372
9/9
✓ Branch 45 → 46 taken 9 times.
✓ Branch 46 → 47 taken 9 times.
✓ Branch 47 → 48 taken 9 times.
✓ Branch 50 → 51 taken 9 times.
✓ Branch 51 → 52 taken 9 times.
✓ Branch 55 → 56 taken 6 times.
✓ Branch 56 → 57 taken 6 times.
✓ Branch 61 → 53 taken 6 times.
✓ Branch 61 → 62 taken 9 times.
15 for (const auto &component : object.value("components").toArray()) row.components << component.toString();
373
3/3
✓ Branch 63 → 64 taken 9 times.
✓ Branch 64 → 65 taken 9 times.
✓ Branch 65 → 66 taken 9 times.
9 row.readOnly = object.value("readOnly").toBool();
374
3/3
✓ Branch 68 → 69 taken 9 times.
✓ Branch 69 → 70 taken 9 times.
✓ Branch 70 → 71 taken 9 times.
9 row.push = object.value("push").toBool();
375
2/2
✓ Branch 73 → 74 taken 8 times.
✓ Branch 73 → 75 taken 1 time.
9 const QStringList &selected = row.calendar ? m_calendarConfig.selectedCalendars : m_account.selectedBooks;
376
1/2
✗ Branch 88 → 78 not taken.
✓ Branch 88 → 89 taken 9 times.
9 for (const auto &path : selected)
377 ✗ if (collectionKey(path) == collectionKey(row.path)) row.selected = row.initiallySelected = true;
378
1/1
✓ Branch 89 → 90 taken 9 times.
9 list.append(row);
379 9 }
380
1/1
✓ Branch 95 → 96 taken 3 times.
3 beginResetModel();
381 3 m_rows = list;
382
1/1
✓ Branch 97 → 98 taken 3 times.
3 endResetModel();
383 3 return true;
384 8 }
385
386 8 bool DavCollectionDiscovery::open(const QString &accountId) {
387 // Start the discovery (it captures the account), then show the cache
388 // until the server answers (state "cached": toggles wait for "ready").
389
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 if (!discover(accountId)) return false;
390
2/2
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 14 taken 5 times.
8 if (readCache()) {
391 3 m_state = QStringLiteral("cached");
392 3 emit stateChanged();
393 }
394 8 return true;
395 }
396
397 // D-22: the collections an account had when it was first discovered, plus
398 // every collection the user added or dismissed since. Anything else that
399 // shows up on the server is "new" until it is added or dismissed.
400
401 23 void DavCollectionDiscovery::rememberKnown(const QStringList &keys) {
402 23 QHash<QString, QStringList> firstLists;
403
2/2
✓ Branch 3 → 4 taken 17 times.
✓ Branch 3 → 76 taken 6 times.
23 if (keys.isEmpty()) {
404 // After a complete enumeration: the lists that arrived seed their kind.
405
4/4
✓ Branch 4 → 5 taken 17 times.
✓ Branch 5 → 6 taken 17 times.
✓ Branch 33 → 7 taken 50 times.
✓ Branch 33 → 34 taken 17 times.
67 for (const auto &row : m_rows) {
406
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 49 times.
50 if (!row.available) continue;
407
3/4
✓ Branch 10 → 11 taken 34 times.
✓ Branch 10 → 12 taken 15 times.
✓ Branch 13 → 14 taken 49 times.
✗ Branch 13 → 31 not taken.
49 if (row.calendar ? m_calendarsOk : m_booksOk)
408
9/13
✓ Branch 14 → 15 taken 34 times.
✓ Branch 14 → 18 taken 15 times.
✓ Branch 21 → 22 taken 49 times.
✓ Branch 22 → 23 taken 49 times.
✓ Branch 23 → 24 taken 49 times.
✓ Branch 26 → 27 taken 15 times.
✓ Branch 26 → 28 taken 34 times.
✓ Branch 28 → 29 taken 34 times.
✓ Branch 28 → 30 taken 15 times.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
98 firstLists[row.calendar ? QStringLiteral("calendars") : QStringLiteral("books")] << collectionKey(row.path);
409 }
410
5/12
✓ Branch 34 → 35 taken 17 times.
✗ Branch 34 → 41 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 17 times.
✓ Branch 42 → 43 taken 17 times.
✗ Branch 42 → 44 not taken.
✓ Branch 44 → 45 taken 17 times.
✗ Branch 44 → 46 not taken.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 55 taken 17 times.
✗ Branch 50 → 51 not taken.
✗ Branch 51 → 52 not taken.
34 if (m_calendarsOk && !firstLists.contains(QStringLiteral("calendars"))) firstLists[QStringLiteral("calendars")] = {};
411
8/12
✓ Branch 55 → 56 taken 15 times.
✓ Branch 55 → 62 taken 2 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 15 times.
✓ Branch 63 → 64 taken 15 times.
✓ Branch 63 → 65 taken 2 times.
✓ Branch 65 → 66 taken 15 times.
✓ Branch 65 → 67 taken 2 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 76 taken 17 times.
✗ Branch 71 → 72 not taken.
✗ Branch 72 → 73 not taken.
32 if (m_booksOk && !firstLists.contains(QStringLiteral("books"))) firstLists[QStringLiteral("books")] = {};
412 }
413
1/1
✓ Branch 76 → 77 taken 23 times.
23 m_cachedKeys = storeKnown(m_account.id, firstLists, keys);
414 23 }
415
416 1385 QStringList DavCollectionDiscovery::knownKeys(const QString &accountId) {
417
4/4
✓ Branch 2 → 3 taken 1385 times.
✓ Branch 3 → 4 taken 1385 times.
✓ Branch 5 → 6 taken 1385 times.
✓ Branch 6 → 7 taken 1385 times.
1385 return QSettings().value(knownName(accountId)).toStringList();
418 }
419
420 3 void DavCollectionDiscovery::addKnown(const QString &accountId, const QStringList &keys) {
421
1/1
✓ Branch 3 → 4 taken 3 times.
3 storeKnown(accountId, {}, keys);
422 3 }
423
424 1387 QList<DavCollectionDiscovery::CachedCollection> DavCollectionDiscovery::cachedCollections(const Account &account) {
425 1387 QList<CachedCollection> result;
426
7/7
✓ Branch 2 → 3 taken 1387 times.
✓ Branch 3 → 4 taken 1387 times.
✓ Branch 5 → 6 taken 1387 times.
✓ Branch 6 → 7 taken 1387 times.
✓ Branch 7 → 8 taken 1387 times.
✓ Branch 8 → 9 taken 1387 times.
✓ Branch 9 → 10 taken 1387 times.
1387 const QJsonArray rows = QJsonDocument::fromJson(QSettings().value(cacheName(account.id)).toString().toUtf8()).array();
427
3/3
✓ Branch 17 → 18 taken 1387 times.
✓ Branch 71 → 19 taken 597 times.
✓ Branch 71 → 72 taken 1387 times.
1984 for (const auto &value : rows) {
428
1/1
✓ Branch 20 → 21 taken 597 times.
597 const auto object = value.toObject();
429 597 CachedCollection item;
430
3/3
✓ Branch 21 → 22 taken 597 times.
✓ Branch 22 → 23 taken 597 times.
✓ Branch 23 → 24 taken 597 times.
597 item.name = object.value("name").toString();
431
3/3
✓ Branch 28 → 29 taken 597 times.
✓ Branch 29 → 30 taken 597 times.
✓ Branch 30 → 31 taken 597 times.
597 item.path = object.value("path").toString();
432
3/3
✓ Branch 35 → 36 taken 597 times.
✓ Branch 36 → 37 taken 597 times.
✓ Branch 37 → 38 taken 597 times.
597 item.calendar = object.value("calendar").toBool();
433
9/9
✓ Branch 40 → 41 taken 597 times.
✓ Branch 41 → 42 taken 597 times.
✓ Branch 42 → 43 taken 597 times.
✓ Branch 45 → 46 taken 597 times.
✓ Branch 46 → 47 taken 597 times.
✓ Branch 50 → 51 taken 234 times.
✓ Branch 51 → 52 taken 234 times.
✓ Branch 56 → 48 taken 234 times.
✓ Branch 56 → 57 taken 597 times.
831 for (const auto &component : object.value("components").toArray()) item.components << component.toString();
434
1/1
✓ Branch 58 → 59 taken 597 times.
597 item.kind = kindOf(item.calendar, item.components);
435
1/1
✓ Branch 61 → 62 taken 597 times.
597 item.key = keyFor(account.serverUrl, item.path);
436
2/3
✓ Branch 65 → 66 taken 597 times.
✗ Branch 65 → 67 not taken.
✓ Branch 66 → 67 taken 597 times.
597 if (!item.key.isEmpty()) result.append(item);
437 597 }
438 1387 return result;
439 1387 }
440
441 6 void DavCollectionDiscovery::recordCalendars(const Account &account, const QList<Item> &calendars) {
442
3/6
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 6 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 6 times.
6 if (account.id.isEmpty() || calendars.size() > 256) return;
443
1/1
✓ Branch 10 → 11 taken 6 times.
6 QSettings settings;
444
1/1
✓ Branch 11 → 12 taken 6 times.
6 QJsonArray rows;
445
10/10
✓ Branch 12 → 13 taken 6 times.
✓ Branch 14 → 15 taken 6 times.
✓ Branch 15 → 16 taken 6 times.
✓ Branch 16 → 17 taken 6 times.
✓ Branch 17 → 18 taken 6 times.
✓ Branch 18 → 19 taken 6 times.
✓ Branch 24 → 25 taken 6 times.
✓ Branch 25 → 26 taken 6 times.
✓ Branch 42 → 27 taken 11 times.
✓ Branch 42 → 43 taken 6 times.
17 for (const auto &value : QJsonDocument::fromJson(settings.value(cacheName(account.id)).toString().toUtf8()).array())
446
8/8
✓ Branch 28 → 29 taken 11 times.
✓ Branch 29 → 30 taken 11 times.
✓ Branch 30 → 31 taken 11 times.
✓ Branch 31 → 32 taken 11 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 40 taken 9 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
17 if (!value.toObject().value("calendar").toBool()) rows.append(value);
447 6 QStringList keys;
448
2/2
✓ Branch 91 → 46 taken 14 times.
✓ Branch 91 → 92 taken 6 times.
20 for (const auto &item : calendars) {
449
2/2
✓ Branch 47 → 48 taken 14 times.
✓ Branch 48 → 49 taken 14 times.
14 const QString key = keyFor(account.serverUrl, item.path.trimmed());
450
3/6
✓ Branch 51 → 52 taken 14 times.
✗ Branch 51 → 54 not taken.
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 55 taken 14 times.
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 58 taken 14 times.
14 if (key.isEmpty() || keys.contains(key)) continue;
451
1/1
✓ Branch 58 → 59 taken 14 times.
14 keys << key;
452
3/5
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 14 times.
✗ Branch 61 → 63 not taken.
✓ Branch 62 → 63 taken 14 times.
✓ Branch 74 → 75 taken 14 times.
28 rows.append(QJsonObject{{"name", item.name.isEmpty() ? item.path.left(512) : item.name.left(512)},
453 14 {"path", item.path.trimmed()}, {"calendar", true}, {"color", item.color},
454 14 {"components", QJsonArray::fromStringList(item.components)}, {"readOnly", item.readOnly},
455
2/4
✓ Branch 78 → 79 taken 98 times.
✓ Branch 78 → 80 taken 14 times.
✗ Branch 165 → 166 not taken.
✗ Branch 165 → 167 not taken.
112 {"push", item.push}});
456 14 }
457
5/5
✓ Branch 92 → 93 taken 6 times.
✓ Branch 93 → 94 taken 6 times.
✓ Branch 94 → 95 taken 6 times.
✓ Branch 96 → 97 taken 6 times.
✓ Branch 98 → 99 taken 6 times.
6 settings.setValue(cacheName(account.id), QString::fromUtf8(QJsonDocument(rows).toJson(QJsonDocument::Compact)));
458
3/5
✓ Branch 110 → 111 taken 6 times.
✓ Branch 114 → 115 taken 6 times.
✓ Branch 114 → 116 taken 6 times.
✗ Branch 209 → 210 not taken.
✗ Branch 209 → 211 not taken.
24 storeKnown(account.id, {{QStringLiteral("calendars"), keys}}, {});
459 60 }
460
461 47 QVariantList DavCollectionDiscovery::newCollections() const {
462
6/6
✓ Branch 4 → 5 taken 39 times.
✓ Branch 4 → 9 taken 8 times.
✓ Branch 7 → 8 taken 35 times.
✓ Branch 7 → 9 taken 4 times.
✓ Branch 10 → 11 taken 35 times.
✓ Branch 10 → 12 taken 12 times.
47 if (m_state != QLatin1String("ready") && m_state != QLatin1String("saved")) return {};
463 12 QVariantList result;
464
2/2
✓ Branch 45 → 14 taken 36 times.
✓ Branch 45 → 46 taken 12 times.
48 for (const auto &row : m_rows) {
465
1/1
✓ Branch 15 → 16 taken 36 times.
36 const QString key = collectionKey(row.path);
466
7/8
✓ Branch 16 → 17 taken 28 times.
✓ Branch 16 → 20 taken 8 times.
✓ Branch 17 → 18 taken 28 times.
✗ Branch 17 → 20 not taken.
✓ Branch 19 → 20 taken 25 times.
✓ Branch 19 → 21 taken 3 times.
✓ Branch 22 → 23 taken 33 times.
✓ Branch 22 → 24 taken 3 times.
36 if (row.selected || !row.available || m_cachedKeys.contains(key)) continue;
467
3/5
✓ Branch 30 → 31 taken 3 times.
✓ Branch 34 → 35 taken 9 times.
✓ Branch 34 → 36 taken 3 times.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
12 result.append(QVariantMap{{"key", key}, {"name", row.name}, {"kind", contentKind(row)}});
468 36 }
469 12 return result;
470 15 }
471
472 1 QVariantMap DavCollectionDiscovery::addNew(const QString &key) { return toggleKey(key, true); }
473
474 4 QVariantMap DavCollectionDiscovery::toggleKey(const QString &key, bool selected) {
475
2/2
✓ Branch 11 → 3 taken 8 times.
✓ Branch 11 → 12 taken 1 time.
9 for (int row = 0; row < m_rows.size(); ++row)
476
3/3
✓ Branch 4 → 5 taken 8 times.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 5 times.
8 if (collectionKey(m_rows.at(row).path) == key) return toggle(row, selected);
477
2/4
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 1 time.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
4 return {{QStringLiteral("ok"), false}};
478 3 }
479
480 ✗ void DavCollectionDiscovery::dismissNew(const QString &key) {
481 ✗ rememberKnown({key});
482 ✗ emit stateChanged();
483 ✗ }
484