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: 90.7% 185 / 0 / 204
Functions: 72.0% 18 / 0 / 25
Branches: 71.3% 295 / 0 / 414

app/core/CalendarSubscriptions.cpp
Line Branch Exec Source
1 #include "service/AuthenticatedRequest.h"
2 #include "CalendarSubscriptions.h"
3
4 #include "data/CalendarModels.h"
5 #include "data/CalendarStore.h"
6 #include "data/DavCredentials.h"
7 #include "data/IcsFile.h"
8 #include "service/DavNetworkLimits.h"
9
10 #include <QLoggingCategory>
11 #include <QNetworkAccessManager>
12 #include <QNetworkReply>
13 #include <QSettings>
14 #include <QUrl>
15 #include <QUuid>
16
17
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 11 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
12 Q_LOGGING_CATEGORY(lcSubscriptions, "mailjd.calendar.subscriptions")
18
19 namespace {
20 const QString kPrefix = QStringLiteral("subscription:");
21 constexpr int kMinimumIntervalMinutes = 15;
22 } // namespace
23
24 948 CalendarSubscriptions::CalendarSubscriptions(CalendarStore *store, QObject *parent)
25
1/1
✓ Branch 9 → 10 taken 948 times.
948 : QObject(parent), m_store(store) {
26 948 m_passwordReader = [](QObject *owner, const Subscription &entry, std::function<void(QByteArray, QString)> done) {
27 ✗ DavCredentials::readPasswordAsync(owner, accountIdFor(entry.id), entry.url, entry.username, std::move(done));
28 948 };
29 948 m_passwordWriter = [](const Subscription &entry, const QByteArray &password) {
30 ✗ return DavCredentials::writePasswordBlocking(accountIdFor(entry.id), entry.url, entry.username, password);
31 948 };
32 // Due subscriptions are checked every 15 minutes besides every sync run.
33
1/1
✓ Branch 12 → 13 taken 948 times.
948 m_timer.setInterval(kMinimumIntervalMinutes * 60 * 1000);
34
1/1
✓ Branch 13 → 14 taken 948 times.
948 connect(&m_timer, &QTimer::timeout, this, [this] { refreshDue(false); });
35
1/1
✓ Branch 15 → 16 taken 948 times.
948 m_timer.start();
36 948 }
37
38
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 6 taken 1 time.
6 qint64 CalendarSubscriptions::now() const { return m_clock ? m_clock() : QDateTime::currentSecsSinceEpoch(); }
39
40 14 QString CalendarSubscriptions::accountIdFor(const QString &subscriptionId) { return kPrefix + subscriptionId; }
41
42 ✗ bool CalendarSubscriptions::isSubscription(const QString &accountId) { return accountId.startsWith(kPrefix); }
43
44 181 QList<CalendarSubscriptions::Subscription> CalendarSubscriptions::subscriptions() {
45 181 QList<Subscription> list;
46
1/1
✓ Branch 2 → 3 taken 181 times.
181 QSettings settings;
47
1/1
✓ Branch 7 → 8 taken 181 times.
181 const int count = settings.beginReadArray(QStringLiteral("calendar/subscriptions"));
48
2/2
✓ Branch 98 → 11 taken 32 times.
✓ Branch 98 → 99 taken 181 times.
213 for (int i = 0; i < count; ++i) {
49
1/1
✓ Branch 11 → 12 taken 32 times.
32 settings.setArrayIndex(i);
50 32 Subscription entry;
51
2/2
✓ Branch 16 → 17 taken 32 times.
✓ Branch 17 → 18 taken 32 times.
32 entry.id = settings.value(QStringLiteral("id")).toString();
52
2/2
✓ Branch 27 → 28 taken 32 times.
✓ Branch 28 → 29 taken 32 times.
32 entry.url = settings.value(QStringLiteral("url")).toString();
53
2/2
✓ Branch 38 → 39 taken 32 times.
✓ Branch 39 → 40 taken 32 times.
32 entry.name = settings.value(QStringLiteral("name")).toString();
54
2/2
✓ Branch 50 → 51 taken 32 times.
✓ Branch 51 → 52 taken 32 times.
64 entry.intervalMinutes = qMax(kMinimumIntervalMinutes, settings.value(QStringLiteral("interval"), 360).toInt());
55
2/2
✓ Branch 61 → 62 taken 32 times.
✓ Branch 62 → 63 taken 32 times.
32 entry.lastRefresh = settings.value(QStringLiteral("lastRefresh")).toLongLong();
56
2/2
✓ Branch 70 → 71 taken 32 times.
✓ Branch 71 → 72 taken 32 times.
32 entry.lastError = settings.value(QStringLiteral("lastError")).toString();
57
2/2
✓ Branch 81 → 82 taken 32 times.
✓ Branch 82 → 83 taken 32 times.
32 entry.username = settings.value(QStringLiteral("username")).toString();
58
4/7
✓ Branch 89 → 90 taken 32 times.
✗ Branch 89 → 93 not taken.
✓ Branch 91 → 92 taken 32 times.
✗ Branch 91 → 93 not taken.
✓ Branch 94 → 95 taken 32 times.
✗ Branch 94 → 96 not taken.
✓ Branch 95 → 96 taken 32 times.
32 if (!entry.id.isEmpty() && !entry.url.isEmpty()) list.append(entry);
59 32 }
60
1/1
✓ Branch 99 → 100 taken 181 times.
181 settings.endArray();
61 181 return list;
62 181 }
63
64 9 void CalendarSubscriptions::save(const QList<Subscription> &list) {
65
1/1
✓ Branch 2 → 3 taken 9 times.
9 QSettings settings;
66
1/1
✓ Branch 7 → 8 taken 9 times.
9 settings.remove(QStringLiteral("calendar/subscriptions"));
67
1/1
✓ Branch 15 → 16 taken 9 times.
18 settings.beginWriteArray(QStringLiteral("calendar/subscriptions"), int(list.size()));
68
2/2
✓ Branch 92 → 19 taken 8 times.
✓ Branch 92 → 93 taken 9 times.
17 for (int i = 0; i < list.size(); ++i) {
69
1/1
✓ Branch 19 → 20 taken 8 times.
8 settings.setArrayIndex(i);
70
1/1
✓ Branch 26 → 27 taken 8 times.
16 settings.setValue(QStringLiteral("id"), list.at(i).id);
71
1/1
✓ Branch 36 → 37 taken 8 times.
16 settings.setValue(QStringLiteral("url"), list.at(i).url);
72
1/1
✓ Branch 46 → 47 taken 8 times.
16 settings.setValue(QStringLiteral("name"), list.at(i).name);
73
1/1
✓ Branch 56 → 57 taken 8 times.
16 settings.setValue(QStringLiteral("interval"), list.at(i).intervalMinutes);
74
1/1
✓ Branch 66 → 67 taken 8 times.
16 settings.setValue(QStringLiteral("lastRefresh"), list.at(i).lastRefresh);
75
1/1
✓ Branch 76 → 77 taken 8 times.
16 settings.setValue(QStringLiteral("lastError"), list.at(i).lastError);
76
1/1
✓ Branch 86 → 87 taken 8 times.
16 settings.setValue(QStringLiteral("username"), list.at(i).username);
77 }
78
1/1
✓ Branch 93 → 94 taken 9 times.
9 settings.endArray();
79 9 }
80
81 6 QString CalendarSubscriptions::add(const QString &url, const QString &name, int intervalMinutes, QString *error,
82 const QString &username, const QByteArray &password) {
83
2/2
✓ Branch 4 → 5 taken 6 times.
✓ Branch 5 → 6 taken 6 times.
6 QUrl parsed = QUrl::fromUserInput(url.trimmed());
84
10/16
✓ Branch 9 → 10 taken 6 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 16 taken 1 time.
✓ Branch 13 → 14 taken 5 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 5 times.
✓ Branch 18 → 19 taken 5 times.
✓ Branch 18 → 20 taken 1 time.
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 30 taken 5 times.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 173 not taken.
✗ Branch 176 → 177 not taken.
✗ Branch 176 → 178 not taken.
6 if (parsed.scheme() == QLatin1String("webcal") || parsed.scheme() == QLatin1String("webcals"))
85
1/1
✓ Branch 26 → 27 taken 1 time.
1 parsed.setScheme(QStringLiteral("https"));
86
12/19
✓ Branch 30 → 31 taken 6 times.
✓ Branch 31 → 32 taken 6 times.
✗ Branch 31 → 44 not taken.
✓ Branch 33 → 34 taken 6 times.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 40 taken 3 times.
✓ Branch 37 → 38 taken 3 times.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 44 taken 2 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 4 times.
✓ Branch 50 → 51 taken 6 times.
✗ Branch 50 → 52 not taken.
✓ Branch 52 → 53 taken 2 times.
✓ Branch 52 → 59 taken 4 times.
✗ Branch 192 → 193 not taken.
✗ Branch 192 → 194 not taken.
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 199 not taken.
16 if (!parsed.isValid() || (parsed.scheme() != QLatin1String("https") && parsed.scheme() != QLatin1String("http")) ||
87
5/7
✓ Branch 41 → 42 taken 4 times.
✓ Branch 46 → 47 taken 4 times.
✓ Branch 46 → 48 taken 2 times.
✓ Branch 48 → 49 taken 3 times.
✓ Branch 48 → 50 taken 3 times.
✗ Branch 187 → 188 not taken.
✗ Branch 187 → 189 not taken.
10 parsed.host().isEmpty()) {
88
2/3
✓ Branch 53 → 54 taken 2 times.
✗ Branch 53 → 58 not taken.
✓ Branch 54 → 55 taken 2 times.
2 if (error) *error = tr("Enter an https:// or webcal:// address of a calendar file.");
89 2 return {};
90 }
91 // Credentials never live in the stored URL; a protected feed needs https
92 // (no password over plain HTTP).
93
1/1
✓ Branch 60 → 61 taken 4 times.
4 parsed.setUserInfo({});
94
11/16
✓ Branch 62 → 63 taken 4 times.
✓ Branch 64 → 65 taken 2 times.
✓ Branch 64 → 70 taken 2 times.
✓ Branch 66 → 67 taken 2 times.
✓ Branch 68 → 69 taken 1 time.
✓ Branch 68 → 70 taken 1 time.
✓ Branch 71 → 72 taken 2 times.
✓ Branch 71 → 73 taken 2 times.
✓ Branch 73 → 74 taken 4 times.
✗ Branch 73 → 75 not taken.
✓ Branch 75 → 76 taken 1 time.
✓ Branch 75 → 82 taken 3 times.
✗ Branch 206 → 207 not taken.
✗ Branch 206 → 208 not taken.
✗ Branch 211 → 212 not taken.
✗ Branch 211 → 213 not taken.
4 if (!username.trimmed().isEmpty() && parsed.scheme() != QLatin1String("https")) {
95
2/3
✓ Branch 76 → 77 taken 1 time.
✗ Branch 76 → 81 not taken.
✓ Branch 77 → 78 taken 1 time.
1 if (error) *error = tr("A calendar with a password needs an https:// or webcal:// address.");
96 1 return {};
97 }
98
1/1
✓ Branch 82 → 83 taken 3 times.
3 QList<Subscription> list = subscriptions();
99
4/4
✓ Branch 83 → 84 taken 3 times.
✓ Branch 84 → 85 taken 3 times.
✓ Branch 101 → 86 taken 1 time.
✓ Branch 101 → 102 taken 2 times.
3 for (const Subscription &entry : list)
100
2/3
✓ Branch 88 → 89 taken 1 time.
✓ Branch 91 → 92 taken 1 time.
✗ Branch 91 → 99 not taken.
1 if (entry.url == parsed.toString()) {
101
2/3
✓ Branch 92 → 93 taken 1 time.
✗ Branch 92 → 97 not taken.
✓ Branch 93 → 94 taken 1 time.
1 if (error) *error = tr("This calendar is already subscribed.");
102 1 return {};
103 }
104 2 Subscription entry;
105
3/3
✓ Branch 102 → 103 taken 2 times.
✓ Branch 103 → 104 taken 2 times.
✓ Branch 104 → 105 taken 2 times.
2 entry.id = QUuid::createUuid().toString(QUuid::WithoutBraces).left(8);
106
1/1
✓ Branch 109 → 110 taken 2 times.
2 entry.url = parsed.toString();
107
3/5
✓ Branch 112 → 113 taken 2 times.
✗ Branch 114 → 115 not taken.
✓ Branch 114 → 117 taken 2 times.
✗ Branch 116 → 118 not taken.
✓ Branch 117 → 118 taken 2 times.
2 entry.name = name.trimmed().isEmpty() ? parsed.host() : name.trimmed();
108 2 entry.intervalMinutes = qMax(kMinimumIntervalMinutes, intervalMinutes);
109
1/1
✓ Branch 122 → 123 taken 2 times.
2 entry.username = username.trimmed();
110
5/7
✓ Branch 126 → 127 taken 1 time.
✓ Branch 126 → 130 taken 1 time.
✓ Branch 127 → 128 taken 1 time.
✗ Branch 128 → 129 not taken.
✓ Branch 128 → 130 taken 1 time.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 138 taken 2 times.
2 if (!entry.username.isEmpty() && !m_passwordWriter(entry, password)) {
111 ✗ if (error) *error = tr("The password could not be stored in the keyring.");
112 ✗ return {};
113 }
114
1/1
✓ Branch 138 → 139 taken 2 times.
2 list.append(entry);
115
1/1
✓ Branch 139 → 140 taken 2 times.
2 save(list);
116
1/1
✓ Branch 140 → 141 taken 2 times.
2 storeCalendar(entry);
117
9/9
✓ Branch 141 → 142 taken 2 times.
✓ Branch 145 → 146 taken 2 times.
✓ Branch 146 → 147 taken 2 times.
✓ Branch 147 → 148 taken 2 times.
✓ Branch 148 → 149 taken 2 times.
✓ Branch 149 → 150 taken 2 times.
✓ Branch 150 → 151 taken 2 times.
✓ Branch 154 → 143 taken 2 times.
✓ Branch 154 → 155 taken 2 times.
4 qCInfo(lcSubscriptions) << "Subscribed" << entry.id << "every" << entry.intervalMinutes << "min";
118
1/1
✓ Branch 155 → 156 taken 2 times.
2 emit changed();
119
1/1
✓ Branch 156 → 157 taken 2 times.
2 emit storeChanged();
120
1/1
✓ Branch 157 → 158 taken 2 times.
2 refresh(entry.id);
121 2 return entry.id;
122 6 }
123
124 1 bool CalendarSubscriptions::remove(const QString &id) {
125
1/1
✓ Branch 2 → 3 taken 1 time.
1 QList<Subscription> list = subscriptions();
126
3/3
✓ Branch 3 → 4 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
2 const auto found = std::find_if(list.begin(), list.end(), [&](const Subscription &entry) { return entry.id == id; });
127
2/3
✓ Branch 6 → 7 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
1 if (found == list.end()) return false;
128
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 23 taken 1 time.
1 if (!found->username.isEmpty())
129 ✗ DavCredentials::deletePasswordAsync(this, accountIdFor(id), found->url, found->username, [](bool) {});
130
2/2
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
1 m_store->removeStaleEvents(accountIdFor(id), found->url, {}, true);
131
2/2
✓ Branch 29 → 30 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
1 m_store->removeAccountCalendars(accountIdFor(id)); // one calendar per subscription account
132
1/1
✓ Branch 33 → 34 taken 1 time.
1 list.erase(found);
133
1/1
✓ Branch 34 → 35 taken 1 time.
1 save(list);
134
6/6
✓ Branch 35 → 36 taken 1 time.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 45 → 37 taken 1 time.
✓ Branch 45 → 46 taken 1 time.
2 qCInfo(lcSubscriptions) << "Unsubscribed" << id;
135
1/1
✓ Branch 46 → 47 taken 1 time.
1 emit changed();
136
1/1
✓ Branch 47 → 48 taken 1 time.
1 emit storeChanged();
137 1 return true;
138 1 }
139
140 // The subscription's read-only calendar in the store (name and address).
141 2 void CalendarSubscriptions::storeCalendar(const Subscription &entry) {
142 2 CalendarInfo calendar;
143
1/1
✓ Branch 2 → 3 taken 2 times.
2 calendar.accountId = accountIdFor(entry.id);
144 2 calendar.path = entry.url;
145 2 calendar.displayName = entry.name;
146 2 calendar.readOnly = true;
147
1/1
✓ Branch 7 → 8 taken 2 times.
2 m_store->upsertCalendar(calendar, calendar.accountId);
148 2 }
149
150 ✗ bool CalendarSubscriptions::update(const QString &id, const QString &name, int intervalMinutes) {
151 ✗ QList<Subscription> list = subscriptions();
152 ✗ const auto found = std::find_if(list.begin(), list.end(), [&](const Subscription &entry) { return entry.id == id; });
153 ✗ if (found == list.end() || name.trimmed().isEmpty()) return false;
154 ✗ found->name = name.trimmed();
155 ✗ found->intervalMinutes = qMax(kMinimumIntervalMinutes, intervalMinutes);
156 ✗ save(list);
157 ✗ storeCalendar(*found);
158 ✗ qCInfo(lcSubscriptions) << "Updated" << id << "every" << found->intervalMinutes << "min";
159 ✗ emit changed();
160 ✗ emit storeChanged();
161 ✗ return true;
162 ✗ }
163
164 87 void CalendarSubscriptions::refreshDue(bool force) {
165
5/5
✓ Branch 2 → 3 taken 87 times.
✓ Branch 3 → 4 taken 87 times.
✓ Branch 4 → 5 taken 87 times.
✓ Branch 16 → 6 taken 3 times.
✓ Branch 16 → 17 taken 87 times.
90 for (const Subscription &entry : subscriptions())
166
7/8
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 1 time.
✓ Branch 13 → 14 taken 2 times.
90 if (force || now() - entry.lastRefresh >= qint64(entry.intervalMinutes) * 60) refresh(entry.id);
167 87 }
168
169 6 void CalendarSubscriptions::refresh(const QString &id) {
170
3/9
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 8 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 6 times.
✗ Branch 6 → 7 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 6 times.
9 if (m_running.contains(id) || (m_networkGate && !m_networkGate())) return;
171
1/1
✓ Branch 12 → 13 taken 6 times.
6 const auto list = subscriptions();
172
1/1
✓ Branch 15 → 16 taken 6 times.
12 const auto found = std::find_if(list.begin(), list.end(), [&](const Subscription &entry) { return entry.id == id; });
173
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 6 times.
6 if (found == list.end()) return;
174
1/1
✓ Branch 20 → 21 taken 6 times.
6 m_running.insert(id);
175
1/1
✓ Branch 21 → 22 taken 6 times.
6 emit changed();
176
2/2
✓ Branch 24 → 25 taken 3 times.
✓ Branch 24 → 30 taken 3 times.
6 if (found->username.isEmpty()) {
177
1/1
✓ Branch 27 → 28 taken 3 times.
3 download(*found, {});
178 3 return;
179 }
180 3 const Subscription entry = *found;
181
2/2
✓ Branch 33 → 34 taken 3 times.
✓ Branch 34 → 35 taken 3 times.
3 m_passwordReader(this, entry, [this, entry](QByteArray password, QString error) {
182
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 23 taken 2 times.
3 if (password.isEmpty()) {
183
8/8
✓ Branch 4 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 16 → 6 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
2 qCWarning(lcSubscriptions) << "Subscription" << entry.id << "has no readable password:" << error;
184
2/2
✓ Branch 17 → 18 taken 1 time.
✓ Branch 19 → 20 taken 1 time.
1 finished(entry.id, {}, tr("The password of this calendar is missing in the keyring."));
185 1 return;
186 }
187 2 download(entry, password);
188 });
189 6 }
190
191 5 void CalendarSubscriptions::download(const Subscription &subscription, const QByteArray &password) {
192 5 const QString id = subscription.id;
193
3/13
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 5 times.
✗ Branch 7 → 8 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 13 taken 5 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 56 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
5 auto *nam = m_namFactory ? m_namFactory(this) : new QNetworkAccessManager(this);
194
2/2
✓ Branch 13 → 14 taken 5 times.
✓ Branch 14 → 15 taken 5 times.
5 QNetworkRequest request{QUrl(subscription.url)};
195
3/3
✓ Branch 16 → 17 taken 5 times.
✓ Branch 17 → 18 taken 5 times.
✓ Branch 18 → 19 taken 5 times.
5 request.setRawHeader("Accept", "text/calendar, */*;q=0.5");
196 5 const QByteArray authorization = subscription.username.isEmpty() ? QByteArray()
197
15/23
✓ Branch 22 → 23 taken 3 times.
✓ Branch 22 → 24 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 30 → 31 taken 2 times.
✓ Branch 30 → 32 taken 3 times.
✓ Branch 32 → 33 taken 2 times.
✓ Branch 32 → 34 taken 3 times.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 36 taken 3 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 38 taken 3 times.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
5 : "Basic " + (subscription.username.toUtf8() + ':' + password).toBase64();
198
3/3
✓ Branch 39 → 40 taken 5 times.
✓ Branch 42 → 43 taken 5 times.
✓ Branch 43 → 44 taken 5 times.
5 AuthenticatedRequest::start(this, nam, request, authorization, {}, false,
199 AuthenticatedRequest::ForeignRedirect::FollowWithoutCredentials,
200 10 [this, nam, id](AuthenticatedRequest::Result result) {
201
1/1
✓ Branch 2 → 3 taken 5 times.
5 nam->deleteLater();
202
3/4
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 2 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 3 times.
5 const bool failed = result.outcome != AuthenticatedRequest::Outcome::Ok || result.status != 200;
203
2/2
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 20 taken 3 times.
5 if (failed)
204
6/6
✓ Branch 8 → 9 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 18 → 10 taken 2 times.
✓ Branch 18 → 19 taken 2 times.
4 qCWarning(lcSubscriptions) << "Subscription download failed: HTTP" << result.status;
205
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 28 taken 1 time.
1 const QString message = result.outcome == AuthenticatedRequest::Outcome::Unauthorized && result.leftOrigin
206
1/8
✗ Branch 22 → 23 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 5 times.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
5 ? tr("This calendar moved to another server (%1). Update its address.").arg(result.url.host())
207 5 : result.outcome == AuthenticatedRequest::Outcome::Unauthorized
208
2/2
✓ Branch 28 → 29 taken 1 time.
✓ Branch 28 → 30 taken 4 times.
5 ? tr("The calendar refused the user name or password.")
209
5/7
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 28 taken 4 times.
✗ Branch 26 → 27 not taken.
✓ Branch 29 → 31 taken 1 time.
✓ Branch 30 → 31 taken 4 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 5 times.
15 : tr("The calendar could not be downloaded from this address.");
210
5/5
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 37 taken 3 times.
✓ Branch 38 → 39 taken 2 times.
✓ Branch 38 → 40 taken 3 times.
✓ Branch 41 → 42 taken 5 times.
5 finished(id, failed ? QByteArray() : result.body, failed ? message : QString());
211 5 });
212 5 }
213
214 6 void CalendarSubscriptions::finished(const QString &id, const QByteArray &body, const QString &error) {
215
1/1
✓ Branch 2 → 3 taken 6 times.
6 m_running.remove(id);
216
1/1
✓ Branch 3 → 4 taken 6 times.
6 QList<Subscription> list = subscriptions();
217
3/3
✓ Branch 4 → 5 taken 6 times.
✓ Branch 5 → 6 taken 6 times.
✓ Branch 6 → 7 taken 6 times.
12 const auto found = std::find_if(list.begin(), list.end(), [&](const Subscription &entry) { return entry.id == id; });
218
2/4
✓ Branch 7 → 8 taken 6 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 12 taken 6 times.
✗ Branch 10 → 11 not taken.
6 if (found == list.end()) { emit changed(); return; } // removed meanwhile
219 6 QString problem = error;
220 6 QList<IcsFile::Entry> entries;
221
2/2
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 34 taken 3 times.
6 if (problem.isEmpty()) {
222 3 QString parseError;
223
2/2
✓ Branch 15 → 16 taken 3 times.
✓ Branch 16 → 17 taken 3 times.
3 entries = IcsFile::read(QString::fromUtf8(body), &parseError);
224
2/7
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 26 taken 3 times.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 32 taken 3 times.
✗ Branch 28 → 29 not taken.
3 if (entries.isEmpty() && !body.contains("BEGIN:VCALENDAR")) problem = tr("The address is not a calendar file.");
225 3 }
226
2/2
✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 54 taken 3 times.
6 if (!problem.isEmpty()) {
227 // The last copy stays; the error is shown in the calendar's options.
228
8/8
✓ Branch 36 → 37 taken 3 times.
✓ Branch 40 → 41 taken 3 times.
✓ Branch 41 → 42 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
✓ Branch 43 → 44 taken 3 times.
✓ Branch 44 → 45 taken 3 times.
✓ Branch 48 → 38 taken 3 times.
✓ Branch 48 → 49 taken 3 times.
6 qCWarning(lcSubscriptions) << "Subscription" << id << "not refreshed:" << problem;
229 3 found->lastError = problem;
230
1/1
✓ Branch 51 → 52 taken 3 times.
3 save(list);
231
1/1
✓ Branch 52 → 53 taken 3 times.
3 emit changed();
232 3 return;
233 }
234
1/1
✓ Branch 54 → 55 taken 3 times.
3 const QString accountId = accountIdFor(id);
235
1/1
✓ Branch 56 → 57 taken 3 times.
3 const QString color = m_store->calendarColor(accountId, found->url);
236 3 QStringList uids;
237
1/1
✓ Branch 57 → 58 taken 3 times.
3 m_store->beginTransaction();
238
4/4
✓ Branch 58 → 59 taken 3 times.
✓ Branch 59 → 60 taken 3 times.
✓ Branch 82 → 61 taken 5 times.
✓ Branch 82 → 83 taken 3 times.
8 for (const IcsFile::Entry &entry : entries) {
239
1/1
✓ Branch 62 → 63 taken 5 times.
5 uids << entry.uid;
240
1/1
✓ Branch 65 → 66 taken 5 times.
5 m_store->deleteEvent(entry.uid, accountId, found->url);
241
2/2
✓ Branch 79 → 69 taken 5 times.
✓ Branch 79 → 80 taken 5 times.
10 for (CalendarEvent row : entry.rows) {
242 5 row.accountId = accountId;
243 5 row.calendarPath = found->url;
244 5 row.color = color;
245
1/1
✓ Branch 75 → 76 taken 5 times.
5 m_store->upsertEvent(row);
246 5 }
247 }
248
1/1
✓ Branch 83 → 84 taken 3 times.
3 m_store->commitTransaction();
249
1/1
✓ Branch 85 → 86 taken 3 times.
3 m_store->removeStaleEvents(accountId, found->url, uids, true);
250
1/1
✓ Branch 86 → 87 taken 3 times.
3 found->lastRefresh = now();
251 3 found->lastError.clear();
252
1/1
✓ Branch 90 → 91 taken 3 times.
3 save(list);
253
9/9
✓ Branch 91 → 92 taken 3 times.
✓ Branch 95 → 96 taken 3 times.
✓ Branch 96 → 97 taken 3 times.
✓ Branch 97 → 98 taken 3 times.
✓ Branch 98 → 99 taken 3 times.
✓ Branch 100 → 101 taken 3 times.
✓ Branch 101 → 102 taken 3 times.
✓ Branch 105 → 93 taken 3 times.
✓ Branch 105 → 106 taken 3 times.
6 qCInfo(lcSubscriptions) << "Subscription" << id << "refreshed with" << entries.size() << "appointments";
254
1/1
✓ Branch 106 → 107 taken 3 times.
3 emit changed();
255
1/1
✓ Branch 107 → 108 taken 3 times.
3 emit storeChanged();
256 12 }
257