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.8% 334 / 0 / 368
Functions: 92.3% 60 / 0 / 65
Branches: 68.7% 426 / 0 / 620

app/core/CalendarSyncCoordinator.cpp
Line Branch Exec Source
1 #include "CalendarSyncCoordinator.h"
2 #include "CalendarFreeBusy.h"
3 #include "data/DavSelectionList.h"
4 #include "CalendarOutbox.h"
5 #include "CalendarSyncStatus.h"
6 #include "CalendarSubscriptions.h"
7 #include "CalendarSyncApply.h"
8 #include "CalendarAccountSync.h"
9 #include "DavSyncScheduler.h"
10 #include "data/CalendarStore.h"
11 #include "data/DavCredentials.h"
12 #include "service/CalDavClient.h"
13 #include <QLoggingCategory>
14
15
4/7
✓ Branch 2 → 3 taken 6 times.
✓ Branch 2 → 8 taken 39 times.
✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 6 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
45 Q_LOGGING_CATEGORY(lcCalendarSync, "mailjd.calendarsync")
16
17 #include <QSettings>
18 #include <QTimer>
19 #include <algorithm>
20 #include <memory>
21 #include <QSet>
22 #include "util/SecureUtil.h"
23
24 946 CalendarSyncCoordinator::CalendarSyncCoordinator(const QString &calendarDbPath,
25 946 QObject *parent)
26
1/1
✓ Branch 5 → 6 taken 946 times.
946 : QObject(parent) {
27
3/6
✓ Branch 14 → 15 taken 946 times.
✓ Branch 15 → 16 taken 946 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 946 times.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
946 m_store = new CalendarStore(this);
28
2/3
✓ Branch 18 → 19 taken 946 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 32 taken 946 times.
946 if (!m_store->open(calendarDbPath)) {
29 ✗ qCWarning(lcCalendarSync) << "CalendarSyncCoordinator: store open failed at"
30 ✗ << calendarDbPath;
31 }
32 // Defaults: the production keychain resolver and the real client factory.
33 946 m_credentialResolver = [](const QString &accountId, const QString &serverUrl,
34 const QString &username) {
35 ✗ return DavCredentials::readPasswordBlocking(accountId, serverUrl, username);
36 946 };
37 946 m_asyncCredentialResolver = [](QObject *owner, const QString &id, const QString &url,
38 const QString &user, DavCredentials::PasswordCallback callback) {
39 ✗ DavCredentials::readPasswordAsync(owner, id, url, user, std::move(callback));
40 946 };
41
1/1
✓ Branch 34 → 35 taken 946 times.
946 m_initialSyncTimer.setSingleShot(true);
42
1/1
✓ Branch 35 → 36 taken 946 times.
946 connect(&m_initialSyncTimer, &QTimer::timeout, this, [this] {
43
2/2
✓ Branch 2 → 3 taken 102 times.
✓ Branch 3 → 4 taken 102 times.
102 m_status->forgetAllExcept(syncAccountIds()); // S106-056: removed accounts
44
2/3
✓ Branch 5 → 6 taken 102 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 102 times.
102 if (!syncAccountIds().isEmpty()) m_scheduler->requestAll(DavSyncReasonInitial);
45 102 });
46 // Sprint 96 S96.3.1: the scheduler owns every automatic read trigger.
47 ✗ m_scheduler = new DavSyncScheduler([this](const CalendarSyncRequest &request) {
48 141 return startAccountSync(request);
49
3/4
✓ Branch 37 → 38 taken 946 times.
✓ Branch 39 → 40 taken 946 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 44 taken 946 times.
946 }, this);
50 1031 m_scheduler->setAccountsProvider([] { return syncAccountIds(); });
51
3/4
✓ Branch 3 → 4 taken 19 times.
✓ Branch 3 → 6 taken 262 times.
✓ Branch 5 → 6 taken 19 times.
✗ Branch 5 → 7 not taken.
1227 m_scheduler->setNetworkGate([this] { return !m_networkGate || m_networkGate(); });
52 // Queued local changes go out before the server state is read again.
53 946 m_scheduler->setRoundHook([this](unsigned) {
54
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 84 times.
84 if (!m_store->isOpen()) return;
55 84 m_outbox->flush(false);
56 84 m_subscriptions->refreshDue();
57 });
58 959 m_clientFactory = [](const QString &serverUrl, const QString &username,
59 const QString &password) {
60
2/5
✓ Branch 3 → 4 taken 13 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 13 times.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
26 return new CalDavClient(serverUrl, username, password);
61 946 };
62 946 CalendarOutbox::Deps deps;
63 946 deps.store = m_store;
64 2062 deps.endpoint = [this](const QString &accountId) -> std::optional<CalendarOutbox::Endpoint> {
65
1/1
✓ Branch 2 → 3 taken 170 times.
170 const auto endpoint = endpointForAccount(accountId);
66
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 168 times.
170 if (!endpoint)
67 2 return std::nullopt;
68 168 return CalendarOutbox::Endpoint{endpoint->serverUrl, endpoint->username, endpoint->password};
69 1116 };
70 1059 deps.hasEndpoint = [](const QString &accountId) { // no keyring read
71
1/1
✓ Branch 2 → 3 taken 113 times.
113 QSettings s;
72
1/1
✓ Branch 7 → 8 taken 113 times.
113 const int count = s.beginReadArray(QStringLiteral("carddav/accounts"));
73
2/2
✓ Branch 60 → 11 taken 73 times.
✓ Branch 60 → 61 taken 62 times.
135 for (int i = 0; i < count; ++i) {
74
1/1
✓ Branch 11 → 12 taken 73 times.
73 s.setArrayIndex(i);
75
4/4
✓ Branch 16 → 17 taken 73 times.
✓ Branch 17 → 18 taken 73 times.
✓ Branch 23 → 24 taken 51 times.
✓ Branch 23 → 59 taken 22 times.
73 if (s.value(QStringLiteral("id")).toString() == accountId)
76
6/18
✓ Branch 28 → 29 taken 51 times.
✓ Branch 29 → 30 taken 51 times.
✓ Branch 31 → 32 taken 51 times.
✗ Branch 31 → 41 not taken.
✓ Branch 52 → 53 taken 51 times.
✗ Branch 52 → 54 not taken.
✓ Branch 54 → 55 taken 51 times.
✗ Branch 54 → 56 not taken.
✓ Branch 56 → 57 taken 51 times.
✗ Branch 56 → 58 not taken.
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 102 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
204 return !s.value(QStringLiteral("serverUrl")).toString().isEmpty() &&
77
8/22
✓ Branch 36 → 37 taken 51 times.
✓ Branch 37 → 38 taken 51 times.
✓ Branch 39 → 40 taken 51 times.
✗ Branch 39 → 41 not taken.
✓ Branch 42 → 43 taken 51 times.
✗ Branch 42 → 44 not taken.
✓ Branch 44 → 45 taken 51 times.
✗ Branch 44 → 46 not taken.
✓ Branch 46 → 47 taken 51 times.
✗ Branch 46 → 48 not taken.
✓ Branch 48 → 49 taken 51 times.
✗ Branch 48 → 50 not taken.
✓ Branch 50 → 51 taken 51 times.
✗ Branch 50 → 52 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 94 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
102 !s.value(QStringLiteral("username")).toString().isEmpty();
78 }
79 62 return false;
80 1059 };
81 2123 deps.makeClient = [this](const QString &url, const QString &user, const QString &password) {
82 231 return makeClient(url, user, password);
83 946 };
84
4/4
✓ Branch 3 → 4 taken 161 times.
✓ Branch 3 → 6 taken 269 times.
✓ Branch 5 → 6 taken 42 times.
✓ Branch 5 → 7 taken 119 times.
1376 deps.networkAllowed = [this] { return !m_networkGate || m_networkGate(); };
85 2034 deps.pushDontNotify = [this](const QString &accountId, const QString &path) {
86
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 140 times.
142 return m_pushDontNotify ? m_pushDontNotify(accountId, path) : QString();
87 946 };
88 2532 deps.now = [this] {
89
2/2
✓ Branch 3 → 4 taken 389 times.
✓ Branch 3 → 6 taken 251 times.
640 return m_clock ? m_clock() : QDateTime::currentSecsSinceEpoch();
90 946 };
91
3/6
✓ Branch 61 → 62 taken 946 times.
✓ Branch 62 → 63 taken 946 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 946 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
946 m_status = new CalendarSyncStatus(this);
92
3/6
✓ Branch 65 → 66 taken 946 times.
✓ Branch 68 → 69 taken 946 times.
✗ Branch 70 → 71 not taken.
✓ Branch 70 → 72 taken 946 times.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
946 m_outbox = new CalendarOutbox(std::move(deps), this);
93
1/1
✓ Branch 72 → 73 taken 946 times.
946 connect(m_outbox, &CalendarOutbox::storeChanged, this, &CalendarSyncCoordinator::storeChanged);
94
1/1
✓ Branch 74 → 75 taken 946 times.
946 connect(m_outbox, &CalendarOutbox::statusMessage, this, &CalendarSyncCoordinator::statusMessage);
95
1/1
✓ Branch 76 → 77 taken 946 times.
946 m_apply = std::make_unique<CalendarSyncApply>(m_store);
96
3/6
✓ Branch 79 → 80 taken 946 times.
✓ Branch 80 → 81 taken 946 times.
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 946 times.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
946 m_subscriptions = new CalendarSubscriptions(m_store, this);
97 // Sprint 108 S108.8.5: free/busy through the schedule outbox.
98 ✗ m_freeBusy = new CalendarFreeBusy([this](const QString &accountId) -> CalDavClient * {
99 ✗ if (m_networkGate && !m_networkGate()) return nullptr;
100 ✗ const auto endpoint = endpointForAccount(accountId);
101 ✗ if (!endpoint) return nullptr;
102 ✗ CalDavClient *client = makeClient(endpoint->serverUrl, endpoint->username, QString::fromUtf8(endpoint->password));
103 ✗ connect(client, &CalDavClient::allRequestsFinished, client, &QObject::deleteLater);
104 ✗ return client;
105
3/4
✓ Branch 83 → 84 taken 946 times.
✓ Branch 85 → 86 taken 946 times.
✗ Branch 87 → 88 not taken.
✓ Branch 87 → 90 taken 946 times.
946 }, this);
106
0/4
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
946 m_subscriptions->setNetworkGate([this] { return !m_networkGate || m_networkGate(); });
107
1/1
✓ Branch 93 → 94 taken 946 times.
946 connect(m_subscriptions, &CalendarSubscriptions::storeChanged, this, &CalendarSyncCoordinator::storeChanged);
108 946 }
109
110 71 void CalendarSyncCoordinator::setClock(std::function<qint64()> clock) { m_clock = std::move(clock); }
111
112 13 void CalendarSyncCoordinator::retryPendingWrites() {
113
3/6
✓ Branch 2 → 3 taken 13 times.
✗ Branch 2 → 6 not taken.
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 6 not taken.
✓ Branch 7 → 8 taken 13 times.
✗ Branch 7 → 9 not taken.
13 if (m_store && m_store->isOpen())
114 13 m_outbox->flush(true);
115 13 }
116
117 315 std::optional<CalendarOutboxEntry> CalendarSyncCoordinator::pendingWrite(
118 const QString &uid, const QString &accountId, const QString &calendarPath) const {
119
3/6
✓ Branch 2 → 3 taken 315 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 315 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 315 times.
315 if (!m_store || !m_store->isOpen())
120 ✗ return std::nullopt;
121 315 return m_store->outboxEntry(accountId, calendarPath, uid);
122 }
123
124 43 bool CalendarSyncCoordinator::restoreEventSeries(const QString &uid, const QString &accountId,
125 const QString &calendarPath,
126 const QList<CalendarEvent> &rows) {
127
4/8
✓ Branch 3 → 4 taken 43 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 43 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 43 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 43 times.
43 if (readOnlyCalendar(accountId, calendarPath) || !m_store || !m_store->isOpen())
128 ✗ return false;
129 43 return m_outbox->restoreSeries(uid, accountId, calendarPath, rows);
130 }
131
132 1 bool CalendarSyncCoordinator::applyOccurrenceUpdate(const QString &uid, const QString &accountId,
133 const QString &calendarPath,
134 const QList<CalendarEvent> &exceptionRows) {
135
4/8
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
1 if (readOnlyCalendar(accountId, calendarPath) || !m_store || !m_store->isOpen())
136 ✗ return false;
137 1 return m_outbox->applyOccurrenceUpdate(uid, accountId, calendarPath, exceptionRows);
138 }
139
140 1 bool CalendarSyncCoordinator::applyOccurrenceCancel(const QString &uid, const QString &accountId,
141 const QString &calendarPath,
142 const QDateTime &recurrenceId) {
143
4/8
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
1 if (readOnlyCalendar(accountId, calendarPath) || !m_store || !m_store->isOpen())
144 ✗ return false;
145 1 return m_outbox->applyOccurrenceCancel(uid, accountId, calendarPath, recurrenceId);
146 }
147
148 27 bool CalendarSyncCoordinator::resolveEventConflict(const QString &uid, const QString &accountId,
149 const QString &calendarPath, const QString &choice) {
150
3/6
✓ Branch 2 → 3 taken 27 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 27 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 27 times.
27 if (!m_store || !m_store->isOpen())
151 ✗ return false;
152 27 return m_outbox->resolveConflict(uid, accountId, calendarPath, choice);
153 }
154
155 1875 CalendarSyncCoordinator::~CalendarSyncCoordinator() {
156
2/2
✓ Branch 12 → 5 taken 19 times.
✓ Branch 12 → 13 taken 946 times.
965 for (const auto &run : std::as_const(m_runs))
157
1/2
✓ Branch 7 → 8 taken 19 times.
✗ Branch 7 → 10 not taken.
19 if (run) run->cancel();
158 946 qDeleteAll(m_runs);
159 946 m_runs.clear();
160 1875 }
161
162 127 void CalendarSyncCoordinator::setCredentialResolver(CredentialResolver resolver) {
163 127 m_credentialResolver = resolver;
164
1/1
✓ Branch 3 → 4 taken 127 times.
254 m_asyncCredentialResolver = [resolver](QObject *owner, const QString &id, const QString &url,
165 const QString &user, DavCredentials::PasswordCallback callback) {
166
3/11
✓ Branch 2 → 3 taken 135 times.
✓ Branch 6 → 7 taken 135 times.
✓ Branch 7 → 8 taken 135 times.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
135 QTimer::singleShot(0, owner, [resolver, id, url, user, callback] {
167
2/2
✓ Branch 3 → 4 taken 124 times.
✓ Branch 4 → 5 taken 124 times.
124 callback(resolver(id, url, user), {});
168 124 });
169
1/1
✓ Branch 4 → 5 taken 127 times.
262 };
170 127 }
171
172 10 bool CalendarSyncCoordinator::open() { return m_store->isOpen(); }
173
174 1353 int CalendarSyncCoordinator::configuredIntervalMinutes() {
175
1/1
✓ Branch 2 → 3 taken 1353 times.
1353 QSettings s;
176
2/2
✓ Branch 8 → 9 taken 1353 times.
✓ Branch 9 → 10 taken 1353 times.
4059 return s.value(QStringLiteral("caldav/syncIntervalMin"), 15).toInt();
177 1353 }
178
179 830 void CalendarSyncCoordinator::startBackgroundSync() {
180
1/1
✓ Branch 2 → 3 taken 830 times.
830 const int interval = configuredIntervalMinutes();
181
1/1
✓ Branch 3 → 4 taken 830 times.
830 m_initialSyncTimer.stop();
182
1/1
✓ Branch 5 → 6 taken 830 times.
830 m_scheduler->setPollIntervalMs(qint64(qMax(0, interval)) * 60 * 1000);
183
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 830 times.
830 if (interval <= 0) return;
184 // The Widgets frontend syncs 3 s after startup; keep that freshness.
185
1/1
✓ Branch 8 → 9 taken 830 times.
830 m_initialSyncTimer.start(3000);
186 }
187
188 11 void CalendarSyncCoordinator::stopBackgroundSync() {
189 11 m_initialSyncTimer.stop();
190 11 m_scheduler->stop();
191 11 const bool wasRunning = !m_runs.isEmpty();
192
2/2
✓ Branch 18 → 8 taken 1 time.
✓ Branch 18 → 19 taken 11 times.
12 for (const auto &run : std::as_const(m_runs)) {
193
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
1 if (!run) continue;
194
1/1
✓ Branch 13 → 14 taken 1 time.
1 run->cancel();
195
1/1
✓ Branch 15 → 16 taken 1 time.
1 run->deleteLater();
196 }
197 11 m_runs.clear();
198
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 10 times.
11 if (wasRunning) emit syncStateChanged(false);
199 11 }
200
201 std::optional<CalendarSyncAccount>
202 2710 CalendarSyncCoordinator::syncConfiguration(const QString &id) {
203
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2710 times.
2710 if (id.isEmpty()) return std::nullopt;
204 2710 CalendarSyncAccount account;
205 2710 account.accountId = id;
206
1/1
✓ Branch 6 → 7 taken 2710 times.
2710 QSettings settings;
207 2710 int found = 0;
208
1/1
✓ Branch 11 → 12 taken 2710 times.
2710 const int count = settings.beginReadArray(QStringLiteral("carddav/accounts"));
209
2/2
✓ Branch 64 → 15 taken 2735 times.
✓ Branch 64 → 65 taken 2710 times.
5445 for (int i = 0; i < count; ++i) {
210
1/1
✓ Branch 15 → 16 taken 2735 times.
2735 settings.setArrayIndex(i);
211
4/4
✓ Branch 20 → 21 taken 2735 times.
✓ Branch 21 → 22 taken 2735 times.
✓ Branch 27 → 28 taken 395 times.
✓ Branch 27 → 29 taken 2340 times.
2735 if (settings.value(QStringLiteral("id")).toString() != id) continue;
212 2340 ++found;
213
2/2
✓ Branch 33 → 34 taken 2340 times.
✓ Branch 34 → 35 taken 2340 times.
2340 account.serverUrl = settings.value(QStringLiteral("serverUrl")).toString();
214
2/2
✓ Branch 44 → 45 taken 2340 times.
✓ Branch 45 → 46 taken 2340 times.
2340 account.username = settings.value(QStringLiteral("username")).toString();
215
2/2
✓ Branch 56 → 57 taken 2340 times.
✓ Branch 57 → 58 taken 2340 times.
4680 account.requiresLocalAuthorization = settings.value(QStringLiteral("requiresLocalAuthorization"), false).toBool();
216 }
217
1/1
✓ Branch 65 → 66 taken 2710 times.
2710 settings.endArray();
218
7/8
✓ Branch 66 → 67 taken 2340 times.
✓ Branch 66 → 71 taken 370 times.
✓ Branch 68 → 69 taken 2335 times.
✓ Branch 68 → 71 taken 5 times.
✗ Branch 70 → 71 not taken.
✓ Branch 70 → 72 taken 2335 times.
✓ Branch 73 → 74 taken 375 times.
✓ Branch 73 → 75 taken 2335 times.
2710 if (found != 1 || account.serverUrl.isEmpty() || account.username.isEmpty()) return std::nullopt;
219 2335 found = 0;
220
1/1
✓ Branch 79 → 80 taken 2335 times.
2335 const int configs = settings.beginReadArray(QStringLiteral("caldav/configs"));
221
2/2
✓ Branch 121 → 83 taken 1739 times.
✓ Branch 121 → 122 taken 2335 times.
4074 for (int i = 0; i < configs; ++i) {
222
1/1
✓ Branch 83 → 84 taken 1739 times.
1739 settings.setArrayIndex(i);
223
3/4
✓ Branch 88 → 89 taken 1739 times.
✓ Branch 89 → 90 taken 1739 times.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 1739 times.
1739 if (settings.value(QStringLiteral("carddavAccountId")).toString() != id) continue;
224 1739 ++found;
225
2/2
✓ Branch 101 → 102 taken 1739 times.
✓ Branch 102 → 103 taken 1739 times.
1739 account.selectedCalendars = davPathList(settings.value(QStringLiteral("selectedCalendars")));
226
2/2
✓ Branch 112 → 113 taken 1739 times.
✓ Branch 113 → 114 taken 1739 times.
1739 account.readOnlyCalendars = davPathList(settings.value(QStringLiteral("readOnlyCalendars")));
227 }
228
1/1
✓ Branch 122 → 123 taken 2335 times.
2335 settings.endArray();
229
1/2
✓ Branch 123 → 124 taken 2335 times.
✗ Branch 123 → 125 not taken.
2335 return found <= 1 ? std::optional<CalendarSyncAccount>(account) : std::nullopt;
230 2710 }
231
232 374 QStringList CalendarSyncCoordinator::syncAccountIds() {
233
1/1
✓ Branch 2 → 3 taken 374 times.
374 QSettings settings;
234 374 QStringList ids;
235
1/1
✓ Branch 7 → 8 taken 374 times.
374 const int count = settings.beginReadArray(QStringLiteral("carddav/accounts"));
236
2/2
✓ Branch 26 → 11 taken 239 times.
✓ Branch 26 → 27 taken 374 times.
613 for (int i = 0; i < count; ++i) {
237
1/1
✓ Branch 11 → 12 taken 239 times.
239 settings.setArrayIndex(i);
238
2/2
✓ Branch 16 → 17 taken 239 times.
✓ Branch 17 → 18 taken 239 times.
239 const QString id = settings.value(QStringLiteral("id")).toString();
239
2/3
✓ Branch 22 → 23 taken 239 times.
✗ Branch 22 → 24 not taken.
✓ Branch 23 → 24 taken 239 times.
239 if (!ids.contains(id)) ids << id;
240 239 }
241
1/1
✓ Branch 27 → 28 taken 374 times.
374 settings.endArray();
242 374 QStringList result;
243
2/2
✓ Branch 45 → 31 taken 239 times.
✓ Branch 45 → 46 taken 374 times.
613 for (const auto &id : std::as_const(ids)) {
244
1/1
✓ Branch 32 → 33 taken 239 times.
239 const auto account = syncConfiguration(id);
245
6/7
✓ Branch 34 → 35 taken 239 times.
✗ Branch 34 → 39 not taken.
✓ Branch 37 → 38 taken 169 times.
✓ Branch 37 → 39 taken 70 times.
✓ Branch 40 → 41 taken 169 times.
✓ Branch 40 → 42 taken 70 times.
✓ Branch 41 → 42 taken 169 times.
239 if (account && !account->selectedCalendars.isEmpty()) result << id;
246 239 }
247 374 return result;
248 374 }
249
250 // Manual "Sync now": every account, discovery and CTag check. Requests made
251 // while an account runs are queued as one follow-up batch, never dropped.
252 88 void CalendarSyncCoordinator::triggerSync() {
253
6/6
✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 7 taken 75 times.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 10 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 85 times.
88 if (m_networkGate && !m_networkGate()) return; // offline: no keyring, no DAV
254
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 16 taken 85 times.
85 if (!m_store->isOpen()) {
255 ✗ emit statusMessage(tr("Calendar sync not available"));
256 ✗ return;
257 }
258
3/3
✓ Branch 16 → 17 taken 85 times.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 24 taken 84 times.
85 if (syncAccountIds().isEmpty()) {
259
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
1 emit statusMessage(tr("No calendars configured for sync"));
260 1 return;
261 }
262 84 m_scheduler->requestAll(DavSyncReasonManual);
263 }
264
265 4 void CalendarSyncCoordinator::forgetAccount(const QString &accountId) {
266
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
4 if (accountId.isEmpty()) return;
267
2/3
✓ Branch 5 → 6 taken 4 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 15 taken 4 times.
4 if (auto run = m_runs.take(accountId)) {
268 ✗ run->cancel();
269 ✗ run->deleteLater();
270 ✗ if (m_runs.isEmpty()) emit syncStateChanged(false);
271 4 }
272
3/6
✓ Branch 16 → 17 taken 4 times.
✗ Branch 16 → 20 not taken.
✓ Branch 18 → 19 taken 4 times.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 4 times.
✗ Branch 21 → 23 not taken.
4 if (m_store && m_store->isOpen()) m_store->removeAccountCalendars(accountId);
273 4 m_status->forget(accountId);
274
5/5
✓ Branch 24 → 25 taken 4 times.
✓ Branch 28 → 29 taken 4 times.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 33 → 26 taken 4 times.
✓ Branch 33 → 34 taken 4 times.
8 qCInfo(lcCalendarSync) << "Calendars of a removed account forgotten";
275 4 emit storeChanged();
276 }
277
278 62 void CalendarSyncCoordinator::requestCollections(const QString &accountId, const QStringList &paths,
279 unsigned reasons) {
280 62 m_scheduler->requestCollections(accountId, paths, reasons);
281 62 }
282
283 3 void CalendarSyncCoordinator::requestAccount(const QString &accountId, unsigned reasons) {
284 3 m_scheduler->requestAccount(accountId, reasons);
285 3 }
286
287 // Scheduler executor: starts one account run (several accounts may run in
288 // parallel; one account never twice).
289 141 bool CalendarSyncCoordinator::startAccountSync(const CalendarSyncRequest &request) {
290
8/12
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 6 taken 131 times.
✓ Branch 4 → 5 taken 10 times.
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 141 times.
✓ Branch 7 → 8 taken 141 times.
✗ Branch 7 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 141 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 141 times.
141 if ((m_networkGate && !m_networkGate()) || !m_store->isOpen() || m_runs.contains(request.accountId))
291 ✗ return false;
292
1/1
✓ Branch 14 → 15 taken 141 times.
141 const auto account = syncConfiguration(request.accountId);
293
5/6
✓ Branch 16 → 17 taken 140 times.
✓ Branch 16 → 20 taken 1 time.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 140 times.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 140 times.
141 if (!account || account->selectedCalendars.isEmpty()) return false;
294 140 CalendarAccountSync::Deps deps;
295 140 deps.store = m_store;
296 140 deps.apply = m_apply.get();
297 140 deps.status = m_status;
298 402 deps.makeClient = [this](const QString &url, const QString &user, const QString &password) {
299 122 return makeClient(url, user, password);
300 140 };
301
1/1
✓ Branch 27 → 28 taken 140 times.
140 deps.resolveCredentials = m_asyncCredentialResolver;
302 140 deps.queryOnlyCollections = &m_queryOnlyCollections;
303 437 deps.adoptServerColor = [this](const QString &accountId, const QString &path, const QString &color) {
304 157 adoptServerColor(accountId, path, color);
305 140 };
306 140 const CalendarSyncAccount snapshot = *account;
307
3/3
✓ Branch 2 → 3 taken 876 times.
✓ Branch 3 → 4 taken 876 times.
✓ Branch 32 → 33 taken 140 times.
1016 deps.configurationCurrent = [snapshot] { return syncConfiguration(snapshot.accountId) == snapshot; };
308
3/6
✓ Branch 34 → 35 taken 140 times.
✓ Branch 38 → 39 taken 140 times.
✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 140 times.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
140 auto *run = new CalendarAccountSync(*account, request, std::move(deps), this);
309
1/1
✓ Branch 42 → 43 taken 140 times.
140 connect(run, &CalendarAccountSync::storeChanged, this, &CalendarSyncCoordinator::storeChanged);
310
1/1
✓ Branch 44 → 45 taken 140 times.
140 connect(run, &CalendarAccountSync::statusMessage, this, &CalendarSyncCoordinator::statusMessage);
311
1/1
✓ Branch 46 → 47 taken 140 times.
140 connect(run, &CalendarAccountSync::collectionsDiscovered, this, &CalendarSyncCoordinator::collectionsDiscovered);
312
1/1
✓ Branch 48 → 49 taken 140 times.
140 connect(run, &CalendarAccountSync::finished, this, [this, run](const CalendarSyncResult &result) {
313
2/3
✓ Branch 5 → 6 taken 120 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 7 taken 120 times.
120 if (m_runs.value(result.accountId) == run) m_runs.remove(result.accountId);
314
1/1
✓ Branch 7 → 8 taken 120 times.
120 run->deleteLater();
315
1/1
✓ Branch 8 → 9 taken 120 times.
120 QPointer<CalendarSyncCoordinator> guard(this);
316
1/1
✓ Branch 9 → 10 taken 120 times.
120 emit accountSyncFinished(result);
317
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 120 times.
120 if (!guard) return;
318
1/1
✓ Branch 13 → 14 taken 120 times.
120 m_scheduler->batchFinished(result);
319
3/3
✓ Branch 15 → 16 taken 93 times.
✓ Branch 15 → 17 taken 27 times.
✓ Branch 16 → 17 taken 93 times.
120 if (m_runs.isEmpty()) emit syncStateChanged(false);
320
1/2
✓ Branch 19 → 20 taken 120 times.
✗ Branch 19 → 22 not taken.
120 });
321 140 const bool first = m_runs.isEmpty();
322
2/2
✓ Branch 51 → 52 taken 140 times.
✓ Branch 52 → 53 taken 140 times.
140 m_runs.insert(request.accountId, run);
323
2/3
✓ Branch 54 → 55 taken 140 times.
✗ Branch 54 → 56 not taken.
✓ Branch 55 → 56 taken 140 times.
140 if (first) emit syncStateChanged(true);
324
1/1
✓ Branch 56 → 57 taken 140 times.
140 run->start();
325 140 return true;
326 141 }
327
328 7 void CalendarSyncCoordinator::requestFreeBusy(const QString &accountId, const QString &organizer,
329 const QStringList &attendees, const QDateTime &from,
330 const QDateTime &to, CalendarFreeBusy::Done done) {
331
2/2
✓ Branch 4 → 5 taken 7 times.
✓ Branch 5 → 6 taken 7 times.
7 m_freeBusy->request(accountId, CalendarSyncStatus::scheduleOutbox(accountId), organizer, attendees, from, to,
332 7 std::move(done));
333 7 }
334
335 354 CalDavClient *CalendarSyncCoordinator::makeClient(const QString &serverUrl,
336 const QString &username,
337 const QString &password) {
338 354 return m_clientFactory(serverUrl, username, password);
339 }
340
341 std::optional<CalendarSyncCoordinator::AccountEndpoint>
342 173 CalendarSyncCoordinator::endpointForAccount(const QString &accountId) const {
343
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 173 times.
173 if (accountId.isEmpty())
344 ✗ return std::nullopt;
345
1/1
✓ Branch 5 → 6 taken 173 times.
173 QSettings s;
346
1/1
✓ Branch 10 → 11 taken 173 times.
173 const int count = s.beginReadArray(QStringLiteral("carddav/accounts"));
347
2/2
✓ Branch 68 → 14 taken 173 times.
✓ Branch 68 → 69 taken 1 time.
174 for (int i = 0; i < count; ++i) {
348
1/1
✓ Branch 14 → 15 taken 173 times.
173 s.setArrayIndex(i);
349
4/4
✓ Branch 19 → 20 taken 173 times.
✓ Branch 20 → 21 taken 173 times.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 28 taken 172 times.
173 if (s.value(QStringLiteral("id")).toString() != accountId)
350 1 continue;
351 172 AccountEndpoint endpoint;
352 172 endpoint.accountId = accountId;
353
2/2
✓ Branch 33 → 34 taken 172 times.
✓ Branch 34 → 35 taken 172 times.
172 endpoint.serverUrl = s.value(QStringLiteral("serverUrl")).toString();
354
2/2
✓ Branch 44 → 45 taken 172 times.
✓ Branch 45 → 46 taken 172 times.
172 endpoint.username = s.value(QStringLiteral("username")).toString();
355
1/1
✓ Branch 51 → 52 taken 172 times.
344 endpoint.password = m_credentialResolver(accountId, endpoint.serverUrl,
356 172 endpoint.username);
357
1/1
✓ Branch 54 → 55 taken 172 times.
172 s.endArray();
358
6/8
✓ Branch 56 → 57 taken 172 times.
✗ Branch 56 → 61 not taken.
✓ Branch 58 → 59 taken 172 times.
✗ Branch 58 → 61 not taken.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 60 → 62 taken 170 times.
✓ Branch 63 → 64 taken 2 times.
✓ Branch 63 → 65 taken 170 times.
344 if (endpoint.serverUrl.isEmpty() || endpoint.username.isEmpty() ||
359 172 endpoint.password.isEmpty())
360 2 return std::nullopt;
361 170 return endpoint;
362 172 }
363
1/1
✓ Branch 69 → 70 taken 1 time.
1 s.endArray();
364 1 return std::nullopt;
365 173 }
366
367 332 bool CalendarSyncCoordinator::readOnlyCalendar(const QString &accountId,
368 const QString &calendarPath) const {
369
1/1
✓ Branch 2 → 3 taken 332 times.
332 const auto config = syncConfiguration(accountId);
370
6/6
✓ Branch 4 → 5 taken 106 times.
✓ Branch 4 → 9 taken 226 times.
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 9 taken 102 times.
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 12 taken 328 times.
332 if (config && config->readOnlyCalendars.contains(calendarPath)) return true;
371
5/5
✓ Branch 12 → 13 taken 328 times.
✓ Branch 13 → 14 taken 328 times.
✓ Branch 14 → 15 taken 328 times.
✓ Branch 28 → 16 taken 562 times.
✓ Branch 28 → 29 taken 327 times.
889 for (const auto &calendar : m_store->allCalendars())
372
10/10
✓ Branch 18 → 19 taken 515 times.
✓ Branch 18 → 23 taken 47 times.
✓ Branch 20 → 21 taken 326 times.
✓ Branch 20 → 23 taken 189 times.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 325 times.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 561 times.
✓ Branch 31 → 32 taken 327 times.
✓ Branch 31 → 34 taken 1 time.
889 if (calendar.accountId == accountId && calendar.path == calendarPath && calendar.readOnly) return true;
373 327 return false;
374 332 }
375
376 89 QList<CalendarInfo> CalendarSyncCoordinator::calendars() const {
377
4/7
✓ Branch 2 → 3 taken 89 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 89 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 89 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 89 times.
89 if (!m_store || !m_store->isOpen())
378 ✗ return {};
379
1/1
✓ Branch 9 → 10 taken 89 times.
89 auto calendars = m_store->allCalendars();
380
1/1
✓ Branch 10 → 11 taken 89 times.
89 calendars.removeIf([](const CalendarInfo &calendar) {
381
1/1
✓ Branch 2 → 3 taken 143 times.
143 const auto config = syncConfiguration(calendar.accountId);
382
5/6
✓ Branch 3 → 4 taken 130 times.
✓ Branch 3 → 9 taken 13 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 10 taken 129 times.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
286 return calendar.readOnly || (config && config->readOnlyCalendars.contains(calendar.path));
383 143 });
384
3/3
✓ Branch 11 → 12 taken 89 times.
✓ Branch 12 → 13 taken 89 times.
✓ Branch 13 → 14 taken 89 times.
89 std::sort(calendars.begin(), calendars.end(),
385 79 [](const CalendarInfo &a, const CalendarInfo &b) {
386 79 return a.displayName < b.displayName;
387 });
388 89 return calendars;
389 89 }
390
391 284 std::optional<CalendarTask> CalendarSyncCoordinator::taskFor(
392 const QString &uid, const QString &accountId, const QString &calendarPath) const {
393
3/6
✓ Branch 2 → 3 taken 284 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 284 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 284 times.
284 if (!m_store || !m_store->isOpen())
394 ✗ return std::nullopt;
395 284 return m_store->taskFor(uid, accountId, calendarPath);
396 }
397
398 342 std::optional<CalendarEvent> CalendarSyncCoordinator::eventFor(
399 const QString &uid, const QString &accountId, const QString &calendarPath) const {
400
3/6
✓ Branch 2 → 3 taken 342 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 342 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 342 times.
342 if (!m_store || !m_store->isOpen())
401 ✗ return std::nullopt;
402 342 return m_store->eventFor(uid, accountId, calendarPath);
403 }
404
405 115 bool CalendarSyncCoordinator::saveTask(const CalendarTask &task, bool wasNew) {
406 Q_UNUSED(wasNew);
407
8/10
✓ Branch 2 → 3 taken 115 times.
✗ Branch 2 → 7 not taken.
✓ Branch 3 → 4 taken 115 times.
✓ Branch 4 → 5 taken 115 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 115 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 114 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 114 times.
115 if (!m_store || !m_store->isOpen() || readOnlyCalendar(task.accountId, task.calendarPath)) return false;
408
1/1
✓ Branch 11 → 12 taken 114 times.
114 const auto calendars = m_store->allCalendars();
409
1/2
✓ Branch 31 → 14 taken 121 times.
✗ Branch 31 → 32 not taken.
121 for (const auto &calendar : calendars)
410
5/6
✓ Branch 16 → 17 taken 121 times.
✗ Branch 16 → 20 not taken.
✓ Branch 18 → 19 taken 114 times.
✓ Branch 18 → 20 taken 7 times.
✓ Branch 21 → 22 taken 114 times.
✓ Branch 21 → 29 taken 7 times.
121 if (calendar.accountId == task.accountId && calendar.path == task.calendarPath)
411
6/6
✓ Branch 22 → 23 taken 114 times.
✓ Branch 23 → 24 taken 112 times.
✓ Branch 23 → 27 taken 2 times.
✓ Branch 24 → 25 taken 112 times.
✓ Branch 25 → 26 taken 111 times.
✓ Branch 25 → 27 taken 1 time.
114 return calendar.supportsTasks() && m_outbox->saveTask(task);
412 ✗ return false;
413 114 }
414
415 9 bool CalendarSyncCoordinator::moveTask(const CalendarTask &source, const CalendarTask &target) {
416
4/8
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 9 not taken.
✓ Branch 4 → 5 taken 9 times.
✗ Branch 4 → 9 not taken.
✓ Branch 6 → 7 taken 9 times.
✗ Branch 6 → 9 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 9 times.
18 if (!m_store || !m_store->isOpen() || readOnlyCalendar(source.accountId, source.calendarPath) ||
417
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 9 times.
9 readOnlyCalendar(target.accountId, target.calendarPath)) return false;
418
4/5
✓ Branch 13 → 14 taken 9 times.
✓ Branch 14 → 15 taken 9 times.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 34 → 17 taken 18 times.
✗ Branch 34 → 35 not taken.
18 for (const auto &calendar : m_store->allCalendars())
419
5/6
✓ Branch 19 → 20 taken 18 times.
✗ Branch 19 → 23 not taken.
✓ Branch 21 → 22 taken 9 times.
✓ Branch 21 → 23 taken 9 times.
✓ Branch 24 → 25 taken 9 times.
✓ Branch 24 → 32 taken 9 times.
18 if (calendar.accountId == target.accountId && calendar.path == target.calendarPath)
420
5/8
✓ Branch 25 → 26 taken 9 times.
✓ Branch 26 → 27 taken 9 times.
✗ Branch 26 → 30 not taken.
✓ Branch 27 → 28 taken 9 times.
✓ Branch 28 → 29 taken 9 times.
✗ Branch 28 → 30 not taken.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 40 taken 9 times.
9 return calendar.supportsTasks() && m_outbox->moveTask(source, target);
421 ✗ return false;
422 }
423
424 15 bool CalendarSyncCoordinator::removeTask(const QString &uid, const QString &accountId, const QString &calendarPath) {
425
6/8
✓ Branch 2 → 3 taken 15 times.
✗ Branch 2 → 7 not taken.
✓ Branch 4 → 5 taken 15 times.
✗ Branch 4 → 7 not taken.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 14 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 14 times.
15 if (!m_store || !m_store->isOpen() || readOnlyCalendar(accountId, calendarPath)) return false;
426 14 return m_outbox->removeTask(uid, accountId, calendarPath);
427 }
428
429 117 bool CalendarSyncCoordinator::saveEvent(const CalendarEvent &event, bool wasNew) {
430 Q_UNUSED(wasNew);
431
4/4
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 8 taken 116 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
117 if (readOnlyCalendar(event.accountId, event.calendarPath)) { emit statusMessage(tr("This calendar is read-only.")); return false; }
432
3/6
✓ Branch 8 → 9 taken 116 times.
✗ Branch 8 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 116 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 18 taken 116 times.
116 if (!m_store || !m_store->isOpen()) {
433 ✗ emit statusMessage(tr("Calendar storage not available"));
434 ✗ return false;
435 }
436
2/2
✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 24 taken 113 times.
116 if (!m_outbox->saveEvent(event)) {
437
2/2
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
3 emit statusMessage(tr("The local calendar write failed."));
438 3 return false;
439 }
440 113 return true;
441 }
442
443 3 void CalendarSyncCoordinator::pushCalendarColor(const QString &accountId, const QString &calendarPath,
444 const QString &color) {
445
1/1
✓ Branch 2 → 3 taken 3 times.
3 const auto endpoint = endpointForAccount(accountId);
446
8/12
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 11 taken 1 time.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 11 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 12 taken 1 time.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 26 taken 1 time.
3 if (!endpoint || readOnlyCalendar(accountId, calendarPath) || (m_networkGate && !m_networkGate())) {
447
6/6
✓ Branch 14 → 15 taken 2 times.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 24 → 16 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
4 qCInfo(lcCalendarSync) << "Calendar color kept locally for" << calendarPath;
448 2 return;
449 }
450
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
1 CalDavClient *client = makeClient(endpoint->serverUrl, endpoint->username, QString::fromUtf8(endpoint->password));
451
1/1
✓ Branch 32 → 33 taken 1 time.
1 connect(client, &CalDavClient::allRequestsFinished, client, &QObject::deleteLater);
452
1/1
✓ Branch 35 → 36 taken 1 time.
1 connect(client, &CalDavClient::writeFailed, this, [calendarPath](const QString &error) {
453 ✗ qCInfo(lcCalendarSync) << "Calendar color stays local for" << calendarPath << error;
454 ✗ });
455 // The server has our color now: the next discovery must not take it for
456 // a change made elsewhere.
457
1/5
✓ Branch 41 → 42 taken 1 time.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
1 connect(client, &CalDavClient::calendarColorSaved, this, [accountId, calendarPath, color] {
458
4/4
✓ Branch 2 → 3 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
1 QSettings().setValue(serverColorKey(accountId, calendarPath), color.toLower());
459 1 });
460
1/1
✓ Branch 44 → 45 taken 1 time.
1 client->setCalendarColor(calendarPath, color);
461 3 }
462
463 // CAL-SERVER-COLOR: discovery never overwrote a stored color, so a color
464 // changed in another client (Nextcloud web, phone) never arrived. The last
465 // color seen on the server is remembered; only when the server reports a
466 // different one was it changed elsewhere, and then it is adopted. A color
467 // chosen in MailJD stays until the server's color changes.
468 7 QString CalendarSyncCoordinator::serverColorKey(const QString &accountId, const QString &calendarPath) {
469
2/2
✓ Branch 2 → 3 taken 7 times.
✓ Branch 6 → 7 taken 7 times.
14 return QStringLiteral("calendar/serverColor/") + calendarSelectionKey(accountId, calendarPath);
470 }
471
472 157 void CalendarSyncCoordinator::adoptServerColor(const QString &accountId, const QString &calendarPath,
473 const QString &serverColor) {
474
2/2
✓ Branch 2 → 3 taken 157 times.
✓ Branch 3 → 4 taken 157 times.
157 const QString color = serverColor.trimmed().toLower();
475
2/2
✓ Branch 6 → 7 taken 151 times.
✓ Branch 6 → 8 taken 6 times.
157 if (color.isEmpty()) return;
476
1/1
✓ Branch 8 → 9 taken 6 times.
6 QSettings settings;
477
1/1
✓ Branch 9 → 10 taken 6 times.
6 const QString key = serverColorKey(accountId, calendarPath);
478
2/2
✓ Branch 11 → 12 taken 6 times.
✓ Branch 12 → 13 taken 6 times.
6 const QString lastSeen = settings.value(key).toString();
479
1/1
✓ Branch 16 → 17 taken 6 times.
6 settings.setValue(key, color);
480 // First sight: the stored color came from this server already (upsert).
481
6/6
✓ Branch 19 → 20 taken 4 times.
✓ Branch 19 → 22 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 23 taken 2 times.
✓ Branch 24 → 25 taken 4 times.
✓ Branch 24 → 26 taken 2 times.
6 if (lastSeen.isEmpty() || lastSeen == color) return;
482
4/4
✓ Branch 26 → 27 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 1 time.
2 if (m_store->calendarColor(accountId, calendarPath).toLower() == color) return;
483
6/6
✓ Branch 33 → 34 taken 1 time.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 43 → 35 taken 1 time.
✓ Branch 43 → 44 taken 1 time.
2 qCInfo(lcCalendarSync) << "Calendar color changed on the server for" << calendarPath;
484
1/1
✓ Branch 44 → 45 taken 1 time.
1 m_store->setCalendarColor(accountId, calendarPath, color);
485 172 }
486
487 20 bool CalendarSyncCoordinator::removeEvent(const QString &uid, const QString &accountId,
488 const QString &calendarPath) {
489
4/4
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 8 taken 19 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
20 if (readOnlyCalendar(accountId, calendarPath)) { emit statusMessage(tr("This calendar is read-only.")); return false; }
490
3/6
✓ Branch 8 → 9 taken 19 times.
✗ Branch 8 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 19 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 19 times.
19 if (!m_store || !m_store->isOpen())
491 ✗ return false;
492 19 return m_outbox->removeSeries(uid, accountId, calendarPath);
493 }
494
495 42 void CalendarSyncCoordinator::beginTaskBatch() { m_outbox->beginBatch(); }
496 42 void CalendarSyncCoordinator::endTaskBatch() { m_outbox->endBatch(); }
497