app/core/ContactOutbox.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ContactOutbox.h" | ||
| 2 | |||
| 3 | #include "data/ContactQueries.h" | ||
| 4 | #include "data/ContactStore.h" | ||
| 5 | #include "data/VCardCodec.h" | ||
| 6 | #include "data/VCardMerge.h" | ||
| 7 | #include "service/CardDavClient.h" | ||
| 8 | #include "util/SecureUtil.h" | ||
| 9 | |||
| 10 | #include <QDateTime> | ||
| 11 | #include <QLoggingCategory> | ||
| 12 | #include <QPointer> | ||
| 13 | #include <QUuid> | ||
| 14 | |||
| 15 |
4/7✓ Branch 2 → 3 taken 11 times.
✓ Branch 2 → 8 taken 469 times.
✓ Branch 4 → 5 taken 11 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 11 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
480 | Q_LOGGING_CATEGORY(lcContactOutbox, "mailjd.contacts.outbox") |
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | // Handles exactly one response of `request` on `client`, then disconnects. | ||
| 20 | template <typename Handler> | ||
| 21 | 147 | void onResponse(CardDavClient *client, quint64 request, QObject *context, Handler handler) { | |
| 22 |
5/6void (anonymous namespace)::onResponse<ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#2}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#2}):
✗ Branch 2 → 3 not taken.
void (anonymous namespace)::onResponse<ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}):
✓ Branch 2 → 3 taken 1 time.
void (anonymous namespace)::onResponse<ContactOutbox::onDeleteFinished(ContactOutboxEntry const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::onDeleteFinished(ContactOutboxEntry const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}):
✓ Branch 2 → 3 taken 8 times.
void (anonymous namespace)::onResponse<ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}):
✓ Branch 2 → 3 taken 90 times.
void (anonymous namespace)::onResponse<ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#1}):
✓ Branch 2 → 3 taken 17 times.
void (anonymous namespace)::onResponse<ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}::operator()(DavResponse const&) const::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}::operator()(DavResponse const&) const::{lambda(DavResponse const&)#1}):
✓ Branch 2 → 3 taken 31 times.
|
147 | auto connection = std::make_shared<QMetaObject::Connection>(); |
| 23 | 147 | *connection = QObject::connect(client, &CardDavClient::resourceResponse, context, | |
| 24 |
5/18void (anonymous namespace)::onResponse<ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#2}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#2}):
✗ Branch 5 → 6 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::onPutFinished(ContactOutboxEntry const&, QString const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}):
✓ Branch 5 → 6 taken 1 time.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<ContactOutbox::onDeleteFinished(ContactOutboxEntry const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::onDeleteFinished(ContactOutboxEntry const&, DavResponse const&, CardDavClient*)::{lambda(DavResponse const&)#1}):
✓ Branch 5 → 6 taken 8 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}):
✓ Branch 5 → 6 taken 90 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#1}):
✓ Branch 5 → 6 taken 17 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}::operator()(DavResponse const&) const::{lambda(DavResponse const&)#1}>(CardDavClient*, unsigned long long, QObject*, ContactOutbox::push(ContactOutboxEntry const&, CardDavClient*)::{lambda(DavResponse const&)#2}::operator()(DavResponse const&) const::{lambda(DavResponse const&)#1}):
✓ Branch 5 → 6 taken 31 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
|
437 | [connection, request, handler](const DavResponse &response) { |
| 25 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 143 times.
|
143 | if (response.request != request) |
| 26 | ✗ | return; | |
| 27 | 143 | QObject::disconnect(*connection); | |
| 28 | 143 | handler(response); | |
| 29 | }); | ||
| 30 | 147 | } | |
| 31 | |||
| 32 | // S106-160 (INV-12): 403 (a read-only collection) and 507 (no storage) | ||
| 33 | // are permanent — F-19's bounded retries end in the failed state; they | ||
| 34 | // used to back off forever as transient errors. | ||
| 35 | 16 | bool retryableStatus(int status) { | |
| 36 |
6/10✓ Branch 2 → 3 taken 16 times.
✗ Branch 2 → 8 not taken.
✓ Branch 3 → 4 taken 16 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 16 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 9 taken 10 times.
|
22 | return status == 0 || status == 401 || status == 408 || status == 429 || |
| 37 |
2/2✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 3 times.
|
22 | (status >= 500 && status != 507); |
| 38 | } | ||
| 39 | |||
| 40 | // Technical text for the log and "Details" only (G16). | ||
| 41 | 19 | QString responseError(const DavResponse &response) { | |
| 42 |
2/2✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 16 times.
|
19 | if (!response.error.isEmpty()) |
| 43 | 3 | return response.error; | |
| 44 |
3/3✓ Branch 9 → 10 taken 16 times.
✓ Branch 10 → 11 taken 16 times.
✓ Branch 12 → 13 taken 16 times.
|
48 | return QStringLiteral("%1 answered HTTP %2").arg(QString::fromLatin1(response.method)).arg(response.status); |
| 45 | } | ||
| 46 | |||
| 47 | // The card with another UID (conflict "keep both"). | ||
| 48 | 2 | QString withUid(const QString &raw, const QString &uid) { | |
| 49 | 2 | ICal::Component card; | |
| 50 | 2 | QString error; | |
| 51 | 2 | int skipped = 0; | |
| 52 | // S106-152: a card whose parse dropped lines is not copied — the copy | ||
| 53 | // would be uploaded without them. | ||
| 54 |
4/7✓ Branch 2 → 3 taken 2 times.
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2 times.
|
2 | if (!VCardCodec::parseTree(raw, &card, &error, &skipped) || skipped > 0) |
| 55 | ✗ | return {}; | |
| 56 |
2/2✓ Branch 9 → 10 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
|
4 | card.setPropertyValue(QStringLiteral("UID"), ICal::escapeText(uid)); |
| 57 |
1/1✓ Branch 17 → 18 taken 2 times.
|
2 | return ICal::serialize(card); |
| 58 | 2 | } | |
| 59 | |||
| 60 | } // namespace | ||
| 61 | |||
| 62 |
1/1✓ Branch 9 → 10 taken 956 times.
|
956 | ContactOutbox::ContactOutbox(Deps deps, QObject *parent) : QObject(parent), m_deps(std::move(deps)) { |
| 63 |
1/2✗ Branch 11 → 12 not taken.
✓ Branch 11 → 14 taken 956 times.
|
956 | if (!m_deps.now) |
| 64 | ✗ | m_deps.now = [] { return QDateTime::currentSecsSinceEpoch(); }; | |
| 65 |
1/1✓ Branch 14 → 15 taken 956 times.
|
956 | m_retryTimer.setSingleShot(true); |
| 66 |
1/1✓ Branch 15 → 16 taken 956 times.
|
956 | connect(&m_retryTimer, &QTimer::timeout, this, [this] { flush(false); }); |
| 67 | 956 | } | |
| 68 | |||
| 69 | 1561 | QString ContactOutbox::key(qint64 bookId, const QString &uid) { return ContactQueries::cardKey(bookId, uid); } | |
| 70 | |||
| 71 | // F-29 (SPRINT-104): permanent rejections stop after this many attempts. | ||
| 72 | constexpr int kMaxPermanentAttempts = 3; | ||
| 73 | |||
| 74 | 4 | qint64 ContactOutbox::backoffSeconds(int attempts) { | |
| 75 | static const qint64 steps[] = {30, 60, 120, 300, 600, 1800}; | ||
| 76 |
1/1✓ Branch 4 → 5 taken 4 times.
|
4 | const int index = qBound(0, attempts - 1, int(std::size(steps)) - 1); |
| 77 | 4 | return steps[index]; | |
| 78 | } | ||
| 79 | |||
| 80 | 456 | void ContactOutbox::changed(const QStringList &keys) { | |
| 81 |
2/2✓ Branch 2 → 3 taken 35 times.
✓ Branch 2 → 5 taken 421 times.
|
456 | if (m_batchDepth > 0) { |
| 82 | 35 | m_batchKeys += keys; | |
| 83 | 35 | return; | |
| 84 | } | ||
| 85 | 421 | emit cardsChanged(keys); | |
| 86 | } | ||
| 87 | |||
| 88 | 20 | void ContactOutbox::endBatch() { | |
| 89 |
5/6✓ Branch 2 → 3 taken 20 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 19 times.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 6 → 7 taken 19 times.
✓ Branch 6 → 12 taken 1 time.
|
20 | if (m_batchDepth > 0 && --m_batchDepth == 0) { |
| 90 |
2/2✓ Branch 8 → 9 taken 18 times.
✓ Branch 8 → 10 taken 1 time.
|
19 | if (!m_batchKeys.isEmpty()) |
| 91 | 18 | emit cardsChanged(m_batchKeys); | |
| 92 | 19 | m_batchKeys.clear(); | |
| 93 | 19 | flush(false); | |
| 94 | } | ||
| 95 | 20 | } | |
| 96 | |||
| 97 | 255 | std::optional<AddressBookInfo> ContactOutbox::writableBook(qint64 bookId, QString *error) const { | |
| 98 |
1/1✓ Branch 2 → 3 taken 255 times.
|
255 | const auto book = m_deps.store->book(bookId); |
| 99 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 11 taken 254 times.
|
255 | if (!book) { |
| 100 |
1/2✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 10 not taken.
|
1 | if (error) |
| 101 |
1/1✓ Branch 6 → 7 taken 1 time.
|
1 | *error = tr("The address book is no longer available."); |
| 102 | 1 | return std::nullopt; | |
| 103 | } | ||
| 104 |
2/2✓ Branch 12 → 13 taken 6 times.
✓ Branch 12 → 19 taken 248 times.
|
254 | if (book->readOnly) { |
| 105 |
1/2✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 18 not taken.
|
6 | if (error) |
| 106 |
1/1✓ Branch 14 → 15 taken 6 times.
|
6 | *error = tr("This address book is read-only."); |
| 107 | 6 | return std::nullopt; | |
| 108 | } | ||
| 109 |
1/1✓ Branch 19 → 20 taken 248 times.
|
248 | return book; |
| 110 | 255 | } | |
| 111 | |||
| 112 | // ─── local changes ───────────────────────────────────────── | ||
| 113 | |||
| 114 | 187 | QString ContactOutbox::saveCard(qint64 bookId, const QString &uid, const ContactCard &edited, QString *error) { | |
| 115 |
1/1✓ Branch 2 → 3 taken 187 times.
|
187 | const auto book = writableBook(bookId, error); |
| 116 |
1/1✓ Branch 3 → 4 taken 187 times.
|
187 | const auto stored = m_deps.store->card(bookId, uid); |
| 117 |
6/6✓ Branch 5 → 6 taken 183 times.
✓ Branch 5 → 8 taken 4 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 182 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 22 taken 182 times.
|
187 | if (!book || !stored) { |
| 118 |
5/6✓ Branch 11 → 12 taken 5 times.
✗ Branch 11 → 15 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 4 times.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 21 taken 4 times.
|
5 | if (error && book) |
| 119 |
1/1✓ Branch 17 → 18 taken 1 time.
|
1 | *error = tr("The contact no longer exists."); |
| 120 | 5 | return {}; | |
| 121 | } | ||
| 122 |
2/2✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 30 taken 181 times.
|
182 | if (!stored->writable) { |
| 123 |
1/2✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 29 not taken.
|
1 | if (error) |
| 124 |
1/1✓ Branch 25 → 26 taken 1 time.
|
1 | *error = tr("This contact cannot be changed in MailJD. Edit it where it was created."); |
| 125 | 1 | return {}; | |
| 126 | } | ||
| 127 | 181 | QString codecError; | |
| 128 |
1/1✓ Branch 32 → 33 taken 181 times.
|
181 | const QString raw = VCardCodec::patch(stored->raw, edited, &codecError); |
| 129 |
1/2✗ Branch 35 → 36 not taken.
✓ Branch 35 → 53 taken 181 times.
|
181 | if (raw.isEmpty()) { |
| 130 | ✗ | qCWarning(lcContactOutbox) << "Patch refused:" << codecError; | |
| 131 | ✗ | if (error) | |
| 132 | ✗ | *error = tr("This contact cannot be changed in MailJD. Edit it where it was created."); | |
| 133 | ✗ | return {}; | |
| 134 | } | ||
| 135 |
2/2✓ Branch 55 → 56 taken 16 times.
✓ Branch 55 → 58 taken 165 times.
|
181 | if (raw == stored->raw) |
| 136 |
1/1✓ Branch 56 → 57 taken 16 times.
|
16 | return key(bookId, uid); |
| 137 |
3/4✓ Branch 58 → 59 taken 165 times.
✓ Branch 59 → 60 taken 165 times.
✗ Branch 59 → 61 not taken.
✓ Branch 60 → 62 taken 165 times.
|
165 | return replaceRaw(bookId, uid, raw) ? key(bookId, uid) : QString(); |
| 138 | 187 | } | |
| 139 | |||
| 140 | 221 | bool ContactOutbox::replaceRaw(qint64 bookId, const QString &uid, const QString &raw) { | |
| 141 |
1/1✓ Branch 2 → 3 taken 221 times.
|
221 | auto stored = m_deps.store->card(bookId, uid); |
| 142 |
1/1✓ Branch 3 → 4 taken 221 times.
|
221 | const auto book = m_deps.store->book(bookId); |
| 143 |
5/6✓ Branch 5 → 6 taken 220 times.
✓ Branch 5 → 8 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 220 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 220 times.
|
221 | if (!stored || !book) |
| 144 | 1 | return false; | |
| 145 | 220 | stored->raw = raw; | |
| 146 | 220 | const bool remote = !book->isLocal(); | |
| 147 |
2/2✓ Branch 16 → 17 taken 218 times.
✓ Branch 16 → 25 taken 2 times.
|
220 | if (remote) |
| 148 | 218 | stored->syncState = QStringLiteral("pending"); | |
| 149 |
3/3✓ Branch 26 → 27 taken 220 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 219 times.
|
220 | if (!m_deps.store->putCard(*stored)) |
| 150 | 1 | return false; | |
| 151 |
2/2✓ Branch 29 → 30 taken 217 times.
✓ Branch 29 → 50 taken 2 times.
|
219 | if (remote) { |
| 152 |
1/1✓ Branch 35 → 36 taken 217 times.
|
434 | m_deps.store->enqueueOutbox(bookId, uid, QStringLiteral("put"), stored->href, stored->etag); |
| 153 |
6/6✓ Branch 38 → 39 taken 217 times.
✓ Branch 42 → 43 taken 217 times.
✓ Branch 43 → 44 taken 217 times.
✓ Branch 44 → 45 taken 217 times.
✓ Branch 48 → 40 taken 217 times.
✓ Branch 48 → 49 taken 217 times.
|
434 | qCInfo(lcContactOutbox) << "Queued put of a card in book" << bookId; |
| 154 | } | ||
| 155 |
3/5✓ Branch 52 → 53 taken 219 times.
✓ Branch 55 → 56 taken 219 times.
✓ Branch 55 → 57 taken 219 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
|
438 | changed({key(bookId, uid)}); |
| 156 |
2/2✓ Branch 57 → 58 taken 217 times.
✓ Branch 57 → 59 taken 2 times.
|
219 | if (remote) |
| 157 |
1/1✓ Branch 58 → 59 taken 217 times.
|
217 | flush(false); |
| 158 | 219 | return true; | |
| 159 | 440 | } | |
| 160 | |||
| 161 | 10 | QString ContactOutbox::importRaw(qint64 bookId, const QString &raw, QString *error) { | |
| 162 |
1/1✓ Branch 2 → 3 taken 10 times.
|
10 | ContactCard card; |
| 163 | 10 | bool writable = false; | |
| 164 |
3/3✓ Branch 3 → 4 taken 10 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 9 times.
|
10 | if (!VCardCodec::parse(raw, &card, error, &writable)) |
| 165 | 1 | return {}; | |
| 166 |
6/8✓ Branch 7 → 8 taken 6 times.
✓ Branch 7 → 12 taken 3 times.
✓ Branch 10 → 11 taken 6 times.
✗ Branch 10 → 12 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 6 times.
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 17 taken 6 times.
|
9 | if (card.uid.isEmpty() || card.version == QLatin1String("2.1") || !writable) |
| 167 |
1/1✓ Branch 15 → 16 taken 3 times.
|
3 | return createCard(bookId, card, error); |
| 168 |
1/1✓ Branch 17 → 18 taken 6 times.
|
6 | const auto book = writableBook(bookId, error); |
| 169 |
2/2✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 21 taken 5 times.
|
6 | if (!book) |
| 170 | 1 | return {}; | |
| 171 |
3/3✓ Branch 21 → 22 taken 5 times.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 31 taken 4 times.
|
5 | if (m_deps.store->card(bookId, card.uid)) { |
| 172 |
1/2✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 30 not taken.
|
1 | if (error) |
| 173 |
1/1✓ Branch 26 → 27 taken 1 time.
|
1 | *error = tr("This contact is already in that address book."); |
| 174 | 1 | return {}; | |
| 175 | } | ||
| 176 | 4 | StoredCard stored; | |
| 177 | 4 | stored.bookId = bookId; | |
| 178 | 4 | stored.uid = card.uid; | |
| 179 |
2/2✓ Branch 35 → 36 taken 4 times.
✓ Branch 36 → 37 taken 4 times.
|
4 | stored.raw = raw.trimmed() + QStringLiteral("\r\n"); |
| 180 |
2/2✓ Branch 44 → 45 taken 3 times.
✓ Branch 44 → 76 taken 1 time.
|
4 | if (!book->isLocal()) { |
| 181 | 3 | QString path = book->path; | |
| 182 |
3/3✓ Branch 49 → 50 taken 3 times.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 55 taken 2 times.
|
3 | if (!path.endsWith(QLatin1Char('/'))) |
| 183 |
1/1✓ Branch 53 → 54 taken 1 time.
|
1 | path += QLatin1Char('/'); |
| 184 |
4/4✓ Branch 58 → 59 taken 3 times.
✓ Branch 59 → 60 taken 3 times.
✓ Branch 60 → 61 taken 3 times.
✓ Branch 61 → 62 taken 3 times.
|
3 | stored.href = path + QUuid::createUuid().toString(QUuid::WithoutBraces) + QStringLiteral(".vcf"); |
| 185 | 3 | stored.syncState = QStringLiteral("pending"); | |
| 186 | 3 | } | |
| 187 |
2/3✓ Branch 76 → 77 taken 4 times.
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 79 taken 4 times.
|
4 | if (!m_deps.store->putCard(stored)) |
| 188 | ✗ | return {}; | |
| 189 |
2/2✓ Branch 81 → 82 taken 3 times.
✓ Branch 81 → 91 taken 1 time.
|
4 | if (!book->isLocal()) |
| 190 |
1/1✓ Branch 86 → 87 taken 3 times.
|
6 | m_deps.store->enqueueOutbox(bookId, card.uid, QStringLiteral("put"), stored.href, QString()); |
| 191 |
3/5✓ Branch 93 → 94 taken 4 times.
✓ Branch 96 → 97 taken 4 times.
✓ Branch 96 → 98 taken 4 times.
✗ Branch 165 → 166 not taken.
✗ Branch 165 → 167 not taken.
|
8 | changed({key(bookId, card.uid)}); |
| 192 |
6/6✓ Branch 98 → 99 taken 4 times.
✓ Branch 102 → 103 taken 4 times.
✓ Branch 103 → 104 taken 4 times.
✓ Branch 104 → 105 taken 4 times.
✓ Branch 108 → 100 taken 4 times.
✓ Branch 108 → 109 taken 4 times.
|
8 | qCInfo(lcContactOutbox) << "Imported a card into book" << bookId; |
| 193 |
2/2✓ Branch 111 → 112 taken 3 times.
✓ Branch 111 → 113 taken 1 time.
|
4 | if (!book->isLocal()) |
| 194 |
1/1✓ Branch 112 → 113 taken 3 times.
|
3 | flush(false); |
| 195 |
1/1✓ Branch 113 → 114 taken 4 times.
|
4 | return key(bookId, card.uid); |
| 196 | 14 | } | |
| 197 | |||
| 198 | 22 | QString ContactOutbox::createCard(qint64 bookId, const ContactCard &input, QString *error) { | |
| 199 |
1/1✓ Branch 2 → 3 taken 22 times.
|
22 | const auto book = writableBook(bookId, error); |
| 200 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 21 times.
|
22 | if (!book) |
| 201 | 1 | return {}; | |
| 202 | 21 | ContactCard card = input; | |
| 203 |
2/2✓ Branch 8 → 9 taken 18 times.
✓ Branch 8 → 14 taken 3 times.
|
21 | if (card.uid.isEmpty()) |
| 204 |
2/2✓ Branch 9 → 10 taken 18 times.
✓ Branch 10 → 11 taken 18 times.
|
18 | card.uid = QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 205 |
3/3✓ Branch 14 → 15 taken 21 times.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 24 taken 20 times.
|
21 | if (m_deps.store->card(bookId, card.uid)) { |
| 206 |
1/2✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 23 not taken.
|
1 | if (error) |
| 207 |
1/1✓ Branch 19 → 20 taken 1 time.
|
1 | *error = tr("This contact is already in that address book."); |
| 208 | 1 | return {}; | |
| 209 | } | ||
| 210 | // D-3: 3.0 unless the book accepts only vCard 4.0. | ||
| 211 |
3/5✓ Branch 29 → 30 taken 20 times.
✓ Branch 32 → 33 taken 20 times.
✓ Branch 32 → 34 taken 20 times.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
|
40 | const bool only40 = book->supportedVersions == QStringList{QStringLiteral("4.0")}; |
| 212 | 20 | StoredCard stored; | |
| 213 | 20 | stored.bookId = bookId; | |
| 214 | 20 | stored.uid = card.uid; | |
| 215 |
4/11✗ Branch 37 → 38 not taken.
✓ Branch 37 → 41 taken 20 times.
✓ Branch 44 → 45 taken 20 times.
✓ Branch 48 → 49 taken 20 times.
✗ Branch 48 → 50 not taken.
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 52 taken 20 times.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 146 not taken.
✗ Branch 148 → 149 not taken.
✗ Branch 148 → 150 not taken.
|
40 | stored.raw = VCardCodec::create(card, only40 ? QStringLiteral("4.0") : QStringLiteral("3.0")); |
| 216 |
2/2✓ Branch 55 → 56 taken 16 times.
✓ Branch 55 → 87 taken 4 times.
|
20 | if (!book->isLocal()) { |
| 217 | // A fresh UUID names the resource, never the (possibly foreign) UID. | ||
| 218 | 16 | QString path = book->path; | |
| 219 |
3/3✓ Branch 60 → 61 taken 16 times.
✓ Branch 61 → 62 taken 1 time.
✓ Branch 61 → 66 taken 15 times.
|
16 | if (!path.endsWith(QLatin1Char('/'))) |
| 220 |
1/1✓ Branch 64 → 65 taken 1 time.
|
1 | path += QLatin1Char('/'); |
| 221 |
4/4✓ Branch 69 → 70 taken 16 times.
✓ Branch 70 → 71 taken 16 times.
✓ Branch 71 → 72 taken 16 times.
✓ Branch 72 → 73 taken 16 times.
|
16 | stored.href = path + QUuid::createUuid().toString(QUuid::WithoutBraces) + QStringLiteral(".vcf"); |
| 222 | 16 | stored.syncState = QStringLiteral("pending"); | |
| 223 | 16 | } | |
| 224 |
4/7✓ Branch 88 → 89 taken 20 times.
✗ Branch 88 → 91 not taken.
✓ Branch 89 → 90 taken 20 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 20 times.
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 20 times.
|
20 | if (stored.raw.isEmpty() || !m_deps.store->putCard(stored)) |
| 225 | ✗ | return {}; | |
| 226 |
2/2✓ Branch 97 → 98 taken 16 times.
✓ Branch 97 → 107 taken 4 times.
|
20 | if (!book->isLocal()) |
| 227 |
1/1✓ Branch 102 → 103 taken 16 times.
|
32 | m_deps.store->enqueueOutbox(bookId, card.uid, QStringLiteral("put"), stored.href, QString()); |
| 228 |
3/5✓ Branch 109 → 110 taken 20 times.
✓ Branch 112 → 113 taken 20 times.
✓ Branch 112 → 114 taken 20 times.
✗ Branch 190 → 191 not taken.
✗ Branch 190 → 192 not taken.
|
40 | changed({key(bookId, card.uid)}); |
| 229 |
1/1✓ Branch 114 → 115 taken 20 times.
|
20 | flush(false); |
| 230 |
1/1✓ Branch 115 → 116 taken 20 times.
|
20 | return key(bookId, card.uid); |
| 231 | 82 | } | |
| 232 | |||
| 233 | 53 | bool ContactOutbox::removeCard(qint64 bookId, const QString &uid, StoredCard *removed) { | |
| 234 |
1/1✓ Branch 2 → 3 taken 53 times.
|
53 | const auto book = m_deps.store->book(bookId); |
| 235 |
1/1✓ Branch 3 → 4 taken 53 times.
|
53 | const auto stored = m_deps.store->card(bookId, uid); |
| 236 |
7/8✓ Branch 5 → 6 taken 53 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 51 times.
✓ Branch 7 → 10 taken 2 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 50 times.
✓ Branch 12 → 13 taken 3 times.
✓ Branch 12 → 14 taken 50 times.
|
53 | if (!book || !stored || book->readOnly) |
| 237 | 3 | return false; | |
| 238 |
2/2✓ Branch 14 → 15 taken 25 times.
✓ Branch 14 → 17 taken 25 times.
|
50 | if (removed) |
| 239 | 25 | *removed = *stored; | |
| 240 |
1/1✓ Branch 17 → 18 taken 50 times.
|
50 | const auto existing = m_deps.store->outboxEntry(bookId, uid); |
| 241 |
3/3✓ Branch 18 → 19 taken 50 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 48 times.
|
50 | if (!m_deps.store->removeCard(bookId, uid)) |
| 242 | 2 | return false; | |
| 243 |
2/2✓ Branch 22 → 23 taken 10 times.
✓ Branch 22 → 25 taken 38 times.
|
48 | const QString baseEtag = existing ? existing->baseEtag : stored->etag; |
| 244 |
5/6✓ Branch 30 → 31 taken 44 times.
✓ Branch 30 → 35 taken 4 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 44 times.
✓ Branch 37 → 38 taken 44 times.
✓ Branch 37 → 74 taken 4 times.
|
48 | if (book->isLocal() || stored->syncState == QLatin1String("legacy")) { |
| 245 | // Nothing on a server to remove. | ||
| 246 |
2/2✓ Branch 39 → 40 taken 25 times.
✓ Branch 39 → 41 taken 19 times.
|
44 | } else if (baseEtag.isEmpty()) { |
| 247 |
1/1✓ Branch 40 → 74 taken 25 times.
|
25 | m_deps.store->removeOutbox(bookId, uid); // never reached the server |
| 248 | } else { | ||
| 249 |
2/3✗ Branch 42 → 43 not taken.
✓ Branch 42 → 45 taken 19 times.
✓ Branch 50 → 51 taken 19 times.
|
38 | m_deps.store->enqueueOutbox(bookId, uid, QStringLiteral("delete"), existing ? existing->href : stored->href, |
| 250 | baseEtag); | ||
| 251 | // Sprint 107: the card's last server version stays with its delete — | ||
| 252 | // the resolver's base when the server changed it (D-12/D-22). | ||
| 253 |
2/3✓ Branch 53 → 54 taken 19 times.
✓ Branch 55 → 56 taken 19 times.
✗ Branch 55 → 61 not taken.
|
19 | if (auto entry = m_deps.store->outboxEntry(bookId, uid)) { |
| 254 | 19 | entry->baseRaw = stored->baseRaw; | |
| 255 |
1/1✓ Branch 60 → 61 taken 19 times.
|
19 | m_deps.store->updateOutbox(*entry); |
| 256 | 19 | } | |
| 257 |
6/6✓ Branch 62 → 63 taken 19 times.
✓ Branch 66 → 67 taken 19 times.
✓ Branch 67 → 68 taken 19 times.
✓ Branch 68 → 69 taken 19 times.
✓ Branch 72 → 64 taken 19 times.
✓ Branch 72 → 73 taken 19 times.
|
38 | qCInfo(lcContactOutbox) << "Queued delete of a card in book" << bookId; |
| 258 | } | ||
| 259 |
3/5✓ Branch 76 → 77 taken 48 times.
✓ Branch 79 → 80 taken 48 times.
✓ Branch 79 → 81 taken 48 times.
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 107 not taken.
|
96 | changed({key(bookId, uid)}); |
| 260 |
1/1✓ Branch 81 → 82 taken 48 times.
|
48 | flush(false); |
| 261 | 48 | return true; | |
| 262 | 101 | } | |
| 263 | |||
| 264 | 20 | bool ContactOutbox::restoreCard(const StoredCard &input) { | |
| 265 |
1/1✓ Branch 2 → 3 taken 20 times.
|
20 | const auto book = m_deps.store->book(input.bookId); |
| 266 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 19 times.
|
20 | if (!book) |
| 267 | 1 | return false; | |
| 268 | 19 | StoredCard card = input; | |
| 269 | 19 | card.id = 0; | |
| 270 |
1/1✓ Branch 7 → 8 taken 19 times.
|
19 | const auto entry = m_deps.store->outboxEntry(card.bookId, card.uid); |
| 271 |
2/2✓ Branch 10 → 11 taken 17 times.
✓ Branch 10 → 18 taken 2 times.
|
19 | if (!book->isLocal()) |
| 272 | 17 | card.syncState = QStringLiteral("pending"); | |
| 273 |
3/3✓ Branch 18 → 19 taken 19 times.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 21 taken 18 times.
|
19 | if (!m_deps.store->putCard(card)) |
| 274 | 1 | return false; | |
| 275 |
2/2✓ Branch 23 → 24 taken 16 times.
✓ Branch 23 → 53 taken 2 times.
|
18 | if (!book->isLocal()) { |
| 276 | // A queued delete turns back into a put of the same base; a delete that | ||
| 277 | // already reached the server makes the restore a create. | ||
| 278 |
2/2✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 28 taken 14 times.
|
16 | const QString baseEtag = entry ? entry->baseEtag : QString(); |
| 279 |
2/2✓ Branch 30 → 31 taken 2 times.
✓ Branch 30 → 33 taken 14 times.
|
16 | const QString href = entry ? entry->href : card.href; |
| 280 |
1/1✓ Branch 38 → 39 taken 16 times.
|
32 | m_deps.store->enqueueOutbox(card.bookId, card.uid, QStringLiteral("put"), href, baseEtag); |
| 281 |
6/6✓ Branch 42 → 43 taken 14 times.
✓ Branch 42 → 46 taken 2 times.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 46 taken 13 times.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 47 → 50 taken 15 times.
|
16 | if (!entry && !card.etag.isEmpty()) { |
| 282 | 1 | card.etag.clear(); | |
| 283 |
1/1✓ Branch 49 → 50 taken 1 time.
|
1 | m_deps.store->putCard(card); |
| 284 | } | ||
| 285 | 16 | } | |
| 286 |
3/5✓ Branch 55 → 56 taken 18 times.
✓ Branch 58 → 59 taken 18 times.
✓ Branch 58 → 60 taken 18 times.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
|
36 | changed({key(card.bookId, card.uid)}); |
| 287 |
1/1✓ Branch 60 → 61 taken 18 times.
|
18 | flush(false); |
| 288 | 18 | return true; | |
| 289 | 38 | } | |
| 290 | |||
| 291 | 21 | QString ContactOutbox::moveCard(qint64 fromBookId, const QString &uid, qint64 toBookId, QString *error) { | |
| 292 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 20 times.
|
21 | if (fromBookId == toBookId) |
| 293 |
1/1✓ Branch 3 → 4 taken 1 time.
|
1 | return key(fromBookId, uid); |
| 294 |
1/1✓ Branch 5 → 6 taken 20 times.
|
20 | const auto target = writableBook(toBookId, error); |
| 295 |
1/1✓ Branch 6 → 7 taken 20 times.
|
20 | const auto source = writableBook(fromBookId, error); |
| 296 |
1/1✓ Branch 7 → 8 taken 20 times.
|
20 | const auto stored = m_deps.store->card(fromBookId, uid); |
| 297 |
6/8✓ Branch 9 → 10 taken 19 times.
✓ Branch 9 → 14 taken 1 time.
✓ Branch 11 → 12 taken 19 times.
✗ Branch 11 → 14 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 19 times.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 19 times.
|
20 | if (!target || !source || !stored) |
| 298 | 1 | return {}; | |
| 299 |
3/3✓ Branch 18 → 19 taken 19 times.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 28 taken 18 times.
|
19 | if (m_deps.store->card(toBookId, uid)) { |
| 300 |
1/2✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 27 not taken.
|
1 | if (error) |
| 301 |
1/1✓ Branch 23 → 24 taken 1 time.
|
1 | *error = tr("This contact is already in that address book."); |
| 302 | 1 | return {}; | |
| 303 | } | ||
| 304 | 18 | beginBatch(); | |
| 305 | 18 | StoredCard copy; | |
| 306 | 18 | copy.bookId = toBookId; | |
| 307 | 18 | copy.uid = uid; | |
| 308 | 18 | copy.raw = stored->raw; | |
| 309 | 18 | qint64 createEntry = 0; | |
| 310 |
2/2✓ Branch 34 → 35 taken 15 times.
✓ Branch 34 → 66 taken 3 times.
|
18 | if (!target->isLocal()) { |
| 311 | 15 | QString path = target->path; | |
| 312 |
3/3✓ Branch 39 → 40 taken 15 times.
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 45 taken 14 times.
|
15 | if (!path.endsWith(QLatin1Char('/'))) |
| 313 |
1/1✓ Branch 43 → 44 taken 1 time.
|
1 | path += QLatin1Char('/'); |
| 314 |
4/4✓ Branch 48 → 49 taken 15 times.
✓ Branch 49 → 50 taken 15 times.
✓ Branch 50 → 51 taken 15 times.
✓ Branch 51 → 52 taken 15 times.
|
15 | copy.href = path + QUuid::createUuid().toString(QUuid::WithoutBraces) + QStringLiteral(".vcf"); |
| 315 | 15 | copy.syncState = QStringLiteral("pending"); | |
| 316 | 15 | } | |
| 317 | // S106-180: copy, its upload, the removal and its dependency are one | ||
| 318 | // transaction — a failure after the copy used to leave a duplicate. | ||
| 319 |
1/1✓ Branch 66 → 67 taken 18 times.
|
18 | bool ok = m_deps.store->beginTransaction(); |
| 320 |
3/5✓ Branch 67 → 68 taken 18 times.
✗ Branch 67 → 71 not taken.
✓ Branch 68 → 69 taken 18 times.
✓ Branch 69 → 70 taken 18 times.
✗ Branch 69 → 71 not taken.
|
18 | ok = ok && m_deps.store->putCard(copy) > 0; |
| 321 |
5/6✓ Branch 72 → 73 taken 18 times.
✗ Branch 72 → 77 not taken.
✓ Branch 75 → 76 taken 15 times.
✓ Branch 75 → 77 taken 3 times.
✓ Branch 78 → 79 taken 15 times.
✓ Branch 78 → 88 taken 3 times.
|
18 | if (ok && !target->isLocal()) { |
| 322 |
1/1✓ Branch 83 → 84 taken 15 times.
|
30 | createEntry = m_deps.store->enqueueOutbox(toBookId, uid, QStringLiteral("put"), copy.href, QString()); |
| 323 | 15 | ok = createEntry > 0; | |
| 324 | } | ||
| 325 |
1/2✓ Branch 88 → 89 taken 18 times.
✗ Branch 88 → 104 not taken.
|
18 | if (ok) { |
| 326 |
1/1✓ Branch 89 → 90 taken 18 times.
|
18 | ok = removeCard(fromBookId, uid); |
| 327 | // The source delete waits until the target exists on its server. | ||
| 328 |
9/9✓ Branch 90 → 91 taken 18 times.
✓ Branch 91 → 92 taken 17 times.
✓ Branch 91 → 96 taken 1 time.
✓ Branch 93 → 94 taken 10 times.
✓ Branch 93 → 96 taken 7 times.
✓ Branch 94 → 95 taken 9 times.
✓ Branch 94 → 96 taken 1 time.
✓ Branch 97 → 98 taken 9 times.
✓ Branch 97 → 102 taken 9 times.
|
18 | if (auto entry = m_deps.store->outboxEntry(fromBookId, uid); ok && entry && createEntry > 0) { |
| 329 | 9 | entry->dependsOn = createEntry; | |
| 330 |
1/1✓ Branch 100 → 101 taken 9 times.
|
9 | ok = m_deps.store->updateOutbox(*entry); |
| 331 | 18 | } | |
| 332 | } | ||
| 333 |
2/2✓ Branch 104 → 105 taken 17 times.
✓ Branch 104 → 107 taken 1 time.
|
18 | if (ok) |
| 334 |
1/1✓ Branch 105 → 106 taken 17 times.
|
17 | ok = m_deps.store->commitTransaction(); |
| 335 |
2/2✓ Branch 107 → 108 taken 1 time.
✓ Branch 107 → 120 taken 17 times.
|
18 | if (!ok) { |
| 336 |
1/1✓ Branch 108 → 109 taken 1 time.
|
1 | m_deps.store->rollbackTransaction(); |
| 337 |
5/5✓ Branch 109 → 110 taken 1 time.
✓ Branch 113 → 114 taken 1 time.
✓ Branch 114 → 115 taken 1 time.
✓ Branch 118 → 111 taken 1 time.
✓ Branch 118 → 119 taken 1 time.
|
2 | qCWarning(lcContactOutbox) << "Moving a card between books failed; nothing changed"; |
| 338 | } | ||
| 339 |
3/5✓ Branch 123 → 124 taken 18 times.
✓ Branch 126 → 127 taken 36 times.
✓ Branch 126 → 128 taken 18 times.
✗ Branch 188 → 189 not taken.
✗ Branch 188 → 190 not taken.
|
54 | changed({key(toBookId, uid), key(fromBookId, uid)}); |
| 340 |
1/1✓ Branch 128 → 129 taken 18 times.
|
18 | endBatch(); |
| 341 |
3/4✓ Branch 129 → 130 taken 1 time.
✓ Branch 129 → 135 taken 17 times.
✓ Branch 130 → 131 taken 1 time.
✗ Branch 130 → 135 not taken.
|
18 | if (!ok && error) |
| 342 |
1/1✓ Branch 131 → 132 taken 1 time.
|
1 | *error = tr("The contact could not be moved."); |
| 343 |
3/3✓ Branch 135 → 136 taken 17 times.
✓ Branch 135 → 137 taken 1 time.
✓ Branch 136 → 138 taken 17 times.
|
18 | return ok ? key(toBookId, uid) : QString(); |
| 344 | 38 | } | |
| 345 | |||
| 346 | 18 | bool ContactOutbox::resolveConflict(qint64 bookId, const QString &uid, const QString &choice) { | |
| 347 |
1/1✓ Branch 2 → 3 taken 18 times.
|
18 | auto entry = m_deps.store->outboxEntry(bookId, uid); |
| 348 |
5/6✓ Branch 4 → 5 taken 16 times.
✓ Branch 4 → 9 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 16 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 16 times.
|
18 | if (!entry || entry->state != QLatin1String("conflict")) |
| 349 | 2 | return false; | |
| 350 |
6/6✓ Branch 13 → 14 taken 16 times.
✓ Branch 17 → 18 taken 16 times.
✓ Branch 18 → 19 taken 16 times.
✓ Branch 19 → 20 taken 16 times.
✓ Branch 23 → 15 taken 16 times.
✓ Branch 23 → 24 taken 16 times.
|
32 | qCInfo(lcContactOutbox) << "Resolving a contact conflict with" << choice; |
| 351 | // D-22: a moved card's question is about its old book's server version. | ||
| 352 |
1/2✗ Branch 31 → 32 not taken.
✓ Branch 31 → 61 taken 16 times.
|
32 | if (entry->conflictFields.contains(QStringLiteral("moved"))) { |
| 353 | ✗ | const auto card = m_deps.store->card(bookId, uid); | |
| 354 | ✗ | if (choice == QLatin1String("server")) | |
| 355 | ✗ | return resolveWith(bookId, uid, entry->serverRaw, entry->serverEtag, choice); | |
| 356 | ✗ | if (choice == QLatin1String("mine") && card) | |
| 357 | ✗ | return resolveWith(bookId, uid, card->raw, entry->serverEtag, QStringLiteral("local")); | |
| 358 | ✗ | return false; | |
| 359 | ✗ | } | |
| 360 | // D-21: the losing side is kept; the written bytes are unchanged (G2). | ||
| 361 |
7/7✓ Branch 61 → 62 taken 16 times.
✓ Branch 63 → 64 taken 14 times.
✓ Branch 63 → 68 taken 2 times.
✓ Branch 66 → 67 taken 12 times.
✓ Branch 66 → 68 taken 2 times.
✓ Branch 69 → 70 taken 12 times.
✓ Branch 69 → 89 taken 4 times.
|
16 | if (const auto card = m_deps.store->card(bookId, uid); card && choice != QLatin1String("both")) |
| 362 |
7/9✓ Branch 72 → 73 taken 7 times.
✓ Branch 72 → 75 taken 5 times.
✓ Branch 79 → 80 taken 7 times.
✓ Branch 79 → 83 taken 5 times.
✓ Branch 84 → 85 taken 12 times.
✓ Branch 86 → 87 taken 7 times.
✓ Branch 86 → 88 taken 5 times.
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 276 not taken.
|
31 | recordHistory(bookId, uid, choice == QLatin1String("mine") ? QStringLiteral("local") : choice, |
| 363 | 40 | choice == QLatin1String("mine") ? entry->serverRaw : card->raw); | |
| 364 |
2/2✓ Branch 92 → 93 taken 7 times.
✓ Branch 92 → 103 taken 9 times.
|
16 | if (choice == QLatin1String("server")) { |
| 365 |
2/3✓ Branch 96 → 97 taken 7 times.
✗ Branch 97 → 98 not taken.
✓ Branch 97 → 99 taken 7 times.
|
7 | if (!adoptServerVersion(*entry, entry->serverRaw, entry->serverEtag)) |
| 366 | ✗ | return false; | |
| 367 |
2/2✓ Branch 99 → 100 taken 7 times.
✓ Branch 100 → 101 taken 7 times.
|
7 | emit statusMessage(tr("The version from the server was kept.")); |
| 368 | 7 | return true; | |
| 369 | } | ||
| 370 |
2/2✓ Branch 105 → 106 taken 2 times.
✓ Branch 105 → 190 taken 7 times.
|
9 | if (choice == QLatin1String("both")) { |
| 371 |
1/1✓ Branch 106 → 107 taken 2 times.
|
2 | const auto stored = m_deps.store->card(bookId, uid); |
| 372 |
2/2✓ Branch 107 → 108 taken 2 times.
✓ Branch 108 → 109 taken 2 times.
|
2 | const QString copyUid = QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 373 |
1/2✗ Branch 110 → 111 not taken.
✓ Branch 110 → 112 taken 2 times.
|
2 | if (!stored) |
| 374 | ✗ | return false; | |
| 375 | 2 | StoredCard copy; | |
| 376 | 2 | copy.bookId = bookId; | |
| 377 | 2 | copy.uid = copyUid; | |
| 378 |
1/1✓ Branch 114 → 115 taken 2 times.
|
2 | copy.raw = withUid(stored->raw, copyUid); |
| 379 |
1/1✓ Branch 117 → 118 taken 2 times.
|
2 | const auto book = m_deps.store->book(bookId); |
| 380 |
3/6✓ Branch 119 → 120 taken 2 times.
✗ Branch 119 → 122 not taken.
✗ Branch 121 → 122 not taken.
✓ Branch 121 → 123 taken 2 times.
✗ Branch 124 → 125 not taken.
✓ Branch 124 → 126 taken 2 times.
|
2 | if (copy.raw.isEmpty() || !book) |
| 381 | ✗ | return false; | |
| 382 | 2 | QString path = book->path; | |
| 383 |
2/3✓ Branch 130 → 131 taken 2 times.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 136 taken 2 times.
|
2 | if (!path.endsWith(QLatin1Char('/'))) |
| 384 | ✗ | path += QLatin1Char('/'); | |
| 385 |
4/4✓ Branch 139 → 140 taken 2 times.
✓ Branch 140 → 141 taken 2 times.
✓ Branch 141 → 142 taken 2 times.
✓ Branch 142 → 143 taken 2 times.
|
2 | copy.href = path + QUuid::createUuid().toString(QUuid::WithoutBraces) + QStringLiteral(".vcf"); |
| 386 | 2 | copy.syncState = QStringLiteral("pending"); | |
| 387 |
2/3✓ Branch 155 → 156 taken 2 times.
✗ Branch 156 → 157 not taken.
✓ Branch 156 → 158 taken 2 times.
|
2 | if (!m_deps.store->putCard(copy)) |
| 388 | ✗ | return false; | |
| 389 |
1/1✓ Branch 162 → 163 taken 2 times.
|
4 | m_deps.store->enqueueOutbox(bookId, copyUid, QStringLiteral("put"), copy.href, QString()); |
| 390 |
2/3✓ Branch 169 → 170 taken 2 times.
✗ Branch 170 → 171 not taken.
✓ Branch 170 → 172 taken 2 times.
|
2 | if (!adoptServerVersion(*entry, entry->serverRaw, entry->serverEtag)) |
| 391 | ✗ | return false; | |
| 392 |
3/5✓ Branch 174 → 175 taken 2 times.
✓ Branch 177 → 178 taken 2 times.
✓ Branch 177 → 179 taken 2 times.
✗ Branch 319 → 320 not taken.
✗ Branch 319 → 321 not taken.
|
4 | changed({key(bookId, copyUid)}); |
| 393 |
2/2✓ Branch 179 → 180 taken 2 times.
✓ Branch 180 → 181 taken 2 times.
|
2 | emit statusMessage(tr("Both versions were kept.")); |
| 394 |
1/1✓ Branch 182 → 183 taken 2 times.
|
2 | flush(false); |
| 395 | 2 | return true; | |
| 396 | 2 | } | |
| 397 |
1/2✗ Branch 192 → 193 not taken.
✓ Branch 192 → 194 taken 7 times.
|
7 | if (choice != QLatin1String("mine")) |
| 398 | ✗ | return false; | |
| 399 | 7 | const bool deletedOnServer = entry->serverRaw.isEmpty(); | |
| 400 |
2/6✗ Branch 199 → 200 not taken.
✓ Branch 199 → 202 taken 7 times.
✗ Branch 200 → 201 not taken.
✗ Branch 200 → 202 not taken.
✗ Branch 203 → 204 not taken.
✓ Branch 203 → 213 taken 7 times.
|
7 | if (entry->operation == QLatin1String("delete") && deletedOnServer) { |
| 401 | ✗ | m_deps.store->removeOutbox(bookId, uid); | |
| 402 | ✗ | changed({key(bookId, uid)}); | |
| 403 | ✗ | return true; | |
| 404 | } | ||
| 405 | 7 | entry->state = QStringLiteral("pending"); | |
| 406 |
2/2✓ Branch 220 → 221 taken 2 times.
✓ Branch 220 → 222 taken 5 times.
|
7 | entry->baseEtag = deletedOnServer ? QString() : entry->serverEtag; |
| 407 | 7 | entry->attempts = 0; | |
| 408 | 7 | entry->nextAttempt = 0; | |
| 409 |
1/1✓ Branch 230 → 231 taken 7 times.
|
7 | entry->conflictFields.clear(); |
| 410 | 7 | entry->serverRaw.clear(); | |
| 411 | 7 | entry->serverEtag.clear(); | |
| 412 |
1/1✓ Branch 236 → 237 taken 7 times.
|
7 | m_deps.store->updateOutbox(*entry); |
| 413 |
1/1✓ Branch 240 → 241 taken 7 times.
|
14 | m_deps.store->setSyncState(bookId, uid, QStringLiteral("pending")); |
| 414 |
3/5✓ Branch 245 → 246 taken 7 times.
✓ Branch 248 → 249 taken 7 times.
✓ Branch 248 → 250 taken 7 times.
✗ Branch 365 → 366 not taken.
✗ Branch 365 → 367 not taken.
|
14 | changed({key(bookId, uid)}); |
| 415 |
2/2✓ Branch 250 → 251 taken 7 times.
✓ Branch 251 → 252 taken 7 times.
|
7 | emit statusMessage(tr("Your version will be saved on the server.")); |
| 416 |
1/1✓ Branch 253 → 254 taken 7 times.
|
7 | flush(true); |
| 417 | 7 | return true; | |
| 418 | 27 | } | |
| 419 | |||
| 420 | // ─── pushing ─────────────────────────────────────────────── | ||
| 421 | |||
| 422 | 494 | void ContactOutbox::flush(bool ignoreBackoff) { | |
| 423 |
7/9✓ Branch 2 → 3 taken 477 times.
✓ Branch 2 → 6 taken 17 times.
✓ Branch 3 → 4 taken 477 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 477 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 477 times.
✓ Branch 8 → 9 taken 17 times.
✓ Branch 8 → 10 taken 477 times.
|
494 | if (m_batchDepth > 0 || !m_deps.store || !m_deps.store->isOpen()) |
| 424 | 74 | return; | |
| 425 |
6/7✓ Branch 11 → 12 taken 477 times.
✗ Branch 11 → 15 not taken.
✓ Branch 12 → 13 taken 477 times.
✓ Branch 13 → 14 taken 57 times.
✓ Branch 13 → 15 taken 420 times.
✓ Branch 16 → 17 taken 57 times.
✓ Branch 16 → 18 taken 420 times.
|
477 | if (m_deps.networkAllowed && !m_deps.networkAllowed()) |
| 426 | 57 | return; | |
| 427 |
1/1✓ Branch 18 → 19 taken 420 times.
|
420 | const qint64 now = m_deps.now(); |
| 428 | 420 | QHash<QString, QList<ContactOutboxEntry>> due; | |
| 429 |
1/1✓ Branch 19 → 20 taken 420 times.
|
420 | const QList<ContactOutboxEntry> entries = m_deps.store->outboxEntries(); |
| 430 | 420 | QSet<qint64> queuedIds; | |
| 431 | 420 | QSet<qint64> stuckIds; // S106-064: dependencies parked in failed/conflict | |
| 432 |
2/2✓ Branch 39 → 24 taken 481 times.
✓ Branch 39 → 40 taken 420 times.
|
901 | for (const ContactOutboxEntry &entry : entries) { |
| 433 |
1/1✓ Branch 25 → 26 taken 481 times.
|
481 | queuedIds.insert(entry.id); |
| 434 |
6/6✓ Branch 28 → 29 taken 474 times.
✓ Branch 28 → 32 taken 7 times.
✓ Branch 31 → 32 taken 4 times.
✓ Branch 31 → 33 taken 470 times.
✓ Branch 34 → 35 taken 11 times.
✓ Branch 34 → 37 taken 470 times.
|
481 | if (entry.state == QLatin1String("failed") || entry.state == QLatin1String("conflict")) |
| 435 |
1/1✓ Branch 35 → 36 taken 11 times.
|
11 | stuckIds.insert(entry.id); |
| 436 | } | ||
| 437 |
2/2✓ Branch 115 → 42 taken 481 times.
✓ Branch 115 → 116 taken 420 times.
|
901 | for (const ContactOutboxEntry &entry : entries) { |
| 438 |
9/11✓ Branch 45 → 46 taken 470 times.
✓ Branch 45 → 49 taken 11 times.
✓ Branch 46 → 47 taken 470 times.
✓ Branch 48 → 49 taken 12 times.
✓ Branch 48 → 50 taken 458 times.
✓ Branch 51 → 52 taken 470 times.
✓ Branch 51 → 53 taken 11 times.
✓ Branch 53 → 54 taken 23 times.
✓ Branch 53 → 55 taken 458 times.
✗ Branch 156 → 157 not taken.
✗ Branch 156 → 158 not taken.
|
481 | if (entry.state != QLatin1String("pending") || m_inFlight.contains(key(entry.bookId, entry.uid))) |
| 439 | 41 | continue; | |
| 440 | // S106-064: the step this entry waits for (the copy of a move) failed or | ||
| 441 | // waits for the user. The source delete must not run (the card would be | ||
| 442 | // lost), and it must not wait silently forever either: it fails too, so | ||
| 443 | // the move shows as not done in both books. | ||
| 444 |
6/6✓ Branch 55 → 56 taken 25 times.
✓ Branch 55 → 59 taken 433 times.
✓ Branch 57 → 58 taken 1 time.
✓ Branch 57 → 59 taken 24 times.
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 85 taken 457 times.
|
458 | if (entry.dependsOn != 0 && stuckIds.contains(entry.dependsOn)) { |
| 445 | 1 | ContactOutboxEntry failed = entry; | |
| 446 | 1 | failed.state = QStringLiteral("failed"); | |
| 447 | 1 | failed.nextAttempt = 0; | |
| 448 | 1 | failed.lastError = QStringLiteral("The copy into the other address book was not stored"); | |
| 449 |
1/1✓ Branch 74 → 75 taken 1 time.
|
1 | m_deps.store->updateOutbox(failed); |
| 450 |
3/5✓ Branch 77 → 78 taken 1 time.
✓ Branch 80 → 81 taken 1 time.
✓ Branch 80 → 82 taken 1 time.
✗ Branch 164 → 165 not taken.
✗ Branch 164 → 166 not taken.
|
2 | changed({key(entry.bookId, entry.uid)}); |
| 451 | 1 | continue; | |
| 452 | 1 | } | |
| 453 |
8/8✓ Branch 85 → 86 taken 412 times.
✓ Branch 85 → 87 taken 45 times.
✓ Branch 86 → 87 taken 411 times.
✓ Branch 86 → 89 taken 1 time.
✓ Branch 88 → 89 taken 15 times.
✓ Branch 88 → 90 taken 441 times.
✓ Branch 91 → 92 taken 16 times.
✓ Branch 91 → 93 taken 441 times.
|
457 | if ((!ignoreBackoff && entry.nextAttempt > now) || queuedIds.contains(entry.dependsOn)) |
| 454 | 16 | continue; | |
| 455 |
1/1✓ Branch 93 → 94 taken 441 times.
|
441 | const auto book = m_deps.store->book(entry.bookId); |
| 456 |
5/6✓ Branch 95 → 96 taken 441 times.
✗ Branch 95 → 99 not taken.
✓ Branch 98 → 99 taken 1 time.
✓ Branch 98 → 100 taken 440 times.
✓ Branch 101 → 102 taken 1 time.
✓ Branch 101 → 104 taken 440 times.
|
441 | if (!book || book->isLocal()) { |
| 457 |
1/1✓ Branch 102 → 103 taken 1 time.
|
1 | m_deps.store->removeOutbox(entry.bookId, entry.uid); |
| 458 | 1 | continue; | |
| 459 | } | ||
| 460 |
2/2✓ Branch 105 → 106 taken 440 times.
✓ Branch 106 → 107 taken 440 times.
|
440 | due[book->accountId].append(entry); |
| 461 | 441 | } | |
| 462 |
2/2✓ Branch 144 → 117 taken 289 times.
✓ Branch 144 → 145 taken 420 times.
|
709 | for (auto it = due.cbegin(); it != due.cend(); ++it) { |
| 463 | 289 | const QString accountId = it.key(); | |
| 464 |
1/2✗ Branch 120 → 121 not taken.
✓ Branch 120 → 122 taken 289 times.
|
289 | if (m_resolving.contains(accountId)) |
| 465 | ✗ | continue; | |
| 466 |
1/1✓ Branch 122 → 123 taken 289 times.
|
289 | m_resolving.insert(accountId); |
| 467 |
1/1✓ Branch 123 → 124 taken 289 times.
|
289 | QPointer<ContactOutbox> guard(this); |
| 468 | 289 | const QList<ContactOutboxEntry> batch = it.value(); | |
| 469 |
2/6✓ Branch 129 → 130 taken 289 times.
✓ Branch 130 → 131 taken 289 times.
✗ Branch 186 → 187 not taken.
✗ Branch 186 → 188 not taken.
✗ Branch 189 → 190 not taken.
✗ Branch 189 → 191 not taken.
|
289 | m_deps.endpoint(this, accountId, [guard, accountId, batch](std::optional<Endpoint> endpoint) { |
| 470 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 289 times.
|
289 | if (!guard) |
| 471 | ✗ | return; | |
| 472 | 289 | guard->m_resolving.remove(accountId); | |
| 473 |
2/2✓ Branch 8 → 9 taken 182 times.
✓ Branch 8 → 10 taken 107 times.
|
289 | if (!endpoint) |
| 474 | 182 | return; // stays queued until the account is reachable | |
| 475 | 107 | guard->pushAccount(accountId, batch, *endpoint); | |
| 476 | 107 | SecureUtil::zeroMemory(endpoint->password); | |
| 477 | }); | ||
| 478 | 289 | } | |
| 479 |
1/1✓ Branch 145 → 146 taken 420 times.
|
420 | scheduleRetry(); |
| 480 | 421 | } | |
| 481 | |||
| 482 | 107 | void ContactOutbox::pushAccount(const QString &accountId, const QList<ContactOutboxEntry> &entries, | |
| 483 | const Endpoint &endpoint) { | ||
| 484 | Q_UNUSED(accountId); | ||
| 485 |
2/2✓ Branch 2 → 3 taken 107 times.
✓ Branch 3 → 4 taken 107 times.
|
107 | CardDavClient *client = m_deps.makeClient(endpoint.serverUrl, endpoint.username, QString::fromUtf8(endpoint.password)); |
| 486 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 107 times.
|
107 | if (!client) |
| 487 | ✗ | return; | |
| 488 |
1/1✓ Branch 7 → 8 taken 107 times.
|
107 | client->setParent(this); |
| 489 |
1/1✓ Branch 8 → 9 taken 107 times.
|
107 | auto remaining = std::make_shared<int>(0); |
| 490 |
2/2✓ Branch 41 → 11 taken 107 times.
✓ Branch 41 → 42 taken 107 times.
|
214 | for (const ContactOutboxEntry &queued : entries) { |
| 491 | // The entry may have changed while the credentials were read. | ||
| 492 |
1/1✓ Branch 12 → 13 taken 107 times.
|
107 | const auto entry = m_deps.store->outboxEntry(queued.bookId, queued.uid); |
| 493 |
6/13✓ Branch 14 → 15 taken 107 times.
✗ Branch 14 → 24 not taken.
✓ Branch 18 → 19 taken 107 times.
✗ Branch 18 → 24 not taken.
✓ Branch 21 → 22 taken 107 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 107 times.
✓ Branch 26 → 27 taken 107 times.
✗ Branch 26 → 28 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 107 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
107 | if (!entry || entry->state != QLatin1String("pending") || m_inFlight.contains(key(entry->bookId, entry->uid))) |
| 494 | ✗ | continue; | |
| 495 | 107 | ++*remaining; | |
| 496 |
1/1✓ Branch 32 → 33 taken 107 times.
|
107 | push(*entry, client); |
| 497 | 107 | } | |
| 498 |
1/2✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 107 times.
|
107 | if (*remaining == 0) |
| 499 | ✗ | client->deleteLater(); | |
| 500 | else | ||
| 501 |
1/1✓ Branch 45 → 46 taken 107 times.
|
107 | QTimer::singleShot(10 * 60 * 1000, client, &QObject::deleteLater); // DavNetworkLimits bound each request |
| 502 | 107 | } | |
| 503 | |||
| 504 | 434 | void ContactOutbox::scheduleRetry() { | |
| 505 | 434 | const qint64 now = m_deps.now(); | |
| 506 | 434 | qint64 next = 0; | |
| 507 |
5/5✓ Branch 3 → 4 taken 434 times.
✓ Branch 4 → 5 taken 434 times.
✓ Branch 5 → 6 taken 434 times.
✓ Branch 20 → 7 taken 497 times.
✓ Branch 20 → 21 taken 434 times.
|
931 | for (const ContactOutboxEntry &entry : m_deps.store->outboxEntries()) |
| 508 |
7/10✓ Branch 10 → 11 taken 485 times.
✓ Branch 10 → 15 taken 12 times.
✓ Branch 11 → 12 taken 27 times.
✓ Branch 11 → 15 taken 458 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 27 times.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
✓ Branch 16 → 17 taken 27 times.
✓ Branch 16 → 18 taken 470 times.
|
497 | if (entry.state == QLatin1String("pending") && entry.nextAttempt > now && (next == 0 || entry.nextAttempt < next)) |
| 509 | 461 | next = entry.nextAttempt; | |
| 510 |
2/2✓ Branch 22 → 23 taken 407 times.
✓ Branch 22 → 25 taken 27 times.
|
434 | if (next == 0) { |
| 511 | 407 | m_retryTimer.stop(); | |
| 512 | 407 | return; | |
| 513 | } | ||
| 514 |
1/1✓ Branch 26 → 27 taken 27 times.
|
27 | m_retryTimer.start(int(qBound(qint64(1), next - now, qint64(1800)) * 1000)); |
| 515 | } | ||
| 516 | |||
| 517 |
2/2✓ Branch 2 → 3 taken 128 times.
✓ Branch 3 → 4 taken 128 times.
|
128 | void ContactOutbox::finishInFlight(const ContactOutboxEntry &entry) { m_inFlight.remove(key(entry.bookId, entry.uid)); } |
| 518 | |||
| 519 | 107 | void ContactOutbox::push(const ContactOutboxEntry &entry, CardDavClient *client) { | |
| 520 | // One client serves every book of the account: name this entry's book. | ||
| 521 |
2/2✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 15 taken 94 times.
|
107 | if (m_deps.pushDontNotify) { |
| 522 |
1/1✓ Branch 4 → 5 taken 13 times.
|
13 | const auto book = m_deps.store->book(entry.bookId); |
| 523 |
2/3✓ Branch 6 → 7 taken 13 times.
✗ Branch 6 → 10 not taken.
✓ Branch 9 → 11 taken 13 times.
|
13 | client->setPushDontNotify(book ? m_deps.pushDontNotify(book->accountId, book->path) : QString()); |
| 524 | 13 | } | |
| 525 |
2/2✓ Branch 17 → 18 taken 17 times.
✓ Branch 17 → 30 taken 90 times.
|
107 | if (entry.operation == QLatin1String("delete")) { |
| 526 |
1/2✗ Branch 19 → 20 not taken.
✓ Branch 19 → 22 taken 17 times.
|
17 | if (entry.baseEtag.isEmpty()) { |
| 527 | ✗ | m_deps.store->removeOutbox(entry.bookId, entry.uid, entry.revision); | |
| 528 | 17 | return; | |
| 529 | } | ||
| 530 |
2/2✓ Branch 22 → 23 taken 17 times.
✓ Branch 23 → 24 taken 17 times.
|
17 | m_inFlight.insert(key(entry.bookId, entry.uid)); |
| 531 |
1/1✓ Branch 25 → 26 taken 17 times.
|
17 | const quint64 request = client->deleteResource(entry.href, entry.baseEtag); |
| 532 |
1/3✓ Branch 27 → 28 taken 17 times.
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 91 not taken.
|
17 | onResponse(client, request, this, [this, entry, client](const DavResponse &response) { |
| 533 | 17 | onDeleteFinished(entry, response, client); | |
| 534 | 17 | }); | |
| 535 | 17 | return; | |
| 536 | } | ||
| 537 |
1/1✓ Branch 30 → 31 taken 90 times.
|
90 | const auto card = m_deps.store->card(entry.bookId, entry.uid); |
| 538 |
1/2✗ Branch 32 → 33 not taken.
✓ Branch 32 → 35 taken 90 times.
|
90 | if (!card) { |
| 539 | ✗ | m_deps.store->removeOutbox(entry.bookId, entry.uid, entry.revision); | |
| 540 | ✗ | return; | |
| 541 | } | ||
| 542 |
2/2✓ Branch 35 → 36 taken 90 times.
✓ Branch 36 → 37 taken 90 times.
|
90 | m_inFlight.insert(key(entry.bookId, entry.uid)); |
| 543 | 90 | const QString body = card->raw; | |
| 544 |
1/2✗ Branch 41 → 42 not taken.
✓ Branch 41 → 44 taken 90 times.
|
90 | const QString url = entry.href.isEmpty() ? card->href : entry.href; |
| 545 |
2/2✓ Branch 47 → 48 taken 90 times.
✓ Branch 48 → 49 taken 90 times.
|
90 | const quint64 request = client->putResource(url, body.toUtf8(), entry.baseEtag, entry.baseEtag.isEmpty()); |
| 546 |
9/9✓ Branch 50 → 51 taken 90 times.
✓ Branch 54 → 55 taken 90 times.
✓ Branch 55 → 56 taken 90 times.
✓ Branch 56 → 57 taken 90 times.
✓ Branch 58 → 59 taken 21 times.
✓ Branch 58 → 60 taken 69 times.
✓ Branch 61 → 62 taken 90 times.
✓ Branch 65 → 52 taken 90 times.
✓ Branch 65 → 66 taken 90 times.
|
180 | qCInfo(lcContactOutbox) << "PUT card revision" << entry.revision << (entry.baseEtag.isEmpty() ? "create" : "update"); |
| 547 |
1/5✓ Branch 68 → 69 taken 90 times.
✗ Branch 107 → 108 not taken.
✗ Branch 107 → 109 not taken.
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 112 not taken.
|
90 | onResponse(client, request, this, [this, entry, body, client](const DavResponse &response) { |
| 548 |
2/2✓ Branch 2 → 3 taken 31 times.
✓ Branch 2 → 18 taken 55 times.
|
86 | if (response.status == 412) { |
| 549 |
5/5✓ Branch 3 → 4 taken 31 times.
✓ Branch 7 → 8 taken 31 times.
✓ Branch 8 → 9 taken 31 times.
✓ Branch 12 → 5 taken 31 times.
✓ Branch 12 → 13 taken 31 times.
|
62 | qCInfo(lcContactOutbox) << "Precondition failed — fetching the server version"; |
| 550 | 31 | const quint64 get = client->getResource(response.url); | |
| 551 |
1/1✓ Branch 15 → 16 taken 31 times.
|
62 | onResponse(client, get, this, [this, entry](const DavResponse &server) { onServerVersion(entry, server); }); |
| 552 | 31 | return; | |
| 553 | } | ||
| 554 | 55 | onPutFinished(entry, body, response, client); | |
| 555 | }); | ||
| 556 | 90 | } | |
| 557 | |||
| 558 | 55 | void ContactOutbox::onPutFinished(const ContactOutboxEntry &entry, const QString &body, const DavResponse &response, | |
| 559 | CardDavClient *client) { | ||
| 560 |
5/6✓ Branch 2 → 3 taken 55 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 41 times.
✓ Branch 3 → 5 taken 14 times.
✓ Branch 4 → 5 taken 22 times.
✓ Branch 4 → 25 taken 19 times.
|
55 | if (response.status == 200 || response.status == 201 || response.status == 204) { |
| 561 |
1/2✓ Branch 6 → 7 taken 36 times.
✗ Branch 6 → 8 not taken.
|
36 | const QString href = response.location.isEmpty() ? response.url : response.location; |
| 562 |
2/2✓ Branch 11 → 12 taken 35 times.
✓ Branch 11 → 16 taken 1 time.
|
36 | if (!response.etag.isEmpty()) { |
| 563 |
1/1✓ Branch 13 → 14 taken 35 times.
|
35 | adoptPushed(entry, body, href, response.etag, QString()); |
| 564 | 35 | return; | |
| 565 | } | ||
| 566 | // CB-13: no ETag — the server changed the card while storing it. | ||
| 567 |
1/1✓ Branch 16 → 17 taken 1 time.
|
1 | const quint64 get = client->getResource(href); |
| 568 |
1/5✓ Branch 20 → 21 taken 1 time.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
1 | onResponse(client, get, this, [this, entry, body, href](const DavResponse &server) { |
| 569 |
3/6✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 6 not taken.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 12 not taken.
|
1 | if (server.status == 200 && !server.etag.isEmpty()) |
| 570 |
2/2✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
|
1 | adoptPushed(entry, body, href, server.etag, QString::fromUtf8(server.body)); |
| 571 | else | ||
| 572 | ✗ | adoptPushed(entry, body, href, QString(), QString()); | |
| 573 | 1 | }); | |
| 574 | 1 | return; | |
| 575 | 36 | } | |
| 576 |
2/6✗ Branch 25 → 26 not taken.
✓ Branch 25 → 29 taken 19 times.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 36 taken 19 times.
|
19 | if (response.status == 404 && !entry.baseEtag.isEmpty()) { |
| 577 | // F108-12: deleted only when a GET agrees (Nextcloud answers 404 to writes it refuses in shared books). | ||
| 578 | ✗ | onResponse(client, client->getResource(response.url), this, [this, entry](const DavResponse &server) { | |
| 579 | ✗ | if (server.status == 200) retryLater(entry, tr("The server refuses changes to this card here (it still has it)."), true); | |
| 580 | ✗ | else if (server.status == 404 || server.status == 410) markConflict(entry, QString(), QString(), {QStringLiteral("deleted")}); | |
| 581 | ✗ | else retryLater(entry, responseError(server), !retryableStatus(server.status)); | |
| 582 | ✗ | }); | |
| 583 | ✗ | return; | |
| 584 | } | ||
| 585 | // S106-059: a local refusal (weak ETag, rejected URL) repeats on every | ||
| 586 | // attempt — it counts as permanent and reaches "failed" instead of being | ||
| 587 | // retried forever. | ||
| 588 |
2/2✓ Branch 41 → 42 taken 19 times.
✓ Branch 42 → 43 taken 19 times.
|
19 | retryLater(entry, responseError(response), |
| 589 |
4/4✓ Branch 36 → 37 taken 16 times.
✓ Branch 36 → 39 taken 3 times.
✓ Branch 38 → 39 taken 13 times.
✓ Branch 38 → 40 taken 3 times.
|
19 | response.refusedLocally || !retryableStatus(response.status)); |
| 590 | } | ||
| 591 | |||
| 592 | // The pushed (or the server's repaired) card becomes the merge base. | ||
| 593 | 36 | void ContactOutbox::adoptPushed(const ContactOutboxEntry &entry, const QString &body, const QString &href, | |
| 594 | const QString &etag, const QString &serverRaw) { | ||
| 595 | 36 | ContactStore *store = m_deps.store; | |
| 596 |
1/1✓ Branch 2 → 3 taken 36 times.
|
36 | finishInFlight(entry); |
| 597 |
1/1✓ Branch 3 → 4 taken 36 times.
|
36 | auto card = store->card(entry.bookId, entry.uid); |
| 598 |
1/1✓ Branch 4 → 5 taken 36 times.
|
36 | const bool current = store->removeOutbox(entry.bookId, entry.uid, entry.revision); |
| 599 |
1/2✓ Branch 6 → 7 taken 36 times.
✗ Branch 6 → 26 not taken.
|
36 | if (card) { |
| 600 | 36 | card->href = href; | |
| 601 | 36 | card->etag = etag; | |
| 602 |
2/2✓ Branch 12 → 13 taken 35 times.
✓ Branch 12 → 14 taken 1 time.
|
36 | card->baseRaw = serverRaw.isEmpty() ? body : serverRaw; |
| 603 |
1/2✓ Branch 17 → 18 taken 36 times.
✗ Branch 17 → 24 not taken.
|
36 | if (current) { |
| 604 | 36 | card->syncState.clear(); | |
| 605 |
2/2✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 24 taken 35 times.
|
36 | if (!serverRaw.isEmpty()) |
| 606 | 1 | card->raw = serverRaw; // what the server actually stores | |
| 607 | } | ||
| 608 |
1/1✓ Branch 25 → 26 taken 36 times.
|
36 | store->putCard(*card); |
| 609 | } | ||
| 610 |
1/2✗ Branch 26 → 27 not taken.
✓ Branch 26 → 38 taken 36 times.
|
36 | if (!current) { |
| 611 | // A newer local change arrived during the push: rebase it. | ||
| 612 | ✗ | if (auto newer = store->outboxEntry(entry.bookId, entry.uid)) { | |
| 613 | ✗ | newer->baseEtag = etag; | |
| 614 | ✗ | newer->href = href; | |
| 615 | ✗ | store->updateOutbox(*newer); | |
| 616 | ✗ | } | |
| 617 | } | ||
| 618 |
8/9✓ Branch 38 → 39 taken 36 times.
✓ Branch 42 → 43 taken 36 times.
✓ Branch 43 → 44 taken 36 times.
✓ Branch 44 → 45 taken 36 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 36 times.
✓ Branch 49 → 50 taken 36 times.
✓ Branch 53 → 40 taken 36 times.
✓ Branch 53 → 54 taken 36 times.
|
72 | qCInfo(lcContactOutbox) << "Pushed a card of book" << entry.bookId << (etag.isEmpty() ? "(ETag unknown)" : ""); |
| 619 |
3/5✓ Branch 56 → 57 taken 36 times.
✓ Branch 59 → 60 taken 36 times.
✓ Branch 59 → 61 taken 36 times.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
|
72 | changed({key(entry.bookId, entry.uid)}); |
| 620 |
1/1✓ Branch 61 → 62 taken 36 times.
|
36 | flush(false); |
| 621 | 72 | } | |
| 622 | |||
| 623 | 17 | void ContactOutbox::onDeleteFinished(const ContactOutboxEntry &entry, const DavResponse &response, | |
| 624 | CardDavClient *client) { | ||
| 625 | 17 | ContactStore *store = m_deps.store; | |
| 626 |
6/6✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 5 taken 2 times.
✓ Branch 3 → 4 taken 9 times.
✓ Branch 3 → 5 taken 6 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 38 taken 8 times.
|
17 | if (response.status == 200 || response.status == 204 || response.status == 404) { |
| 627 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 16 taken 9 times.
|
9 | if (!store->removeOutbox(entry.bookId, entry.uid, entry.revision)) { |
| 628 | // Restored meanwhile: it no longer exists on the server — create it. | ||
| 629 | ✗ | if (auto current = store->outboxEntry(entry.bookId, entry.uid)) { | |
| 630 | ✗ | current->baseEtag.clear(); | |
| 631 | ✗ | store->updateOutbox(*current); | |
| 632 | ✗ | } | |
| 633 | } | ||
| 634 |
7/7✓ Branch 16 → 17 taken 9 times.
✓ Branch 20 → 21 taken 9 times.
✓ Branch 21 → 22 taken 9 times.
✓ Branch 22 → 23 taken 9 times.
✓ Branch 23 → 24 taken 9 times.
✓ Branch 27 → 18 taken 9 times.
✓ Branch 27 → 28 taken 9 times.
|
18 | qCInfo(lcContactOutbox) << "Deleted a card of book" << entry.bookId << "on the server"; |
| 635 | 9 | finishInFlight(entry); | |
| 636 |
3/5✓ Branch 31 → 32 taken 9 times.
✓ Branch 34 → 35 taken 9 times.
✓ Branch 34 → 36 taken 9 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
|
18 | changed({key(entry.bookId, entry.uid)}); |
| 637 | 9 | flush(false); | |
| 638 | 9 | return; | |
| 639 | } | ||
| 640 |
1/2✓ Branch 38 → 39 taken 8 times.
✗ Branch 38 → 44 not taken.
|
8 | if (response.status == 412) { |
| 641 | 8 | const quint64 get = client->getResource(response.url); | |
| 642 |
1/1✓ Branch 41 → 42 taken 8 times.
|
8 | onResponse(client, get, this, [this, entry](const DavResponse &server) { |
| 643 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 8 times.
|
8 | if (server.status == 404) { |
| 644 | ✗ | onDeleteFinished(entry, server, nullptr); | |
| 645 | ✗ | return; | |
| 646 | } | ||
| 647 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 11 taken 8 times.
|
8 | if (server.status != 200) { |
| 648 | ✗ | retryLater(entry, responseError(server), !retryableStatus(server.status)); | |
| 649 | ✗ | return; | |
| 650 | } | ||
| 651 | // D-22 (Sprint 107): the source of a move changed on the server — its | ||
| 652 | // changes travel into the moved card instead of a question. | ||
| 653 |
2/2✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 16 taken 4 times.
|
8 | if (entry.dependsOn != 0) { |
| 654 |
2/2✓ Branch 12 → 13 taken 4 times.
✓ Branch 13 → 14 taken 4 times.
|
4 | mergeMovedCard(entry, QString::fromUtf8(server.body), server.etag); |
| 655 | 4 | return; | |
| 656 | } | ||
| 657 |
4/6✓ Branch 20 → 21 taken 4 times.
✓ Branch 21 → 22 taken 4 times.
✓ Branch 25 → 26 taken 4 times.
✓ Branch 25 → 27 taken 4 times.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
|
8 | markConflict(entry, QString::fromUtf8(server.body), server.etag, {QStringLiteral("changed")}); |
| 658 |
1/5✓ Branch 19 → 20 taken 4 times.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 52 not taken.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
|
8 | }); |
| 659 | 8 | return; | |
| 660 | } | ||
| 661 | ✗ | retryLater(entry, responseError(response), !retryableStatus(response.status)); | |
| 662 | 9 | } | |
| 663 | |||
| 664 | 32 | void ContactOutbox::onServerVersion(const ContactOutboxEntry &entry, const DavResponse &response) { | |
| 665 | 32 | ContactStore *store = m_deps.store; | |
| 666 |
2/2✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 18 taken 29 times.
|
32 | if (response.status == 404) { |
| 667 |
3/5✓ Branch 9 → 10 taken 3 times.
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 3 times.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
|
6 | markConflict(entry, QString(), QString(), {QStringLiteral("deleted")}); |
| 668 | 29 | return; | |
| 669 | } | ||
| 670 |
1/2✗ Branch 18 → 19 not taken.
✓ Branch 18 → 24 taken 29 times.
|
29 | if (response.status != 200) { |
| 671 | ✗ | retryLater(entry, responseError(response), !retryableStatus(response.status)); | |
| 672 | ✗ | return; | |
| 673 | } | ||
| 674 |
1/1✓ Branch 24 → 25 taken 29 times.
|
29 | const QString theirs = QString::fromUtf8(response.body); |
| 675 |
1/1✓ Branch 25 → 26 taken 29 times.
|
29 | const auto card = store->card(entry.bookId, entry.uid); |
| 676 |
3/6✓ Branch 27 → 28 taken 29 times.
✗ Branch 27 → 31 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 29 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 45 taken 29 times.
|
29 | if (!card || card->baseRaw.isEmpty()) { |
| 677 | ✗ | markConflict(entry, theirs, response.etag, {QStringLiteral("created")}); | |
| 678 | ✗ | return; | |
| 679 | } | ||
| 680 |
1/1✓ Branch 47 → 48 taken 29 times.
|
29 | const VCardMerge::Result merged = VCardMerge::merge(card->baseRaw, card->raw, theirs); |
| 681 |
2/2✓ Branch 48 → 49 taken 26 times.
✓ Branch 48 → 65 taken 3 times.
|
29 | if (!merged.ok) { |
| 682 |
1/1✓ Branch 56 → 57 taken 26 times.
|
26 | markConflict(entry, theirs, response.etag, |
| 683 |
3/14✗ Branch 50 → 51 not taken.
✓ Branch 50 → 55 taken 26 times.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 62 taken 26 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 64 taken 26 times.
✗ Branch 167 → 168 not taken.
✗ Branch 167 → 171 not taken.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
|
52 | merged.conflicts.isEmpty() ? QStringList{QStringLiteral("unreadable")} : merged.conflicts); |
| 684 | 26 | return; | |
| 685 | } | ||
| 686 | // The merge becomes the local state; the next push sends it. | ||
| 687 | 3 | StoredCard rebased = *card; | |
| 688 | 3 | rebased.raw = merged.merged; | |
| 689 | 3 | rebased.baseRaw = theirs; | |
| 690 | 3 | rebased.etag = response.etag; | |
| 691 |
1/1✓ Branch 70 → 71 taken 3 times.
|
3 | store->putCard(rebased); |
| 692 |
1/1✓ Branch 74 → 75 taken 3 times.
|
6 | store->enqueueOutbox(entry.bookId, entry.uid, QStringLiteral("put"), entry.href, response.etag); |
| 693 |
2/3✓ Branch 77 → 78 taken 3 times.
✓ Branch 79 → 80 taken 3 times.
✗ Branch 79 → 84 not taken.
|
3 | if (auto current = store->outboxEntry(entry.bookId, entry.uid)) { |
| 694 | 3 | current->baseEtag = response.etag; | |
| 695 |
1/1✓ Branch 83 → 84 taken 3 times.
|
3 | store->updateOutbox(*current); |
| 696 | 3 | } | |
| 697 |
1/1✓ Branch 85 → 86 taken 3 times.
|
3 | finishInFlight(entry); |
| 698 |
6/6✓ Branch 86 → 87 taken 3 times.
✓ Branch 90 → 91 taken 3 times.
✓ Branch 91 → 92 taken 3 times.
✓ Branch 92 → 93 taken 3 times.
✓ Branch 96 → 88 taken 3 times.
✓ Branch 96 → 97 taken 3 times.
|
6 | qCInfo(lcContactOutbox) << "Merged a server change into a card of book" << entry.bookId; |
| 699 |
3/5✓ Branch 99 → 100 taken 3 times.
✓ Branch 102 → 103 taken 3 times.
✓ Branch 102 → 104 taken 3 times.
✗ Branch 201 → 202 not taken.
✗ Branch 201 → 203 not taken.
|
6 | changed({key(entry.bookId, entry.uid)}); |
| 700 |
2/2✓ Branch 104 → 105 taken 3 times.
✓ Branch 105 → 106 taken 3 times.
|
3 | emit statusMessage(tr("Your change was combined with a change made on another device.")); |
| 701 |
1/1✓ Branch 107 → 108 taken 3 times.
|
3 | flush(true); |
| 702 | 90 | } | |
| 703 | |||
| 704 | // ─── outcomes ────────────────────────────────────────────── | ||
| 705 | |||
| 706 | 19 | void ContactOutbox::retryLater(const ContactOutboxEntry &entry, const QString &error, bool permanent) { | |
| 707 | 19 | ContactStore *store = m_deps.store; | |
| 708 |
1/1✓ Branch 2 → 3 taken 19 times.
|
19 | finishInFlight(entry); |
| 709 |
1/1✓ Branch 3 → 4 taken 19 times.
|
19 | auto current = store->outboxEntry(entry.bookId, entry.uid); |
| 710 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 19 times.
|
19 | if (!current) |
| 711 | ✗ | return; | |
| 712 | 19 | current->attempts += 1; | |
| 713 | // F-29 (SPRINT-104): the same bounded-retry family as the calendar | ||
| 714 | // outbox — a permanent rejection stops after kMaxPermanentAttempts | ||
| 715 | // with a visible "failed" state instead of retrying forever. | ||
| 716 |
6/6✓ Branch 8 → 9 taken 16 times.
✓ Branch 8 → 12 taken 3 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 11 times.
✓ Branch 13 → 14 taken 5 times.
✓ Branch 13 → 51 taken 14 times.
|
19 | if (permanent && current->attempts >= kMaxPermanentAttempts) { |
| 717 | 5 | current->state = QStringLiteral("failed"); | |
| 718 | 5 | current->nextAttempt = 0; | |
| 719 | 5 | current->lastError = error; | |
| 720 |
1/1✓ Branch 25 → 26 taken 5 times.
|
5 | store->updateOutbox(*current); |
| 721 |
8/8✓ Branch 26 → 27 taken 5 times.
✓ Branch 30 → 31 taken 5 times.
✓ Branch 31 → 32 taken 5 times.
✓ Branch 33 → 34 taken 5 times.
✓ Branch 34 → 35 taken 5 times.
✓ Branch 35 → 36 taken 5 times.
✓ Branch 39 → 28 taken 5 times.
✓ Branch 39 → 40 taken 5 times.
|
10 | qCWarning(lcContactOutbox) << "Push gave up after" << current->attempts << "attempts:" << error; |
| 722 |
3/5✓ Branch 42 → 43 taken 5 times.
✓ Branch 45 → 46 taken 5 times.
✓ Branch 45 → 47 taken 5 times.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
|
10 | changed({key(entry.bookId, entry.uid)}); |
| 723 |
2/2✓ Branch 47 → 48 taken 5 times.
✓ Branch 48 → 49 taken 5 times.
|
5 | emit statusMessage(tr("The server keeps refusing this change. It stays in this address book as not yet on the server.")); |
| 724 | 5 | return; | |
| 725 | } | ||
| 726 |
4/4✓ Branch 51 → 52 taken 14 times.
✓ Branch 52 → 53 taken 11 times.
✓ Branch 52 → 54 taken 3 times.
✓ Branch 55 → 56 taken 3 times.
|
14 | current->nextAttempt = m_deps.now() + (permanent ? 1800 : backoffSeconds(current->attempts)); |
| 727 | 14 | current->lastError = error; | |
| 728 |
1/1✓ Branch 61 → 62 taken 14 times.
|
14 | store->updateOutbox(*current); |
| 729 |
8/8✓ Branch 62 → 63 taken 14 times.
✓ Branch 66 → 67 taken 14 times.
✓ Branch 67 → 68 taken 14 times.
✓ Branch 69 → 70 taken 14 times.
✓ Branch 70 → 71 taken 14 times.
✓ Branch 71 → 72 taken 14 times.
✓ Branch 75 → 64 taken 14 times.
✓ Branch 75 → 76 taken 14 times.
|
28 | qCWarning(lcContactOutbox) << "Push failed (attempt" << current->attempts << "):" << error; |
| 730 |
3/5✓ Branch 78 → 79 taken 14 times.
✓ Branch 81 → 82 taken 14 times.
✓ Branch 81 → 83 taken 14 times.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
|
28 | changed({key(entry.bookId, entry.uid)}); |
| 731 |
5/5✓ Branch 83 → 84 taken 11 times.
✓ Branch 83 → 85 taken 3 times.
✓ Branch 84 → 86 taken 11 times.
✓ Branch 85 → 86 taken 3 times.
✓ Branch 86 → 87 taken 14 times.
|
14 | emit statusMessage(permanent ? tr("The server did not accept the change. MailJD will try again later.") |
| 732 | : tr("Not yet on the server. MailJD retries automatically.")); | ||
| 733 |
1/1✓ Branch 88 → 89 taken 14 times.
|
14 | scheduleRetry(); |
| 734 | 38 | } | |
| 735 | |||
| 736 | 35 | void ContactOutbox::markConflict(const ContactOutboxEntry &entry, const QString &serverRaw, const QString &serverEtag, | |
| 737 | const QStringList &fields) { | ||
| 738 | 35 | ContactStore *store = m_deps.store; | |
| 739 |
1/1✓ Branch 2 → 3 taken 35 times.
|
35 | finishInFlight(entry); |
| 740 |
1/1✓ Branch 3 → 4 taken 35 times.
|
35 | auto current = store->outboxEntry(entry.bookId, entry.uid); |
| 741 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 35 times.
|
35 | if (!current) |
| 742 | ✗ | return; | |
| 743 | 35 | current->state = QStringLiteral("conflict"); | |
| 744 | 35 | current->serverRaw = serverRaw; | |
| 745 | 35 | current->serverEtag = serverEtag; | |
| 746 | 35 | current->conflictFields = fields; | |
| 747 |
1/1✓ Branch 21 → 22 taken 35 times.
|
35 | store->updateOutbox(*current); |
| 748 |
1/1✓ Branch 25 → 26 taken 35 times.
|
70 | store->setSyncState(entry.bookId, entry.uid, QStringLiteral("conflict")); |
| 749 |
7/7✓ Branch 28 → 29 taken 35 times.
✓ Branch 32 → 33 taken 35 times.
✓ Branch 33 → 34 taken 35 times.
✓ Branch 34 → 35 taken 35 times.
✓ Branch 36 → 37 taken 35 times.
✓ Branch 42 → 30 taken 35 times.
✓ Branch 42 → 43 taken 35 times.
|
70 | qCWarning(lcContactOutbox) << "Conflict on a card of book" << entry.bookId << fields; |
| 750 |
3/5✓ Branch 45 → 46 taken 35 times.
✓ Branch 48 → 49 taken 35 times.
✓ Branch 48 → 50 taken 35 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
|
70 | changed({key(entry.bookId, entry.uid)}); |
| 751 |
2/2✓ Branch 50 → 51 taken 35 times.
✓ Branch 51 → 52 taken 35 times.
|
35 | emit statusMessage(tr("This contact was changed on another device. Open it to choose which version to keep.")); |
| 752 | 70 | } | |
| 753 | |||
| 754 | 9 | bool ContactOutbox::adoptServerVersion(const ContactOutboxEntry &entry, const QString &raw, const QString &etag) { | |
| 755 | 9 | ContactStore *store = m_deps.store; | |
| 756 |
1/1✓ Branch 2 → 3 taken 9 times.
|
9 | auto card = store->card(entry.bookId, entry.uid); |
| 757 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 9 times.
|
9 | if (raw.isEmpty()) { |
| 758 | ✗ | store->removeCard(entry.bookId, entry.uid); | |
| 759 | } else { | ||
| 760 | 9 | StoredCard adopted = card.value_or(StoredCard{}); | |
| 761 | 9 | adopted.bookId = entry.bookId; | |
| 762 | 9 | adopted.uid = entry.uid; | |
| 763 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 9 times.
|
9 | adopted.href = entry.href.isEmpty() ? adopted.href : entry.href; |
| 764 | 9 | adopted.raw = raw; | |
| 765 | 9 | adopted.baseRaw = raw; | |
| 766 | 9 | adopted.etag = etag; | |
| 767 | 9 | adopted.syncState.clear(); | |
| 768 |
2/3✓ Branch 18 → 19 taken 9 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 9 times.
|
9 | if (!store->putCard(adopted)) |
| 769 | ✗ | return false; | |
| 770 | 9 | } | |
| 771 |
1/1✓ Branch 27 → 28 taken 9 times.
|
9 | store->removeOutbox(entry.bookId, entry.uid); |
| 772 |
1/1✓ Branch 28 → 29 taken 9 times.
|
9 | finishInFlight(entry); |
| 773 |
3/5✓ Branch 31 → 32 taken 9 times.
✓ Branch 34 → 35 taken 9 times.
✓ Branch 34 → 36 taken 9 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
|
18 | changed({key(entry.bookId, entry.uid)}); |
| 774 | 9 | return true; | |
| 775 | 18 | } | |
| 776 |