app/core/CalendarAccountSync.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "CalendarAccountSync.h" | ||
| 2 | |||
| 3 | #include "CalendarSyncApply.h" | ||
| 4 | #include "CalendarSyncStatus.h" | ||
| 5 | #include "data/CalendarStore.h" | ||
| 6 | #include "service/CalDavClient.h" | ||
| 7 | #include "util/SecureUtil.h" | ||
| 8 | |||
| 9 | #include <QLoggingCategory> | ||
| 10 | #include <memory> | ||
| 11 | |||
| 12 | Q_DECLARE_LOGGING_CATEGORY(lcCalendarSync) | ||
| 13 | |||
| 14 | 141 | CalendarAccountSync::CalendarAccountSync(const CalendarSyncAccount &account, const CalendarSyncRequest &request, | |
| 15 | 141 | Deps deps, QObject *parent) | |
| 16 |
1/1✓ Branch 8 → 9 taken 141 times.
|
141 | : QObject(parent), m_account(account), m_request(request), m_deps(std::move(deps)) { |
| 17 |
1/1✓ Branch 15 → 16 taken 141 times.
|
141 | qRegisterMetaType<CalendarSyncResult>(); |
| 18 | 141 | m_result.accountId = account.accountId; | |
| 19 | 141 | m_result.reasons = request.reasons; | |
| 20 |
1/1✓ Branch 17 → 18 taken 141 times.
|
141 | m_timeout.setSingleShot(true); |
| 21 |
1/1✓ Branch 18 → 19 taken 141 times.
|
141 | connect(&m_timeout, &QTimer::timeout, this, [this] { |
| 22 | ✗ | fail(QStringLiteral("timeout"), QStringLiteral("No answer within 60 s")); | |
| 23 | ✗ | emit statusMessage(tr("Calendar sync timed out. Try again.")); | |
| 24 | ✗ | finish(false); | |
| 25 | ✗ | }); | |
| 26 | 141 | } | |
| 27 | |||
| 28 | 880 | bool CalendarAccountSync::current() const { | |
| 29 |
5/6✓ Branch 2 → 3 taken 880 times.
✗ Branch 2 → 8 not taken.
✓ Branch 4 → 5 taken 876 times.
✓ Branch 4 → 7 taken 4 times.
✓ Branch 6 → 7 taken 874 times.
✓ Branch 6 → 8 taken 2 times.
|
880 | return !m_finished && (!m_deps.configurationCurrent || m_deps.configurationCurrent()); |
| 30 | } | ||
| 31 | |||
| 32 | 141 | void CalendarAccountSync::start() { | |
| 33 | 141 | m_deps.status->started(m_account.accountId); | |
| 34 | 141 | m_timeout.start(m_deps.timeoutMs); | |
| 35 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 140 times.
|
141 | if (m_account.requiresLocalAuthorization) { |
| 36 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
|
2 | QTimer::singleShot(0, this, [this] { onCredentials({}, {}); }); |
| 37 | 1 | return; | |
| 38 | } | ||
| 39 |
1/1✓ Branch 8 → 9 taken 140 times.
|
140 | m_deps.resolveCredentials(this, m_account.accountId, m_account.serverUrl, m_account.username, |
| 40 | 280 | [this](QByteArray password, const QString &error) { | |
| 41 |
1/1✓ Branch 4 → 5 taken 127 times.
|
127 | onCredentials(std::move(password), error); |
| 42 | 127 | }); | |
| 43 | } | ||
| 44 | |||
| 45 | 20 | void CalendarAccountSync::cancel() { | |
| 46 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 20 times.
|
20 | if (m_finished) return; |
| 47 | 20 | m_finished = true; | |
| 48 | 20 | m_timeout.stop(); | |
| 49 |
2/2✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 9 taken 14 times.
|
20 | if (m_client) m_client->disconnect(this); |
| 50 | // S106-053: close the status run — the account stayed "running" for the | ||
| 51 | // rest of the session when sync was switched off mid-run. | ||
| 52 | 20 | m_deps.status->cancelled(m_account.accountId); | |
| 53 | } | ||
| 54 | |||
| 55 | 128 | void CalendarAccountSync::onCredentials(QByteArray password, const QString &error) { | |
| 56 |
3/4✓ Branch 2 → 3 taken 127 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 127 times.
|
128 | if (m_finished || m_credentialsResolved) return; |
| 57 | 127 | m_credentialsResolved = true; | |
| 58 |
2/2✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 9 taken 125 times.
|
127 | if (!current()) { |
| 59 | 2 | finish(false); | |
| 60 | 2 | return; | |
| 61 | } | ||
| 62 |
5/6✓ Branch 10 → 11 taken 123 times.
✓ Branch 10 → 12 taken 2 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 123 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 30 taken 123 times.
|
125 | if (password.isEmpty() || m_account.requiresLocalAuthorization) { |
| 63 | // A missing keyring entry used to be reported as "authorize locally" | ||
| 64 | // in the status bar only; it now names the cause. | ||
| 65 | 1 | const QString kind = m_account.requiresLocalAuthorization ? QStringLiteral("authorization") | |
| 66 |
6/6✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 19 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 1 time.
|
4 | : QStringLiteral("password"); |
| 67 |
1/1✓ Branch 26 → 27 taken 2 times.
|
2 | fail(kind, error); |
| 68 |
1/1✓ Branch 27 → 28 taken 2 times.
|
2 | finish(false); |
| 69 | 2 | return; | |
| 70 | 2 | } | |
| 71 |
1/1✓ Branch 32 → 33 taken 123 times.
|
123 | run(std::move(password)); |
| 72 | } | ||
| 73 | |||
| 74 | 10 | void CalendarAccountSync::fail(const QString &kind, const QString &detail) { | |
| 75 |
1/2✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 5 not taken.
|
10 | if (m_result.errorKind.isEmpty()) m_result.errorKind = kind; |
| 76 | 10 | m_deps.status->failed(m_account.accountId, kind, detail); | |
| 77 | // G16: the server's text stays in the log and the status details; the | ||
| 78 | // status line gets the plain-language message for the kind of failure. | ||
| 79 |
2/2✓ Branch 6 → 7 taken 10 times.
✓ Branch 7 → 8 taken 10 times.
|
10 | emit statusMessage(CalendarSyncStatus::messageForKind(kind)); |
| 80 | 10 | } | |
| 81 | |||
| 82 | 121 | void CalendarAccountSync::finish(bool completed) { | |
| 83 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 121 times.
|
121 | if (m_finished) return; |
| 84 | 121 | m_finished = true; | |
| 85 | 121 | m_timeout.stop(); | |
| 86 |
2/2✓ Branch 6 → 7 taken 116 times.
✓ Branch 6 → 9 taken 5 times.
|
121 | if (m_client) m_client->disconnect(this); |
| 87 | 121 | m_result.completed = completed; | |
| 88 |
2/2✓ Branch 19 → 12 taken 169 times.
✓ Branch 19 → 20 taken 121 times.
|
290 | for (const QString &path : std::as_const(m_requested)) |
| 89 |
3/3✓ Branch 14 → 15 taken 7 times.
✓ Branch 14 → 17 taken 162 times.
✓ Branch 15 → 16 taken 7 times.
|
169 | if (!m_result.syncedPaths.contains(path)) m_result.failedPaths.insert(path); |
| 90 | 121 | m_deps.status->finished(m_account.accountId); | |
| 91 | 121 | emit finished(m_result); | |
| 92 | } | ||
| 93 | |||
| 94 | 123 | void CalendarAccountSync::run(QByteArray password) { | |
| 95 |
2/2✓ Branch 2 → 3 taken 123 times.
✓ Branch 3 → 4 taken 123 times.
|
123 | auto *client = m_deps.makeClient(m_account.serverUrl, m_account.username, QString::fromUtf8(password)); |
| 96 |
1/1✓ Branch 5 → 6 taken 123 times.
|
123 | SecureUtil::zeroMemory(password); |
| 97 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 122 times.
|
123 | if (!client) { |
| 98 |
1/1✓ Branch 7 → 8 taken 1 time.
|
1 | finish(false); |
| 99 | 83 | return; | |
| 100 | } | ||
| 101 |
1/1✓ Branch 9 → 10 taken 122 times.
|
122 | m_client = client; |
| 102 |
1/1✓ Branch 10 → 11 taken 122 times.
|
122 | client->setParent(this); |
| 103 |
1/1✓ Branch 11 → 12 taken 122 times.
|
122 | connect(client, &CalDavClient::syncFailed, this, [this](const QString &error) { |
| 104 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
|
5 | if (!current()) return; |
| 105 |
7/7✓ Branch 5 → 6 taken 5 times.
✓ Branch 9 → 10 taken 5 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 12 → 13 taken 5 times.
✓ Branch 16 → 7 taken 5 times.
✓ Branch 16 → 17 taken 5 times.
|
10 | qCWarning(lcCalendarSync) << "Sync failed for account" << m_account.accountId << error; |
| 106 |
2/2✓ Branch 19 → 20 taken 5 times.
✓ Branch 20 → 21 taken 5 times.
|
5 | fail(CalendarSyncStatus::kindForNetworkError(m_client->lastNetworkError()), error); |
| 107 | }); | ||
| 108 |
1/1✓ Branch 13 → 14 taken 122 times.
|
122 | connect(client, &CalDavClient::calendarsDiscovered, this, &CalendarAccountSync::onDiscovered); |
| 109 |
1/1✓ Branch 15 → 16 taken 122 times.
|
122 | connect(client, &CalDavClient::collectionSynced, this, &CalendarAccountSync::onCollectionSynced); |
| 110 |
1/1✓ Branch 17 → 18 taken 122 times.
|
122 | connect(client, &CalDavClient::collectionSyncRejected, this, &CalendarAccountSync::onCollectionRejected); |
| 111 | 122 | connect(client, &CalDavClient::eventsSynced, this, | |
| 112 |
1/1✓ Branch 19 → 20 taken 122 times.
|
122 | [this](const QString &path, const QList<CalendarEvent> &events, bool complete) { |
| 113 |
3/6✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 5 times.
|
5 | if (!current() || !selected(path)) return; |
| 114 | 5 | onQueryResult(path, true, m_deps.apply->applyQueryEvents(m_account.accountId, path, events, complete), | |
| 115 | complete); | ||
| 116 | }); | ||
| 117 | 122 | connect(client, &CalDavClient::tasksSynced, this, | |
| 118 |
1/1✓ Branch 21 → 22 taken 122 times.
|
122 | [this](const QString &path, const QList<CalendarTask> &tasks, bool complete) { |
| 119 |
3/6✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 5 times.
|
5 | if (!current() || !selected(path)) return; |
| 120 | 5 | onQueryResult(path, false, m_deps.apply->applyQueryTasks(m_account.accountId, path, tasks, complete), | |
| 121 | complete); | ||
| 122 | }); | ||
| 123 |
1/1✓ Branch 23 → 24 taken 122 times.
|
238 | connect(client, &CalDavClient::allRequestsFinished, this, [this] { finish(true); }); |
| 124 | // F-54 (SPRINT-104): the run's 60 s budget is an inactivity budget — | ||
| 125 | // every answered request restarts it (like the contact sync's timer), so | ||
| 126 | // a batched initial download of a large calendar survives the run. | ||
| 127 |
1/1✓ Branch 25 → 26 taken 122 times.
|
122 | connect(client, &CalDavClient::requestAnswered, this, [this] { |
| 128 |
1/2✓ Branch 2 → 3 taken 268 times.
✗ Branch 2 → 4 not taken.
|
268 | if (!m_finished) m_timeout.start(m_deps.timeoutMs); |
| 129 | 268 | }); | |
| 130 | |||
| 131 | // A targeted run needs every forced collection to be known locally (its | ||
| 132 | // components decide the query fallback); otherwise it discovers first. | ||
| 133 | 122 | QStringList targets; | |
| 134 |
2/2✓ Branch 27 → 28 taken 40 times.
✓ Branch 27 → 63 taken 82 times.
|
122 | if (!m_request.discover) { |
| 135 | 40 | QSet<QString> known; | |
| 136 |
5/5✓ Branch 29 → 30 taken 40 times.
✓ Branch 30 → 31 taken 40 times.
✓ Branch 31 → 32 taken 40 times.
✓ Branch 40 → 33 taken 113 times.
✓ Branch 40 → 41 taken 40 times.
|
153 | for (const auto &calendar : m_deps.store->allCalendars()) |
| 137 |
2/3✓ Branch 35 → 36 taken 113 times.
✗ Branch 35 → 38 not taken.
✓ Branch 36 → 37 taken 113 times.
|
153 | if (calendar.accountId == m_account.accountId) known.insert(calendar.path); |
| 138 |
4/4✓ Branch 42 → 43 taken 40 times.
✓ Branch 43 → 44 taken 40 times.
✓ Branch 56 → 45 taken 43 times.
✓ Branch 56 → 57 taken 40 times.
|
83 | for (const QString &path : m_request.forcedPaths) |
| 139 |
4/7✓ Branch 47 → 48 taken 43 times.
✗ Branch 47 → 51 not taken.
✓ Branch 49 → 50 taken 43 times.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 43 times.
✗ Branch 52 → 54 not taken.
✓ Branch 53 → 54 taken 43 times.
|
43 | if (selected(path) && known.contains(path)) targets << path; |
| 140 |
1/2✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 40 times.
|
40 | if (targets.size() != m_request.forcedPaths.size()) m_request.discover = true; |
| 141 | 40 | } | |
| 142 |
2/2✓ Branch 63 → 64 taken 82 times.
✓ Branch 63 → 66 taken 40 times.
|
122 | if (m_request.discover) { |
| 143 |
1/1✓ Branch 64 → 65 taken 82 times.
|
82 | client->discoverCalendars(); |
| 144 | 82 | return; | |
| 145 | } | ||
| 146 |
2/2✓ Branch 76 → 69 taken 43 times.
✓ Branch 76 → 77 taken 40 times.
|
83 | for (const QString &path : std::as_const(targets)) { |
| 147 |
1/1✓ Branch 70 → 71 taken 43 times.
|
43 | syncCollection(path); |
| 148 |
2/3✓ Branch 71 → 72 taken 43 times.
✗ Branch 72 → 73 not taken.
✓ Branch 72 → 74 taken 43 times.
|
43 | if (!current()) return; |
| 149 | } | ||
| 150 | 122 | } | |
| 151 | |||
| 152 | 77 | void CalendarAccountSync::onDiscovered(const QList<CalendarInfo> &calendars) { | |
| 153 |
2/3✓ Branch 2 → 3 taken 77 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 77 times.
|
77 | if (!current()) return; |
| 154 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 40 taken 77 times.
|
77 | if (const auto scheduling = m_client->serverScheduling()) { |
| 155 | ✗ | CalendarSyncStatus::setServerScheduling(m_account.accountId, *scheduling); | |
| 156 | // Sprint 108 S108.8.4: the outbox (free/busy), the addresses the server | ||
| 157 | // schedules for, and — once — whether it schedules by itself | ||
| 158 | // (calendar-auto-schedule in the DAV header of OPTIONS). | ||
| 159 | ✗ | const QString outbox = m_client->scheduleOutboxUrl(); | |
| 160 | ✗ | CalendarSyncStatus::setScheduling(m_account.accountId, outbox, m_client->calendarUserAddresses()); | |
| 161 | ✗ | if (*scheduling && !outbox.isEmpty()) { | |
| 162 | ✗ | const QString accountId = m_account.accountId; | |
| 163 | ✗ | CalDavClient *client = m_client; | |
| 164 | ✗ | const quint64 request = client->options(outbox); | |
| 165 | ✗ | auto connection = std::make_shared<QMetaObject::Connection>(); | |
| 166 | ✗ | *connection = connect(client, &CalDavClient::resourceResponse, client, | |
| 167 | ✗ | [connection, request, accountId](const DavResponse &response) { | |
| 168 | ✗ | if (response.request != request) return; | |
| 169 | ✗ | QObject::disconnect(*connection); | |
| 170 | ✗ | if (response.status < 200 || response.status >= 300) return; | |
| 171 | ✗ | const bool automatic = response.davHeader.contains("calendar-auto-schedule"); | |
| 172 | ✗ | CalendarSyncStatus::setAutoSchedule(accountId, automatic); | |
| 173 | ✗ | qCInfo(lcCalendarSync) << "calendar-auto-schedule" << automatic; | |
| 174 | ✗ | }); | |
| 175 | ✗ | } | |
| 176 | ✗ | } | |
| 177 | 77 | QStringList unchanged; | |
| 178 |
2/2✓ Branch 74 → 42 taken 157 times.
✓ Branch 74 → 75 taken 77 times.
|
234 | for (const auto &cal : calendars) { |
| 179 |
1/2✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 157 times.
|
157 | if (!selected(cal.path)) continue; |
| 180 |
1/1✓ Branch 46 → 47 taken 157 times.
|
157 | const QString known = m_deps.store->calendarCtag(m_account.accountId, cal.path); |
| 181 |
7/7✓ Branch 48 → 49 taken 129 times.
✓ Branch 48 → 52 taken 28 times.
✓ Branch 50 → 51 taken 33 times.
✓ Branch 50 → 52 taken 96 times.
✓ Branch 53 → 54 taken 33 times.
✓ Branch 53 → 55 taken 124 times.
✓ Branch 54 → 55 taken 33 times.
|
157 | if (!cal.ctag.isEmpty() && cal.ctag == known) unchanged << cal.path; |
| 182 | 157 | CalendarInfo stored = cal; | |
| 183 | 157 | stored.accountId = m_account.accountId; | |
| 184 | 157 | stored.ctag = known; // committed after a complete sync only | |
| 185 |
4/4✓ Branch 58 → 59 taken 156 times.
✓ Branch 58 → 61 taken 1 time.
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 62 taken 155 times.
|
157 | stored.readOnly = stored.readOnly || m_account.readOnlyCalendars.contains(cal.path); |
| 186 |
1/1✓ Branch 63 → 64 taken 157 times.
|
157 | m_deps.store->upsertCalendar(stored, m_account.accountId); |
| 187 |
2/3✓ Branch 65 → 66 taken 157 times.
✗ Branch 65 → 67 not taken.
✓ Branch 66 → 67 taken 157 times.
|
157 | if (m_deps.adoptServerColor) m_deps.adoptServerColor(m_account.accountId, cal.path, cal.color); |
| 188 |
1/1✓ Branch 67 → 68 taken 157 times.
|
157 | m_discoveredCtags.insert(cal.path, cal.ctag); |
| 189 | 157 | } | |
| 190 |
1/1✓ Branch 75 → 76 taken 77 times.
|
77 | QPointer<CalendarAccountSync> guard(this); |
| 191 |
1/1✓ Branch 76 → 77 taken 77 times.
|
77 | emit collectionsDiscovered(m_account.accountId, calendars); |
| 192 |
4/7✓ Branch 78 → 79 taken 77 times.
✗ Branch 78 → 81 not taken.
✓ Branch 79 → 80 taken 77 times.
✗ Branch 80 → 81 not taken.
✓ Branch 80 → 82 taken 77 times.
✗ Branch 83 → 84 not taken.
✓ Branch 83 → 85 taken 77 times.
|
77 | if (!guard || !current()) return; |
| 193 |
1/1✓ Branch 85 → 86 taken 77 times.
|
77 | emit storeChanged(); |
| 194 |
4/7✓ Branch 87 → 88 taken 77 times.
✗ Branch 87 → 90 not taken.
✓ Branch 88 → 89 taken 77 times.
✗ Branch 89 → 90 not taken.
✓ Branch 89 → 91 taken 77 times.
✗ Branch 92 → 93 not taken.
✓ Branch 92 → 94 taken 77 times.
|
77 | if (!guard || !current()) return; |
| 195 |
2/2✓ Branch 135 → 96 taken 157 times.
✓ Branch 135 → 136 taken 77 times.
|
234 | for (const auto &cal : calendars) { |
| 196 |
1/2✗ Branch 98 → 99 not taken.
✓ Branch 98 → 100 taken 157 times.
|
157 | if (!selected(cal.path)) continue; |
| 197 | // An unfinished enumeration continues even when the CTag is unchanged; | ||
| 198 | // a forced collection (push, recovery) ignores the CTag. | ||
| 199 |
4/4✓ Branch 101 → 102 taken 33 times.
✓ Branch 101 → 107 taken 124 times.
✓ Branch 103 → 104 taken 28 times.
✓ Branch 103 → 107 taken 5 times.
|
185 | if (unchanged.contains(cal.path) && !m_request.forcedPaths.contains(cal.path) && |
| 200 |
6/9✓ Branch 104 → 105 taken 28 times.
✓ Branch 105 → 106 taken 28 times.
✗ Branch 105 → 107 not taken.
✓ Branch 108 → 109 taken 28 times.
✓ Branch 108 → 110 taken 129 times.
✓ Branch 110 → 111 taken 28 times.
✓ Branch 110 → 124 taken 129 times.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
|
185 | !m_deps.store->enumerationState(m_account.accountId, cal.path).active) { |
| 201 |
6/6✓ Branch 111 → 112 taken 28 times.
✓ Branch 115 → 116 taken 28 times.
✓ Branch 116 → 117 taken 28 times.
✓ Branch 117 → 118 taken 28 times.
✓ Branch 121 → 113 taken 28 times.
✓ Branch 121 → 122 taken 28 times.
|
56 | qCInfo(lcCalendarSync) << "Collection unchanged (CTag), skipped:" << cal.path; |
| 202 |
1/1✓ Branch 122 → 123 taken 28 times.
|
28 | m_result.syncedPaths.insert(cal.path); |
| 203 | 28 | continue; | |
| 204 | 28 | } | |
| 205 |
1/1✓ Branch 124 → 125 taken 129 times.
|
129 | syncCollection(cal.path); |
| 206 |
4/7✓ Branch 126 → 127 taken 129 times.
✗ Branch 126 → 129 not taken.
✓ Branch 127 → 128 taken 129 times.
✗ Branch 128 → 129 not taken.
✓ Branch 128 → 130 taken 129 times.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 133 taken 129 times.
|
129 | if (!guard || !current()) return; |
| 207 | } | ||
| 208 | 77 | } | |
| 209 | |||
| 210 | 172 | void CalendarAccountSync::syncCollection(const QString &path) { | |
| 211 |
1/1✓ Branch 2 → 3 taken 172 times.
|
172 | m_requested.insert(path); |
| 212 |
3/3✓ Branch 3 → 4 taken 172 times.
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 8 taken 169 times.
|
172 | if (m_deps.queryOnlyCollections->contains(calendarSelectionKey(m_account.accountId, path))) |
| 213 | 3 | queryCollection(path); | |
| 214 | else | ||
| 215 |
2/2✓ Branch 8 → 9 taken 169 times.
✓ Branch 9 → 10 taken 169 times.
|
169 | requestPage(path, m_deps.apply->nextRequestToken(m_account.accountId, path)); |
| 216 | 172 | } | |
| 217 | |||
| 218 | // The classic full download: one calendar-query for events, one for tasks. | ||
| 219 | 5 | void CalendarAccountSync::queryCollection(const QString &path) { | |
| 220 | 5 | QStringList components; | |
| 221 |
5/5✓ Branch 2 → 3 taken 5 times.
✓ Branch 3 → 4 taken 5 times.
✓ Branch 4 → 5 taken 5 times.
✓ Branch 17 → 6 taken 5 times.
✓ Branch 17 → 18 taken 5 times.
|
10 | for (const auto &calendar : m_deps.store->allCalendars()) |
| 222 |
3/6✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 12 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 5 times.
✗ Branch 13 → 15 not taken.
|
10 | if (calendar.accountId == m_account.accountId && calendar.path == path) components = calendar.supportedComponents; |
| 223 |
3/8✗ Branch 20 → 21 not taken.
✓ Branch 20 → 26 taken 5 times.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 5 times.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 5 times.
|
5 | const bool events = components.isEmpty() || components.contains(QStringLiteral("VEVENT")); |
| 224 |
3/8✗ Branch 33 → 34 not taken.
✓ Branch 33 → 39 taken 5 times.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 5 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 5 times.
|
5 | const bool tasks = components.isEmpty() || components.contains(QStringLiteral("VTODO")); |
| 225 |
1/1✓ Branch 47 → 48 taken 5 times.
|
5 | m_queryPending.insert(path, {m_discoveredCtags.value(path), int(events) + int(tasks)}); |
| 226 |
2/3✓ Branch 50 → 51 taken 5 times.
✗ Branch 50 → 53 not taken.
✓ Branch 52 → 53 taken 5 times.
|
5 | if (events) m_client->syncCalendar(path); |
| 227 |
2/3✓ Branch 53 → 54 taken 5 times.
✗ Branch 53 → 56 not taken.
✓ Branch 55 → 56 taken 5 times.
|
5 | if (tasks) m_client->syncTasks(path); |
| 228 | 5 | } | |
| 229 | |||
| 230 | 175 | void CalendarAccountSync::requestPage(const QString &path, const QString &token) { | |
| 231 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 17 taken 175 times.
|
175 | if (++m_pages[path] > kMaxSyncPagesPerRun) { |
| 232 | // The checkpoint is committed; the next run continues from it. | ||
| 233 | ✗ | qCWarning(lcCalendarSync) << "Page limit reached, continuing next run:" << m_account.accountId << path; | |
| 234 | ✗ | return; | |
| 235 | } | ||
| 236 | 175 | m_client->syncCollection(path, token); | |
| 237 | } | ||
| 238 | |||
| 239 | 166 | void CalendarAccountSync::onCollectionSynced(const QString &path, const DavCollectionChanges &changes) { | |
| 240 |
4/7✓ Branch 2 → 3 taken 166 times.
✓ Branch 3 → 4 taken 166 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 166 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 166 times.
|
166 | if (!current() || !selected(path)) return; |
| 241 |
1/1✓ Branch 11 → 12 taken 166 times.
|
166 | const auto outcome = m_deps.apply->applySyncPage(m_account.accountId, path, changes, m_discoveredCtags.value(path)); |
| 242 |
4/4✓ Branch 13 → 14 taken 135 times.
✓ Branch 13 → 15 taken 31 times.
✓ Branch 14 → 15 taken 133 times.
✓ Branch 14 → 31 taken 2 times.
|
166 | if (outcome.appliedResources > 0 || outcome.result != CalendarSyncApply::PageResult::Failed) { |
| 243 |
1/1✓ Branch 15 → 16 taken 164 times.
|
164 | QPointer<CalendarAccountSync> guard(this); |
| 244 |
1/1✓ Branch 16 → 17 taken 164 times.
|
164 | emit storeChanged(); |
| 245 |
4/7✓ Branch 18 → 19 taken 164 times.
✗ Branch 18 → 21 not taken.
✓ Branch 19 → 20 taken 164 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 164 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 164 times.
|
164 | if (!guard || !current()) return; |
| 246 | 164 | } | |
| 247 |
2/2✓ Branch 31 → 32 taken 3 times.
✓ Branch 31 → 33 taken 163 times.
|
166 | if (outcome.result == CalendarSyncApply::PageResult::Continue) { |
| 248 |
1/1✓ Branch 32 → 63 taken 3 times.
|
3 | requestPage(path, outcome.continueToken); |
| 249 |
2/2✓ Branch 33 → 34 taken 160 times.
✓ Branch 33 → 36 taken 3 times.
|
163 | } else if (outcome.result == CalendarSyncApply::PageResult::Committed) { |
| 250 |
1/1✓ Branch 34 → 35 taken 160 times.
|
160 | m_result.syncedPaths.insert(path); |
| 251 | } else { | ||
| 252 |
8/8✓ Branch 36 → 37 taken 3 times.
✓ Branch 40 → 41 taken 3 times.
✓ Branch 41 → 42 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
✓ Branch 43 → 44 taken 3 times.
✓ Branch 44 → 45 taken 3 times.
✓ Branch 48 → 38 taken 3 times.
✓ Branch 48 → 49 taken 3 times.
|
6 | qCWarning(lcCalendarSync) << "Collection sync incomplete:" << m_account.accountId << path << outcome.error; |
| 253 | // A failed local write is no server error (user report 2026-10-13: the | ||
| 254 | // status blamed the server for "database is locked"). | ||
| 255 |
7/11✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 53 taken 2 times.
✓ Branch 56 → 57 taken 3 times.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 58 → 60 taken 1 time.
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 62 taken 2 times.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
|
6 | fail(outcome.localFailure ? QStringLiteral("local") : QStringLiteral("server"), outcome.error); |
| 256 | } | ||
| 257 | 166 | } | |
| 258 | |||
| 259 | 5 | void CalendarAccountSync::onCollectionRejected(const QString &path, bool invalidToken) { | |
| 260 |
3/6✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 5 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 5 times.
|
5 | if (!current() || !selected(path)) return; |
| 261 |
2/2✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 35 taken 2 times.
|
5 | if (invalidToken) { |
| 262 | // RFC 6578 §3.2: start over with the whole collection — once per run. | ||
| 263 |
3/6✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 15 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 3 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 30 taken 3 times.
|
3 | if (m_tokenResets.contains(path) || !m_deps.apply->resetCollectionToken(m_account.accountId, path)) { |
| 264 | ✗ | fail(QStringLiteral("server"), QStringLiteral("sync token rejected repeatedly")); | |
| 265 | ✗ | return; | |
| 266 | } | ||
| 267 |
1/1✓ Branch 30 → 31 taken 3 times.
|
3 | m_tokenResets.insert(path); |
| 268 |
1/1✓ Branch 32 → 33 taken 3 times.
|
3 | requestPage(path, QString()); |
| 269 | 3 | return; | |
| 270 | } | ||
| 271 | // This collection lacks sync-collection: calendar-query from now on. | ||
| 272 |
7/7✓ Branch 35 → 36 taken 2 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
✓ Branch 46 → 37 taken 2 times.
✓ Branch 46 → 47 taken 2 times.
|
4 | qCInfo(lcCalendarSync) << "sync-collection unsupported, using calendar-query for" << m_account.accountId << path; |
| 273 |
2/2✓ Branch 47 → 48 taken 2 times.
✓ Branch 48 → 49 taken 2 times.
|
2 | m_deps.queryOnlyCollections->insert(calendarSelectionKey(m_account.accountId, path)); |
| 274 | 2 | queryCollection(path); | |
| 275 | } | ||
| 276 | |||
| 277 | 10 | void CalendarAccountSync::onQueryResult(const QString &path, bool events, bool stored, bool complete) { | |
| 278 | Q_UNUSED(events); | ||
| 279 |
1/1✓ Branch 2 → 3 taken 10 times.
|
10 | auto it = m_queryPending.find(path); |
| 280 |
3/4✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 14 taken 9 times.
|
10 | if (!stored || !complete) { |
| 281 | // Incomplete or not stored: this run commits no CTag for the collection. | ||
| 282 |
2/3✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 1 time.
|
1 | if (it != m_queryPending.end()) m_queryPending.erase(it); |
| 283 |
2/3✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 13 not taken.
✓ Branch 12 → 13 taken 1 time.
|
1 | if (stored) emit storeChanged(); |
| 284 | 1 | return; | |
| 285 | } | ||
| 286 |
1/1✓ Branch 14 → 15 taken 9 times.
|
9 | QPointer<CalendarAccountSync> guard(this); |
| 287 |
6/6✓ Branch 17 → 18 taken 8 times.
✓ Branch 17 → 21 taken 1 time.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 19 → 21 taken 4 times.
✓ Branch 22 → 23 taken 4 times.
✓ Branch 22 → 32 taken 5 times.
|
9 | if (it != m_queryPending.end() && --it->second == 0) { |
| 288 |
1/3✗ Branch 25 → 26 not taken.
✓ Branch 25 → 28 taken 4 times.
✗ Branch 27 → 28 not taken.
|
4 | if (!it->first.isEmpty()) m_deps.store->setCalendarCtag(m_account.accountId, path, it->first); |
| 289 |
1/1✓ Branch 29 → 30 taken 4 times.
|
4 | m_queryPending.erase(it); |
| 290 |
1/1✓ Branch 30 → 31 taken 4 times.
|
4 | m_result.syncedPaths.insert(path); |
| 291 | } | ||
| 292 |
1/1✓ Branch 32 → 33 taken 9 times.
|
9 | emit storeChanged(); |
| 293 | 9 | } | |
| 294 |