app/core/DavPushAccount.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "DavPushAccount.h" | ||
| 2 | |||
| 3 | #include <utility> | ||
| 4 | |||
| 5 | #include "DavPushStatistics.h" | ||
| 6 | #include "service/DavPushXml.h" | ||
| 7 | #include "service/WebPushPayloadCodec.h" | ||
| 8 | #include "util/SecureUtil.h" | ||
| 9 | |||
| 10 | #include <QCryptographicHash> | ||
| 11 | #include <QLoggingCategory> | ||
| 12 | #include <QRandomGenerator> | ||
| 13 | #include <QUuid> | ||
| 14 | |||
| 15 |
4/7✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 92 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
95 | Q_LOGGING_CATEGORY(lcDavPush, "mailjd.davpush") |
| 16 | |||
| 17 | namespace { | ||
| 18 | constexpr int kMinRetrySeconds = 60; | ||
| 19 | constexpr int kMaxRetrySeconds = 3600; | ||
| 20 | |||
| 21 | 6 | int retryDelay(int failures, int retryAfter) { | |
| 22 | 6 | const qint64 exponential = qint64(kMinRetrySeconds) << qMin(failures, 6); | |
| 23 | 6 | return int(qMax<qint64>(retryAfter, qMin<qint64>(exponential, kMaxRetrySeconds))); | |
| 24 | } | ||
| 25 | } // namespace | ||
| 26 | |||
| 27 | 100 | DavPushAccount::DavPushAccount(const QString &accountId, Context context, QObject *parent) | |
| 28 |
1/1✓ Branch 18 → 19 taken 100 times.
|
100 | : QObject(parent), m_accountId(accountId), m_ctx(std::move(context)) { |
| 29 | 100 | m_monotonic.start(); | |
| 30 |
1/1✓ Branch 23 → 24 taken 100 times.
|
100 | m_renewal.setSingleShot(true); |
| 31 |
1/1✓ Branch 24 → 25 taken 100 times.
|
100 | connect(&m_renewal, &QTimer::timeout, this, &DavPushAccount::converge); |
| 32 |
1/1✓ Branch 26 → 27 taken 100 times.
|
100 | m_receiverRecord = m_ctx.store->receiver(accountId); |
| 33 | 100 | } | |
| 34 | |||
| 35 | 200 | DavPushAccount::~DavPushAccount() { | |
| 36 | 100 | stopReceiver(); | |
| 37 | 200 | } | |
| 38 | |||
| 39 | 68 | QString DavPushAccount::collectionScope(const QString &path) const { | |
| 40 | 68 | return DavPushStatistics::collectionScope(m_accountId, path); | |
| 41 | } | ||
| 42 | |||
| 43 | 191 | std::optional<qint64> DavPushAccount::pendingPushSince(const QString &path) const { | |
| 44 |
2/2✓ Branch 3 → 4 taken 190 times.
✓ Branch 3 → 7 taken 1 time.
|
191 | if (!m_pushSince.contains(path)) return std::nullopt; |
| 45 | 1 | return m_monotonic.elapsed() - m_pushSince.value(path); | |
| 46 | } | ||
| 47 | |||
| 48 | 167 | void DavPushAccount::apply(const Target &target) { | |
| 49 | 167 | const bool receiverChanged = !(target.receiver == m_target.receiver); | |
| 50 | 167 | m_target = target; | |
| 51 |
2/2✓ Branch 4 → 5 taken 43 times.
✓ Branch 4 → 8 taken 124 times.
|
167 | if (receiverChanged) { |
| 52 | 43 | ++m_generation; | |
| 53 | 43 | m_secretLoading = false; | |
| 54 | 43 | m_tokenLoaded = false; | |
| 55 | // S106-073: the subscriber token belongs to the previous service. A new | ||
| 56 | // service configured without a token must not receive it as a bearer. | ||
| 57 | 43 | SecureUtil::zeroMemory(m_providerToken); | |
| 58 | 43 | m_providerToken.clear(); | |
| 59 | 43 | stopReceiver(); // restarted with the new service/token below | |
| 60 | } | ||
| 61 |
6/6✓ Branch 8 → 9 taken 92 times.
✓ Branch 8 → 11 taken 75 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 91 times.
✓ Branch 13 → 14 taken 76 times.
✓ Branch 13 → 16 taken 91 times.
|
167 | if (!target.active || !target.receiver.isConfigured()) { |
| 62 | 76 | pause(); | |
| 63 | 76 | return; | |
| 64 | } | ||
| 65 |
1/2✗ Branch 17 → 18 not taken.
✓ Branch 17 → 30 taken 91 times.
|
91 | if (!WebPushPayloadCodec::available(&m_receiverProblem)) { |
| 66 | ✗ | m_receiverProblem = QStringLiteral("cryptography unavailable: %1").arg(m_receiverProblem); | |
| 67 | ✗ | stopReceiver(); | |
| 68 | ✗ | emit changed(); | |
| 69 | ✗ | return; | |
| 70 | } | ||
| 71 | 91 | m_receiverProblem.clear(); | |
| 72 |
9/11✓ Branch 32 → 33 taken 38 times.
✓ Branch 32 → 37 taken 53 times.
✓ Branch 33 → 34 taken 38 times.
✓ Branch 35 → 36 taken 36 times.
✓ Branch 35 → 37 taken 2 times.
✓ Branch 38 → 39 taken 38 times.
✓ Branch 38 → 40 taken 53 times.
✓ Branch 40 → 41 taken 36 times.
✓ Branch 40 → 44 taken 55 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
91 | if (target.desiredPaths.isEmpty() && m_ctx.store->collections(m_accountId).isEmpty()) { |
| 73 | 36 | stopReceiver(); | |
| 74 | 36 | emit changed(); | |
| 75 | 36 | return; | |
| 76 | } | ||
| 77 | 55 | ensureReceiver(); | |
| 78 | } | ||
| 79 | |||
| 80 | 246 | void DavPushAccount::pause() { | |
| 81 | 246 | stopReceiver(); | |
| 82 | 246 | m_renewal.stop(); | |
| 83 | 246 | m_nextRenewalAt = 0; | |
| 84 | 246 | emit changed(); | |
| 85 | 246 | } | |
| 86 | |||
| 87 | 1 | void DavPushAccount::forget() { | |
| 88 | 1 | ++m_generation; | |
| 89 | 1 | stopReceiver(); | |
| 90 | 1 | m_renewal.stop(); | |
| 91 |
1/2✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 14 not taken.
|
1 | if (m_receiverRecord) { |
| 92 | 1 | releaseCurrentReceiver(); | |
| 93 |
2/2✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
|
1 | m_ctx.secrets->remove(this, DavPushSecretKeys::receiver(m_ctx.installationId, m_receiverRecord->receiverId), |
| 94 | ✗ | [](bool) {}); | |
| 95 | 1 | m_ctx.store->removeReceiver(m_accountId); | |
| 96 | } | ||
| 97 |
6/6✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 19 → 20 taken 3 times.
✓ Branch 22 → 18 taken 3 times.
✓ Branch 22 → 23 taken 1 time.
|
4 | for (const auto &record : m_ctx.store->collections(m_accountId)) m_ctx.store->removeCollection(m_accountId, record.path); |
| 98 | 1 | m_ctx.store->removeCleanupForAccount(m_accountId); | |
| 99 | 1 | m_receiverRecord.reset(); | |
| 100 | 1 | m_secret.reset(); | |
| 101 | 1 | emit changed(); | |
| 102 | 1 | } | |
| 103 | |||
| 104 | 1 | void DavPushAccount::reconnect() { | |
| 105 |
6/6✓ Branch 2 → 3 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 12 → 4 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
|
2 | qCInfo(lcDavPush) << "Reconnect push for" << m_accountId; |
| 106 | 1 | ++m_generation; | |
| 107 | 1 | stopReceiver(); | |
| 108 | // Renew every own registration (same endpoint: the server updates them). | ||
| 109 |
4/5✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 26 → 18 not taken.
✓ Branch 26 → 27 taken 1 time.
|
1 | for (auto record : m_ctx.store->collections(m_accountId)) { |
| 110 | ✗ | record.nextRetryAt = 0; | |
| 111 | ✗ | record.failures = 0; | |
| 112 | ✗ | record.expiresAt = qMin(record.expiresAt, m_ctx.now()); | |
| 113 | ✗ | m_ctx.store->saveCollection(record); | |
| 114 | 1 | } | |
| 115 | 1 | m_reportedFailure = false; | |
| 116 | 1 | apply(m_target); | |
| 117 | 1 | } | |
| 118 | |||
| 119 | 1 | void DavPushAccount::reloadToken() { | |
| 120 | 1 | ++m_generation; | |
| 121 | 1 | m_secretLoading = false; | |
| 122 | 1 | m_tokenLoaded = false; | |
| 123 | 1 | m_providerToken.clear(); | |
| 124 | 1 | stopReceiver(); | |
| 125 | 1 | } | |
| 126 | |||
| 127 | // --- receiver ------------------------------------------------------------- | ||
| 128 | |||
| 129 | 92 | void DavPushAccount::ensureReceiver() { | |
| 130 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 92 times.
|
181 | if (m_secretLoading) return; |
| 131 | 92 | const auto &wanted = m_target.receiver; | |
| 132 |
7/8✓ Branch 5 → 6 taken 64 times.
✓ Branch 5 → 13 taken 28 times.
✓ Branch 8 → 9 taken 60 times.
✓ Branch 8 → 12 taken 4 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 60 times.
✓ Branch 14 → 15 taken 4 times.
✓ Branch 14 → 31 taken 88 times.
|
92 | if (m_receiverRecord && (m_receiverRecord->server != wanted.server || m_receiverRecord->provider != wanted.provider)) { |
| 133 |
6/6✓ Branch 15 → 16 taken 4 times.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 21 → 22 taken 4 times.
✓ Branch 25 → 17 taken 4 times.
✓ Branch 25 → 26 taken 4 times.
|
8 | qCInfo(lcDavPush) << "Push service changed, preparing a replacement receiver for" << m_accountId; |
| 134 |
1/1✓ Branch 26 → 27 taken 4 times.
|
4 | stopReceiver(); |
| 135 |
1/1✓ Branch 27 → 28 taken 4 times.
|
4 | releaseCurrentReceiver(); |
| 136 | 4 | m_secret.reset(); | |
| 137 |
1/1✓ Branch 29 → 30 taken 4 times.
|
4 | createReceiver(); |
| 138 | 4 | return; | |
| 139 | } | ||
| 140 |
2/2✓ Branch 32 → 33 taken 28 times.
✓ Branch 32 → 35 taken 60 times.
|
88 | if (!m_receiverRecord) { |
| 141 |
1/1✓ Branch 33 → 34 taken 28 times.
|
28 | createReceiver(); |
| 142 | 28 | return; | |
| 143 | } | ||
| 144 |
8/8✓ Branch 36 → 37 taken 57 times.
✓ Branch 36 → 40 taken 3 times.
✓ Branch 37 → 38 taken 6 times.
✓ Branch 37 → 40 taken 51 times.
✓ Branch 38 → 39 taken 3 times.
✓ Branch 38 → 40 taken 3 times.
✓ Branch 41 → 42 taken 3 times.
✓ Branch 41 → 48 taken 57 times.
|
60 | if (m_secret && wanted.hasToken && !m_tokenLoaded) { |
| 145 | // Optional subscriber token of the push service (never the DAV password). | ||
| 146 | 3 | m_secretLoading = true; | |
| 147 | 3 | const quint64 generation = m_generation; | |
| 148 |
2/2✓ Branch 43 → 44 taken 3 times.
✓ Branch 44 → 45 taken 3 times.
|
3 | m_ctx.secrets->read(this, DavPushSecretKeys::providerToken(m_ctx.installationId), |
| 149 | 3 | [this, generation](bool found, QByteArray value) { | |
| 150 | 3 | m_secretLoading = false; | |
| 151 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
|
3 | if (generation != m_generation) return; |
| 152 | 3 | m_tokenLoaded = true; | |
| 153 |
2/2✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 1 time.
|
3 | m_providerToken = found ? value : QByteArray(); |
| 154 | 3 | SecureUtil::zeroMemory(value); | |
| 155 | 3 | ensureReceiver(); | |
| 156 | }); | ||
| 157 | 3 | return; | |
| 158 | } | ||
| 159 |
2/2✓ Branch 49 → 50 taken 54 times.
✓ Branch 49 → 56 taken 3 times.
|
57 | if (m_secret) { |
| 160 | // S106-074: without a wanted collection the stream has no purpose; only | ||
| 161 | // the cleanup of the remaining registrations runs (it needs no stream). | ||
| 162 |
2/2✓ Branch 51 → 52 taken 2 times.
✓ Branch 51 → 53 taken 52 times.
|
54 | if (m_target.desiredPaths.isEmpty()) |
| 163 |
1/1✓ Branch 52 → 54 taken 2 times.
|
2 | stopReceiver(); |
| 164 | else | ||
| 165 |
1/1✓ Branch 53 → 54 taken 52 times.
|
52 | startReceiver(); |
| 166 |
1/1✓ Branch 54 → 55 taken 54 times.
|
54 | converge(); |
| 167 | 54 | return; | |
| 168 | } | ||
| 169 | 3 | m_secretLoading = true; | |
| 170 | 3 | const quint64 generation = m_generation; | |
| 171 |
1/1✓ Branch 57 → 58 taken 3 times.
|
3 | const QString key = DavPushSecretKeys::receiver(m_ctx.installationId, m_receiverRecord->receiverId); |
| 172 |
1/1✓ Branch 59 → 60 taken 3 times.
|
3 | m_ctx.secrets->read(this, key, [this, generation](bool found, QByteArray value) { |
| 173 | 3 | m_secretLoading = false; | |
| 174 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
|
3 | if (generation != m_generation) return; |
| 175 |
3/3✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 1 time.
✓ Branch 5 → 7 taken 2 times.
|
3 | m_secret = found ? DavPushReceiverSecret::parse(value) : std::nullopt; |
| 176 | 3 | SecureUtil::zeroMemory(value); | |
| 177 |
6/6✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 16 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 33 taken 2 times.
|
3 | if (m_secret && !m_secret->hasProviderChannel()) { |
| 178 | // Channel of an earlier build that ntfy servers with subscriber-based | ||
| 179 | // rate limiting refuse (507): a new endpoint, registered anew. | ||
| 180 |
7/7✓ Branch 18 → 19 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 29 → 20 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
|
2 | qCInfo(lcDavPush) << "Receiver channel of an earlier format for" << m_accountId << "- replacing the receiver"; |
| 181 | 1 | m_secret.reset(); | |
| 182 | 1 | createReceiver(); | |
| 183 | 1 | return; | |
| 184 | } | ||
| 185 |
2/2✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 49 taken 1 time.
|
2 | if (!m_secret) { |
| 186 | // Lost or unreadable keyring entry: a new endpoint and new keys — | ||
| 187 | // never renew the old registration with keys it does not know. | ||
| 188 |
7/7✓ Branch 35 → 36 taken 1 time.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 42 → 43 taken 1 time.
✓ Branch 46 → 37 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
|
2 | qCWarning(lcDavPush) << "Receiver secret missing for" << m_accountId << "- replacing the receiver"; |
| 189 | 1 | createReceiver(); | |
| 190 | 1 | return; | |
| 191 | } | ||
| 192 | 1 | ensureReceiver(); | |
| 193 | }); | ||
| 194 | 3 | } | |
| 195 | |||
| 196 | 34 | void DavPushAccount::createReceiver() { | |
| 197 | 34 | QString error; | |
| 198 |
1/1✓ Branch 2 → 3 taken 34 times.
|
34 | const auto keys = WebPushPayloadCodec::generateKeys(&error); |
| 199 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 8 taken 34 times.
|
34 | if (!keys) { |
| 200 | ✗ | m_receiverProblem = error; | |
| 201 | ✗ | emit changed(); | |
| 202 | ✗ | return; | |
| 203 | } | ||
| 204 | 34 | DavPushReceiverSecret secret; | |
| 205 |
1/1✓ Branch 8 → 9 taken 34 times.
|
34 | secret.channel = DavPushReceiverSecret::newChannel(); |
| 206 | 34 | secret.keys = *keys; | |
| 207 | 34 | DavPushReceiverRecord record; | |
| 208 | 34 | record.accountId = m_accountId; | |
| 209 |
2/2✓ Branch 14 → 15 taken 34 times.
✓ Branch 15 → 16 taken 34 times.
|
34 | record.receiverId = QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 210 | 34 | record.provider = m_target.receiver.provider; | |
| 211 | 34 | record.server = m_target.receiver.server; | |
| 212 |
1/1✓ Branch 20 → 21 taken 34 times.
|
34 | record.createdAt = m_ctx.now(); |
| 213 |
2/2✓ Branch 22 → 23 taken 6 times.
✓ Branch 22 → 25 taken 28 times.
|
34 | record.generation = m_receiverRecord ? m_receiverRecord->generation + 1 : 1; |
| 214 |
1/1✓ Branch 26 → 27 taken 34 times.
|
34 | const QString key = DavPushSecretKeys::receiver(m_ctx.installationId, record.receiverId); |
| 215 | 34 | m_secretLoading = true; | |
| 216 | 34 | const quint64 generation = ++m_generation; | |
| 217 | // Durable before use: the secret is in the keyring before any POST names | ||
| 218 | // its endpoint (§3.3). | ||
| 219 |
3/5✓ Branch 29 → 30 taken 34 times.
✓ Branch 30 → 31 taken 34 times.
✓ Branch 31 → 32 taken 34 times.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
|
34 | m_ctx.secrets->write(this, key, secret.serialize(), [this, generation, record, secret](bool ok) { |
| 220 | 34 | m_secretLoading = false; | |
| 221 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 34 times.
|
35 | if (generation != m_generation) return; |
| 222 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 21 taken 33 times.
|
34 | if (!ok) { |
| 223 |
1/1✓ Branch 5 → 6 taken 1 time.
|
1 | m_receiverProblem = tr("The keyring is not available; push stays off."); |
| 224 |
6/6✓ Branch 8 → 9 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 18 → 10 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
|
2 | qCWarning(lcDavPush) << "Cannot store the receiver secret for" << m_accountId; |
| 225 |
1/1✓ Branch 19 → 20 taken 1 time.
|
1 | emit changed(); |
| 226 | 1 | return; | |
| 227 | } | ||
| 228 |
1/1✓ Branch 21 → 22 taken 33 times.
|
33 | const auto previous = m_receiverRecord; |
| 229 |
2/3✓ Branch 22 → 23 taken 33 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 29 taken 33 times.
|
33 | if (!m_ctx.store->saveReceiver(record)) { |
| 230 | ✗ | m_receiverProblem = tr("The push state could not be saved; push stays off."); | |
| 231 | ✗ | emit changed(); | |
| 232 | ✗ | return; | |
| 233 | } | ||
| 234 |
2/2✓ Branch 30 → 31 taken 6 times.
✓ Branch 30 → 38 taken 27 times.
|
33 | if (previous) |
| 235 |
2/2✓ Branch 33 → 34 taken 6 times.
✓ Branch 34 → 35 taken 6 times.
|
6 | m_ctx.secrets->remove(this, DavPushSecretKeys::receiver(m_ctx.installationId, previous->receiverId), |
| 236 | 6 | [](bool) {}); | |
| 237 | 33 | m_receiverRecord = record; | |
| 238 | 33 | m_secret = secret; | |
| 239 |
2/2✓ Branch 44 → 45 taken 33 times.
✓ Branch 45 → 46 taken 33 times.
|
66 | m_ctx.stats->event(DavPushStatistics::accountScope(m_accountId), QStringLiteral("receiver-created")); |
| 240 |
1/1✓ Branch 50 → 51 taken 33 times.
|
33 | ensureReceiver(); |
| 241 |
1/2✓ Branch 53 → 54 taken 33 times.
✗ Branch 53 → 56 not taken.
|
33 | }); |
| 242 | 34 | } | |
| 243 | |||
| 244 | 5 | void DavPushAccount::releaseCurrentReceiver() { | |
| 245 | // Needs the loaded secret (the distributor knows the registration by its | ||
| 246 | // token); without it the distributor's entry stays until it expires there. | ||
| 247 |
4/8✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 9 not taken.
✓ Branch 7 → 8 taken 5 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 15 not taken.
|
5 | if (m_ctx.releaseReceiver && m_receiverRecord && m_secret) |
| 248 | 5 | m_ctx.releaseReceiver(m_receiverRecord->provider, m_receiverRecord->server, m_secret->channel); | |
| 249 | 5 | } | |
| 250 | |||
| 251 | 130 | QString DavPushAccount::endpointHash() const { | |
| 252 |
2/3✓ Branch 3 → 4 taken 130 times.
✗ Branch 3 → 6 not taken.
✓ Branch 5 → 7 taken 130 times.
|
130 | const QString endpoint = m_receiver ? m_receiver->endpoint() : QString(); |
| 253 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 130 times.
|
130 | if (endpoint.isEmpty()) return {}; |
| 254 |
5/5✓ Branch 10 → 11 taken 130 times.
✓ Branch 12 → 13 taken 130 times.
✓ Branch 13 → 14 taken 130 times.
✓ Branch 14 → 15 taken 130 times.
✓ Branch 15 → 16 taken 130 times.
|
130 | return QString::fromLatin1(QCryptographicHash::hash(endpoint.toUtf8(), QCryptographicHash::Sha256).toHex().left(32)); |
| 255 | 130 | } | |
| 256 | |||
| 257 | 52 | void DavPushAccount::startReceiver() { | |
| 258 |
5/6✓ Branch 3 → 4 taken 38 times.
✓ Branch 3 → 6 taken 14 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 38 times.
✓ Branch 8 → 9 taken 14 times.
✓ Branch 8 → 10 taken 38 times.
|
52 | if (m_receiver || !m_secret) return; |
| 259 |
2/2✓ Branch 11 → 12 taken 38 times.
✓ Branch 12 → 13 taken 38 times.
|
38 | m_receiver = m_ctx.makeReceiver(m_receiverRecord->provider, this); |
| 260 |
1/1✓ Branch 14 → 15 taken 38 times.
|
38 | connect(m_receiver, &PushReceiver::messageReceived, this, &DavPushAccount::onMessage); |
| 261 |
1/1✓ Branch 17 → 18 taken 38 times.
|
38 | connect(m_receiver, &PushReceiver::stateChanged, this, &DavPushAccount::onReceiverState); |
| 262 |
1/1✓ Branch 20 → 21 taken 38 times.
|
38 | connect(m_receiver, &PushReceiver::bytesReceived, this, [this](qint64 bytes) { |
| 263 | ✗ | m_ctx.stats->count(DavPushStatistics::accountScope(m_accountId), QStringLiteral("provider.bytes"), bytes); | |
| 264 | ✗ | }); | |
| 265 |
1/1✓ Branch 23 → 24 taken 38 times.
|
38 | connect(m_receiver, &PushReceiver::streamResumed, this, [this](bool afterGap) { |
| 266 |
2/2✓ Branch 6 → 7 taken 10 times.
✓ Branch 7 → 8 taken 10 times.
|
20 | m_ctx.stats->event(DavPushStatistics::accountScope(m_accountId), QStringLiteral("receiver-connected")); |
| 267 | // S106-082: the first stream of this process catches up too — the replay | ||
| 268 | // cursor is not persisted, so pushes sent while MailJD was not running | ||
| 269 | // are lost, and with a sync interval of 0 no startup sync covers them. | ||
| 270 | 10 | const bool firstStream = !std::exchange(m_streamSeen, true); | |
| 271 |
4/4✓ Branch 13 → 14 taken 9 times.
✓ Branch 13 → 16 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 8 times.
|
10 | if (!afterGap && !firstStream) return; |
| 272 | // Messages may have been lost while the stream was down. | ||
| 273 | 9 | QStringList registered; | |
| 274 |
5/5✓ Branch 16 → 17 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
✓ Branch 18 → 19 taken 9 times.
✓ Branch 34 → 20 taken 26 times.
✓ Branch 34 → 35 taken 9 times.
|
35 | for (const auto &record : m_ctx.store->collections(m_accountId)) |
| 275 |
7/8✓ Branch 22 → 23 taken 16 times.
✓ Branch 22 → 29 taken 10 times.
✓ Branch 24 → 25 taken 16 times.
✗ Branch 24 → 29 not taken.
✓ Branch 25 → 26 taken 14 times.
✓ Branch 25 → 28 taken 2 times.
✓ Branch 30 → 31 taken 3 times.
✓ Branch 30 → 32 taken 23 times.
|
40 | if (!record.registrationUrl.isEmpty() && m_target.desiredPaths.contains(record.path) && |
| 276 |
2/2✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 13 times.
|
14 | (afterGap || !m_caughtUpThisProcess.contains(record.path))) |
| 277 |
1/1✓ Branch 31 → 32 taken 3 times.
|
12 | registered << record.path; |
| 278 |
3/3✓ Branch 37 → 38 taken 2 times.
✓ Branch 37 → 39 taken 7 times.
✓ Branch 38 → 39 taken 2 times.
|
9 | if (!registered.isEmpty()) m_ctx.requestSync(m_accountId, registered, DavSyncReasonRecovery); |
| 279 | 9 | }); | |
| 280 | // The DAV server's VAPID key (one per server) for distributors that ask. | ||
| 281 | 38 | QString vapid; | |
| 282 |
5/5✓ Branch 25 → 26 taken 38 times.
✓ Branch 26 → 27 taken 38 times.
✓ Branch 27 → 28 taken 38 times.
✓ Branch 38 → 29 taken 105 times.
✓ Branch 38 → 39 taken 37 times.
|
142 | for (const auto &record : m_ctx.store->collections(m_accountId)) |
| 283 |
2/2✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 36 taken 104 times.
|
105 | if (!record.offer.vapidPublicKey.isEmpty()) { |
| 284 |
1/1✓ Branch 32 → 33 taken 1 time.
|
1 | vapid = WebPushPayloadCodec::encodeBase64Url(record.offer.vapidPublicKey); |
| 285 | 1 | break; | |
| 286 | 38 | } | |
| 287 | // Only a service configured with a token gets one (S106-073). | ||
| 288 |
2/2✓ Branch 40 → 41 taken 3 times.
✓ Branch 40 → 42 taken 35 times.
|
38 | const QByteArray token = m_target.receiver.hasToken ? m_providerToken : QByteArray(); |
| 289 | 76 | m_receiver->start({m_receiverRecord->server, m_secret->channel, token, {}, vapid}); | |
| 290 | 76 | } | |
| 291 | |||
| 292 | 434 | void DavPushAccount::stopReceiver() { | |
| 293 |
2/2✓ Branch 3 → 4 taken 396 times.
✓ Branch 3 → 5 taken 38 times.
|
434 | if (!m_receiver) return; |
| 294 | 38 | PushReceiver *receiver = m_receiver; | |
| 295 | 38 | m_receiver = nullptr; | |
| 296 | 38 | receiver->disconnect(this); | |
| 297 | 38 | receiver->stop(); | |
| 298 | 38 | receiver->deleteLater(); | |
| 299 | } | ||
| 300 | |||
| 301 | 50 | void DavPushAccount::onReceiverState() { | |
| 302 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 50 times.
|
50 | if (!m_receiver) return; |
| 303 | 50 | const auto state = m_receiver->state(); | |
| 304 |
1/1✓ Branch 7 → 8 taken 50 times.
|
50 | const QString scope = DavPushStatistics::accountScope(m_accountId); |
| 305 |
2/2✓ Branch 8 → 9 taken 11 times.
✓ Branch 8 → 13 taken 39 times.
|
50 | if (state == DavPushReceiverState::Connected) { |
| 306 |
3/3✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 9 times.
✓ Branch 10 → 11 taken 2 times.
|
11 | if (m_reportedFailure) m_ctx.pushHealth(m_accountId, false); |
| 307 | 11 | m_reportedFailure = false; | |
| 308 | // A (new) endpoint is known: register or re-register what needs it. | ||
| 309 |
1/1✓ Branch 11 → 12 taken 11 times.
|
11 | QMetaObject::invokeMethod(this, &DavPushAccount::converge, Qt::QueuedConnection); |
| 310 |
4/6✓ Branch 13 → 14 taken 37 times.
✓ Branch 13 → 15 taken 2 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 32 taken 37 times.
✓ Branch 15 → 16 taken 2 times.
✗ Branch 15 → 32 not taken.
|
39 | } else if ((state == DavPushReceiverState::Backoff || state == DavPushReceiverState::Failed) && !m_reportedFailure) { |
| 311 | 2 | m_reportedFailure = true; | |
| 312 |
1/1✓ Branch 19 → 20 taken 2 times.
|
4 | m_ctx.stats->count(scope, QStringLiteral("receiver.failures")); |
| 313 |
1/1✓ Branch 27 → 28 taken 2 times.
|
4 | m_ctx.stats->event(scope, QStringLiteral("receiver-failed"), m_receiver->failureDetail()); |
| 314 |
1/1✓ Branch 31 → 32 taken 2 times.
|
2 | m_ctx.pushHealth(m_accountId, true); |
| 315 | } | ||
| 316 |
3/3✓ Branch 32 → 33 taken 2 times.
✓ Branch 32 → 40 taken 48 times.
✓ Branch 36 → 37 taken 2 times.
|
52 | if (state == DavPushReceiverState::Backoff) m_ctx.stats->count(scope, QStringLiteral("receiver.reconnects")); |
| 317 |
1/1✓ Branch 40 → 41 taken 50 times.
|
50 | emit changed(); |
| 318 | 50 | } | |
| 319 | |||
| 320 | // --- registrations ---------------------------------------------------------- | ||
| 321 | |||
| 322 | 130 | bool DavPushAccount::needsRegistration(const DavPushCollectionRecord &record) const { | |
| 323 |
6/6✓ Branch 3 → 4 taken 74 times.
✓ Branch 3 → 7 taken 56 times.
✓ Branch 6 → 7 taken 12 times.
✓ Branch 6 → 8 taken 62 times.
✓ Branch 9 → 10 taken 68 times.
✓ Branch 9 → 11 taken 62 times.
|
130 | if (record.registrationUrl.isEmpty() || record.registrationReceiver != m_receiverRecord->receiverId) return true; |
| 324 | // The push service handed out a new endpoint (UnifiedPush distributors). | ||
| 325 |
1/1✓ Branch 11 → 12 taken 62 times.
|
62 | const QString current = endpointHash(); |
| 326 |
6/8✓ Branch 13 → 14 taken 62 times.
✗ Branch 13 → 19 not taken.
✓ Branch 15 → 16 taken 62 times.
✗ Branch 15 → 19 not taken.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 60 times.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 22 taken 60 times.
|
62 | if (!record.registrationEndpoint.isEmpty() && !current.isEmpty() && record.registrationEndpoint != current) return true; |
| 327 |
1/2✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 60 times.
|
60 | const qint64 base = record.renewedAt > 0 ? record.renewedAt : record.registeredAt; |
| 328 | 60 | const qint64 lifetime = qMax<qint64>(0, record.expiresAt - base); | |
| 329 | // 75 % of the confirmed lifetime; never later than 10 min before expiry. | ||
| 330 | 60 | const qint64 renewAt = qMin(base + lifetime * 3 / 4, record.expiresAt - 600); | |
| 331 |
1/1✓ Branch 27 → 28 taken 60 times.
|
60 | return m_ctx.now() >= renewAt; |
| 332 | 62 | } | |
| 333 | |||
| 334 | 74 | void DavPushAccount::converge() { | |
| 335 |
4/8✓ Branch 2 → 3 taken 74 times.
✗ Branch 2 → 7 not taken.
✓ Branch 4 → 5 taken 74 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 74 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 74 times.
|
74 | if (!m_target.active || !m_receiverRecord || !m_secret) return; |
| 336 | // Registrations need the endpoint; UnifiedPush delivers it asynchronously | ||
| 337 | // (converge runs again when the receiver reports Connected). | ||
| 338 |
7/9✓ Branch 12 → 13 taken 72 times.
✓ Branch 12 → 18 taken 2 times.
✓ Branch 14 → 15 taken 72 times.
✓ Branch 16 → 17 taken 67 times.
✓ Branch 16 → 18 taken 5 times.
✓ Branch 19 → 20 taken 72 times.
✓ Branch 19 → 21 taken 2 times.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
|
74 | const bool endpointKnown = m_receiver && !m_receiver->endpoint().isEmpty(); |
| 339 |
1/1✓ Branch 21 → 22 taken 74 times.
|
74 | const qint64 now = m_ctx.now(); |
| 340 | 74 | QStringList toRegister; | |
| 341 | 74 | QList<QPair<QString, QString>> toRemove; // path, registration URL | |
| 342 |
2/2✓ Branch 53 → 25 taken 141 times.
✓ Branch 53 → 54 taken 74 times.
|
215 | for (const QString &path : std::as_const(m_target.desiredPaths)) { |
| 343 |
1/1✓ Branch 26 → 27 taken 141 times.
|
141 | auto record = m_ctx.store->collection(m_accountId, path); |
| 344 |
6/8✓ Branch 28 → 29 taken 141 times.
✗ Branch 28 → 33 not taken.
✓ Branch 30 → 31 taken 137 times.
✓ Branch 30 → 33 taken 4 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 137 times.
✓ Branch 35 → 36 taken 4 times.
✓ Branch 35 → 37 taken 137 times.
|
141 | if (!record || m_inFlight.contains(path) || record->nextRetryAt > now) continue; |
| 345 |
8/8✓ Branch 37 → 38 taken 130 times.
✓ Branch 37 → 42 taken 7 times.
✓ Branch 39 → 40 taken 130 times.
✓ Branch 40 → 41 taken 72 times.
✓ Branch 40 → 42 taken 58 times.
✓ Branch 43 → 44 taken 72 times.
✓ Branch 43 → 45 taken 65 times.
✓ Branch 44 → 45 taken 72 times.
|
137 | if (endpointKnown && needsRegistration(*record)) toRegister << path; |
| 346 | 141 | } | |
| 347 |
5/5✓ Branch 54 → 55 taken 74 times.
✓ Branch 55 → 56 taken 74 times.
✓ Branch 56 → 57 taken 74 times.
✓ Branch 74 → 58 taken 216 times.
✓ Branch 74 → 75 taken 74 times.
|
290 | for (const auto &record : m_ctx.store->collections(m_accountId)) |
| 348 |
6/6✓ Branch 60 → 61 taken 80 times.
✓ Branch 60 → 66 taken 136 times.
✓ Branch 62 → 63 taken 4 times.
✓ Branch 62 → 66 taken 76 times.
✓ Branch 67 → 68 taken 4 times.
✓ Branch 67 → 72 taken 212 times.
|
220 | if (!record.registrationUrl.isEmpty() && !m_target.desiredPaths.contains(record.path) && |
| 349 |
1/2✓ Branch 64 → 65 taken 4 times.
✗ Branch 64 → 66 not taken.
|
4 | !m_unregistering.contains(record.path)) |
| 350 |
1/1✓ Branch 69 → 70 taken 4 times.
|
78 | toRemove.append({record.path, record.registrationUrl}); |
| 351 | 74 | QList<DavPushCleanupEntry> cleanup; | |
| 352 |
5/5✓ Branch 76 → 77 taken 74 times.
✓ Branch 77 → 78 taken 74 times.
✓ Branch 78 → 79 taken 74 times.
✓ Branch 86 → 80 taken 26 times.
✓ Branch 86 → 87 taken 74 times.
|
100 | for (const auto &entry : m_ctx.store->cleanupEntries(m_accountId)) |
| 353 |
3/3✓ Branch 82 → 83 taken 12 times.
✓ Branch 82 → 84 taken 14 times.
✓ Branch 83 → 84 taken 12 times.
|
100 | if (!m_cleanupInFlight.contains(entry.id)) cleanup.append(entry); |
| 354 | // Marked as in flight at once: a second converge() while the credentials | ||
| 355 | // are still being read must not queue the same request again. | ||
| 356 |
3/3✓ Branch 92 → 93 taken 72 times.
✓ Branch 95 → 91 taken 72 times.
✓ Branch 95 → 96 taken 74 times.
|
146 | for (const QString &path : std::as_const(toRegister)) m_inFlight.insert(path); |
| 357 |
3/3✓ Branch 102 → 103 taken 4 times.
✓ Branch 105 → 99 taken 4 times.
✓ Branch 105 → 106 taken 74 times.
|
78 | for (const auto &[path, url] : std::as_const(toRemove)) m_unregistering.insert(path); |
| 358 |
3/3✓ Branch 110 → 111 taken 12 times.
✓ Branch 113 → 109 taken 12 times.
✓ Branch 113 → 114 taken 74 times.
|
86 | for (const auto &entry : std::as_const(cleanup)) m_cleanupInFlight.insert(entry.id); |
| 359 |
8/8✓ Branch 115 → 116 taken 36 times.
✓ Branch 115 → 120 taken 38 times.
✓ Branch 117 → 118 taken 32 times.
✓ Branch 117 → 120 taken 4 times.
✓ Branch 119 → 120 taken 6 times.
✓ Branch 119 → 121 taken 26 times.
✓ Branch 122 → 123 taken 48 times.
✓ Branch 122 → 131 taken 26 times.
|
74 | if (!toRegister.isEmpty() || !toRemove.isEmpty() || !cleanup.isEmpty()) { |
| 360 |
2/6✓ Branch 126 → 127 taken 48 times.
✓ Branch 127 → 128 taken 48 times.
✗ Branch 166 → 167 not taken.
✗ Branch 166 → 168 not taken.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
|
48 | withClient([this, toRegister, toRemove, cleanup](DavPushClient *client) { |
| 361 |
2/2✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 28 taken 45 times.
|
48 | if (!client) { |
| 362 | // No credentials: nothing was sent; retry with the next converge. | ||
| 363 |
3/3✓ Branch 6 → 7 taken 4 times.
✓ Branch 9 → 5 taken 4 times.
✓ Branch 9 → 10 taken 3 times.
|
7 | for (const QString &path : toRegister) m_inFlight.remove(path); |
| 364 |
1/3✗ Branch 15 → 16 not taken.
✗ Branch 18 → 12 not taken.
✓ Branch 18 → 19 taken 3 times.
|
3 | for (const auto &[path, url] : toRemove) m_unregistering.remove(path); |
| 365 |
1/3✗ Branch 22 → 23 not taken.
✗ Branch 25 → 21 not taken.
✓ Branch 25 → 26 taken 3 times.
|
3 | for (const auto &entry : cleanup) m_cleanupInFlight.remove(entry.id); |
| 366 | 3 | emit changed(); | |
| 367 | 3 | return; | |
| 368 | } | ||
| 369 |
3/3✓ Branch 31 → 32 taken 68 times.
✓ Branch 34 → 30 taken 68 times.
✓ Branch 34 → 35 taken 45 times.
|
113 | for (const QString &path : toRegister) registerPath(client, path); |
| 370 |
3/3✓ Branch 40 → 41 taken 4 times.
✓ Branch 43 → 37 taken 4 times.
✓ Branch 43 → 44 taken 45 times.
|
49 | for (const auto &[path, url] : toRemove) unregisterUrl(client, path, url, 0); |
| 371 |
3/3✓ Branch 48 → 49 taken 12 times.
✓ Branch 52 → 46 taken 12 times.
✓ Branch 52 → 53 taken 45 times.
|
57 | for (const auto &entry : cleanup) unregisterUrl(client, QString(), entry.registrationUrl, entry.id); |
| 372 | }); | ||
| 373 | } | ||
| 374 |
1/1✓ Branch 131 → 132 taken 74 times.
|
74 | scheduleRenewal(); |
| 375 |
1/1✓ Branch 132 → 133 taken 74 times.
|
74 | emit changed(); |
| 376 | 74 | } | |
| 377 | |||
| 378 | 48 | void DavPushAccount::withClient(std::function<void(DavPushClient *client)> work) { | |
| 379 |
1/1✓ Branch 3 → 4 taken 48 times.
|
48 | m_clientWork.append(std::move(work)); |
| 380 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 48 times.
|
48 | if (m_clientPending) return; |
| 381 | 48 | m_clientPending = true; | |
| 382 | 48 | const quint64 generation = m_generation; | |
| 383 | 48 | const CalendarSyncAccount account = m_target.account; | |
| 384 |
2/2✓ Branch 8 → 9 taken 48 times.
✓ Branch 9 → 10 taken 48 times.
|
48 | m_ctx.resolveCredentials(this, account.accountId, account.serverUrl, account.username, |
| 385 | 96 | [this, generation, account](QByteArray password, const QString &error) { | |
| 386 | 48 | m_clientPending = false; | |
| 387 | 48 | const auto work = std::exchange(m_clientWork, {}); | |
| 388 |
5/6✓ Branch 5 → 6 taken 48 times.
✗ Branch 5 → 8 not taken.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 45 times.
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 35 taken 45 times.
|
48 | if (generation != m_generation || password.isEmpty()) { |
| 389 |
8/9✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 26 not taken.
✓ Branch 13 → 14 taken 3 times.
✓ Branch 17 → 18 taken 3 times.
✓ Branch 18 → 19 taken 3 times.
✓ Branch 19 → 20 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
✓ Branch 24 → 15 taken 3 times.
✓ Branch 24 → 25 taken 3 times.
|
6 | if (password.isEmpty()) qCInfo(lcDavPush) << "No DAV credentials for push requests of" << m_accountId << error; |
| 390 |
1/1✓ Branch 26 → 27 taken 3 times.
|
3 | SecureUtil::zeroMemory(password); |
| 391 |
3/3✓ Branch 30 → 31 taken 3 times.
✓ Branch 33 → 29 taken 3 times.
✓ Branch 33 → 34 taken 3 times.
|
6 | for (const auto &item : work) item(nullptr); |
| 392 | 3 | return; | |
| 393 | } | ||
| 394 |
2/2✓ Branch 35 → 36 taken 45 times.
✓ Branch 36 → 37 taken 45 times.
|
45 | auto *client = m_ctx.makeClient(account.serverUrl, account.username, QString::fromUtf8(password)); |
| 395 |
1/1✓ Branch 38 → 39 taken 45 times.
|
45 | SecureUtil::zeroMemory(password); |
| 396 |
1/1✓ Branch 39 → 40 taken 45 times.
|
45 | client->setParent(this); |
| 397 | // Released after its requests (DAV timeouts bound them to 30 s). | ||
| 398 |
1/1✓ Branch 40 → 41 taken 45 times.
|
45 | QTimer::singleShot(120000, client, &QObject::deleteLater); |
| 399 |
1/1✓ Branch 41 → 42 taken 45 times.
|
45 | connect(client, &DavPushClient::registered, this, [this, generation](const DavPushClient::Result &result) { |
| 400 | 66 | onRegistered(result, generation); | |
| 401 | 66 | }); | |
| 402 |
3/3✓ Branch 46 → 47 taken 45 times.
✓ Branch 49 → 45 taken 45 times.
✓ Branch 49 → 50 taken 45 times.
|
90 | for (const auto &item : work) item(client); |
| 403 |
2/2✓ Branch 52 → 53 taken 45 times.
✓ Branch 52 → 55 taken 3 times.
|
48 | }); |
| 404 | 48 | } | |
| 405 | |||
| 406 | 68 | void DavPushAccount::registerPath(DavPushClient *client, const QString &path) { | |
| 407 |
1/1✓ Branch 2 → 3 taken 68 times.
|
68 | const auto record = m_ctx.store->collection(m_accountId, path); |
| 408 |
1/1✓ Branch 3 → 4 taken 68 times.
|
68 | const QString hash = endpointHash(); |
| 409 |
5/6✓ Branch 5 → 6 taken 68 times.
✗ Branch 5 → 20 not taken.
✓ Branch 8 → 9 taken 14 times.
✓ Branch 8 → 20 taken 54 times.
✓ Branch 21 → 22 taken 12 times.
✓ Branch 21 → 24 taken 56 times.
|
82 | if (record && !record->registrationUrl.isEmpty() && |
| 410 |
2/2✓ Branch 12 → 13 taken 4 times.
✓ Branch 12 → 19 taken 10 times.
|
14 | (record->registrationReceiver != m_receiverRecord->receiverId || |
| 411 |
3/4✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 20 not taken.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 2 times.
|
4 | (!record->registrationEndpoint.isEmpty() && record->registrationEndpoint != hash))) |
| 412 |
1/1✓ Branch 23 → 24 taken 12 times.
|
12 | m_replaced.insert(path, record->registrationUrl); |
| 413 |
2/3✓ Branch 25 → 26 taken 68 times.
✗ Branch 25 → 28 not taken.
✓ Branch 27 → 29 taken 68 times.
|
68 | const QString endpoint = m_receiver ? m_receiver->endpoint() : QString(); |
| 414 |
1/2✗ Branch 30 → 31 not taken.
✓ Branch 30 → 33 taken 68 times.
|
68 | if (endpoint.isEmpty()) { |
| 415 | ✗ | m_inFlight.remove(path); | |
| 416 | ✗ | return; | |
| 417 | } | ||
| 418 |
1/1✓ Branch 33 → 34 taken 68 times.
|
68 | m_registeringEndpoint.insert(path, hash); |
| 419 |
1/1✓ Branch 36 → 37 taken 68 times.
|
68 | client->registerSubscription(path, endpoint, m_secret->keys.publicKey, m_secret->keys.authSecret); |
| 420 |
1/1✓ Branch 37 → 38 taken 68 times.
|
68 | emit changed(); |
| 421 | 68 | } | |
| 422 | |||
| 423 | 66 | void DavPushAccount::onRegistered(const DavPushClient::Result &result, quint64 generation) { | |
| 424 | 66 | const QString path = result.collectionPath; | |
| 425 |
1/1✓ Branch 3 → 4 taken 66 times.
|
66 | m_inFlight.remove(path); |
| 426 |
1/1✓ Branch 4 → 5 taken 66 times.
|
66 | const QString registeredEndpoint = m_registeringEndpoint.take(path); |
| 427 |
1/1✓ Branch 5 → 6 taken 66 times.
|
66 | auto record = m_ctx.store->collection(m_accountId, path); |
| 428 |
5/6✓ Branch 6 → 7 taken 64 times.
✓ Branch 6 → 9 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 64 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 14 taken 64 times.
|
66 | if (generation != m_generation || !record) { |
| 429 |
1/1✓ Branch 12 → 13 taken 2 times.
|
2 | emit changed(); |
| 430 | 2 | return; | |
| 431 | } | ||
| 432 |
1/1✓ Branch 14 → 15 taken 64 times.
|
64 | const QString scope = collectionScope(path); |
| 433 |
4/4✓ Branch 17 → 18 taken 8 times.
✓ Branch 17 → 21 taken 56 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 6 times.
|
64 | const bool renewal = record->registrationUrl == result.location && !result.location.isEmpty(); |
| 434 |
2/2✓ Branch 22 → 23 taken 58 times.
✓ Branch 22 → 123 taken 6 times.
|
64 | if (result.outcome == DavPushClient::Outcome::Ok) { |
| 435 |
1/1✓ Branch 23 → 24 taken 58 times.
|
58 | const qint64 now = m_ctx.now(); |
| 436 |
4/4✓ Branch 26 → 27 taken 12 times.
✓ Branch 26 → 29 taken 46 times.
✓ Branch 28 → 29 taken 10 times.
✓ Branch 28 → 30 taken 2 times.
|
58 | const bool first = record->registrationUrl.isEmpty() || m_replaced.contains(path); |
| 437 | 58 | record->registrationUrl = result.location; | |
| 438 |
1/2✓ Branch 34 → 35 taken 58 times.
✗ Branch 34 → 37 not taken.
|
58 | record->registrationReceiver = m_receiverRecord ? m_receiverRecord->receiverId : QString(); |
| 439 | 58 | record->registrationEndpoint = registeredEndpoint; | |
| 440 | 58 | record->expiresAt = result.expiresAt; | |
| 441 |
2/2✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 47 taken 56 times.
|
58 | if (renewal) { |
| 442 | 2 | record->renewedAt = now; | |
| 443 | } else { | ||
| 444 | 56 | record->registeredAt = now; | |
| 445 | 56 | record->renewedAt = 0; | |
| 446 | } | ||
| 447 | 58 | record->failures = 0; | |
| 448 | 58 | record->nextRetryAt = 0; | |
| 449 | 58 | record->lastError.clear(); | |
| 450 |
1/1✓ Branch 55 → 56 taken 58 times.
|
58 | m_ctx.store->saveCollection(*record); |
| 451 |
7/11✓ Branch 56 → 57 taken 2 times.
✓ Branch 56 → 60 taken 56 times.
✓ Branch 63 → 64 taken 58 times.
✓ Branch 65 → 66 taken 56 times.
✓ Branch 65 → 67 taken 2 times.
✓ Branch 67 → 68 taken 2 times.
✓ Branch 67 → 69 taken 56 times.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 173 not taken.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
|
116 | m_ctx.stats->count(scope, renewal ? QStringLiteral("subscription.renewed") : QStringLiteral("subscription.registered")); |
| 452 |
7/11✓ Branch 70 → 71 taken 2 times.
✓ Branch 70 → 74 taken 56 times.
✓ Branch 77 → 78 taken 58 times.
✓ Branch 79 → 80 taken 56 times.
✓ Branch 79 → 81 taken 2 times.
✓ Branch 81 → 82 taken 2 times.
✓ Branch 81 → 83 taken 56 times.
✗ Branch 182 → 183 not taken.
✗ Branch 182 → 184 not taken.
✗ Branch 186 → 187 not taken.
✗ Branch 186 → 188 not taken.
|
116 | m_ctx.stats->event(scope, renewal ? QStringLiteral("renewed") : QStringLiteral("registered")); |
| 453 |
10/10✓ Branch 84 → 85 taken 58 times.
✓ Branch 88 → 89 taken 58 times.
✓ Branch 89 → 90 taken 2 times.
✓ Branch 89 → 91 taken 56 times.
✓ Branch 92 → 93 taken 58 times.
✓ Branch 93 → 94 taken 58 times.
✓ Branch 94 → 95 taken 58 times.
✓ Branch 95 → 96 taken 58 times.
✓ Branch 99 → 86 taken 58 times.
✓ Branch 99 → 100 taken 58 times.
|
116 | qCInfo(lcDavPush) << (renewal ? "Renewed" : "Registered") << "push for" << m_accountId << path; |
| 454 | // The replaced registration goes once the new one is confirmed. | ||
| 455 |
2/2✓ Branch 101 → 102 taken 10 times.
✓ Branch 101 → 110 taken 48 times.
|
58 | if (m_replaced.contains(path)) { |
| 456 |
1/1✓ Branch 102 → 103 taken 10 times.
|
10 | const QString old = m_replaced.take(path); |
| 457 |
1/2✓ Branch 104 → 105 taken 10 times.
✗ Branch 104 → 108 not taken.
|
10 | if (old != result.location) { |
| 458 |
1/1✓ Branch 105 → 106 taken 10 times.
|
10 | m_ctx.store->queueCleanup(m_accountId, old, now); |
| 459 |
1/1✓ Branch 106 → 107 taken 10 times.
|
10 | QMetaObject::invokeMethod(this, &DavPushAccount::converge, Qt::QueuedConnection); |
| 460 | } | ||
| 461 | 10 | } | |
| 462 | // Close the gap between the last sync and the subscription. | ||
| 463 |
5/7✓ Branch 110 → 111 taken 56 times.
✓ Branch 110 → 119 taken 2 times.
✓ Branch 113 → 114 taken 56 times.
✓ Branch 116 → 117 taken 56 times.
✓ Branch 116 → 118 taken 56 times.
✗ Branch 206 → 207 not taken.
✗ Branch 206 → 208 not taken.
|
170 | if (first) m_ctx.requestSync(m_accountId, {path}, DavSyncReasonInitial); |
| 464 |
1/1✓ Branch 119 → 120 taken 58 times.
|
58 | m_caughtUpThisProcess.insert(path); // S106-082: no second catch-up needed |
| 465 |
1/1✓ Branch 120 → 121 taken 58 times.
|
58 | scheduleRenewal(); |
| 466 |
1/1✓ Branch 121 → 122 taken 58 times.
|
58 | emit changed(); |
| 467 | 58 | return; | |
| 468 | } | ||
| 469 |
1/1✓ Branch 126 → 127 taken 6 times.
|
12 | m_ctx.stats->count(scope, QStringLiteral("subscription.failed")); |
| 470 |
1/1✓ Branch 130 → 131 taken 6 times.
|
6 | int retry = retryDelay(record->failures, result.retryAfterSeconds); |
| 471 |
3/3✓ Branch 131 → 132 taken 2 times.
✓ Branch 131 → 133 taken 2 times.
✓ Branch 131 → 137 taken 2 times.
|
6 | switch (result.outcome) { |
| 472 | 2 | case DavPushClient::Outcome::Forbidden: | |
| 473 | case DavPushClient::Outcome::Incompatible: | ||
| 474 | 2 | retry = kMaxRetrySeconds; // profile problem: re-checked by discovery | |
| 475 | 2 | break; | |
| 476 | 2 | case DavPushClient::Outcome::NotFound: | |
| 477 |
1/1✓ Branch 134 → 135 taken 2 times.
|
2 | m_ctx.requestSync(m_accountId, {}, DavSyncReasonRecovery); // collection gone: rediscover |
| 478 | 2 | break; | |
| 479 | 2 | default: | |
| 480 | 2 | break; | |
| 481 | } | ||
| 482 |
1/1✓ Branch 140 → 141 taken 6 times.
|
6 | recordError(*record, result.detail, retry); |
| 483 |
1/1✓ Branch 145 → 146 taken 6 times.
|
12 | m_ctx.stats->event(scope, QStringLiteral("register-failed"), result.detail); |
| 484 |
1/1✓ Branch 148 → 149 taken 6 times.
|
6 | m_ctx.pushHealth(m_accountId, true); |
| 485 |
1/1✓ Branch 149 → 150 taken 6 times.
|
6 | emit changed(); |
| 486 | 300 | } | |
| 487 | |||
| 488 | 6 | void DavPushAccount::recordError(DavPushCollectionRecord record, const QString &error, int retrySeconds) { | |
| 489 | 6 | const qint64 now = m_ctx.now(); | |
| 490 | 6 | record.lastError = error; | |
| 491 | 6 | record.lastErrorAt = now; | |
| 492 | 6 | record.failures += 1; | |
| 493 | 6 | record.nextRetryAt = now + retrySeconds; | |
| 494 | 6 | m_ctx.store->saveCollection(record); | |
| 495 | 6 | scheduleRenewal(); | |
| 496 | 6 | } | |
| 497 | |||
| 498 | 16 | void DavPushAccount::unregisterUrl(DavPushClient *client, const QString &path, const QString &url, qint64 cleanupId) { | |
| 499 | 16 | connect(client, &DavPushClient::unregistered, this, | |
| 500 |
2/6✓ Branch 4 → 5 taken 16 times.
✓ Branch 5 → 6 taken 16 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
|
32 | [this, path, url, cleanupId, request = client->unregisterSubscription(url)](const DavPushClient::Result &result) { |
| 501 |
2/2✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 4 taken 15 times.
|
25 | if (result.request != request) return; |
| 502 |
2/2✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 12 times.
|
15 | if (!path.isEmpty()) m_unregistering.remove(path); |
| 503 | 15 | m_cleanupInFlight.remove(cleanupId); | |
| 504 | 15 | const bool done = result.outcome == DavPushClient::Outcome::Ok; | |
| 505 |
2/2✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 29 taken 12 times.
|
15 | if (!path.isEmpty()) { |
| 506 |
1/1✓ Branch 10 → 11 taken 3 times.
|
3 | auto record = m_ctx.store->collection(m_accountId, path); |
| 507 |
3/6✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 17 not taken.
✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 24 not taken.
|
3 | if (record && record->registrationUrl == url) { |
| 508 | 3 | record->registrationUrl.clear(); | |
| 509 | 3 | record->expiresAt = 0; | |
| 510 |
1/1✓ Branch 23 → 24 taken 3 times.
|
3 | m_ctx.store->saveCollection(*record); |
| 511 | } | ||
| 512 |
4/4✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 27 taken 2 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
|
3 | if (!done) m_ctx.store->queueCleanup(m_accountId, url, m_ctx.now()); |
| 513 |
1/2✓ Branch 29 → 30 taken 12 times.
✗ Branch 29 → 31 not taken.
|
15 | } else if (done) { |
| 514 | 12 | m_ctx.store->removeCleanup(cleanupId); | |
| 515 | } else { | ||
| 516 | ✗ | m_ctx.store->updateCleanupAttempt(cleanupId); | |
| 517 | } | ||
| 518 |
2/2✓ Branch 39 → 40 taken 15 times.
✓ Branch 40 → 41 taken 15 times.
|
15 | m_ctx.stats->count(DavPushStatistics::accountScope(m_accountId), |
| 519 |
6/10✓ Branch 32 → 33 taken 14 times.
✓ Branch 32 → 36 taken 1 time.
✓ Branch 43 → 44 taken 1 time.
✓ Branch 43 → 45 taken 14 times.
✓ Branch 45 → 46 taken 14 times.
✓ Branch 45 → 47 taken 1 time.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
|
45 | done ? QStringLiteral("subscription.removed") : QStringLiteral("subscription.remove-failed")); |
| 520 | 15 | emit changed(); | |
| 521 | }); | ||
| 522 | 16 | } | |
| 523 | |||
| 524 | 138 | void DavPushAccount::scheduleRenewal() { | |
| 525 | 138 | qint64 next = 0; | |
| 526 |
2/2✓ Branch 42 → 5 taken 272 times.
✓ Branch 42 → 43 taken 138 times.
|
410 | for (const QString &path : std::as_const(m_target.desiredPaths)) { |
| 527 |
1/1✓ Branch 6 → 7 taken 272 times.
|
272 | const auto record = m_ctx.store->collection(m_accountId, path); |
| 528 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 272 times.
|
272 | if (!record) continue; |
| 529 | 272 | qint64 due = 0; | |
| 530 |
2/2✓ Branch 11 → 12 taken 9 times.
✓ Branch 11 → 14 taken 263 times.
|
272 | if (record->nextRetryAt > 0) { |
| 531 | 9 | due = record->nextRetryAt; | |
| 532 |
2/2✓ Branch 16 → 17 taken 171 times.
✓ Branch 16 → 30 taken 92 times.
|
263 | } else if (!record->registrationUrl.isEmpty()) { |
| 533 |
2/2✓ Branch 18 → 19 taken 3 times.
✓ Branch 18 → 21 taken 168 times.
|
171 | const qint64 base = record->renewedAt > 0 ? record->renewedAt : record->registeredAt; |
| 534 | 171 | const qint64 lifetime = qMax<qint64>(0, record->expiresAt - base); | |
| 535 | // Earlier-only jitter up to 5 % of the lifetime. | ||
| 536 |
2/3✓ Branch 25 → 26 taken 171 times.
✗ Branch 25 → 52 not taken.
✓ Branch 26 → 27 taken 171 times.
|
171 | const qint64 jitter = qint64(lifetime * 0.05 * QRandomGenerator::global()->generateDouble()); |
| 537 | 171 | due = qMin(base + lifetime * 3 / 4, record->expiresAt - 600) - jitter; | |
| 538 | } | ||
| 539 |
6/6✓ Branch 30 → 31 taken 180 times.
✓ Branch 30 → 34 taken 92 times.
✓ Branch 31 → 32 taken 77 times.
✓ Branch 31 → 33 taken 103 times.
✓ Branch 32 → 33 taken 39 times.
✓ Branch 32 → 34 taken 38 times.
|
272 | if (due > 0 && (next == 0 || due < next)) next = due; |
| 540 | 272 | } | |
| 541 | 138 | m_nextRenewalAt = next; | |
| 542 |
2/2✓ Branch 43 → 44 taken 35 times.
✓ Branch 43 → 46 taken 103 times.
|
138 | if (next == 0) { |
| 543 |
1/1✓ Branch 44 → 45 taken 35 times.
|
35 | m_renewal.stop(); |
| 544 | 35 | return; | |
| 545 | } | ||
| 546 |
1/1✓ Branch 46 → 47 taken 103 times.
|
103 | const qint64 delay = qMax<qint64>(kMinRetrySeconds, next - m_ctx.now()); |
| 547 |
1/1✓ Branch 49 → 50 taken 103 times.
|
103 | m_renewal.start(int(qMin<qint64>(delay, 24 * 3600) * 1000)); |
| 548 | } | ||
| 549 | |||
| 550 | // --- messages --------------------------------------------------------------- | ||
| 551 | |||
| 552 | 9 | void DavPushAccount::onMessage(const QByteArray &payload, const QString &messageId) { | |
| 553 | Q_UNUSED(messageId); | ||
| 554 |
1/1✓ Branch 2 → 3 taken 9 times.
|
9 | const QString scope = DavPushStatistics::accountScope(m_accountId); |
| 555 |
1/1✓ Branch 6 → 7 taken 9 times.
|
18 | m_ctx.stats->count(scope, QStringLiteral("messages.received")); |
| 556 |
8/8✓ Branch 9 → 10 taken 9 times.
✓ Branch 13 → 14 taken 9 times.
✓ Branch 14 → 15 taken 9 times.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
✓ Branch 18 → 19 taken 9 times.
✓ Branch 22 → 11 taken 9 times.
✓ Branch 22 → 23 taken 9 times.
|
18 | qCDebug(lcDavPush) << "Push payload received for" << m_accountId << payload.size() << "bytes"; |
| 557 |
1/2✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 9 times.
|
9 | if (!m_secret) return; |
| 558 | 9 | QString error; | |
| 559 |
1/1✓ Branch 27 → 28 taken 9 times.
|
9 | const auto plain = WebPushPayloadCodec::decrypt(payload, m_secret->keys, &error); |
| 560 |
2/2✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 43 taken 7 times.
|
9 | if (!plain) { |
| 561 |
1/1✓ Branch 33 → 34 taken 2 times.
|
4 | m_ctx.stats->count(scope, QStringLiteral("messages.discarded.decrypt")); |
| 562 |
1/1✓ Branch 39 → 40 taken 2 times.
|
4 | m_ctx.stats->event(scope, QStringLiteral("message-discarded"), error); |
| 563 | 2 | return; | |
| 564 | } | ||
| 565 |
1/1✓ Branch 44 → 45 taken 7 times.
|
7 | const auto message = DavPushXml::parsePushMessage(*plain, &error); |
| 566 |
2/2✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 60 taken 6 times.
|
7 | if (!message) { |
| 567 |
1/1✓ Branch 50 → 51 taken 1 time.
|
2 | m_ctx.stats->count(scope, QStringLiteral("messages.discarded.parse")); |
| 568 |
1/1✓ Branch 56 → 57 taken 1 time.
|
2 | m_ctx.stats->event(scope, QStringLiteral("message-discarded"), error); |
| 569 | 1 | return; | |
| 570 | } | ||
| 571 |
2/2✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 73 taken 5 times.
|
6 | if (message->isKeyRotation()) { |
| 572 | // Read the new VAPID key through authenticated discovery (§3.3); never | ||
| 573 | // change endpoints from push fields alone. | ||
| 574 |
1/1✓ Branch 66 → 67 taken 1 time.
|
2 | m_ctx.stats->count(scope, QStringLiteral("messages.key-rotation")); |
| 575 |
1/1✓ Branch 70 → 71 taken 1 time.
|
1 | m_ctx.requestSync(m_accountId, {}, DavSyncReasonRecovery); |
| 576 | 1 | return; | |
| 577 | } | ||
| 578 | 5 | QStringList paths; | |
| 579 |
5/5✓ Branch 73 → 74 taken 5 times.
✓ Branch 74 → 75 taken 5 times.
✓ Branch 75 → 76 taken 5 times.
✓ Branch 89 → 77 taken 17 times.
✓ Branch 89 → 90 taken 5 times.
|
22 | for (const auto &record : m_ctx.store->collections(m_accountId)) |
| 580 |
5/6✓ Branch 80 → 81 taken 4 times.
✓ Branch 80 → 84 taken 13 times.
✓ Branch 82 → 83 taken 4 times.
✗ Branch 82 → 84 not taken.
✓ Branch 85 → 86 taken 4 times.
✓ Branch 85 → 87 taken 13 times.
|
17 | if (record.offer.topic == message->topic && m_target.knownPaths.contains(record.path)) |
| 581 |
1/1✓ Branch 86 → 87 taken 4 times.
|
9 | paths << record.path; |
| 582 |
2/2✓ Branch 92 → 93 taken 1 time.
✓ Branch 92 → 100 taken 4 times.
|
5 | if (paths.isEmpty()) { |
| 583 |
1/1✓ Branch 96 → 97 taken 1 time.
|
2 | m_ctx.stats->count(scope, QStringLiteral("messages.discarded.unknown-topic")); |
| 584 | 1 | return; | |
| 585 | } | ||
| 586 |
1/1✓ Branch 103 → 104 taken 4 times.
|
8 | m_ctx.stats->count(scope, QStringLiteral("messages.accepted")); |
| 587 | 4 | QStringList dirty; | |
| 588 |
1/1✓ Branch 106 → 107 taken 4 times.
|
4 | const qint64 now = m_ctx.now(); |
| 589 |
2/2✓ Branch 173 → 110 taken 4 times.
✓ Branch 173 → 174 taken 4 times.
|
8 | for (const QString &path : std::as_const(paths)) { |
| 590 |
1/1✓ Branch 111 → 112 taken 4 times.
|
4 | auto record = m_ctx.store->collection(m_accountId, path); |
| 591 |
1/2✓ Branch 113 → 114 taken 4 times.
✗ Branch 113 → 117 not taken.
|
4 | if (record) { |
| 592 | 4 | record->lastPushAt = now; | |
| 593 |
1/1✓ Branch 116 → 117 taken 4 times.
|
4 | m_ctx.store->saveCollection(*record); |
| 594 | } | ||
| 595 | // Equal tokens are a possible no-op; never compare them otherwise. | ||
| 596 |
3/4✓ Branch 121 → 122 taken 4 times.
✗ Branch 121 → 129 not taken.
✓ Branch 125 → 126 taken 2 times.
✓ Branch 125 → 129 taken 2 times.
|
8 | if (message->contentUpdate && !message->syncToken.isEmpty() && |
| 597 |
6/11✓ Branch 118 → 119 taken 4 times.
✗ Branch 118 → 129 not taken.
✓ Branch 122 → 123 taken 4 times.
✓ Branch 127 → 128 taken 2 times.
✗ Branch 127 → 129 not taken.
✓ Branch 130 → 131 taken 4 times.
✗ Branch 130 → 132 not taken.
✓ Branch 132 → 133 taken 2 times.
✓ Branch 132 → 142 taken 2 times.
✗ Branch 280 → 281 not taken.
✗ Branch 280 → 282 not taken.
|
12 | message->syncToken == m_ctx.storedToken(m_accountId, path) && !message->propertyUpdate) { |
| 598 |
2/2✓ Branch 136 → 137 taken 2 times.
✓ Branch 137 → 138 taken 2 times.
|
4 | m_ctx.stats->count(collectionScope(path), QStringLiteral("messages.token-noop")); |
| 599 | 2 | continue; | |
| 600 | } | ||
| 601 |
1/4✗ Branch 143 → 144 not taken.
✓ Branch 143 → 153 taken 2 times.
✗ Branch 147 → 148 not taken.
✗ Branch 148 → 149 not taken.
|
2 | if (m_pushSince.contains(path)) m_ctx.stats->count(collectionScope(path), QStringLiteral("messages.coalesced")); |
| 602 |
1/1✓ Branch 154 → 155 taken 2 times.
|
2 | else m_pushSince.insert(path, m_monotonic.elapsed()); |
| 603 |
2/2✓ Branch 159 → 160 taken 2 times.
✓ Branch 160 → 161 taken 2 times.
|
4 | m_ctx.stats->count(collectionScope(path), QStringLiteral("messages.mapped")); |
| 604 |
1/1✓ Branch 164 → 165 taken 2 times.
|
2 | dirty << path; |
| 605 | 4 | } | |
| 606 |
9/9✓ Branch 174 → 175 taken 4 times.
✓ Branch 178 → 179 taken 4 times.
✓ Branch 179 → 180 taken 4 times.
✓ Branch 180 → 181 taken 4 times.
✓ Branch 181 → 182 taken 4 times.
✓ Branch 183 → 184 taken 4 times.
✓ Branch 184 → 185 taken 4 times.
✓ Branch 192 → 176 taken 4 times.
✓ Branch 192 → 193 taken 4 times.
|
8 | qCInfo(lcDavPush) << "Push message for" << m_accountId << "mapped" << paths.size() << "collection(s)," |
| 607 |
2/2✓ Branch 187 → 188 taken 4 times.
✓ Branch 188 → 189 taken 4 times.
|
4 | << paths.size() - dirty.size() << "already applied (same token)"; |
| 608 |
3/3✓ Branch 194 → 195 taken 2 times.
✓ Branch 194 → 196 taken 2 times.
✓ Branch 195 → 196 taken 2 times.
|
4 | if (!dirty.isEmpty()) m_ctx.requestSync(m_accountId, dirty, DavSyncReasonPush); |
| 609 |
1/1✓ Branch 196 → 197 taken 4 times.
|
4 | emit changed(); |
| 610 | 23 | } | |
| 611 |