app/core/CalendarOutbox.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "data/ICalTodoCodec.h" | ||
| 2 | #include "CalendarOutbox.h" | ||
| 3 | |||
| 4 | #include "data/CalendarStore.h" | ||
| 5 | #include "data/ICalEventCodec.h" | ||
| 6 | #include "data/ICalMerge.h" | ||
| 7 | #include "service/CalDavClient.h" | ||
| 8 | |||
| 9 | #include <QDateTime> | ||
| 10 | #include <QLoggingCategory> | ||
| 11 | #include <utility> | ||
| 12 | |||
| 13 |
4/7✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 8 taken 464 times.
✓ Branch 4 → 5 taken 15 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 15 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
479 | Q_LOGGING_CATEGORY(lcCalendarOutbox, "mailjd.calendar.outbox") |
| 14 | |||
| 15 | namespace { | ||
| 16 | |||
| 17 | // Handles exactly one response of `request` on `client`, then disconnects. | ||
| 18 | template <typename Handler> | ||
| 19 | 197 | void onResponse(CalDavClient *client, quint64 request, QObject *context, Handler handler) { | |
| 20 |
3/3void (anonymous namespace)::onResponse<CalendarOutbox::fetchServerVersion(CalendarOutboxEntry const&, QString const&, QByteArray const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}>(CalDavClient*, unsigned long long, QObject*, CalendarOutbox::fetchServerVersion(CalendarOutboxEntry const&, QString const&, QByteArray const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}):
✓ Branch 2 → 3 taken 55 times.
void (anonymous namespace)::onResponse<CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#2}>(CalDavClient*, unsigned long long, QObject*, CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#2}):
✓ Branch 2 → 3 taken 125 times.
void (anonymous namespace)::onResponse<CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}>(CalDavClient*, unsigned long long, QObject*, CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}):
✓ Branch 2 → 3 taken 17 times.
|
197 | auto connection = std::make_shared<QMetaObject::Connection>(); |
| 21 | 197 | *connection = QObject::connect(client, &CalDavClient::resourceResponse, context, | |
| 22 |
3/9void (anonymous namespace)::onResponse<CalendarOutbox::fetchServerVersion(CalendarOutboxEntry const&, QString const&, QByteArray const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}>(CalDavClient*, unsigned long long, QObject*, CalendarOutbox::fetchServerVersion(CalendarOutboxEntry const&, QString const&, QByteArray const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}):
✓ Branch 5 → 6 taken 55 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#2}>(CalDavClient*, unsigned long long, QObject*, CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#2}):
✓ Branch 5 → 6 taken 125 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
void (anonymous namespace)::onResponse<CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}>(CalDavClient*, unsigned long long, QObject*, CalendarOutbox::push(CalendarOutboxEntry const&, CalendarOutbox::Endpoint const&)::{lambda(DavResponse const&)#1}):
✓ Branch 5 → 6 taken 17 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
|
587 | [connection, request, handler](const DavResponse &response) { |
| 23 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 193 times.
|
193 | if (response.request != request) |
| 24 | ✗ | return; | |
| 25 | 193 | QObject::disconnect(*connection); | |
| 26 | 193 | handler(response); | |
| 27 | }); | ||
| 28 | 197 | } | |
| 29 | |||
| 30 | // S106-160 (INV-12): 403 (a read-only collection) and 507 (no storage) | ||
| 31 | // are permanent — F-19's bounded retries end in the failed state; they | ||
| 32 | // used to back off forever as transient errors. | ||
| 33 | 13 | bool retryableStatus(int status) { | |
| 34 |
6/10✓ Branch 2 → 3 taken 13 times.
✗ Branch 2 → 8 not taken.
✓ Branch 3 → 4 taken 13 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 13 times.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 10 times.
✓ Branch 6 → 9 taken 3 times.
|
23 | return status == 0 || status == 401 || status == 408 || status == 429 || |
| 35 |
2/2✓ Branch 7 → 8 taken 7 times.
✓ Branch 7 → 9 taken 3 times.
|
23 | (status >= 500 && status != 507); |
| 36 | } | ||
| 37 | |||
| 38 | 13 | QString responseError(const DavResponse &response) { | |
| 39 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 13 times.
|
13 | if (!response.error.isEmpty()) |
| 40 | ✗ | return response.error; | |
| 41 | 26 | return QStringLiteral("%1 %2 answered HTTP %3") | |
| 42 |
2/2✓ Branch 8 → 9 taken 13 times.
✓ Branch 9 → 10 taken 13 times.
|
39 | .arg(QString::fromLatin1(response.method), response.url) |
| 43 |
1/1✓ Branch 11 → 12 taken 13 times.
|
13 | .arg(response.status); |
| 44 | } | ||
| 45 | |||
| 46 | 321 | CalendarEvent masterOf(const QList<CalendarEvent> &rows) { | |
| 47 |
1/2✓ Branch 11 → 4 taken 321 times.
✗ Branch 11 → 12 not taken.
|
321 | for (const CalendarEvent &row : rows) |
| 48 |
2/3✓ Branch 5 → 6 taken 321 times.
✓ Branch 6 → 7 taken 321 times.
✗ Branch 6 → 9 not taken.
|
321 | if (!row.recurrenceId.isValid()) |
| 49 | 321 | return row; | |
| 50 | ✗ | return rows.value(0); | |
| 51 | } | ||
| 52 | |||
| 53 | 214 | QList<CalendarEvent> exceptionsOf(const QList<CalendarEvent> &rows, const CalendarEvent &master) { | |
| 54 | 214 | QList<CalendarEvent> exceptions; | |
| 55 |
2/2✓ Branch 17 → 4 taken 216 times.
✓ Branch 17 → 18 taken 214 times.
|
430 | for (const CalendarEvent &row : rows) |
| 56 |
5/5✓ Branch 5 → 6 taken 216 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 12 taken 214 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 214 times.
|
218 | if (row.recurrenceId.isValid() && |
| 57 |
2/6✓ Branch 7 → 8 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 2 times.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
2 | !(row.recurrenceId == master.recurrenceId && !master.recurrenceId.isValid())) |
| 58 |
1/1✓ Branch 14 → 15 taken 2 times.
|
2 | exceptions.append(row); |
| 59 |
2/3✓ Branch 18 → 19 taken 214 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 214 times.
|
214 | if (master.recurrenceId.isValid()) |
| 60 | ✗ | exceptions.removeFirst(); // the "master" is the first exception itself | |
| 61 | 214 | return exceptions; | |
| 62 | ✗ | } | |
| 63 | |||
| 64 | } // namespace | ||
| 65 | |||
| 66 | 946 | CalendarOutbox::CalendarOutbox(Deps deps, QObject *parent) | |
| 67 |
1/1✓ Branch 7 → 8 taken 946 times.
|
946 | : QObject(parent), m_deps(std::move(deps)) { |
| 68 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 12 taken 946 times.
|
946 | if (!m_deps.now) |
| 69 | ✗ | m_deps.now = [] { return QDateTime::currentSecsSinceEpoch(); }; | |
| 70 |
1/1✓ Branch 12 → 13 taken 946 times.
|
946 | m_retryTimer.setSingleShot(true); |
| 71 |
1/1✓ Branch 13 → 14 taken 946 times.
|
946 | connect(&m_retryTimer, &QTimer::timeout, this, [this] { flush(false); }); |
| 72 | 946 | } | |
| 73 | |||
| 74 | 644 | QString CalendarOutbox::key(const QString &accountId, const QString &calendarPath, const QString &uid) { | |
| 75 |
3/3✓ Branch 4 → 5 taken 644 times.
✓ Branch 5 → 6 taken 644 times.
✓ Branch 6 → 7 taken 644 times.
|
644 | return calendarSelectionKey(accountId, calendarPath) + QLatin1Char('\n') + uid; |
| 76 | } | ||
| 77 | |||
| 78 | // F-19 (SPRINT-104): permanent rejections stop after this many attempts | ||
| 79 | // (about 1.5 hours with the 1800 s permanent interval) with a visible | ||
| 80 | // "failed" state the user can resolve. | ||
| 81 | constexpr int kMaxPermanentAttempts = 3; | ||
| 82 | |||
| 83 | 10 | qint64 CalendarOutbox::backoffSeconds(int attempts) { | |
| 84 | static const qint64 steps[] = {30, 60, 120, 300, 600, 1800}; | ||
| 85 |
1/1✓ Branch 4 → 5 taken 10 times.
|
10 | const int index = qBound(0, attempts - 1, int(std::size(steps)) - 1); |
| 86 | 10 | return steps[index]; | |
| 87 | } | ||
| 88 | |||
| 89 | ✗ | bool CalendarOutbox::hasEntry(const QString &uid, const QString &accountId, | |
| 90 | const QString &calendarPath) const { | ||
| 91 | ✗ | return m_deps.store->outboxEntry(accountId, calendarPath, uid).has_value(); | |
| 92 | } | ||
| 93 | |||
| 94 | // ─── Local changes ───────────────────────────────────────── | ||
| 95 | |||
| 96 | 116 | bool CalendarOutbox::saveEvent(const CalendarEvent &event) { | |
| 97 | 116 | CalendarStore *store = m_deps.store; | |
| 98 | 116 | const auto previous = store->occurrenceEvent(event.uid, event.accountId, event.calendarPath, | |
| 99 |
1/1✓ Branch 2 → 3 taken 116 times.
|
116 | event.recurrenceId); |
| 100 |
1/1✓ Branch 3 → 4 taken 116 times.
|
116 | const auto master = store->eventFor(event.uid, event.accountId, event.calendarPath); |
| 101 | // A local edit never changes what the server told us about the resource. | ||
| 102 | 116 | CalendarEvent stored = event; | |
| 103 | 92 | const auto inherit = [&stored](const CalendarEvent &source) { | |
| 104 |
2/2✓ Branch 3 → 4 taken 74 times.
✓ Branch 3 → 5 taken 18 times.
|
92 | if (stored.etag.isEmpty()) stored.etag = source.etag; |
| 105 |
2/2✓ Branch 6 → 7 taken 74 times.
✓ Branch 6 → 8 taken 18 times.
|
92 | if (stored.resourceHref.isEmpty()) stored.resourceHref = source.resourceHref; |
| 106 |
2/2✓ Branch 9 → 10 taken 73 times.
✓ Branch 9 → 11 taken 19 times.
|
92 | if (stored.resourceIcal.isEmpty()) stored.resourceIcal = source.resourceIcal; |
| 107 | 208 | }; | |
| 108 |
2/2✓ Branch 6 → 7 taken 89 times.
✓ Branch 6 → 13 taken 27 times.
|
116 | if (previous) { |
| 109 | 89 | inherit(*previous); | |
| 110 |
2/2✓ Branch 10 → 11 taken 58 times.
✓ Branch 10 → 17 taken 31 times.
|
89 | if (stored.icalRaw.isEmpty()) stored.icalRaw = previous->icalRaw; |
| 111 |
2/2✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 17 taken 24 times.
|
27 | } else if (master) { |
| 112 | 3 | inherit(*master); | |
| 113 | } | ||
| 114 |
1/1✓ Branch 17 → 18 taken 116 times.
|
116 | stored.lastModified = QDateTime::currentDateTimeUtc(); |
| 115 |
3/3✓ Branch 20 → 21 taken 116 times.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 21 → 23 taken 113 times.
|
116 | if (!store->upsertEvent(stored)) |
| 116 | 3 | return false; | |
| 117 |
2/2✓ Branch 24 → 25 taken 91 times.
✓ Branch 24 → 26 taken 22 times.
|
113 | const CalendarEvent resource = master ? *master : stored; |
| 118 |
1/1✓ Branch 31 → 32 taken 113 times.
|
226 | store->enqueueOutbox(stored.accountId, stored.calendarPath, stored.uid, QStringLiteral("put"), |
| 119 | resource.resourceHref, resource.etag); | ||
| 120 |
8/8✓ Branch 34 → 35 taken 113 times.
✓ Branch 38 → 39 taken 113 times.
✓ Branch 39 → 40 taken 113 times.
✓ Branch 40 → 41 taken 113 times.
✓ Branch 41 → 42 taken 113 times.
✓ Branch 42 → 43 taken 113 times.
✓ Branch 46 → 36 taken 113 times.
✓ Branch 46 → 47 taken 113 times.
|
226 | qCInfo(lcCalendarOutbox) << "Queued put" << stored.uid << "in" << stored.calendarPath; |
| 121 |
1/1✓ Branch 47 → 48 taken 113 times.
|
113 | publishChange(); |
| 122 |
2/3✓ Branch 49 → 50 taken 113 times.
✗ Branch 49 → 52 not taken.
✓ Branch 50 → 51 taken 113 times.
|
113 | const bool syncs = m_deps.hasEndpoint ? m_deps.hasEndpoint(stored.accountId) |
| 123 |
1/5✗ Branch 52 → 53 not taken.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 57 taken 113 times.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
|
113 | : m_deps.endpoint(stored.accountId).has_value(); |
| 124 |
5/5✓ Branch 57 → 58 taken 51 times.
✓ Branch 57 → 59 taken 62 times.
✓ Branch 58 → 60 taken 51 times.
✓ Branch 59 → 60 taken 62 times.
✓ Branch 60 → 61 taken 113 times.
|
113 | emit statusMessage(syncs ? tr("Saved – syncing…") : tr("Saved on this device")); |
| 125 |
1/1✓ Branch 62 → 63 taken 113 times.
|
113 | requestFlush(); |
| 126 | 113 | return true; | |
| 127 | 116 | } | |
| 128 | |||
| 129 | 20 | bool CalendarOutbox::removeSeries(const QString &uid, const QString &accountId, | |
| 130 | const QString &calendarPath) { | ||
| 131 | 20 | CalendarStore *store = m_deps.store; | |
| 132 |
1/1✓ Branch 2 → 3 taken 20 times.
|
20 | const QList<CalendarEvent> rows = store->eventSeries(uid, accountId, calendarPath); |
| 133 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 20 times.
|
20 | if (rows.isEmpty()) |
| 134 | ✗ | return false; | |
| 135 |
1/1✓ Branch 6 → 7 taken 20 times.
|
20 | const CalendarEvent master = masterOf(rows); |
| 136 |
1/1✓ Branch 7 → 8 taken 20 times.
|
20 | const auto existing = store->outboxEntry(accountId, calendarPath, uid); |
| 137 |
2/3✓ Branch 9 → 10 taken 20 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 20 times.
|
20 | if (!store->deleteEvent(uid, accountId, calendarPath)) |
| 138 | ✗ | return false; | |
| 139 |
2/2✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 17 taken 15 times.
|
20 | const QString baseEtag = existing ? existing->baseEtag : master.etag; |
| 140 | // F-55 (SPRINT-104): the empty ETag alone does not mean "never reached | ||
| 141 | // the server" — a successful PUT whose answer carries no ETag header | ||
| 142 | // leaves it empty while the resource exists (the contact outbox fetches | ||
| 143 | // it in that case). The stored resource record is the authority; an | ||
| 144 | // empty-ETag delete of a resource that exists queues the delete without | ||
| 145 | // an If-Match (the server's 412/404 answers resolve it). | ||
| 146 | const bool onServer = | ||
| 147 |
1/1✓ Branch 19 → 20 taken 20 times.
|
20 | !store->resourceFor(accountId, calendarPath, uid).isEmpty(); |
| 148 |
6/6✓ Branch 23 → 24 taken 17 times.
✓ Branch 23 → 26 taken 3 times.
✓ Branch 24 → 25 taken 13 times.
✓ Branch 24 → 26 taken 4 times.
✓ Branch 27 → 28 taken 13 times.
✓ Branch 27 → 30 taken 7 times.
|
20 | if (baseEtag.isEmpty() && !onServer) { |
| 149 | // Never reached the server: nothing to delete there. | ||
| 150 |
1/1✓ Branch 28 → 29 taken 13 times.
|
13 | store->removeOutbox(accountId, calendarPath, uid); |
| 151 |
1/1✓ Branch 29 → 55 taken 13 times.
|
13 | store->removeResource(accountId, calendarPath, uid); |
| 152 | } else { | ||
| 153 |
3/3✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 34 taken 5 times.
✓ Branch 38 → 39 taken 7 times.
|
16 | store->enqueueOutbox(accountId, calendarPath, uid, QStringLiteral("delete"), |
| 154 | 9 | existing ? existing->href : master.resourceHref, baseEtag); | |
| 155 |
8/8✓ Branch 41 → 42 taken 7 times.
✓ Branch 45 → 46 taken 7 times.
✓ Branch 46 → 47 taken 7 times.
✓ Branch 47 → 48 taken 7 times.
✓ Branch 48 → 49 taken 7 times.
✓ Branch 49 → 50 taken 7 times.
✓ Branch 53 → 43 taken 7 times.
✓ Branch 53 → 54 taken 7 times.
|
14 | qCInfo(lcCalendarOutbox) << "Queued delete" << uid << "in" << calendarPath; |
| 156 | } | ||
| 157 |
1/1✓ Branch 55 → 56 taken 20 times.
|
20 | publishChange(); |
| 158 |
2/2✓ Branch 56 → 57 taken 20 times.
✓ Branch 57 → 58 taken 20 times.
|
20 | emit statusMessage(tr("Appointment deleted")); |
| 159 |
1/1✓ Branch 59 → 60 taken 20 times.
|
20 | requestFlush(); |
| 160 | 20 | return true; | |
| 161 | 20 | } | |
| 162 | |||
| 163 | 45 | bool CalendarOutbox::restoreSeries(const QString &uid, const QString &accountId, | |
| 164 | const QString &calendarPath, const QList<CalendarEvent> &rows) { | ||
| 165 | 45 | CalendarStore *store = m_deps.store; | |
| 166 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 44 times.
|
45 | if (rows.isEmpty()) |
| 167 |
1/1✓ Branch 4 → 5 taken 1 time.
|
1 | return removeSeries(uid, accountId, calendarPath); |
| 168 |
1/1✓ Branch 6 → 7 taken 44 times.
|
44 | const auto existing = store->outboxEntry(accountId, calendarPath, uid); |
| 169 | // Rows from outside the store (an answered invitation, an imported file) | ||
| 170 | // replace the stored series: they keep its server identity (S93.5.2). | ||
| 171 |
1/1✓ Branch 8 → 9 taken 44 times.
|
44 | const auto previous = store->occurrenceEvent(uid, accountId, calendarPath, {}); |
| 172 | 44 | QList<CalendarEvent> stored = rows; | |
| 173 |
4/4✓ Branch 11 → 12 taken 44 times.
✓ Branch 12 → 13 taken 44 times.
✓ Branch 28 → 14 taken 46 times.
✓ Branch 28 → 29 taken 18 times.
|
64 | for (CalendarEvent &row : stored) { |
| 174 |
2/2✓ Branch 16 → 17 taken 26 times.
✓ Branch 16 → 18 taken 20 times.
|
46 | if (!previous) break; |
| 175 |
1/2✓ Branch 19 → 20 taken 20 times.
✗ Branch 19 → 22 not taken.
|
20 | if (row.etag.isEmpty()) row.etag = previous->etag; |
| 176 |
1/2✓ Branch 23 → 24 taken 20 times.
✗ Branch 23 → 26 not taken.
|
20 | if (row.resourceHref.isEmpty()) row.resourceHref = previous->resourceHref; |
| 177 | } | ||
| 178 |
1/1✓ Branch 29 → 30 taken 44 times.
|
44 | store->beginTransaction(); |
| 179 |
1/1✓ Branch 31 → 32 taken 44 times.
|
44 | store->deleteEvent(uid, accountId, calendarPath); |
| 180 | 44 | bool ok = true; | |
| 181 |
4/4✓ Branch 33 → 34 taken 44 times.
✓ Branch 34 → 35 taken 44 times.
✓ Branch 44 → 36 taken 48 times.
✓ Branch 44 → 45 taken 44 times.
|
92 | for (const CalendarEvent &row : stored) |
| 182 |
3/5✓ Branch 37 → 38 taken 48 times.
✓ Branch 38 → 39 taken 48 times.
✗ Branch 38 → 41 not taken.
✓ Branch 39 → 40 taken 48 times.
✗ Branch 39 → 41 not taken.
|
48 | ok = store->upsertEvent(row) && ok; |
| 183 |
1/1✓ Branch 45 → 46 taken 44 times.
|
44 | store->commitTransaction(); |
| 184 |
1/2✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 44 times.
|
44 | if (!ok) |
| 185 | ✗ | return false; | |
| 186 | // The resource still exists on the server unless a confirmed delete | ||
| 187 | // removed its stored copy: then the restore is a create. | ||
| 188 |
1/1✓ Branch 48 → 49 taken 44 times.
|
44 | const bool onServer = !store->resourceFor(accountId, calendarPath, uid).isEmpty(); |
| 189 |
1/1✓ Branch 51 → 52 taken 44 times.
|
44 | const CalendarEvent master = masterOf(stored); |
| 190 |
3/3✓ Branch 60 → 61 taken 14 times.
✓ Branch 60 → 63 taken 30 times.
✓ Branch 67 → 68 taken 44 times.
|
102 | store->enqueueOutbox(accountId, calendarPath, uid, QStringLiteral("put"), |
| 191 | 58 | existing ? existing->href : master.resourceHref, | |
| 192 |
4/4✓ Branch 53 → 54 taken 14 times.
✓ Branch 53 → 56 taken 30 times.
✓ Branch 56 → 57 taken 20 times.
✓ Branch 56 → 58 taken 10 times.
|
88 | existing ? existing->baseEtag : (onServer ? master.etag : QString())); |
| 193 |
6/6✓ Branch 72 → 73 taken 30 times.
✓ Branch 72 → 75 taken 14 times.
✓ Branch 73 → 74 taken 10 times.
✓ Branch 73 → 75 taken 20 times.
✓ Branch 76 → 77 taken 10 times.
✓ Branch 76 → 92 taken 34 times.
|
44 | if (!existing && !onServer) { |
| 194 |
1/1✓ Branch 77 → 78 taken 10 times.
|
10 | auto entry = store->outboxEntry(accountId, calendarPath, uid); |
| 195 |
3/6✓ Branch 79 → 80 taken 10 times.
✗ Branch 79 → 84 not taken.
✗ Branch 82 → 83 not taken.
✓ Branch 82 → 84 taken 10 times.
✗ Branch 85 → 86 not taken.
✓ Branch 85 → 90 taken 10 times.
|
10 | if (entry && !entry->baseEtag.isEmpty()) { |
| 196 | ✗ | entry->baseEtag.clear(); | |
| 197 | ✗ | store->updateOutbox(*entry); | |
| 198 | } | ||
| 199 | 10 | } | |
| 200 |
1/1✓ Branch 92 → 93 taken 44 times.
|
44 | publishChange(); |
| 201 |
1/1✓ Branch 93 → 94 taken 44 times.
|
44 | requestFlush(); |
| 202 | 44 | return true; | |
| 203 | 44 | } | |
| 204 | |||
| 205 | // ─── Pushing ─────────────────────────────────────────────── | ||
| 206 | |||
| 207 | 407 | void CalendarOutbox::requestFlush() { | |
| 208 |
2/2✓ Branch 2 → 3 taken 158 times.
✓ Branch 2 → 4 taken 249 times.
|
407 | if (m_flushRequested) return; |
| 209 | 249 | m_flushRequested = true; | |
| 210 |
1/1✓ Branch 4 → 5 taken 249 times.
|
249 | QTimer::singleShot(0, this, [this] { |
| 211 | 198 | m_flushRequested = false; | |
| 212 | 198 | flush(false); | |
| 213 | 198 | }); | |
| 214 | } | ||
| 215 | |||
| 216 | 365 | void CalendarOutbox::flush(bool ignoreBackoff) { | |
| 217 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 365 times.
|
365 | if (m_batchDepth > 0) return; |
| 218 |
5/6✓ Branch 5 → 6 taken 365 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 80 times.
✓ Branch 7 → 9 taken 285 times.
✓ Branch 10 → 11 taken 80 times.
✓ Branch 10 → 12 taken 285 times.
|
365 | if (m_deps.networkAllowed && !m_deps.networkAllowed()) |
| 219 | 80 | return; | |
| 220 | // S106-159: the credential lookup below may run a nested event loop; a | ||
| 221 | // flush started from inside it waits for this one (it used to push the | ||
| 222 | // same entry a second time — a duplicate PUT and a spurious 412). | ||
| 223 |
2/2✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 284 times.
|
285 | if (m_flushing) { |
| 224 | 1 | m_flushAgain = true; | |
| 225 | 1 | m_flushAgainIgnoringBackoff |= ignoreBackoff; | |
| 226 | 1 | return; | |
| 227 | } | ||
| 228 | 284 | m_flushing = true; | |
| 229 | 284 | flushOnce(ignoreBackoff); | |
| 230 |
2/2✓ Branch 18 → 16 taken 1 time.
✓ Branch 18 → 19 taken 284 times.
|
285 | while (m_flushAgain) { |
| 231 | 1 | const bool again = std::exchange(m_flushAgainIgnoringBackoff, false); | |
| 232 | 1 | m_flushAgain = false; | |
| 233 | 1 | flushOnce(again); | |
| 234 | } | ||
| 235 | 284 | m_flushing = false; | |
| 236 | 284 | scheduleRetry(); | |
| 237 | } | ||
| 238 | |||
| 239 | 285 | void CalendarOutbox::flushOnce(bool ignoreBackoff) { | |
| 240 | 285 | const qint64 now = m_deps.now(); | |
| 241 |
5/5✓ Branch 3 → 4 taken 285 times.
✓ Branch 4 → 5 taken 285 times.
✓ Branch 5 → 6 taken 285 times.
✓ Branch 74 → 7 taken 165 times.
✓ Branch 74 → 75 taken 285 times.
|
450 | for (const CalendarOutboxEntry &listed : m_deps.store->outboxEntries()) { |
| 242 |
1/1✓ Branch 8 → 9 taken 165 times.
|
165 | const QString entryKey = key(listed.accountId, listed.calendarPath, listed.uid); |
| 243 |
6/6✓ Branch 11 → 12 taken 163 times.
✓ Branch 11 → 14 taken 2 times.
✓ Branch 13 → 14 taken 10 times.
✓ Branch 13 → 15 taken 153 times.
✓ Branch 16 → 17 taken 12 times.
✓ Branch 16 → 18 taken 153 times.
|
165 | if (listed.state != QLatin1String("pending") || m_inFlight.contains(entryKey)) |
| 244 | 12 | continue; | |
| 245 | 153 | const CalendarOutboxEntry &entry = listed; | |
| 246 |
4/4✓ Branch 18 → 19 taken 130 times.
✓ Branch 18 → 21 taken 23 times.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 21 taken 129 times.
|
153 | if (!ignoreBackoff && entry.nextAttempt > now) |
| 247 | 1 | continue; | |
| 248 |
2/2✓ Branch 22 → 23 taken 15 times.
✓ Branch 22 → 39 taken 137 times.
|
152 | if (!entry.dependencyKey.isEmpty()) { |
| 249 | 15 | bool waiting = false; | |
| 250 |
5/5✓ Branch 23 → 24 taken 15 times.
✓ Branch 24 → 25 taken 15 times.
✓ Branch 25 → 26 taken 15 times.
✓ Branch 35 → 27 taken 17 times.
✓ Branch 35 → 36 taken 7 times.
|
24 | for (const auto &dependency : m_deps.store->outboxEntries()) |
| 251 |
3/3✓ Branch 28 → 29 taken 17 times.
✓ Branch 31 → 32 taken 8 times.
✓ Branch 31 → 33 taken 9 times.
|
24 | if (key(dependency.accountId, dependency.calendarPath, dependency.uid) == entry.dependencyKey) { waiting = true; break; } |
| 252 |
2/2✓ Branch 37 → 38 taken 8 times.
✓ Branch 37 → 39 taken 7 times.
|
15 | if (waiting) continue; |
| 253 | } | ||
| 254 | // In flight before the (possibly blocking) credential read (S106-159). | ||
| 255 |
1/1✓ Branch 39 → 40 taken 144 times.
|
144 | m_inFlight.insert(entryKey); |
| 256 |
1/1✓ Branch 40 → 41 taken 144 times.
|
144 | const auto endpoint = m_deps.endpoint(entry.accountId); |
| 257 |
1/1✓ Branch 41 → 42 taken 144 times.
|
144 | const auto fresh = m_deps.store->outboxEntry(entry.accountId, entry.calendarPath, entry.uid); |
| 258 |
6/8✓ Branch 43 → 44 taken 142 times.
✓ Branch 43 → 50 taken 2 times.
✓ Branch 45 → 46 taken 142 times.
✗ Branch 45 → 50 not taken.
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 51 taken 142 times.
✓ Branch 52 → 53 taken 2 times.
✓ Branch 52 → 55 taken 142 times.
|
144 | if (!endpoint || !fresh || fresh->state != QLatin1String("pending")) { |
| 259 |
1/1✓ Branch 53 → 54 taken 2 times.
|
2 | m_inFlight.remove(entryKey); // stays queued until the account is reachable |
| 260 | 2 | continue; | |
| 261 | } | ||
| 262 |
1/1✓ Branch 57 → 58 taken 142 times.
|
142 | push(*fresh, *endpoint); |
| 263 | 454 | } | |
| 264 | 285 | } | |
| 265 | |||
| 266 | 297 | void CalendarOutbox::scheduleRetry() { | |
| 267 | 297 | const qint64 now = m_deps.now(); | |
| 268 | 297 | qint64 next = 0; | |
| 269 |
5/5✓ Branch 3 → 4 taken 297 times.
✓ Branch 4 → 5 taken 297 times.
✓ Branch 5 → 6 taken 297 times.
✓ Branch 20 → 7 taken 178 times.
✓ Branch 20 → 21 taken 297 times.
|
475 | for (const CalendarOutboxEntry &entry : m_deps.store->outboxEntries()) |
| 270 |
7/8✓ Branch 10 → 11 taken 176 times.
✓ Branch 10 → 15 taken 2 times.
✓ Branch 11 → 12 taken 22 times.
✓ Branch 11 → 15 taken 154 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 22 times.
✓ Branch 16 → 17 taken 22 times.
✓ Branch 16 → 18 taken 156 times.
|
178 | if (entry.state == QLatin1String("pending") && entry.nextAttempt > now && |
| 271 | ✗ | (next == 0 || entry.nextAttempt < next)) | |
| 272 | 319 | next = entry.nextAttempt; | |
| 273 |
2/2✓ Branch 22 → 23 taken 275 times.
✓ Branch 22 → 25 taken 22 times.
|
297 | if (next == 0) { |
| 274 | 275 | m_retryTimer.stop(); | |
| 275 | 275 | return; | |
| 276 | } | ||
| 277 |
1/1✓ Branch 26 → 27 taken 22 times.
|
22 | m_retryTimer.start(int(qBound(qint64(1), next - now, qint64(1800)) * 1000)); |
| 278 | } | ||
| 279 | |||
| 280 | 231 | CalDavClient *CalendarOutbox::clientFor(const Endpoint &endpoint) { | |
| 281 |
1/1✓ Branch 3 → 4 taken 231 times.
|
231 | CalDavClient *client = m_deps.makeClient(endpoint.serverUrl, endpoint.username, |
| 282 |
1/1✓ Branch 2 → 3 taken 231 times.
|
462 | QString::fromUtf8(endpoint.password)); |
| 283 |
1/1✓ Branch 5 → 6 taken 231 times.
|
231 | connect(client, &CalDavClient::allRequestsFinished, client, &QObject::deleteLater); |
| 284 | 231 | return client; | |
| 285 | } | ||
| 286 | |||
| 287 | 184 | void CalendarOutbox::finishInFlight(const CalendarOutboxEntry &entry) { | |
| 288 |
2/2✓ Branch 2 → 3 taken 184 times.
✓ Branch 3 → 4 taken 184 times.
|
184 | m_inFlight.remove(key(entry.accountId, entry.calendarPath, entry.uid)); |
| 289 | 184 | } | |
| 290 | |||
| 291 | 142 | void CalendarOutbox::push(const CalendarOutboxEntry &entry, const Endpoint &endpoint) { | |
| 292 | 142 | CalendarStore *store = m_deps.store; | |
| 293 |
4/6✓ Branch 4 → 5 taken 17 times.
✓ Branch 4 → 8 taken 125 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 17 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 142 times.
|
142 | if (entry.operation == QLatin1String("delete") && entry.baseEtag.isEmpty()) { |
| 294 | ✗ | store->removeOutbox(entry.accountId, entry.calendarPath, entry.uid, entry.revision); | |
| 295 | ✗ | finishInFlight(entry); | |
| 296 | 17 | return; | |
| 297 | } | ||
| 298 | 142 | QList<CalendarEvent> rows; | |
| 299 |
2/2✓ Branch 15 → 16 taken 125 times.
✓ Branch 15 → 39 taken 17 times.
|
142 | if (entry.operation == QLatin1String("put")) { |
| 300 |
2/2✓ Branch 18 → 19 taken 43 times.
✓ Branch 18 → 30 taken 82 times.
|
125 | if (entry.componentType == QLatin1String("VTODO")) { |
| 301 |
2/3✓ Branch 19 → 20 taken 43 times.
✓ Branch 21 → 22 taken 43 times.
✗ Branch 21 → 28 not taken.
|
43 | if (const auto task = store->taskFor(entry.uid, entry.accountId, entry.calendarPath)) { |
| 302 |
1/1✓ Branch 25 → 26 taken 43 times.
|
43 | CalendarEvent marker; marker.uid = task->uid; rows << marker; |
| 303 | 86 | } | |
| 304 |
1/1✓ Branch 30 → 31 taken 82 times.
|
82 | } else rows = store->eventSeries(entry.uid, entry.accountId, entry.calendarPath); |
| 305 |
1/2✗ Branch 35 → 36 not taken.
✓ Branch 35 → 39 taken 125 times.
|
125 | if (rows.isEmpty()) { |
| 306 | ✗ | store->removeOutbox(entry.accountId, entry.calendarPath, entry.uid, entry.revision); | |
| 307 | ✗ | finishInFlight(entry); | |
| 308 | ✗ | return; | |
| 309 | } | ||
| 310 | } | ||
| 311 |
1/1✓ Branch 39 → 40 taken 142 times.
|
142 | CalDavClient *client = clientFor(endpoint); |
| 312 |
2/3✓ Branch 41 → 42 taken 142 times.
✗ Branch 41 → 46 not taken.
✓ Branch 42 → 43 taken 142 times.
|
142 | if (m_deps.pushDontNotify) client->setPushDontNotify(m_deps.pushDontNotify(entry.accountId, entry.calendarPath)); |
| 313 |
2/2✓ Branch 46 → 47 taken 142 times.
✓ Branch 47 → 48 taken 142 times.
|
142 | m_inFlight.insert(key(entry.accountId, entry.calendarPath, entry.uid)); |
| 314 |
2/2✓ Branch 51 → 52 taken 17 times.
✓ Branch 51 → 60 taken 125 times.
|
142 | if (entry.operation == QLatin1String("delete")) { |
| 315 |
1/1✓ Branch 52 → 53 taken 17 times.
|
17 | const QString url = client->eventResourceUrl(entry.href, entry.calendarPath, entry.uid); |
| 316 |
1/1✓ Branch 53 → 54 taken 17 times.
|
17 | const quint64 request = client->deleteResource(url, entry.baseEtag); |
| 317 |
1/3✓ Branch 56 → 57 taken 17 times.
✗ Branch 143 → 144 not taken.
✗ Branch 143 → 145 not taken.
|
17 | onResponse(client, request, this, [this, entry, endpoint](const DavResponse &response) { |
| 318 | 17 | onDeleteFinished(entry, response, endpoint); | |
| 319 | 17 | }); | |
| 320 | 17 | return; | |
| 321 | 17 | } | |
| 322 |
1/1✓ Branch 60 → 61 taken 125 times.
|
125 | const CalendarEvent master = masterOf(rows); |
| 323 | 125 | const QByteArray body = entry.componentType == QLatin1String("VTODO") | |
| 324 |
1/3✓ Branch 64 → 65 taken 43 times.
✗ Branch 154 → 155 not taken.
✗ Branch 154 → 156 not taken.
|
86 | ? ICalTodoCodec::buildResource(store->taskFor(entry.uid, entry.accountId, entry.calendarPath).value()) |
| 325 |
10/12✓ Branch 63 → 64 taken 43 times.
✓ Branch 63 → 67 taken 82 times.
✓ Branch 65 → 66 taken 43 times.
✓ Branch 66 → 69 taken 43 times.
✓ Branch 67 → 68 taken 82 times.
✓ Branch 68 → 69 taken 82 times.
✓ Branch 69 → 70 taken 82 times.
✓ Branch 69 → 71 taken 43 times.
✓ Branch 71 → 72 taken 43 times.
✓ Branch 71 → 73 taken 82 times.
✗ Branch 150 → 151 not taken.
✗ Branch 150 → 152 not taken.
|
168 | : CalDavClient::seriesToICalendar(master, exceptionsOf(rows, master)); |
| 326 |
1/4✗ Branch 74 → 75 not taken.
✓ Branch 74 → 83 taken 125 times.
✗ Branch 75 → 76 not taken.
✗ Branch 79 → 80 not taken.
|
125 | if (body.isEmpty()) { finishInFlight(entry); retryLater(entry, QStringLiteral("Unreadable stored resource"), true); return; } |
| 327 | 125 | const QString url = client->eventResourceUrl(entry.href.isEmpty() ? master.resourceHref : entry.href, | |
| 328 |
3/3✓ Branch 84 → 85 taken 41 times.
✓ Branch 84 → 86 taken 84 times.
✓ Branch 87 → 88 taken 125 times.
|
125 | entry.calendarPath, entry.uid); |
| 329 |
1/1✓ Branch 89 → 90 taken 125 times.
|
125 | const quint64 request = client->putResource(url, body, entry.baseEtag, entry.baseEtag.isEmpty()); |
| 330 |
8/8✓ Branch 90 → 91 taken 125 times.
✓ Branch 94 → 95 taken 125 times.
✓ Branch 95 → 96 taken 125 times.
✓ Branch 96 → 97 taken 125 times.
✓ Branch 97 → 98 taken 125 times.
✓ Branch 98 → 99 taken 125 times.
✓ Branch 107 → 92 taken 125 times.
✓ Branch 107 → 108 taken 125 times.
|
250 | qCInfo(lcCalendarOutbox) << "PUT" << entry.uid << "revision" << entry.revision |
| 331 |
3/3✓ Branch 100 → 101 taken 23 times.
✓ Branch 100 → 102 taken 102 times.
✓ Branch 103 → 104 taken 125 times.
|
125 | << (entry.baseEtag.isEmpty() ? "create" : "update"); |
| 332 |
1/7✓ Branch 111 → 112 taken 125 times.
✗ Branch 172 → 173 not taken.
✗ Branch 172 → 174 not taken.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
✗ Branch 178 → 179 not taken.
✗ Branch 178 → 180 not taken.
|
125 | onResponse(client, request, this, [this, entry, body, endpoint, client](const DavResponse &response) { |
| 333 |
2/2✓ Branch 2 → 3 taken 49 times.
✓ Branch 2 → 5 taken 72 times.
|
121 | if (response.status == 412) { |
| 334 | 49 | fetchServerVersion(entry, response.url, body, endpoint); | |
| 335 | 49 | return; | |
| 336 | } | ||
| 337 | Q_UNUSED(client); | ||
| 338 | 72 | onPutFinished(entry, body, response, endpoint); | |
| 339 | }); | ||
| 340 | 142 | } | |
| 341 | |||
| 342 | 72 | void CalendarOutbox::onPutFinished(const CalendarOutboxEntry &entry, const QByteArray &body, | |
| 343 | const DavResponse &response, const Endpoint &endpoint) { | ||
| 344 | 72 | CalendarStore *store = m_deps.store; | |
| 345 |
5/6✓ Branch 2 → 3 taken 72 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 53 times.
✓ Branch 3 → 5 taken 19 times.
✓ Branch 4 → 5 taken 33 times.
✓ Branch 4 → 75 taken 20 times.
|
72 | if (response.status == 200 || response.status == 201 || response.status == 204) { |
| 346 |
1/2✓ Branch 6 → 7 taken 52 times.
✗ Branch 6 → 8 not taken.
|
52 | const QString href = response.location.isEmpty() ? response.url : response.location; |
| 347 | // The pushed body becomes the base of every row of the resource. | ||
| 348 |
5/5✓ Branch 10 → 11 taken 52 times.
✓ Branch 11 → 12 taken 52 times.
✓ Branch 12 → 13 taken 52 times.
✓ Branch 23 → 14 taken 29 times.
✓ Branch 23 → 24 taken 52 times.
|
81 | for (CalendarEvent row : store->eventSeries(entry.uid, entry.accountId, entry.calendarPath)) { |
| 349 | 29 | row.etag = response.etag; | |
| 350 | 29 | row.resourceHref = href; | |
| 351 |
1/1✓ Branch 18 → 19 taken 29 times.
|
29 | ICalEventCodec::adoptPushedResource(&row, body); |
| 352 |
1/1✓ Branch 19 → 20 taken 29 times.
|
29 | store->upsertEvent(row); |
| 353 | 81 | } | |
| 354 |
2/2✓ Branch 27 → 28 taken 23 times.
✓ Branch 27 → 41 taken 29 times.
|
52 | if (entry.componentType == QLatin1String("VTODO")) { |
| 355 |
3/3✓ Branch 28 → 29 taken 23 times.
✓ Branch 30 → 31 taken 22 times.
✓ Branch 30 → 39 taken 1 time.
|
23 | if (auto task = store->taskFor(entry.uid, entry.accountId, entry.calendarPath)) { |
| 356 | 22 | task->etag = response.etag; task->resourceHref = href; | |
| 357 |
2/2✓ Branch 36 → 37 taken 22 times.
✓ Branch 38 → 39 taken 22 times.
|
22 | ICalTodoCodec::adoptPushedResource(&*task, body); store->upsertTask(*task); |
| 358 | 23 | } | |
| 359 | } | ||
| 360 |
1/1✓ Branch 42 → 43 taken 52 times.
|
52 | store->upsertResource(entry.accountId, entry.calendarPath, entry.uid, href, response.etag, |
| 361 |
1/1✓ Branch 41 → 42 taken 52 times.
|
104 | QString::fromUtf8(body)); |
| 362 |
3/3✓ Branch 44 → 45 taken 52 times.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 57 taken 50 times.
|
52 | if (!store->removeOutbox(entry.accountId, entry.calendarPath, entry.uid, entry.revision)) { |
| 363 | // A newer local change arrived during the push: rebase it. | ||
| 364 |
2/3✓ Branch 46 → 47 taken 2 times.
✓ Branch 48 → 49 taken 2 times.
✗ Branch 48 → 55 not taken.
|
2 | if (auto current = store->outboxEntry(entry.accountId, entry.calendarPath, entry.uid)) { |
| 365 | 2 | current->baseEtag = response.etag; | |
| 366 | 2 | current->href = href; | |
| 367 |
1/1✓ Branch 54 → 55 taken 2 times.
|
2 | store->updateOutbox(*current); |
| 368 | 2 | } | |
| 369 | } | ||
| 370 |
8/8✓ Branch 57 → 58 taken 52 times.
✓ Branch 61 → 62 taken 52 times.
✓ Branch 62 → 63 taken 52 times.
✓ Branch 63 → 64 taken 52 times.
✓ Branch 64 → 65 taken 52 times.
✓ Branch 65 → 66 taken 52 times.
✓ Branch 69 → 59 taken 52 times.
✓ Branch 69 → 70 taken 52 times.
|
104 | qCInfo(lcCalendarOutbox) << "Pushed" << entry.uid << "etag" << response.etag; |
| 371 |
1/1✓ Branch 70 → 71 taken 52 times.
|
52 | finishInFlight(entry); |
| 372 |
1/1✓ Branch 71 → 72 taken 52 times.
|
52 | publishChange(); |
| 373 |
1/1✓ Branch 72 → 73 taken 52 times.
|
52 | flush(false); |
| 374 | 52 | return; | |
| 375 | 52 | } | |
| 376 |
5/6✓ Branch 75 → 76 taken 8 times.
✓ Branch 75 → 79 taken 12 times.
✓ Branch 77 → 78 taken 8 times.
✗ Branch 77 → 79 not taken.
✓ Branch 80 → 81 taken 8 times.
✓ Branch 80 → 83 taken 12 times.
|
20 | if (response.status == 404 && !entry.baseEtag.isEmpty()) { |
| 377 | 8 | confirmDeletedOnServer(entry, response.url, endpoint); | |
| 378 | 8 | return; | |
| 379 | } | ||
| 380 |
2/2✓ Branch 84 → 85 taken 12 times.
✓ Branch 85 → 86 taken 12 times.
|
12 | retryLater(entry, responseError(response), !retryableStatus(response.status)); |
| 381 | } | ||
| 382 | |||
| 383 | 17 | void CalendarOutbox::onDeleteFinished(const CalendarOutboxEntry &entry, const DavResponse &response, | |
| 384 | const Endpoint &endpoint) { | ||
| 385 | 17 | CalendarStore *store = m_deps.store; | |
| 386 |
5/6✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 5 taken 2 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 8 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 33 taken 7 times.
|
17 | if (response.status == 200 || response.status == 204 || response.status == 404) { |
| 387 |
2/2✓ Branch 6 → 7 taken 9 times.
✓ Branch 6 → 8 taken 1 time.
|
10 | if (store->removeOutbox(entry.accountId, entry.calendarPath, entry.uid, entry.revision)) |
| 388 | 9 | store->removeResource(entry.accountId, entry.calendarPath, entry.uid); | |
| 389 |
2/3✓ Branch 8 → 9 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 15 not taken.
|
1 | else if (auto current = store->outboxEntry(entry.accountId, entry.calendarPath, entry.uid)) { |
| 390 |
1/1✓ Branch 14 → 15 taken 1 time.
|
1 | current->baseEtag.clear(); store->updateOutbox(*current); |
| 391 | 1 | } | |
| 392 |
7/7✓ Branch 17 → 18 taken 10 times.
✓ Branch 21 → 22 taken 10 times.
✓ Branch 22 → 23 taken 10 times.
✓ Branch 23 → 24 taken 10 times.
✓ Branch 24 → 25 taken 10 times.
✓ Branch 28 → 19 taken 10 times.
✓ Branch 28 → 29 taken 10 times.
|
20 | qCInfo(lcCalendarOutbox) << "Deleted" << entry.uid << "on the server"; |
| 393 | 10 | finishInFlight(entry); | |
| 394 | 10 | publishChange(); | |
| 395 | 10 | flush(false); | |
| 396 | 10 | return; | |
| 397 | } | ||
| 398 |
2/2✓ Branch 33 → 34 taken 6 times.
✓ Branch 33 → 38 taken 1 time.
|
7 | if (response.status == 412) { |
| 399 |
1/1✓ Branch 35 → 36 taken 6 times.
|
6 | fetchServerVersion(entry, response.url, QByteArray(), endpoint); |
| 400 | 6 | return; | |
| 401 | } | ||
| 402 |
2/2✓ Branch 39 → 40 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
|
1 | retryLater(entry, responseError(response), !retryableStatus(response.status)); |
| 403 | } | ||
| 404 | |||
| 405 | 55 | void CalendarOutbox::fetchServerVersion(const CalendarOutboxEntry &entry, const QString &url, | |
| 406 | const QByteArray &ours, const Endpoint &endpoint) { | ||
| 407 |
7/7✓ Branch 2 → 3 taken 55 times.
✓ Branch 6 → 7 taken 55 times.
✓ Branch 7 → 8 taken 55 times.
✓ Branch 8 → 9 taken 55 times.
✓ Branch 9 → 10 taken 55 times.
✓ Branch 13 → 4 taken 55 times.
✓ Branch 13 → 14 taken 55 times.
|
110 | qCInfo(lcCalendarOutbox) << "Precondition failed for" << entry.uid << "- fetching the server version"; |
| 408 | 55 | CalDavClient *client = clientFor(endpoint); | |
| 409 | 55 | const quint64 request = client->getResource(url); | |
| 410 |
1/3✓ Branch 18 → 19 taken 55 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
|
55 | onResponse(client, request, this, [this, entry, ours](const DavResponse &response) { |
| 411 | 55 | onServerVersion(entry, ours, response); | |
| 412 | 55 | }); | |
| 413 | 55 | } | |
| 414 | |||
| 415 | 57 | void CalendarOutbox::onServerVersion(const CalendarOutboxEntry &entry, const QByteArray &ours, | |
| 416 | const DavResponse &response) { | ||
| 417 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 18 taken 57 times.
|
57 | if (response.status == 404) { |
| 418 | ✗ | markConflict(entry, QString(), QString(), {QStringLiteral("deleted")}); | |
| 419 | 6 | return; | |
| 420 | } | ||
| 421 |
1/2✗ Branch 18 → 19 not taken.
✓ Branch 18 → 24 taken 57 times.
|
57 | if (response.status != 200) { |
| 422 | ✗ | retryLater(entry, responseError(response), !retryableStatus(response.status)); | |
| 423 | ✗ | return; | |
| 424 | } | ||
| 425 |
1/1✓ Branch 24 → 25 taken 57 times.
|
57 | const QString theirs = QString::fromUtf8(response.body); |
| 426 |
2/2✓ Branch 27 → 28 taken 6 times.
✓ Branch 27 → 43 taken 51 times.
|
57 | if (entry.operation == QLatin1String("delete")) { |
| 427 | // D-22 (Sprint 107): the source of a task move changed on the server — | ||
| 428 | // its changes travel into the moved task instead of a question. | ||
| 429 |
3/3✓ Branch 28 → 29 taken 6 times.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 29 → 32 taken 2 times.
|
6 | if (isMoveSource(entry)) { |
| 430 |
1/1✓ Branch 30 → 31 taken 4 times.
|
4 | mergeMovedSource(entry, theirs, response.etag); |
| 431 | 4 | return; | |
| 432 | } | ||
| 433 |
3/5✓ Branch 36 → 37 taken 2 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 41 taken 2 times.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
|
4 | markConflict(entry, theirs, response.etag, {QStringLiteral("changed")}); |
| 434 | 2 | return; | |
| 435 | } | ||
| 436 |
1/1✓ Branch 43 → 44 taken 51 times.
|
51 | mergeServerVersion(entry, ours, theirs, response.etag); |
| 437 | 61 | } | |
| 438 | |||
| 439 | 51 | void CalendarOutbox::mergeServerVersion(const CalendarOutboxEntry &entry, const QByteArray &ours, | |
| 440 | const QString &theirs, const QString &etag) { | ||
| 441 | 51 | CalendarStore *store = m_deps.store; | |
| 442 |
1/1✓ Branch 2 → 3 taken 51 times.
|
51 | const QString base = store->resourceFor(entry.accountId, entry.calendarPath, entry.uid); |
| 443 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 16 taken 51 times.
|
51 | if (base.isEmpty()) { |
| 444 | ✗ | markConflict(entry, theirs, etag, {QStringLiteral("created")}); | |
| 445 | ✗ | return; | |
| 446 | } | ||
| 447 | // "Ours" is what the store holds now — a local edit made while the GET | ||
| 448 | // was in flight is part of it (S106-158: the VEVENT path merged the body | ||
| 449 | // captured at push time and the newer edit was lost on adoption). | ||
| 450 |
1/1✓ Branch 16 → 17 taken 51 times.
|
51 | QByteArray currentOurs = localBody(entry); |
| 451 |
1/2✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 51 times.
|
51 | if (currentOurs.isEmpty()) |
| 452 | ✗ | currentOurs = ours; | |
| 453 |
2/2✓ Branch 20 → 21 taken 51 times.
✓ Branch 21 → 22 taken 51 times.
|
51 | const ICalMerge::Result merged = ICalMerge::merge(base, QString::fromUtf8(currentOurs), theirs); |
| 454 |
2/2✓ Branch 23 → 24 taken 44 times.
✓ Branch 23 → 40 taken 7 times.
|
51 | if (!merged.ok) { |
| 455 |
1/1✓ Branch 31 → 32 taken 44 times.
|
44 | markConflict(entry, theirs, etag, |
| 456 |
3/14✗ Branch 25 → 26 not taken.
✓ Branch 25 → 30 taken 44 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 37 taken 44 times.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 39 taken 44 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 119 not taken.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
|
88 | merged.conflicts.isEmpty() ? QStringList{QStringLiteral("unreadable")} : merged.conflicts); |
| 457 | 44 | return; | |
| 458 | } | ||
| 459 | // The merge becomes the local state; the next push sends it unchanged. | ||
| 460 | 7 | CalendarOutboxEntry rebased = entry; | |
| 461 |
2/3✓ Branch 41 → 42 taken 7 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 7 times.
|
7 | if (!adoptServerVersion(rebased, merged.merged, etag)) |
| 462 | ✗ | return; | |
| 463 |
1/1✓ Branch 47 → 48 taken 7 times.
|
7 | store->enqueueOutbox(entry.accountId, entry.calendarPath, entry.uid, QStringLiteral("put"), |
| 464 | 7 | entry.href, etag); | |
| 465 |
2/3✓ Branch 50 → 51 taken 7 times.
✓ Branch 52 → 53 taken 7 times.
✗ Branch 52 → 59 not taken.
|
7 | if (auto current = store->outboxEntry(entry.accountId, entry.calendarPath, entry.uid)) { |
| 466 | 7 | current->baseEtag = etag; | |
| 467 | 7 | current->componentType = entry.componentType; | |
| 468 |
1/1✓ Branch 58 → 59 taken 7 times.
|
7 | store->updateOutbox(*current); |
| 469 | 7 | } | |
| 470 |
6/6✓ Branch 60 → 61 taken 7 times.
✓ Branch 64 → 65 taken 7 times.
✓ Branch 65 → 66 taken 7 times.
✓ Branch 66 → 67 taken 7 times.
✓ Branch 70 → 62 taken 7 times.
✓ Branch 70 → 71 taken 7 times.
|
14 | qCInfo(lcCalendarOutbox) << "Merged server change into" << entry.uid; |
| 471 |
2/2✓ Branch 71 → 72 taken 7 times.
✓ Branch 72 → 73 taken 7 times.
|
7 | emit statusMessage(tr("Your change was combined with a change made on the server.")); |
| 472 |
1/1✓ Branch 74 → 75 taken 7 times.
|
7 | flush(true); |
| 473 | 139 | } | |
| 474 | |||
| 475 | // The resource this computer would push now ("" when no row is stored). | ||
| 476 | 201 | QByteArray CalendarOutbox::localBody(const CalendarOutboxEntry &entry) const { | |
| 477 | 201 | CalendarStore *store = m_deps.store; | |
| 478 |
2/2✓ Branch 4 → 5 taken 69 times.
✓ Branch 4 → 14 taken 132 times.
|
201 | if (entry.componentType == QLatin1String("VTODO")) { |
| 479 |
1/1✓ Branch 5 → 6 taken 69 times.
|
69 | const auto task = store->taskFor(entry.uid, entry.accountId, entry.calendarPath); |
| 480 |
3/3✓ Branch 7 → 8 taken 67 times.
✓ Branch 7 → 10 taken 2 times.
✓ Branch 9 → 11 taken 67 times.
|
69 | return task ? ICalTodoCodec::buildResource(*task) : QByteArray(); |
| 481 | 69 | } | |
| 482 |
1/1✓ Branch 14 → 15 taken 132 times.
|
132 | const QList<CalendarEvent> rows = store->eventSeries(entry.uid, entry.accountId, entry.calendarPath); |
| 483 |
1/2✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 132 times.
|
132 | if (rows.isEmpty()) |
| 484 | ✗ | return {}; | |
| 485 |
1/1✓ Branch 18 → 19 taken 132 times.
|
132 | const CalendarEvent master = masterOf(rows); |
| 486 |
2/2✓ Branch 19 → 20 taken 132 times.
✓ Branch 20 → 21 taken 132 times.
|
132 | return CalDavClient::seriesToICalendar(master, exceptionsOf(rows, master)); |
| 487 | 132 | } | |
| 488 | |||
| 489 | // ─── Outcomes ────────────────────────────────────────────── | ||
| 490 | |||
| 491 | 15 | void CalendarOutbox::retryLater(const CalendarOutboxEntry &entry, const QString &error, bool permanent) { | |
| 492 | 15 | CalendarStore *store = m_deps.store; | |
| 493 |
1/1✓ Branch 2 → 3 taken 15 times.
|
15 | finishInFlight(entry); |
| 494 |
1/1✓ Branch 3 → 4 taken 15 times.
|
15 | auto current = store->outboxEntry(entry.accountId, entry.calendarPath, entry.uid); |
| 495 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 15 times.
|
15 | if (!current) |
| 496 | ✗ | return; | |
| 497 | 15 | current->attempts += 1; | |
| 498 | // F-19 (SPRINT-104): a change the server rejects for good (403 on a | ||
| 499 | // read-only calendar, 413, 507) gets BOUNDED retries with a visible | ||
| 500 | // terminal state — not one every 30 minutes forever and no way to | ||
| 501 | // discard it. Transient failures keep the ordinary backoff. | ||
| 502 |
6/6✓ Branch 8 → 9 taken 8 times.
✓ Branch 8 → 12 taken 7 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 12 taken 6 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 47 taken 13 times.
|
15 | if (permanent && current->attempts >= kMaxPermanentAttempts) { |
| 503 | 2 | current->state = QStringLiteral("failed"); | |
| 504 | 2 | current->nextAttempt = 0; | |
| 505 | 2 | current->lastError = error; | |
| 506 |
1/1✓ Branch 25 → 26 taken 2 times.
|
2 | store->updateOutbox(*current); |
| 507 |
6/6✓ Branch 26 → 27 taken 2 times.
✓ Branch 30 → 31 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 32 → 33 taken 2 times.
✓ Branch 41 → 28 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
|
4 | qCWarning(lcCalendarOutbox) << "Push of" << entry.uid |
| 508 |
4/4✓ Branch 33 → 34 taken 2 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
|
2 | << "gave up after" << current->attempts << "attempts:" << error; |
| 509 |
1/1✓ Branch 42 → 43 taken 2 times.
|
2 | publishChange(); |
| 510 |
2/2✓ Branch 43 → 44 taken 2 times.
✓ Branch 44 → 45 taken 2 times.
|
2 | emit statusMessage(tr("The server keeps refusing this change. It stays in this calendar as not yet on the server.")); |
| 511 | 2 | return; | |
| 512 | } | ||
| 513 |
4/4✓ Branch 47 → 48 taken 13 times.
✓ Branch 48 → 49 taken 6 times.
✓ Branch 48 → 50 taken 7 times.
✓ Branch 51 → 52 taken 7 times.
|
13 | current->nextAttempt = m_deps.now() + (permanent ? 1800 : backoffSeconds(current->attempts)); |
| 514 | 13 | current->lastError = error; | |
| 515 |
1/1✓ Branch 57 → 58 taken 13 times.
|
13 | store->updateOutbox(*current); |
| 516 |
8/8✓ Branch 58 → 59 taken 13 times.
✓ Branch 62 → 63 taken 13 times.
✓ Branch 63 → 64 taken 13 times.
✓ Branch 64 → 65 taken 13 times.
✓ Branch 65 → 66 taken 13 times.
✓ Branch 67 → 68 taken 13 times.
✓ Branch 73 → 60 taken 13 times.
✓ Branch 73 → 74 taken 13 times.
|
26 | qCWarning(lcCalendarOutbox) << "Push of" << entry.uid << "failed (attempt" << current->attempts |
| 517 |
2/2✓ Branch 68 → 69 taken 13 times.
✓ Branch 69 → 70 taken 13 times.
|
13 | << "):" << error; |
| 518 |
1/1✓ Branch 74 → 75 taken 13 times.
|
13 | publishChange(); |
| 519 |
5/5✓ Branch 75 → 76 taken 6 times.
✓ Branch 75 → 77 taken 7 times.
✓ Branch 76 → 78 taken 6 times.
✓ Branch 77 → 78 taken 7 times.
✓ Branch 78 → 79 taken 13 times.
|
13 | emit statusMessage(permanent ? tr("The server did not accept the change. MailJD will try again later.") |
| 520 | : tr("Not yet on the server. MailJD retries automatically.")); | ||
| 521 |
1/1✓ Branch 80 → 81 taken 13 times.
|
13 | scheduleRetry(); |
| 522 | 15 | } | |
| 523 | |||
| 524 | 52 | void CalendarOutbox::markConflict(const CalendarOutboxEntry &entry, const QString &serverIcal, | |
| 525 | const QString &serverEtag, const QStringList &fields) { | ||
| 526 | 52 | CalendarStore *store = m_deps.store; | |
| 527 |
1/1✓ Branch 2 → 3 taken 52 times.
|
52 | finishInFlight(entry); |
| 528 |
1/1✓ Branch 3 → 4 taken 52 times.
|
52 | auto current = store->outboxEntry(entry.accountId, entry.calendarPath, entry.uid); |
| 529 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 52 times.
|
52 | if (!current) |
| 530 | ✗ | return; | |
| 531 | 52 | current->state = QStringLiteral("conflict"); | |
| 532 | 52 | current->serverIcal = serverIcal; | |
| 533 | 52 | current->serverEtag = serverEtag; | |
| 534 | 52 | current->conflictFields = fields; | |
| 535 |
1/1✓ Branch 21 → 22 taken 52 times.
|
52 | store->updateOutbox(*current); |
| 536 |
7/7✓ Branch 22 → 23 taken 52 times.
✓ Branch 26 → 27 taken 52 times.
✓ Branch 27 → 28 taken 52 times.
✓ Branch 28 → 29 taken 52 times.
✓ Branch 30 → 31 taken 52 times.
✓ Branch 36 → 24 taken 52 times.
✓ Branch 36 → 37 taken 52 times.
|
104 | qCWarning(lcCalendarOutbox) << "Conflict on" << entry.uid << fields; |
| 537 |
1/1✓ Branch 37 → 38 taken 52 times.
|
52 | publishChange(); |
| 538 |
2/2✓ Branch 38 → 39 taken 52 times.
✓ Branch 39 → 40 taken 52 times.
|
52 | emit statusMessage(tr("Someone changed this appointment on the server. Open it to choose which version to keep.")); |
| 539 | 52 | } | |
| 540 | |||
| 541 | 29 | bool CalendarOutbox::adoptServerVersion(const CalendarOutboxEntry &entry, const QString &ical, | |
| 542 | const QString &etag) { | ||
| 543 | 29 | CalendarStore *store = m_deps.store; | |
| 544 |
2/2✓ Branch 4 → 5 taken 13 times.
✓ Branch 4 → 64 taken 16 times.
|
29 | if (entry.componentType == QLatin1String("VTODO")) { |
| 545 | 13 | ICalTodoCodec::Resource resource; | |
| 546 |
5/7✓ Branch 6 → 7 taken 11 times.
✓ Branch 6 → 10 taken 2 times.
✓ Branch 7 → 8 taken 11 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 11 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 13 times.
|
13 | if (!ical.isEmpty() && !ICalTodoCodec::parseResource(ical, &resource)) return false; |
| 547 |
2/2✓ Branch 13 → 14 taken 13 times.
✓ Branch 14 → 15 taken 13 times.
|
13 | store->beginTransaction(); store->deleteTask(entry.uid, entry.accountId, entry.calendarPath); |
| 548 |
4/4✓ Branch 15 → 16 taken 13 times.
✓ Branch 16 → 17 taken 13 times.
✓ Branch 52 → 18 taken 11 times.
✓ Branch 52 → 53 taken 13 times.
|
24 | for (auto task : resource.tasks) { |
| 549 |
10/25✓ Branch 21 → 22 taken 11 times.
✗ Branch 21 → 29 not taken.
✓ Branch 25 → 26 taken 11 times.
✓ Branch 26 → 27 taken 11 times.
✓ Branch 27 → 28 taken 11 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 11 times.
✓ Branch 31 → 32 taken 11 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 11 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 11 times.
✗ Branch 35 → 37 not taken.
✓ Branch 37 → 38 taken 11 times.
✗ Branch 37 → 39 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 11 times.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
|
22 | if (task.uid != entry.uid || ICal::parseFirst(task.icalRaw, QStringLiteral("VTODO")).hasProperty("RECURRENCE-ID")) continue; |
| 550 | 11 | task.accountId = entry.accountId; task.calendarPath = entry.calendarPath; | |
| 551 |
1/1✓ Branch 45 → 46 taken 11 times.
|
11 | task.etag = etag; task.resourceHref = entry.href; store->upsertTask(task); |
| 552 | 11 | } | |
| 553 |
3/3✓ Branch 54 → 55 taken 2 times.
✓ Branch 54 → 56 taken 11 times.
✓ Branch 55 → 57 taken 2 times.
|
13 | if (ical.isEmpty()) store->removeResource(entry.accountId, entry.calendarPath, entry.uid); |
| 554 |
1/1✓ Branch 56 → 57 taken 11 times.
|
11 | else store->upsertResource(entry.accountId, entry.calendarPath, entry.uid, entry.href, etag, ical); |
| 555 |
2/2✓ Branch 57 → 58 taken 13 times.
✓ Branch 58 → 59 taken 13 times.
|
13 | store->removeOutbox(entry.accountId, entry.calendarPath, entry.uid); store->commitTransaction(); |
| 556 |
2/2✓ Branch 59 → 60 taken 13 times.
✓ Branch 60 → 61 taken 13 times.
|
13 | finishInFlight(entry); publishChange(); return true; |
| 557 | 13 | } | |
| 558 | 16 | ICalEventCodec::Resource resource; | |
| 559 |
5/7✓ Branch 65 → 66 taken 15 times.
✓ Branch 65 → 69 taken 1 time.
✓ Branch 66 → 67 taken 15 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 69 taken 15 times.
✗ Branch 70 → 71 not taken.
✓ Branch 70 → 84 taken 16 times.
|
16 | if (!ical.isEmpty() && !ICalEventCodec::parseResource(ical, &resource)) { |
| 560 | ✗ | qCWarning(lcCalendarOutbox) << "Server version of" << entry.uid << "cannot be read"; | |
| 561 | ✗ | return false; | |
| 562 | } | ||
| 563 | 16 | const QString href = entry.href; | |
| 564 |
1/1✓ Branch 85 → 86 taken 16 times.
|
16 | store->beginTransaction(); |
| 565 |
1/1✓ Branch 87 → 88 taken 16 times.
|
16 | store->deleteEvent(entry.uid, entry.accountId, entry.calendarPath); |
| 566 |
4/4✓ Branch 89 → 90 taken 16 times.
✓ Branch 90 → 91 taken 16 times.
✓ Branch 108 → 92 taken 15 times.
✓ Branch 108 → 109 taken 16 times.
|
31 | for (CalendarEvent event : resource.events) { |
| 567 |
1/2✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 15 times.
|
15 | if (event.uid != entry.uid) |
| 568 | ✗ | continue; | |
| 569 | 15 | event.accountId = entry.accountId; | |
| 570 | 15 | event.calendarPath = entry.calendarPath; | |
| 571 | 15 | event.etag = etag; | |
| 572 | 15 | event.resourceHref = href; | |
| 573 |
1/1✓ Branch 101 → 102 taken 15 times.
|
15 | store->upsertEvent(event); |
| 574 | 15 | } | |
| 575 |
1/1✓ Branch 109 → 110 taken 16 times.
|
16 | store->commitTransaction(); |
| 576 |
2/2✓ Branch 111 → 112 taken 1 time.
✓ Branch 111 → 113 taken 15 times.
|
16 | if (ical.isEmpty()) |
| 577 |
1/1✓ Branch 112 → 114 taken 1 time.
|
1 | store->removeResource(entry.accountId, entry.calendarPath, entry.uid); |
| 578 | else | ||
| 579 |
1/1✓ Branch 113 → 114 taken 15 times.
|
15 | store->upsertResource(entry.accountId, entry.calendarPath, entry.uid, href, etag, ical); |
| 580 |
1/1✓ Branch 114 → 115 taken 16 times.
|
16 | store->removeOutbox(entry.accountId, entry.calendarPath, entry.uid); |
| 581 |
1/1✓ Branch 115 → 116 taken 16 times.
|
16 | finishInFlight(entry); |
| 582 |
1/1✓ Branch 116 → 117 taken 16 times.
|
16 | publishChange(); |
| 583 | 16 | return true; | |
| 584 | 16 | } | |
| 585 |