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 |