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: 91.0% 203 / 0 / 223
Functions: 100.0% 20 / 0 / 20
Branches: 80.2% 227 / 0 / 283

app/core/ContactBookSync.cpp
Line Branch Exec Source
1 #include "ContactBookSync.h"
2
3 #include "ContactPhotoFetch.h"
4 #include "data/ContactQueries.h"
5 #include "data/ContactStore.h"
6 #include "data/VCardCodec.h"
7 #include "service/CardDavClient.h"
8
9 #include <QLoggingCategory>
10 #include <QSet>
11
12
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 107 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
110 Q_LOGGING_CATEGORY(lcContactBookSync, "mailjd.contacts.sync")
13
14 namespace {
15 // Above this many changed cards the model reloads instead of updating rows.
16 constexpr int kBulkThreshold = 200;
17 } // namespace
18
19 82 ContactBookSync::ContactBookSync(ContactStore *store, CardDavClient *client, qint64 bookId, QObject *parent)
20 82 : QObject(parent), m_store(store), m_client(client), m_bookId(bookId) {
21 82 connect(m_client, &CardDavClient::bookFetched, this,
22
1/1
✓ Branch 13 → 14 taken 82 times.
156 [this](const QString &, const AddressBookInfo &book) { onBook(book); });
23 82 connect(m_client, &CardDavClient::changesListed, this,
24
1/1
✓ Branch 15 → 16 taken 82 times.
130 [this](const QString &, const DavCardChanges &changes) { onChanges(changes); });
25
1/1
✓ Branch 17 → 18 taken 82 times.
82 connect(m_client, &CardDavClient::collectionSyncRejected, this, [this](const QString &, bool invalidToken) {
26 2 emit progressed();
27
2/4
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 20 not taken.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 20 not taken.
2 if (invalidToken && !m_fullListing) {
28
5/5
✓ Branch 5 → 6 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 14 → 7 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
4 qCInfo(lcContactBookSync) << "sync-token expired; listing the whole book again";
29 2 m_fullListing = true;
30 2 ++m_requests;
31 2 m_pageRequestToken.clear();
32
1/1
✓ Branch 17 → 18 taken 2 times.
2 m_client->listChanges(m_book.path, QString());
33 2 return;
34 }
35 ✗ ++m_requests;
36 ✗ m_client->listEtags(m_book.path);
37 });
38 82 connect(m_client, &CardDavClient::etagsListed, this,
39
1/1
✓ Branch 19 → 20 taken 82 times.
84 [this](const QString &, const QHash<QString, QString> &etags) { onEtags(etags); });
40 82 connect(m_client, &CardDavClient::cardsFetched, this,
41
1/1
✓ Branch 21 → 22 taken 82 times.
82 [this](const QString &, const QList<DavCardResource> &cards, const QStringList &missing) {
42 89 onCards(cards, missing);
43 89 });
44 82 connect(m_client, &CardDavClient::multigetFailed, this,
45
1/1
✓ Branch 23 → 24 taken 82 times.
82 [this](const QString &, const QStringList &hrefs, const QString &error, bool tooLarge) {
46 1 onBatchFailed(hrefs, error, tooLarge);
47 1 });
48
1/1
✓ Branch 25 → 26 taken 82 times.
90 connect(m_client, &CardDavClient::syncFailed, this, [this](const QString &error) { fail(error); });
49 82 }
50
51 82 void ContactBookSync::start() {
52
1/1
✓ Branch 2 → 3 taken 82 times.
82 const auto book = m_store->book(m_bookId);
53
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 12 taken 82 times.
82 if (!book) {
54 ✗ fail(QStringLiteral("Address book missing from the store"));
55 ✗ return;
56 }
57 82 m_book = *book;
58 82 ++m_requests;
59
1/1
✓ Branch 14 → 15 taken 82 times.
82 m_client->fetchBook(m_book.path);
60 82 }
61
62 74 void ContactBookSync::onBook(const AddressBookInfo &server) {
63
1/1
✓ Branch 2 → 3 taken 74 times.
74 emit progressed();
64 74 m_server = server;
65 // Name and privileges are kept even when nothing else changed.
66 74 AddressBookInfo stored = m_book;
67 74 stored.displayName = server.displayName;
68 74 stored.readOnly = server.readOnly;
69 74 stored.supportsSyncCollection = server.supportsSyncCollection;
70 74 stored.supportedVersions = server.supportedVersions;
71 74 stored.maxResourceSize = server.maxResourceSize;
72
1/1
✓ Branch 7 → 8 taken 74 times.
74 m_store->updateBook(stored);
73 74 m_book = stored;
74
4/4
✓ Branch 10 → 11 taken 71 times.
✓ Branch 10 → 14 taken 3 times.
✓ Branch 12 → 13 taken 25 times.
✓ Branch 12 → 14 taken 46 times.
74 const bool sameToken = !server.syncToken.isEmpty() && server.syncToken == m_book.syncToken;
75
5/6
✓ Branch 16 → 17 taken 3 times.
✓ Branch 16 → 22 taken 71 times.
✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 22 not taken.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 2 times.
74 const bool sameCtag = server.syncToken.isEmpty() && !server.ctag.isEmpty() && server.ctag == m_book.ctag;
76
6/6
✓ Branch 23 → 24 taken 45 times.
✓ Branch 23 → 40 taken 29 times.
✓ Branch 24 → 25 taken 20 times.
✓ Branch 24 → 26 taken 25 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 40 taken 19 times.
74 if (m_book.fetched && (sameToken || sameCtag)) {
77
7/7
✓ Branch 26 → 27 taken 26 times.
✓ Branch 30 → 31 taken 26 times.
✓ Branch 31 → 32 taken 26 times.
✓ Branch 32 → 33 taken 26 times.
✓ Branch 33 → 34 taken 26 times.
✓ Branch 37 → 28 taken 26 times.
✓ Branch 37 → 38 taken 26 times.
52 qCInfo(lcContactBookSync) << "Book" << m_bookId << "unchanged";
78
1/1
✓ Branch 38 → 39 taken 26 times.
26 complete();
79 26 return;
80 }
81 48 ++m_requests;
82
2/2
✓ Branch 40 → 41 taken 46 times.
✓ Branch 40 → 52 taken 2 times.
48 if (server.supportsSyncCollection) {
83
3/4
✓ Branch 41 → 42 taken 18 times.
✓ Branch 41 → 44 taken 28 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 18 times.
46 m_fullListing = !m_book.fetched || m_book.syncToken.isEmpty();
84
2/2
✓ Branch 46 → 47 taken 28 times.
✓ Branch 46 → 48 taken 18 times.
46 m_pageRequestToken = m_fullListing ? QString() : m_book.syncToken;
85
1/1
✓ Branch 51 → 53 taken 46 times.
46 m_client->listChanges(m_book.path, m_pageRequestToken);
86 } else {
87
1/1
✓ Branch 52 → 53 taken 2 times.
2 m_client->listEtags(m_book.path);
88 }
89 74 }
90
91 48 void ContactBookSync::onChanges(const DavCardChanges &changes) {
92
1/1
✓ Branch 2 → 3 taken 48 times.
48 emit progressed();
93 // S106-062: a truncated page that repeats its request token without any
94 // change makes no progress — and every page is bounded. A broken server
95 // used to keep the sync (and its inactivity watchdog) going forever.
96
6/6
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 12 taken 45 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 10 taken 2 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 21 taken 47 times.
54 if (changes.truncated &&
97
2/4
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
4 ((changes.syncToken == m_pageRequestToken && changes.changed.isEmpty() && changes.removed.isEmpty()) ||
98
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
2 ++m_truncatedPages > MaxTruncatedPages)) {
99
1/1
✓ Branch 17 → 18 taken 1 time.
1 fail(QStringLiteral("The truncated address book listing made no progress"));
100 30 return;
101 }
102 47 m_newToken = changes.syncToken;
103
4/4
✓ Branch 22 → 23 taken 30 times.
✓ Branch 22 → 26 taken 17 times.
✓ Branch 23 → 24 taken 29 times.
✓ Branch 23 → 26 taken 1 time.
47 if (changes.full && !changes.truncated) {
104 // A full listing is the whole book: compare like an ETag listing.
105
1/1
✓ Branch 24 → 25 taken 29 times.
29 onEtags(changes.changed);
106 29 return;
107 }
108
3/4
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 29 taken 17 times.
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 29 not taken.
18 if (changes.full && changes.truncated) {
109 1 m_collectingFullListing = true;
110 1 m_fullListingHrefs.clear();
111 }
112
1/1
✓ Branch 29 → 30 taken 18 times.
18 const QHash<QString, QString> stored = m_store->cardEtags(m_bookId);
113
2/2
✓ Branch 42 → 31 taken 16 times.
✓ Branch 42 → 43 taken 18 times.
34 for (auto it = changes.changed.cbegin(); it != changes.changed.cend(); ++it)
114
2/2
✓ Branch 36 → 37 taken 9 times.
✓ Branch 36 → 39 taken 7 times.
16 if (stored.value(it.key()) != it.value())
115
1/1
✓ Branch 38 → 39 taken 9 times.
9 m_toFetch << it.key();
116 // F-35 (SPRINT-104): a truncated page (collection-level 507, RFC 6578
117 // §3.6) is PARTIAL — cards merely missing from it are no removals, and
118 // the listing continues from the returned token.
119 // S106-060/061: the explicit 404 removals of EVERY page are authoritative
120 // (the continuation never announces them again) and accumulate; they used
121 // to be dropped for truncated pages and overwritten page by page.
122
1/1
✓ Branch 43 → 44 taken 18 times.
18 m_removed += changes.removed;
123
2/2
✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 46 taken 16 times.
18 m_moreToken = changes.truncated ? changes.syncToken : QString();
124
2/2
✓ Branch 49 → 50 taken 2 times.
✓ Branch 49 → 82 taken 16 times.
18 if (m_collectingFullListing) {
125
2/2
✓ Branch 56 → 51 taken 2 times.
✓ Branch 56 → 57 taken 2 times.
4 for (auto it = changes.changed.cbegin(); it != changes.changed.cend(); ++it)
126
1/1
✓ Branch 52 → 53 taken 2 times.
2 m_fullListingHrefs.insert(it.key());
127
1/2
✗ Branch 63 → 59 not taken.
✓ Branch 63 → 64 taken 2 times.
2 for (const QString &href : changes.removed)
128 ✗ m_fullListingHrefs.remove(href);
129
2/2
✓ Branch 64 → 65 taken 1 time.
✓ Branch 64 → 82 taken 1 time.
2 if (!changes.truncated) {
130 // S106-957: the whole book has been listed over several pages. A
131 // stored card it never named was deleted on the server — the same
132 // absence rule as an untruncated full listing (onEtags).
133 1 m_collectingFullListing = false;
134
2/2
✓ Branch 80 → 66 taken 3 times.
✓ Branch 80 → 81 taken 1 time.
4 for (auto it = stored.cbegin(); it != stored.cend(); ++it)
135
5/6
✓ Branch 68 → 69 taken 1 time.
✓ Branch 68 → 73 taken 2 times.
✓ Branch 71 → 72 taken 1 time.
✗ Branch 71 → 73 not taken.
✓ Branch 74 → 75 taken 1 time.
✓ Branch 74 → 77 taken 2 times.
3 if (!m_fullListingHrefs.contains(it.key()) && !m_removed.contains(it.key()))
136
1/1
✓ Branch 76 → 77 taken 1 time.
1 m_removed << it.key();
137 1 m_fullListingHrefs.clear();
138 }
139 }
140
1/1
✓ Branch 82 → 83 taken 18 times.
18 fetchNextBatch();
141 18 }
142
143 31 void ContactBookSync::onEtags(const QHash<QString, QString> &etags) {
144
1/1
✓ Branch 2 → 3 taken 31 times.
31 emit progressed();
145 31 m_fullListing = true;
146
1/1
✓ Branch 3 → 4 taken 31 times.
31 const QHash<QString, QString> stored = m_store->cardEtags(m_bookId);
147
2/2
✓ Branch 16 → 5 taken 5293 times.
✓ Branch 16 → 17 taken 31 times.
5324 for (auto it = etags.cbegin(); it != etags.cend(); ++it)
148
2/2
✓ Branch 10 → 11 taken 5292 times.
✓ Branch 10 → 13 taken 1 time.
5293 if (stored.value(it.key()) != it.value())
149
1/1
✓ Branch 12 → 13 taken 5292 times.
5292 m_toFetch << it.key();
150
2/2
✓ Branch 26 → 18 taken 3 times.
✓ Branch 26 → 27 taken 31 times.
34 for (auto it = stored.cbegin(); it != stored.cend(); ++it)
151
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 23 taken 2 times.
3 if (!etags.contains(it.key()))
152
1/1
✓ Branch 22 → 23 taken 1 time.
1 m_removed << it.key();
153
1/1
✓ Branch 27 → 28 taken 31 times.
31 fetchNextBatch();
154 31 }
155
156 139 void ContactBookSync::fetchNextBatch() {
157
6/6
✓ Branch 3 → 4 taken 86 times.
✓ Branch 3 → 7 taken 53 times.
✓ Branch 5 → 6 taken 37 times.
✓ Branch 5 → 7 taken 49 times.
✓ Branch 8 → 9 taken 37 times.
✓ Branch 8 → 17 taken 102 times.
139 if (m_batches.isEmpty() && !m_toFetch.isEmpty()) {
158
2/2
✓ Branch 15 → 10 taken 88 times.
✓ Branch 15 → 16 taken 37 times.
125 for (int i = 0; i < m_toFetch.size(); i += MaxBatch)
159
2/2
✓ Branch 10 → 11 taken 88 times.
✓ Branch 11 → 12 taken 88 times.
88 m_batches.append(m_toFetch.mid(i, MaxBatch));
160 37 m_toFetch.clear();
161 }
162
2/2
✓ Branch 18 → 19 taken 49 times.
✓ Branch 18 → 31 taken 90 times.
139 if (m_batches.isEmpty()) {
163
2/2
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 28 taken 47 times.
49 if (!m_moreToken.isEmpty()) {
164 // F-35: continue the truncated listing from the returned token.
165 2 const QString token = std::exchange(m_moreToken, QString());
166 2 ++m_requests;
167 2 m_fullListing = false;
168 2 m_pageRequestToken = token;
169
1/1
✓ Branch 25 → 26 taken 2 times.
2 m_client->listChanges(m_book.path, token);
170 2 return;
171 2 }
172 47 applyRemovals();
173 47 complete();
174 47 return;
175 }
176 90 ++m_requests;
177
2/2
✓ Branch 31 → 32 taken 90 times.
✓ Branch 32 → 33 taken 90 times.
90 m_client->multiget(m_book.path, m_batches.takeFirst());
178 }
179
180 89 void ContactBookSync::onCards(const QList<DavCardResource> &cards, const QStringList &missing) {
181 89 emit progressed();
182 89 m_store->beginTransaction();
183
2/2
✓ Branch 70 → 6 taken 5301 times.
✓ Branch 70 → 71 taken 89 times.
5390 for (const DavCardResource &resource : cards) {
184
1/1
✓ Branch 7 → 8 taken 5301 times.
5301 ContactCard card;
185 5301 QString error;
186
4/7
✓ Branch 8 → 9 taken 5301 times.
✓ Branch 9 → 10 taken 5301 times.
✗ Branch 9 → 12 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 5301 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 29 taken 5301 times.
5301 if (!VCardCodec::parse(resource.raw, &card, &error) || card.uid.isEmpty()) {
187 ✗ ++m_skipped;
188 ✗ qCWarning(lcContactBookSync) << "Skipped an unreadable card in book" << m_bookId << ":" << error;
189 ✗ continue;
190 ✗ }
191 5301 ++m_downloaded;
192 // A queued local change wins locally; the outbox merges on push.
193
3/3
✓ Branch 29 → 30 taken 5301 times.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 5300 times.
5301 if (const auto entry = m_store->outboxEntry(m_bookId, card.uid); entry)
194
2/2
✓ Branch 35 → 36 taken 5300 times.
✓ Branch 35 → 38 taken 1 time.
5301 continue;
195 // The UID moved to another resource (or the resource got another UID).
196
1/1
✓ Branch 37 → 39 taken 5300 times.
5300 const QString previousUid = m_store->uidForHref(m_bookId, resource.href);
197
4/6
✓ Branch 40 → 41 taken 4 times.
✓ Branch 40 → 44 taken 5296 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 4 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 5300 times.
5300 if (!previousUid.isEmpty() && previousUid != card.uid)
198 ✗ m_store->removeCard(m_bookId, previousUid);
199 5300 StoredCard stored;
200 5300 stored.bookId = m_bookId;
201 5300 stored.uid = card.uid;
202 5300 stored.href = resource.href;
203 5300 stored.etag = resource.etag;
204 5300 stored.raw = resource.raw;
205 5300 stored.baseRaw = resource.raw;
206
1/1
✓ Branch 52 → 53 taken 5300 times.
5300 m_store->putCard(stored);
207
2/2
✓ Branch 53 → 54 taken 5300 times.
✓ Branch 54 → 55 taken 5300 times.
5300 m_changedKeys << ContactQueries::cardKey(m_bookId, card.uid);
208 5302 }
209 89 m_store->commitTransaction();
210 // Members announced as changed but not returned are gone.
211 89 m_removed += missing;
212 89 fetchNextBatch();
213 89 }
214
215 1 void ContactBookSync::onBatchFailed(const QStringList &hrefs, const QString &error, bool tooLarge) {
216 1 emit progressed();
217
3/6
✓ 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 → 18 not taken.
1 if (tooLarge && hrefs.size() > 1) {
218 // Split and retry both halves (bounded answers, G5).
219 1 const int half = int(hrefs.size()) / 2;
220
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
1 m_batches.prepend(hrefs.mid(half));
221
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
1 m_batches.prepend(hrefs.mid(0, half));
222 1 fetchNextBatch();
223 1 return;
224 }
225 ✗ if (tooLarge) {
226 ✗ ++m_skipped;
227 ✗ qCWarning(lcContactBookSync) << "A card of book" << m_bookId << "is larger than the response limit; skipped";
228 ✗ fetchNextBatch();
229 ✗ return;
230 }
231 ✗ fail(error);
232 }
233
234 47 void ContactBookSync::applyRemovals() {
235
4/4
✓ Branch 2 → 3 taken 47 times.
✓ Branch 3 → 4 taken 47 times.
✓ Branch 32 → 5 taken 7 times.
✓ Branch 32 → 33 taken 47 times.
54 for (const QString &href : m_removed) {
236
1/1
✓ Branch 6 → 7 taken 7 times.
7 const QString uid = m_store->uidForHref(m_bookId, href);
237
8/11
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 12 taken 2 times.
✓ Branch 9 → 10 taken 5 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 5 times.
✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 16 taken 2 times.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 5 times.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
7 if (uid.isEmpty() || m_store->outboxEntry(m_bookId, uid))
238 2 continue; // a queued local change keeps it; the outbox meets the 404
239
2/3
✓ Branch 18 → 19 taken 5 times.
✓ Branch 19 → 20 taken 5 times.
✗ Branch 19 → 24 not taken.
5 if (m_store->removeCard(m_bookId, uid))
240
2/2
✓ Branch 20 → 21 taken 5 times.
✓ Branch 21 → 22 taken 5 times.
5 m_changedKeys << ContactQueries::cardKey(m_bookId, uid);
241 7 }
242 47 }
243
244 73 void ContactBookSync::complete() {
245
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 73 times.
73 if (m_done)
246 ✗ return;
247 73 m_done = true;
248
1/1
✓ Branch 4 → 5 taken 73 times.
73 AddressBookInfo book = m_store->book(m_bookId).value_or(m_book);
249 73 const bool firstFetch = !book.fetched;
250
2/2
✓ Branch 8 → 9 taken 45 times.
✓ Branch 8 → 10 taken 28 times.
73 if (!m_newToken.isEmpty())
251 45 book.syncToken = m_newToken;
252
5/6
✓ Branch 11 → 12 taken 25 times.
✓ Branch 11 → 14 taken 3 times.
✓ Branch 12 → 13 taken 25 times.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 25 times.
✓ Branch 15 → 17 taken 3 times.
28 else if (!m_server.syncToken.isEmpty() && book.fetched)
253 25 book.syncToken = m_server.syncToken;
254 73 book.ctag = m_server.ctag;
255 73 book.fetched = true;
256
1/1
✓ Branch 18 → 19 taken 73 times.
73 book.lastSync = QDateTime::currentDateTimeUtc();
257 73 book.lastError.clear();
258 73 book.skippedCards = m_skipped;
259
1/1
✓ Branch 22 → 23 taken 73 times.
73 m_store->updateBook(book);
260 73 bool bulk = m_changedKeys.size() > kBulkThreshold;
261
5/7
✓ Branch 24 → 25 taken 29 times.
✓ Branch 24 → 28 taken 44 times.
✓ Branch 25 → 26 taken 29 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 29 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 73 times.
73 if (firstFetch && m_store->removeLegacyCards(m_bookId) > 0)
262 ✗ bulk = true;
263
10/10
✓ Branch 31 → 32 taken 73 times.
✓ Branch 35 → 36 taken 73 times.
✓ Branch 36 → 37 taken 73 times.
✓ Branch 37 → 38 taken 73 times.
✓ Branch 38 → 39 taken 73 times.
✓ Branch 39 → 40 taken 73 times.
✓ Branch 40 → 41 taken 73 times.
✓ Branch 41 → 42 taken 73 times.
✓ Branch 50 → 33 taken 73 times.
✓ Branch 50 → 51 taken 73 times.
146 qCInfo(lcContactBookSync) << "Book" << m_bookId << "synced: requests" << m_requests << "cards" << m_downloaded
264
4/4
✓ Branch 42 → 43 taken 73 times.
✓ Branch 44 → 45 taken 73 times.
✓ Branch 45 → 46 taken 73 times.
✓ Branch 46 → 47 taken 73 times.
73 << "changed" << m_changedKeys.size() << "skipped" << m_skipped;
265 // 5. S97.5.7: same-origin URI photos not fetched yet (none: no request).
266
3/6
✓ Branch 51 → 52 taken 73 times.
✓ Branch 52 → 53 taken 73 times.
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 55 taken 73 times.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
73 auto *photos = new ContactPhotoFetch(m_store, m_client, m_bookId, this);
267
1/1
✓ Branch 55 → 56 taken 73 times.
73 connect(photos, &ContactPhotoFetch::progressed, this, &ContactBookSync::progressed);
268
1/1
✓ Branch 57 → 58 taken 73 times.
73 connect(photos, &ContactPhotoFetch::finished, this, [this, photos, bulk](const QStringList &keys) {
269 73 m_requests += photos->requests();
270
2/2
✓ Branch 11 → 5 taken 2 times.
✓ Branch 11 → 12 taken 73 times.
75 for (const QString &key : keys)
271
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2 times.
2 if (!m_changedKeys.contains(key))
272 ✗ m_changedKeys.append(key);
273 73 emit finished(true, m_changedKeys, bulk);
274 73 });
275
1/1
✓ Branch 59 → 60 taken 73 times.
73 photos->start();
276 73 }
277
278 9 void ContactBookSync::fail(const QString &error) {
279
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 9 times.
9 if (m_done)
280 ✗ return;
281 9 m_done = true;
282
1/1
✓ Branch 4 → 5 taken 9 times.
9 AddressBookInfo book = m_store->book(m_bookId).value_or(m_book);
283
1/1
✓ Branch 7 → 8 taken 9 times.
9 book.lastError = error.left(500);
284
1/1
✓ Branch 10 → 11 taken 9 times.
9 m_store->updateBook(book);
285
8/8
✓ Branch 11 → 12 taken 9 times.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 16 → 17 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
✓ Branch 18 → 19 taken 9 times.
✓ Branch 19 → 20 taken 9 times.
✓ Branch 23 → 13 taken 9 times.
✓ Branch 23 → 24 taken 9 times.
18 qCWarning(lcContactBookSync) << "Book" << m_bookId << "sync failed:" << error;
286
1/1
✓ Branch 25 → 26 taken 9 times.
9 emit finished(false, m_changedKeys, m_changedKeys.size() > kBulkThreshold);
287 9 }
288