app/core/ContactSyncCoordinator.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ContactSyncCoordinator.h" | ||
| 2 | #include "data/DavSelectionList.h" | ||
| 3 | |||
| 4 | #include "ContactBookSync.h" | ||
| 5 | #include "ContactOutbox.h" | ||
| 6 | #include "data/ContactQueries.h" | ||
| 7 | #include "data/ContactStore.h" | ||
| 8 | #include "data/MailAddressList.h" | ||
| 9 | #include "data/VCardCodec.h" | ||
| 10 | #include "service/CardDavClient.h" | ||
| 11 | #include "util/SecureUtil.h" | ||
| 12 | |||
| 13 | #include <QDateTime> | ||
| 14 | #include <QLoggingCategory> | ||
| 15 | #include <QSettings> | ||
| 16 | #include <QSet> | ||
| 17 | #include <QTimeZone> | ||
| 18 | |||
| 19 |
4/7✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 3 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
4 | Q_LOGGING_CATEGORY(lcContactSync, "mailjd.contactsync") |
| 20 | |||
| 21 | 904 | ContactSyncCoordinator::ContactSyncCoordinator(const QString &contactsDbPath, QObject *parent) | |
| 22 |
2/2✓ Branch 4 → 5 taken 904 times.
✓ Branch 5 → 6 taken 904 times.
|
904 | : QObject(parent), m_databasePath(contactsDbPath) { |
| 23 |
3/6✓ Branch 18 → 19 taken 904 times.
✓ Branch 19 → 20 taken 904 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 904 times.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
|
904 | m_store = new ContactStore(this); |
| 24 |
2/3✓ Branch 22 → 23 taken 904 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 36 taken 904 times.
|
904 | if (!m_store->open(contactsDbPath)) |
| 25 | ✗ | qCWarning(lcContactSync) << "ContactSyncCoordinator: store open failed at" << contactsDbPath; | |
| 26 |
1/1✓ Branch 36 → 37 taken 904 times.
|
904 | m_syncTimer.setSingleShot(false); |
| 27 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 37 → 38 taken 904 times.
|
904 | connect(&m_syncTimer, &QTimer::timeout, this, [this] { startRun({}, DavSyncReasonPoll); }); |
| 28 |
1/1✓ Branch 39 → 40 taken 904 times.
|
904 | m_initialSyncTimer.setSingleShot(true); |
| 29 |
2/2✓ Branch 3 → 4 taken 97 times.
✓ Branch 40 → 41 taken 904 times.
|
1001 | connect(&m_initialSyncTimer, &QTimer::timeout, this, [this] { startRun({}, DavSyncReasonInitial); }); |
| 30 | // Defaults: the production keychain resolver and the real client factory. | ||
| 31 | 904 | m_asyncCredentialResolver = [](QObject *owner, const QString &id, const QString &url, const QString &user, | |
| 32 | DavCredentials::PasswordCallback callback) { | ||
| 33 | ✗ | DavCredentials::readPasswordAsync(owner, id, url, user, std::move(callback)); | |
| 34 | 904 | }; | |
| 35 | 904 | m_clientFactory = [](const QString &serverUrl, const QString &username, const QString &password) { | |
| 36 | ✗ | return new CardDavClient(serverUrl, username, password); | |
| 37 | 904 | }; | |
| 38 | 904 | ContactOutbox::Deps deps; | |
| 39 | 904 | deps.store = m_store; | |
| 40 | 2003 | deps.endpoint = [this](QObject *owner, const QString &accountId, ContactOutbox::EndpointCallback callback) { | |
| 41 |
3/3✓ Branch 2 → 3 taken 195 times.
✓ Branch 3 → 4 taken 195 times.
✓ Branch 4 → 5 taken 195 times.
|
195 | resolveEndpoint(owner, accountId, [callback](QString url, QString user, QByteArray password) { |
| 42 |
5/6✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 6 taken 182 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 13 times.
✓ Branch 8 → 9 taken 182 times.
✓ Branch 8 → 13 taken 13 times.
|
195 | if (url.isEmpty() || password.isEmpty()) { |
| 43 |
1/1✓ Branch 10 → 11 taken 182 times.
|
182 | callback(std::nullopt); |
| 44 | 182 | return; | |
| 45 | } | ||
| 46 |
1/5✓ Branch 17 → 18 taken 13 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
|
13 | callback(ContactOutbox::Endpoint{url, user, password}); |
| 47 | 13 | SecureUtil::zeroMemory(password); | |
| 48 | }); | ||
| 49 | 1099 | }; | |
| 50 | 1821 | deps.makeClient = [this](const QString &url, const QString &user, const QString &password) { | |
| 51 | 13 | return m_clientFactory(url, user, password); | |
| 52 | 904 | }; | |
| 53 |
4/4✓ Branch 3 → 4 taken 76 times.
✓ Branch 3 → 6 taken 252 times.
✓ Branch 5 → 6 taken 29 times.
✓ Branch 5 → 7 taken 47 times.
|
1232 | deps.networkAllowed = [this] { return !m_networkGate || m_networkGate(); }; |
| 54 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 565 times.
|
1469 | deps.now = [this] { return m_clock ? m_clock() : QDateTime::currentSecsSinceEpoch(); }; |
| 55 | 1821 | deps.pushDontNotify = [this](const QString &accountId, const QString &path) { | |
| 56 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 12 times.
|
13 | return m_pushDontNotify ? m_pushDontNotify(accountId, path) : QString(); |
| 57 | 904 | }; | |
| 58 |
3/6✓ Branch 50 → 51 taken 904 times.
✓ Branch 53 → 54 taken 904 times.
✗ Branch 55 → 56 not taken.
✓ Branch 55 → 57 taken 904 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
|
904 | m_outbox = new ContactOutbox(std::move(deps), this); |
| 59 |
1/1✓ Branch 57 → 58 taken 904 times.
|
904 | connect(m_outbox, &ContactOutbox::cardsChanged, this, &ContactSyncCoordinator::cardsChanged); |
| 60 |
1/1✓ Branch 59 → 60 taken 904 times.
|
904 | connect(m_outbox, &ContactOutbox::statusMessage, this, &ContactSyncCoordinator::statusMessage); |
| 61 | 904 | } | |
| 62 | |||
| 63 | 1790 | ContactSyncCoordinator::~ContactSyncCoordinator() { | |
| 64 | 904 | ++m_generation; | |
| 65 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 904 times.
|
904 | delete m_run.data(); |
| 66 | 904 | wipeResolvedPasswords(); | |
| 67 | 1790 | } | |
| 68 | |||
| 69 | 821 | bool ContactSyncCoordinator::open() { return m_store->isOpen(); } | |
| 70 | |||
| 71 | 996 | int ContactSyncCoordinator::configuredIntervalMinutes() { | |
| 72 |
1/1✓ Branch 2 → 3 taken 996 times.
|
996 | QSettings s; |
| 73 | // Preserve the predecessor's independent CardDAV interval. Profiles created | ||
| 74 | // during the Quick shared-interval period keep that value until explicitly | ||
| 75 | // choosing a contact interval; never silently turn an old disabled sync on. | ||
| 76 |
1/1✓ Branch 13 → 14 taken 996 times.
|
1992 | return qBound(0, s.value(QStringLiteral("carddav/syncIntervalMin"), |
| 77 |
3/3✓ Branch 8 → 9 taken 996 times.
✓ Branch 14 → 15 taken 996 times.
✓ Branch 15 → 16 taken 996 times.
|
4980 | s.value(QStringLiteral("caldav/syncIntervalMin"), 15)).toInt(), 1440); |
| 78 | 996 | } | |
| 79 | |||
| 80 | 851 | void ContactSyncCoordinator::startBackgroundSync() { | |
| 81 | 851 | const int interval = configuredIntervalMinutes(); | |
| 82 | 851 | m_initialSyncTimer.stop(); | |
| 83 | 851 | m_syncTimer.stop(); | |
| 84 |
2/2✓ Branch 5 → 6 taken 54 times.
✓ Branch 5 → 7 taken 797 times.
|
851 | if (interval <= 0) |
| 85 | 54 | return; | |
| 86 | 797 | m_syncTimer.setInterval(interval * 60 * 1000); | |
| 87 | 797 | m_syncTimer.start(); | |
| 88 | // Same startup freshness as the calendar coordinator. | ||
| 89 | 797 | m_initialSyncTimer.start(3000); | |
| 90 | } | ||
| 91 | |||
| 92 | 33 | void ContactSyncCoordinator::stopBackgroundSync() { | |
| 93 | 33 | m_syncTimer.stop(); | |
| 94 | 33 | m_initialSyncTimer.stop(); | |
| 95 | 33 | ++m_generation; | |
| 96 | 33 | const bool wasRunning = m_run; | |
| 97 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 32 times.
|
33 | if (m_run) |
| 98 | 1 | m_run->deleteLater(); | |
| 99 | 33 | m_run = nullptr; | |
| 100 | 33 | wipeResolvedPasswords(); | |
| 101 | 33 | m_queuedBooks = 0; | |
| 102 | 33 | m_queuedAll = false; | |
| 103 | 33 | m_queuedFilter.clear(); | |
| 104 | 33 | m_queuedReasons = 0; | |
| 105 | 33 | m_bookResults.clear(); | |
| 106 |
2/2✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 32 times.
|
33 | if (wasRunning) |
| 107 | 1 | emit syncStateChanged(false); | |
| 108 | 33 | } | |
| 109 | |||
| 110 | 3 | void ContactSyncCoordinator::retryPendingWrites() { | |
| 111 |
1/2✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 5 not taken.
|
3 | if (m_store->isOpen()) |
| 112 | 3 | m_outbox->flush(true); | |
| 113 | 3 | } | |
| 114 | |||
| 115 |
3/4✓ Branch 3 → 4 taken 295 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 4 → 5 taken 295 times.
✗ Branch 4 → 6 not taken.
|
296 | bool ContactSyncCoordinator::runCurrent(quint64 generation) const { return m_run && m_generation == generation; } |
| 116 | |||
| 117 | // Selected address books live in the carddav/accounts array (the rows | ||
| 118 | // DavAccountSettings persists); reading them here keeps the coordinator in | ||
| 119 | // the application core. | ||
| 120 | 643 | QList<ContactSyncCoordinator::SyncAccount> ContactSyncCoordinator::readSyncAccounts() { | |
| 121 | 643 | QList<SyncAccount> accounts; | |
| 122 |
1/1✓ Branch 2 → 3 taken 643 times.
|
643 | QSettings settings; |
| 123 |
1/1✓ Branch 7 → 8 taken 643 times.
|
643 | const int count = settings.beginReadArray(QStringLiteral("carddav/accounts")); |
| 124 |
2/2✓ Branch 72 → 11 taken 639 times.
✓ Branch 72 → 73 taken 643 times.
|
1282 | for (int i = 0; i < count; ++i) { |
| 125 |
1/1✓ Branch 11 → 12 taken 639 times.
|
639 | settings.setArrayIndex(i); |
| 126 | 639 | SyncAccount account; | |
| 127 |
2/2✓ Branch 16 → 17 taken 639 times.
✓ Branch 17 → 18 taken 639 times.
|
639 | account.accountId = settings.value(QStringLiteral("id")).toString(); |
| 128 |
2/2✓ Branch 27 → 28 taken 639 times.
✓ Branch 28 → 29 taken 639 times.
|
639 | account.serverUrl = settings.value(QStringLiteral("serverUrl")).toString(); |
| 129 |
2/2✓ Branch 38 → 39 taken 639 times.
✓ Branch 39 → 40 taken 639 times.
|
639 | account.username = settings.value(QStringLiteral("username")).toString(); |
| 130 |
2/2✓ Branch 49 → 50 taken 639 times.
✓ Branch 50 → 51 taken 639 times.
|
639 | account.selectedBooks = davPathList(settings.value(QStringLiteral("selectedBooks"))); |
| 131 |
2/2✓ Branch 61 → 62 taken 639 times.
✓ Branch 62 → 63 taken 639 times.
|
1278 | account.requiresLocalAuthorization = settings.value(QStringLiteral("requiresLocalAuthorization"), false).toBool(); |
| 132 |
1/2✓ Branch 68 → 69 taken 639 times.
✗ Branch 68 → 70 not taken.
|
639 | if (!account.accountId.isEmpty()) |
| 133 |
1/1✓ Branch 69 → 70 taken 639 times.
|
639 | accounts.append(account); |
| 134 | 639 | } | |
| 135 |
1/1✓ Branch 73 → 74 taken 643 times.
|
643 | settings.endArray(); |
| 136 | 643 | return accounts; | |
| 137 | 643 | } | |
| 138 | |||
| 139 | 195 | void ContactSyncCoordinator::resolveEndpoint(QObject *owner, const QString &accountId, | |
| 140 | std::function<void(QString, QString, QByteArray)> done) { | ||
| 141 |
5/5✓ Branch 2 → 3 taken 195 times.
✓ Branch 3 → 4 taken 195 times.
✓ Branch 4 → 5 taken 195 times.
✓ Branch 33 → 6 taken 13 times.
✓ Branch 33 → 34 taken 182 times.
|
195 | for (const SyncAccount &account : readSyncAccounts()) { |
| 142 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 13 times.
|
13 | if (account.accountId != accountId) |
| 143 | ✗ | continue; | |
| 144 |
4/8✓ Branch 11 → 12 taken 13 times.
✗ Branch 11 → 15 not taken.
✓ Branch 13 → 14 taken 13 times.
✗ Branch 13 → 15 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 13 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 13 times.
|
13 | if (account.serverUrl.isEmpty() || account.username.isEmpty() || account.requiresLocalAuthorization) |
| 145 | ✗ | break; | |
| 146 | 13 | const QString url = account.serverUrl; | |
| 147 | 13 | const QString user = account.username; | |
| 148 |
3/7✓ Branch 21 → 22 taken 13 times.
✓ Branch 24 → 25 taken 13 times.
✓ Branch 25 → 26 taken 13 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
|
13 | m_asyncCredentialResolver(owner, accountId, url, user, [done, url, user](QByteArray password, const QString &) { |
| 149 |
1/1✓ Branch 5 → 6 taken 13 times.
|
13 | done(url, user, password); |
| 150 | 13 | SecureUtil::zeroMemory(password); | |
| 151 | 13 | }); | |
| 152 | 13 | return; | |
| 153 | 208 | } | |
| 154 |
1/1✓ Branch 41 → 43 taken 182 times.
|
182 | done({}, {}, {}); |
| 155 | } | ||
| 156 | |||
| 157 | // Books follow the selection: new ones are created, deselected ones leave | ||
| 158 | // the store with their cards (the local and the legacy book stay). | ||
| 159 | 71 | void ContactSyncCoordinator::reconcileBooks(const QList<SyncAccount> &accounts) { | |
| 160 | 71 | QSet<QPair<QString, QString>> selected; | |
| 161 |
2/2✓ Branch 18 → 5 taken 69 times.
✓ Branch 18 → 19 taken 71 times.
|
140 | for (const SyncAccount &account : accounts) |
| 162 |
2/2✓ Branch 15 → 8 taken 85 times.
✓ Branch 15 → 16 taken 69 times.
|
154 | for (const QString &path : account.selectedBooks) { |
| 163 |
1/1✓ Branch 10 → 11 taken 85 times.
|
85 | selected.insert({account.accountId, path}); |
| 164 |
1/1✓ Branch 12 → 13 taken 85 times.
|
85 | m_store->ensureBook(account.accountId, path); |
| 165 | } | ||
| 166 | 71 | bool removed = false; | |
| 167 |
5/5✓ Branch 19 → 20 taken 71 times.
✓ Branch 20 → 21 taken 71 times.
✓ Branch 21 → 22 taken 71 times.
✓ Branch 59 → 23 taken 92 times.
✓ Branch 59 → 60 taken 71 times.
|
163 | for (const AddressBookInfo &book : m_store->books()) { |
| 168 |
5/8✓ Branch 25 → 26 taken 89 times.
✓ Branch 25 → 31 taken 3 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 32 taken 89 times.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 3 times.
✓ Branch 33 → 35 taken 89 times.
|
92 | if (book.isLocal() || (book.accountId.isEmpty() && book.path == QLatin1String("legacy"))) |
| 169 | 3 | continue; | |
| 170 |
2/2✓ Branch 38 → 39 taken 4 times.
✓ Branch 38 → 57 taken 85 times.
|
89 | if (!selected.contains({book.accountId, book.path})) { |
| 171 |
7/7✓ Branch 39 → 40 taken 4 times.
✓ Branch 43 → 44 taken 4 times.
✓ Branch 44 → 45 taken 4 times.
✓ Branch 45 → 46 taken 4 times.
✓ Branch 46 → 47 taken 4 times.
✓ Branch 50 → 41 taken 4 times.
✓ Branch 50 → 51 taken 4 times.
|
8 | qCInfo(lcContactSync) << "Address book" << book.id << "is no longer selected; its cards are removed"; |
| 172 |
2/5✓ Branch 51 → 52 taken 4 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 54 taken 4 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
|
4 | removed = m_store->removeBook(book.id) || removed; |
| 173 | } | ||
| 174 | 71 | } | |
| 175 |
2/2✓ Branch 61 → 62 taken 4 times.
✓ Branch 61 → 63 taken 67 times.
|
71 | if (removed) |
| 176 |
1/1✓ Branch 62 → 63 taken 4 times.
|
4 | emit contactsChanged(); |
| 177 |
1/1✓ Branch 63 → 64 taken 71 times.
|
71 | emit booksChanged(); |
| 178 | 71 | } | |
| 179 | |||
| 180 |
1/1✓ Branch 3 → 4 taken 58 times.
|
58 | void ContactSyncCoordinator::triggerSync() { startRun({}, DavSyncReasonManual); } |
| 181 | |||
| 182 | 377 | QStringList ContactSyncCoordinator::selectedBooks(const QString &accountId) { | |
| 183 |
5/5✓ Branch 2 → 3 taken 377 times.
✓ Branch 3 → 4 taken 377 times.
✓ Branch 4 → 5 taken 377 times.
✓ Branch 13 → 6 taken 467 times.
✓ Branch 13 → 14 taken 3 times.
|
470 | for (const SyncAccount &account : readSyncAccounts()) |
| 184 |
2/2✓ Branch 8 → 9 taken 374 times.
✓ Branch 8 → 11 taken 93 times.
|
467 | if (account.accountId == accountId) |
| 185 |
2/2✓ Branch 16 → 17 taken 3 times.
✓ Branch 16 → 19 taken 374 times.
|
377 | return account.selectedBooks; |
| 186 | 3 | return {}; | |
| 187 | } | ||
| 188 | |||
| 189 | 2 | QString ContactSyncCoordinator::bookSyncToken(const QString &accountId, const QString &path) const { | |
| 190 |
3/4✓ Branch 2 → 3 taken 2 times.
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
|
2 | const auto book = m_store->isOpen() ? m_store->book(accountId, path) : std::nullopt; |
| 191 |
1/2✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 10 not taken.
|
4 | return book ? book->syncToken : QString(); |
| 192 | 2 | } | |
| 193 | |||
| 194 | 13 | void ContactSyncCoordinator::syncBooks(const QString &accountId, const QStringList &paths, unsigned reasons) { | |
| 195 |
1/1✓ Branch 4 → 5 taken 13 times.
|
13 | QSet<QString> books(paths.cbegin(), paths.cend()); |
| 196 |
2/2✓ Branch 6 → 7 taken 11 times.
✓ Branch 6 → 15 taken 2 times.
|
13 | if (books.isEmpty()) { |
| 197 |
1/1✓ Branch 7 → 8 taken 11 times.
|
11 | const QStringList selected = selectedBooks(accountId); |
| 198 |
1/1✓ Branch 10 → 11 taken 11 times.
|
11 | books = QSet<QString>(selected.cbegin(), selected.cend()); |
| 199 | 11 | } | |
| 200 |
2/2✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 12 times.
|
13 | if (books.isEmpty()) |
| 201 | 1 | return; | |
| 202 |
3/5✓ Branch 20 → 21 taken 12 times.
✓ Branch 23 → 24 taken 12 times.
✓ Branch 23 → 25 taken 12 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
|
24 | startRun({{accountId, books}}, reasons); |
| 203 | 25 | } | |
| 204 | |||
| 205 | 170 | void ContactSyncCoordinator::startRun(const QHash<QString, QSet<QString>> &filter, unsigned reasons) { | |
| 206 | // A request during a run (timer, manual, push) becomes one follow-up run. | ||
| 207 |
2/2✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 17 taken 168 times.
|
170 | if (m_run) { |
| 208 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 1 time.
|
2 | if (filter.isEmpty()) |
| 209 | 1 | m_queuedAll = true; | |
| 210 |
2/2✓ Branch 15 → 8 taken 1 time.
✓ Branch 15 → 16 taken 2 times.
|
3 | for (auto it = filter.cbegin(); it != filter.cend(); ++it) |
| 211 |
2/2✓ Branch 9 → 10 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
|
1 | m_queuedFilter[it.key()].unite(it.value()); |
| 212 | 2 | m_queuedReasons |= reasons; | |
| 213 | 104 | return; | |
| 214 | } | ||
| 215 |
7/7✓ Branch 18 → 19 taken 108 times.
✓ Branch 18 → 22 taken 60 times.
✓ Branch 19 → 20 taken 108 times.
✓ Branch 20 → 21 taken 97 times.
✓ Branch 20 → 22 taken 11 times.
✓ Branch 23 → 24 taken 97 times.
✓ Branch 23 → 25 taken 71 times.
|
168 | if (m_networkGate && !m_networkGate()) |
| 216 | 97 | return; // offline: no keyring, no DAV | |
| 217 |
2/3✓ Branch 25 → 26 taken 71 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 31 taken 71 times.
|
71 | if (!m_store->isOpen()) { |
| 218 | ✗ | emit statusMessage(tr("Contacts cannot be synchronized right now.")); | |
| 219 | ✗ | return; | |
| 220 | } | ||
| 221 | 71 | QList<SyncAccount> accounts; | |
| 222 | 71 | int unauthorizedAccounts = 0; | |
| 223 |
1/1✓ Branch 31 → 32 taken 71 times.
|
71 | const QList<SyncAccount> configured = readSyncAccounts(); |
| 224 |
1/1✓ Branch 32 → 33 taken 71 times.
|
71 | reconcileBooks(configured); |
| 225 |
2/2✓ Branch 58 → 35 taken 69 times.
✓ Branch 58 → 59 taken 71 times.
|
140 | for (const SyncAccount &account : configured) { |
| 226 |
7/8✓ Branch 37 → 38 taken 67 times.
✓ Branch 37 → 42 taken 2 times.
✓ Branch 39 → 40 taken 12 times.
✓ Branch 39 → 43 taken 55 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 12 times.
✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 46 taken 67 times.
|
69 | if (account.selectedBooks.isEmpty() || (!filter.isEmpty() && !filter.contains(account.accountId))) |
| 227 | 2 | continue; | |
| 228 |
6/8✓ Branch 47 → 48 taken 67 times.
✗ Branch 47 → 51 not taken.
✓ Branch 49 → 50 taken 67 times.
✗ Branch 49 → 51 not taken.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 52 taken 66 times.
✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 55 taken 66 times.
|
67 | if (account.serverUrl.isEmpty() || account.username.isEmpty() || account.requiresLocalAuthorization) { |
| 229 | 1 | ++unauthorizedAccounts; | |
| 230 | 1 | continue; | |
| 231 | } | ||
| 232 |
1/1✓ Branch 55 → 56 taken 66 times.
|
66 | accounts.append(account); |
| 233 | } | ||
| 234 |
2/2✓ Branch 60 → 61 taken 5 times.
✓ Branch 60 → 72 taken 66 times.
|
71 | if (accounts.isEmpty()) { |
| 235 |
2/2✓ Branch 61 → 62 taken 1 time.
✓ Branch 61 → 66 taken 4 times.
|
5 | if (unauthorizedAccounts > 0) |
| 236 |
2/2✓ Branch 62 → 63 taken 1 time.
✓ Branch 63 → 64 taken 1 time.
|
1 | emit statusMessage(tr("Authorize the DAV account locally to sync contacts.")); |
| 237 | else | ||
| 238 |
2/2✓ Branch 66 → 67 taken 4 times.
✓ Branch 67 → 68 taken 4 times.
|
4 | emit statusMessage(tr("No address books selected for contact sync.")); |
| 239 |
1/1✓ Branch 70 → 71 taken 5 times.
|
5 | m_outbox->flush(false); |
| 240 | 5 | return; | |
| 241 | } | ||
| 242 |
4/7✓ Branch 72 → 73 taken 66 times.
✓ Branch 73 → 74 taken 66 times.
✓ Branch 74 → 75 taken 66 times.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 77 taken 66 times.
✗ Branch 156 → 157 not taken.
✗ Branch 156 → 158 not taken.
|
66 | m_run = new QObject(this); |
| 243 | 66 | const quint64 generation = ++m_generation; | |
| 244 | 66 | m_runAccounts = accounts; | |
| 245 | 66 | m_runFilter = filter; | |
| 246 | 66 | m_runReasons = reasons; | |
| 247 |
1/1✓ Branch 79 → 80 taken 66 times.
|
66 | m_bookResults.clear(); |
| 248 | 66 | m_pendingResolutions = int(accounts.size()); | |
| 249 | 66 | m_queuedBooks = 0; | |
| 250 | 66 | m_failedBooks = 0; | |
| 251 | 66 | m_runRequests = 0; | |
| 252 | 66 | m_runCards = 0; | |
| 253 | 66 | m_runBulk = false; | |
| 254 |
1/1✓ Branch 81 → 82 taken 66 times.
|
66 | m_runKeys.clear(); |
| 255 |
1/1✓ Branch 82 → 83 taken 66 times.
|
66 | wipeResolvedPasswords(); |
| 256 |
1/1✓ Branch 83 → 84 taken 66 times.
|
66 | emit syncStateChanged(true); |
| 257 |
1/1✓ Branch 84 → 85 taken 66 times.
|
66 | QPointer<ContactSyncCoordinator> guard(this); |
| 258 | // Phase 1: resolve each account's secret once, owner-bound and fenced. | ||
| 259 |
4/4✓ Branch 85 → 86 taken 66 times.
✓ Branch 86 → 87 taken 66 times.
✓ Branch 129 → 88 taken 66 times.
✓ Branch 129 → 130 taken 66 times.
|
132 | for (const SyncAccount &account : accounts) { |
| 260 |
1/2✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 66 times.
|
66 | if (!runCurrent(generation)) |
| 261 | ✗ | return; | |
| 262 |
3/6✓ Branch 92 → 93 taken 66 times.
✓ Branch 94 → 95 taken 66 times.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 66 times.
✗ Branch 159 → 160 not taken.
✗ Branch 159 → 161 not taken.
|
66 | auto *operation = new QObject(m_run); |
| 263 |
1/1✓ Branch 97 → 98 taken 66 times.
|
66 | QPointer<QObject> pending(operation); |
| 264 |
1/5✓ Branch 100 → 101 taken 66 times.
✗ Branch 164 → 165 not taken.
✗ Branch 164 → 166 not taken.
✗ Branch 167 → 168 not taken.
✗ Branch 167 → 169 not taken.
|
66 | QTimer::singleShot(60000, operation, [guard, pending, generation] { |
| 265 | ✗ | if (!guard || !pending || generation != guard->m_generation || pending->property("finished").toBool()) | |
| 266 | ✗ | return; | |
| 267 | ✗ | emit guard->statusMessage(tr("Contact sync timed out. Try again.")); | |
| 268 | ✗ | guard->finishResolution(pending, generation); | |
| 269 | }); | ||
| 270 | 66 | const QString accountId = account.accountId; | |
| 271 |
1/1✓ Branch 107 → 108 taken 66 times.
|
66 | m_asyncCredentialResolver(operation, accountId, account.serverUrl, account.username, |
| 272 |
1/5✓ Branch 106 → 107 taken 66 times.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
✗ Branch 178 → 179 not taken.
✗ Branch 178 → 180 not taken.
|
132 | [guard, pending, generation, accountId](QByteArray password, const QString &) { |
| 273 |
2/4✓ Branch 5 → 6 taken 66 times.
✗ Branch 5 → 16 not taken.
✓ Branch 7 → 8 taken 66 times.
✗ Branch 7 → 16 not taken.
|
66 | if (!guard || !pending || generation != guard->m_generation || |
| 274 |
10/20✓ Branch 3 → 4 taken 66 times.
✗ Branch 3 → 16 not taken.
✓ Branch 9 → 10 taken 66 times.
✓ Branch 10 → 11 taken 66 times.
✓ Branch 11 → 12 taken 66 times.
✗ Branch 11 → 16 not taken.
✓ Branch 13 → 14 taken 66 times.
✓ Branch 14 → 15 taken 66 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 66 times.
✓ Branch 18 → 19 taken 66 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 66 times.
✗ Branch 20 → 22 not taken.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 66 times.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
|
132 | pending->property("finished").toBool() || pending->property("resolved").toBool()) |
| 275 | ✗ | return; | |
| 276 |
1/1✓ Branch 26 → 27 taken 66 times.
|
66 | pending->setProperty("resolved", true); |
| 277 |
1/2✗ Branch 29 → 30 not taken.
✓ Branch 29 → 41 taken 66 times.
|
66 | if (password.isEmpty()) |
| 278 | ✗ | qCWarning(lcContactSync) << "Skipping CardDAV account without local credentials"; | |
| 279 | else | ||
| 280 | 66 | guard->m_resolvedPasswords.insert(accountId, password); | |
| 281 | 66 | guard->finishResolution(pending, generation); | |
| 282 | }); | ||
| 283 |
3/6✓ Branch 111 → 112 taken 66 times.
✗ Branch 111 → 114 not taken.
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 115 taken 66 times.
✗ Branch 116 → 117 not taken.
✓ Branch 116 → 118 taken 66 times.
|
66 | if (!guard || !runCurrent(generation)) |
| 284 | ✗ | return; | |
| 285 | 66 | } | |
| 286 | 76 | } | |
| 287 | |||
| 288 | 66 | void ContactSyncCoordinator::finishResolution(QObject *operation, quint64 generation) { | |
| 289 |
7/14✓ Branch 3 → 4 taken 66 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 66 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 66 times.
✓ Branch 6 → 7 taken 66 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 66 times.
✓ Branch 10 → 11 taken 66 times.
✗ Branch 10 → 12 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 66 times.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
|
66 | if (!m_run || generation != m_generation || operation->property("finished").toBool()) |
| 290 | ✗ | return; | |
| 291 |
1/1✓ Branch 15 → 16 taken 66 times.
|
66 | operation->setProperty("finished", true); |
| 292 | 66 | operation->deleteLater(); | |
| 293 |
1/2✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 66 times.
|
66 | if (--m_pendingResolutions > 0) |
| 294 | ✗ | return; | |
| 295 | 66 | startBookSyncs(generation); | |
| 296 | } | ||
| 297 | |||
| 298 | 66 | void ContactSyncCoordinator::startBookSyncs(quint64 generation) { | |
| 299 | struct Target { | ||
| 300 | SyncAccount account; | ||
| 301 | qint64 bookId; | ||
| 302 | }; | ||
| 303 | 66 | QList<Target> targets; | |
| 304 | 66 | int skipped = 0; | |
| 305 |
4/4✓ Branch 2 → 3 taken 66 times.
✓ Branch 3 → 4 taken 66 times.
✓ Branch 42 → 5 taken 66 times.
✓ Branch 42 → 43 taken 66 times.
|
132 | for (const SyncAccount &account : m_runAccounts) { |
| 306 |
2/2✓ Branch 39 → 8 taken 84 times.
✓ Branch 39 → 40 taken 66 times.
|
150 | for (const QString &path : account.selectedBooks) { |
| 307 |
8/8✓ Branch 10 → 11 taken 18 times.
✓ Branch 10 → 15 taken 66 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 16 times.
✓ Branch 16 → 17 taken 18 times.
✓ Branch 16 → 18 taken 66 times.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 82 times.
|
84 | if (!m_runFilter.isEmpty() && !m_runFilter.value(account.accountId).contains(path)) |
| 308 | 2 | continue; | |
| 309 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 27 taken 82 times.
|
82 | if (!m_resolvedPasswords.contains(account.accountId)) { |
| 310 | ✗ | m_bookResults.append({account.accountId, path, false, {}}); | |
| 311 | ✗ | ++skipped; | |
| 312 | ✗ | continue; | |
| 313 | } | ||
| 314 |
2/3✓ Branch 27 → 28 taken 82 times.
✓ Branch 29 → 30 taken 82 times.
✗ Branch 29 → 35 not taken.
|
82 | if (const auto book = m_store->book(account.accountId, path)) |
| 315 | 82 | targets.append({account, book->id}); | |
| 316 | } | ||
| 317 | } | ||
| 318 |
1/2✗ Branch 43 → 44 not taken.
✓ Branch 43 → 48 taken 66 times.
|
66 | if (skipped > 0) { |
| 319 | ✗ | emit statusMessage(tr("Authorize the DAV account locally to sync contacts.")); | |
| 320 | ✗ | m_failedBooks += skipped; | |
| 321 | } | ||
| 322 | 66 | m_queuedBooks = int(targets.size()); | |
| 323 |
1/2✗ Branch 49 → 50 not taken.
✓ Branch 49 → 52 taken 66 times.
|
66 | if (m_queuedBooks == 0) { |
| 324 | ✗ | runCompleted(); | |
| 325 | ✗ | return; | |
| 326 | } | ||
| 327 | 66 | QHash<QString, QStringList> started; | |
| 328 |
4/4✓ Branch 52 → 53 taken 66 times.
✓ Branch 53 → 54 taken 66 times.
✓ Branch 65 → 55 taken 82 times.
✓ Branch 65 → 66 taken 66 times.
|
148 | for (const Target &target : targets) |
| 329 |
2/3✓ Branch 56 → 57 taken 82 times.
✓ Branch 58 → 59 taken 82 times.
✗ Branch 58 → 62 not taken.
|
82 | if (const auto book = m_store->book(target.bookId)) |
| 330 |
2/2✓ Branch 59 → 60 taken 82 times.
✓ Branch 61 → 62 taken 82 times.
|
82 | started[target.account.accountId].append(book->path); |
| 331 |
2/2✓ Branch 73 → 67 taken 66 times.
✓ Branch 73 → 74 taken 66 times.
|
132 | for (auto it = started.cbegin(); it != started.cend(); ++it) |
| 332 |
1/1✓ Branch 69 → 70 taken 66 times.
|
66 | emit booksRunStarted(it.key(), it.value()); |
| 333 |
1/1✓ Branch 74 → 75 taken 66 times.
|
66 | QPointer<ContactSyncCoordinator> guard(this); |
| 334 |
4/4✓ Branch 75 → 76 taken 66 times.
✓ Branch 76 → 77 taken 66 times.
✓ Branch 148 → 78 taken 82 times.
✓ Branch 148 → 149 taken 66 times.
|
148 | for (const Target &target : targets) { |
| 335 |
1/2✗ Branch 80 → 81 not taken.
✓ Branch 80 → 82 taken 82 times.
|
82 | if (!runCurrent(generation)) |
| 336 | ✗ | return; | |
| 337 | 82 | const QString accountId = target.account.accountId; | |
| 338 |
4/8✓ Branch 83 → 84 taken 82 times.
✓ Branch 85 → 86 taken 82 times.
✗ Branch 85 → 89 not taken.
✓ Branch 86 → 87 taken 82 times.
✓ Branch 90 → 91 taken 82 times.
✗ Branch 90 → 92 not taken.
✗ Branch 192 → 193 not taken.
✗ Branch 192 → 194 not taken.
|
82 | const QString bookPath = m_store->book(target.bookId) ? m_store->book(target.bookId)->path : QString(); |
| 339 |
1/1✓ Branch 95 → 96 taken 82 times.
|
82 | CardDavClient *client = m_clientFactory(target.account.serverUrl, target.account.username, |
| 340 |
1/1✓ Branch 94 → 95 taken 82 times.
|
164 | QString::fromUtf8(m_resolvedPasswords.value(target.account.accountId))); |
| 341 |
1/2✗ Branch 98 → 99 not taken.
✓ Branch 98 → 103 taken 82 times.
|
82 | if (!client) { |
| 342 | ✗ | bookFinished(generation, false, {}, false); | |
| 343 | ✗ | continue; | |
| 344 | } | ||
| 345 |
3/6✓ Branch 103 → 104 taken 82 times.
✓ Branch 105 → 106 taken 82 times.
✗ Branch 106 → 107 not taken.
✓ Branch 106 → 108 taken 82 times.
✗ Branch 208 → 209 not taken.
✗ Branch 208 → 210 not taken.
|
82 | auto *sync = new ContactBookSync(m_store, client, target.bookId, m_run); |
| 346 |
1/1✓ Branch 108 → 109 taken 82 times.
|
82 | client->setParent(sync); |
| 347 | // Inactivity timeout: every answer restarts it (large books take many requests). | ||
| 348 |
3/6✓ Branch 109 → 110 taken 82 times.
✓ Branch 110 → 111 taken 82 times.
✗ Branch 111 → 112 not taken.
✓ Branch 111 → 113 taken 82 times.
✗ Branch 211 → 212 not taken.
✗ Branch 211 → 213 not taken.
|
82 | auto *watchdog = new QTimer(sync); |
| 349 |
1/1✓ Branch 113 → 114 taken 82 times.
|
82 | watchdog->setSingleShot(true); |
| 350 |
1/1✓ Branch 114 → 115 taken 82 times.
|
82 | watchdog->setInterval(120000); |
| 351 |
1/1✓ Branch 116 → 117 taken 82 times.
|
82 | connect(sync, &ContactBookSync::progressed, watchdog, qOverload<>(&QTimer::start)); |
| 352 |
1/1✓ Branch 118 → 119 taken 82 times.
|
82 | QPointer<ContactBookSync> pending(sync); |
| 353 |
1/7✓ Branch 123 → 124 taken 82 times.
✗ Branch 218 → 219 not taken.
✗ Branch 218 → 220 not taken.
✗ Branch 221 → 222 not taken.
✗ Branch 221 → 223 not taken.
✗ Branch 224 → 225 not taken.
✗ Branch 224 → 226 not taken.
|
82 | connect(watchdog, &QTimer::timeout, this, [guard, pending, generation, accountId, bookPath] { |
| 354 | ✗ | if (!guard || !pending || pending->property("finished").toBool()) | |
| 355 | ✗ | return; | |
| 356 | ✗ | pending->setProperty("finished", true); | |
| 357 | ✗ | guard->m_bookResults.append({accountId, bookPath, false, pending->pushOffer()}); | |
| 358 | ✗ | emit guard->statusMessage(tr("Contact sync timed out. Try again.")); | |
| 359 | ✗ | guard->bookFinished(generation, false, {}, false); | |
| 360 | ✗ | }); | |
| 361 | 82 | connect(sync, &ContactBookSync::finished, this, | |
| 362 |
1/7✓ Branch 130 → 131 taken 82 times.
✗ Branch 231 → 232 not taken.
✗ Branch 231 → 233 not taken.
✗ Branch 234 → 235 not taken.
✗ Branch 234 → 236 not taken.
✗ Branch 237 → 238 not taken.
✗ Branch 237 → 239 not taken.
|
164 | [guard, pending, generation, accountId, bookPath](bool ok, const QStringList &keys, bool bulk) { |
| 363 |
7/14✓ Branch 3 → 4 taken 82 times.
✗ Branch 3 → 10 not taken.
✓ Branch 5 → 6 taken 82 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 82 times.
✓ Branch 8 → 9 taken 82 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 82 times.
✓ Branch 12 → 13 taken 82 times.
✗ Branch 12 → 14 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 82 times.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
|
82 | if (!guard || !pending || pending->property("finished").toBool()) |
| 364 | ✗ | return; | |
| 365 |
1/1✓ Branch 18 → 19 taken 82 times.
|
82 | pending->setProperty("finished", true); |
| 366 | 164 | guard->m_bookResults.append({accountId, bookPath, ok, pending->pushOffer()}); | |
| 367 | 82 | guard->m_runRequests += pending->requests(); | |
| 368 | 82 | guard->m_runCards += pending->cardsDownloaded(); | |
| 369 | 82 | guard->bookFinished(generation, ok, keys, bulk); | |
| 370 |
1/5✓ Branch 25 → 26 taken 82 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
|
82 | }); |
| 371 |
1/1✓ Branch 133 → 134 taken 82 times.
|
82 | watchdog->start(); |
| 372 |
1/1✓ Branch 134 → 135 taken 82 times.
|
82 | sync->start(); |
| 373 | 82 | } | |
| 374 | 148 | } | |
| 375 | |||
| 376 | 82 | void ContactSyncCoordinator::bookFinished(quint64 generation, bool ok, const QStringList &keys, bool bulk) { | |
| 377 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 81 times.
|
82 | if (!runCurrent(generation)) |
| 378 | 1 | return; | |
| 379 |
2/2✓ Branch 5 → 6 taken 9 times.
✓ Branch 5 → 7 taken 72 times.
|
81 | if (!ok) |
| 380 | 9 | ++m_failedBooks; | |
| 381 | 81 | m_runKeys += keys; | |
| 382 |
3/4✓ Branch 8 → 9 taken 81 times.
✗ Branch 8 → 10 not taken.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 79 times.
|
81 | m_runBulk = m_runBulk || bulk; |
| 383 |
2/2✓ Branch 12 → 13 taken 16 times.
✓ Branch 12 → 14 taken 65 times.
|
81 | if (--m_queuedBooks > 0) |
| 384 | 16 | return; | |
| 385 | 65 | runCompleted(); | |
| 386 | } | ||
| 387 | |||
| 388 | // S106-063: every path that drops the resolved keyring passwords wipes them | ||
| 389 | // first (only the success path did; start, stop and destruction just freed | ||
| 390 | // the buffers). | ||
| 391 | 1068 | void ContactSyncCoordinator::wipeResolvedPasswords() { | |
| 392 |
3/3✓ Branch 2 → 3 taken 1068 times.
✓ Branch 8 → 4 taken 66 times.
✓ Branch 8 → 9 taken 1068 times.
|
1134 | for (auto &secret : m_resolvedPasswords) |
| 393 |
1/1✓ Branch 5 → 6 taken 66 times.
|
66 | SecureUtil::zeroMemory(secret); |
| 394 | 1068 | m_resolvedPasswords.clear(); | |
| 395 | 1068 | } | |
| 396 | |||
| 397 | 65 | void ContactSyncCoordinator::runCompleted() { | |
| 398 | 65 | wipeResolvedPasswords(); | |
| 399 | 65 | m_runAccounts.clear(); | |
| 400 | // F103-27: placeholders no sync owns leave once every server book was fetched. | ||
| 401 |
2/3✓ Branch 4 → 5 taken 65 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 8 taken 65 times.
|
65 | if (const ContactStore::LegacyRetirement retired = m_store->retireLegacyBook(); retired.removed + retired.moved > 0) { |
| 402 | ✗ | emit contactsChanged(); | |
| 403 | ✗ | emit booksChanged(); | |
| 404 | } | ||
| 405 | 65 | m_store->dropLegacyTableWhenDone(); | |
| 406 | 65 | m_lastRequests = m_runRequests; | |
| 407 | 65 | m_lastCards = m_runCards; | |
| 408 |
2/2✓ Branch 9 → 10 taken 9 times.
✓ Branch 9 → 14 taken 56 times.
|
65 | if (m_failedBooks > 0) |
| 409 |
2/2✓ Branch 10 → 11 taken 9 times.
✓ Branch 11 → 12 taken 9 times.
|
9 | emit statusMessage(tr("Some address books could not be synchronized. MailJD tries again later.")); |
| 410 |
1/2✓ Branch 15 → 16 taken 65 times.
✗ Branch 15 → 18 not taken.
|
65 | if (m_run) |
| 411 | 65 | m_run->deleteLater(); | |
| 412 | 65 | m_run = nullptr; | |
| 413 |
2/2✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 63 times.
|
65 | if (m_runBulk) |
| 414 | 2 | emit contactsChanged(); | |
| 415 |
2/2✓ Branch 22 → 23 taken 35 times.
✓ Branch 22 → 24 taken 28 times.
|
63 | else if (!m_runKeys.isEmpty()) |
| 416 | 35 | emit cardsChanged(m_runKeys); | |
| 417 | 65 | emit booksChanged(); | |
| 418 | 65 | reportBooks(); | |
| 419 | 65 | emit syncStateChanged(false); | |
| 420 | 65 | m_outbox->flush(false); | |
| 421 |
6/6✓ Branch 28 → 29 taken 64 times.
✓ Branch 28 → 31 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 63 times.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 47 taken 63 times.
|
65 | if (m_queuedAll || !m_queuedFilter.isEmpty()) { |
| 422 |
4/4✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 40 taken 1 time.
|
2 | const auto filter = m_queuedAll ? QHash<QString, QSet<QString>>() : std::exchange(m_queuedFilter, {}); |
| 423 | 2 | const unsigned reasons = std::exchange(m_queuedReasons, 0u); | |
| 424 | 2 | m_queuedAll = false; | |
| 425 | 2 | m_queuedFilter.clear(); | |
| 426 |
1/3✓ Branch 43 → 44 taken 2 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
4 | QTimer::singleShot(0, this, [this, filter, reasons] { startRun(filter, reasons); }); |
| 427 | 2 | } | |
| 428 | 65 | } | |
| 429 | |||
| 430 | // Sprint 96: what the push source needs from a finished run, per account. | ||
| 431 | 65 | void ContactSyncCoordinator::reportBooks() { | |
| 432 | 65 | QHash<QString, DavPushSyncReport> reports; | |
| 433 | 65 | QHash<QString, QList<DavPushDiscovery>> offers; | |
| 434 |
2/2✓ Branch 21 → 5 taken 81 times.
✓ Branch 21 → 22 taken 65 times.
|
146 | for (const BookResult &result : std::as_const(m_bookResults)) { |
| 435 |
1/1✓ Branch 6 → 7 taken 81 times.
|
81 | DavPushSyncReport &report = reports[result.accountId]; |
| 436 | 81 | report.accountId = result.accountId; | |
| 437 | 81 | report.reasons = m_runReasons; | |
| 438 |
3/3✓ Branch 8 → 9 taken 72 times.
✓ Branch 8 → 10 taken 9 times.
✓ Branch 11 → 12 taken 81 times.
|
81 | (result.ok ? report.syncedPaths : report.failedPaths).append(result.path); |
| 439 |
2/2✓ Branch 12 → 13 taken 73 times.
✓ Branch 12 → 19 taken 8 times.
|
81 | if (result.offer.capability != DavPushCapability::Unknown) |
| 440 |
1/1✓ Branch 13 → 14 taken 73 times.
|
146 | offers[result.accountId].append({result.path, result.offer}); |
| 441 | } | ||
| 442 |
1/1✓ Branch 22 → 23 taken 65 times.
|
65 | m_bookResults.clear(); |
| 443 |
2/2✓ Branch 30 → 24 taken 64 times.
✓ Branch 30 → 31 taken 65 times.
|
129 | for (auto it = offers.cbegin(); it != offers.cend(); ++it) |
| 444 |
1/1✓ Branch 26 → 27 taken 64 times.
|
64 | emit pushOffersRead(it.key(), it.value()); |
| 445 |
2/2✓ Branch 38 → 34 taken 65 times.
✓ Branch 38 → 39 taken 65 times.
|
130 | for (const DavPushSyncReport &report : std::as_const(reports)) |
| 446 |
1/1✓ Branch 35 → 36 taken 65 times.
|
65 | emit booksSynced(report); |
| 447 | 138 | } | |
| 448 | |||
| 449 | // ─── collected addresses ─────────────────────────────────── | ||
| 450 | |||
| 451 | 40 | bool ContactSyncCoordinator::collectAddressesEnabled() { | |
| 452 |
3/3✓ Branch 2 → 3 taken 40 times.
✓ Branch 8 → 9 taken 40 times.
✓ Branch 9 → 10 taken 40 times.
|
80 | return QSettings().value(QStringLiteral("contacts/collectAddresses"), true).toBool(); |
| 453 | } | ||
| 454 | |||
| 455 | 6 | void ContactSyncCoordinator::setCollectAddressesEnabled(bool enabled) { | |
| 456 |
2/2✓ Branch 2 → 3 taken 6 times.
✓ Branch 8 → 9 taken 6 times.
|
12 | QSettings().setValue(QStringLiteral("contacts/collectAddresses"), enabled); |
| 457 | 6 | } | |
| 458 | |||
| 459 | 2 | void ContactSyncCoordinator::recordSentRecipients(const QList<CollectedAddressStore::Recipient> &recipients) { | |
| 460 |
4/7✓ Branch 2 → 3 taken 2 times.
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2 times.
|
2 | if (!m_store->isOpen() || recipients.isEmpty()) |
| 461 | ✗ | return; | |
| 462 |
2/2✓ Branch 10 → 11 taken 2 times.
✓ Branch 11 → 12 taken 2 times.
|
2 | CollectedAddressStore collected(m_store->database()); |
| 463 |
2/2✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
|
4 | if (collected.recordSent(recipients, collectAddressesEnabled(), m_clock |
| 464 |
3/10✗ Branch 14 → 15 not taken.
✓ Branch 14 → 18 taken 2 times.
✗ Branch 16 → 17 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 26 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
|
4 | ? QDateTime::fromSecsSinceEpoch(m_clock(), QTimeZone::UTC) |
| 465 | : QDateTime())) | ||
| 466 |
1/1✓ Branch 25 → 26 taken 2 times.
|
2 | emit contactsChanged(); |
| 467 | 2 | } | |
| 468 | |||
| 469 | ✗ | void ContactSyncCoordinator::recordSentHeaders(const QStringList &headers) { | |
| 470 | ✗ | QList<CollectedAddressStore::Recipient> recipients; | |
| 471 | ✗ | for (const QString &header : headers) | |
| 472 | ✗ | for (const MailAddressList::Mailbox &mailbox : MailAddressList::parse(header)) | |
| 473 | ✗ | recipients.append({mailbox.name, mailbox.address}); | |
| 474 | ✗ | recordSentRecipients(recipients); | |
| 475 | ✗ | } | |
| 476 | |||
| 477 | 2 | bool ContactSyncCoordinator::forgetCollectedAddresses() { | |
| 478 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
|
2 | if (!m_store->isOpen()) |
| 479 | ✗ | return false; | |
| 480 |
3/3✓ Branch 5 → 6 taken 2 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
|
2 | const bool ok = CollectedAddressStore(m_store->database()).forgetAll(); |
| 481 | 2 | emit contactsChanged(); | |
| 482 | 2 | return ok; | |
| 483 | } | ||
| 484 | |||
| 485 | // ─── D-5 local-only contacts ─────────────────────────────── | ||
| 486 | |||
| 487 | 270 | int ContactSyncCoordinator::localOnlyCount() const { | |
| 488 |
3/7✓ Branch 3 → 4 taken 270 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 270 times.
✓ Branch 9 → 10 taken 270 times.
✗ Branch 9 → 11 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
|
270 | return m_store->isOpen() ? int(m_store->pendingLocalOnlyCards().size()) : 0; |
| 489 | } | ||
| 490 | |||
| 491 | 2 | bool ContactSyncCoordinator::uploadLocalOnly(qint64 bookId) { | |
| 492 |
1/1✓ Branch 2 → 3 taken 2 times.
|
2 | const QList<qint64> ids = m_store->pendingLocalOnlyCards(); |
| 493 | 2 | bool ok = true; | |
| 494 | 2 | m_outbox->beginBatch(); | |
| 495 |
2/2✓ Branch 30 → 6 taken 1 time.
✓ Branch 30 → 31 taken 2 times.
|
3 | for (qint64 id : ids) { |
| 496 |
1/1✓ Branch 7 → 8 taken 1 time.
|
1 | const auto card = m_store->cardById(id); |
| 497 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
|
1 | if (!card) |
| 498 | ✗ | continue; | |
| 499 | 1 | QString error; | |
| 500 |
4/9✓ Branch 13 → 14 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
|
1 | ok = !m_outbox->moveCard(card->bookId, card->uid, bookId, &error).isEmpty() && ok; |
| 501 | 1 | } | |
| 502 |
1/1✓ Branch 31 → 32 taken 2 times.
|
2 | m_outbox->endBatch(); |
| 503 |
1/2✓ Branch 32 → 33 taken 2 times.
✗ Branch 32 → 34 not taken.
|
2 | if (ok) |
| 504 |
1/1✓ Branch 33 → 34 taken 2 times.
|
2 | m_store->clearPendingLocalOnlyCards(); |
| 505 |
1/1✓ Branch 34 → 35 taken 2 times.
|
2 | emit contactsChanged(); |
| 506 | 2 | return ok; | |
| 507 | 2 | } | |
| 508 | |||
| 509 | 2 | void ContactSyncCoordinator::keepLocalOnly() { | |
| 510 | 2 | m_store->clearPendingLocalOnlyCards(); | |
| 511 | 2 | emit booksChanged(); | |
| 512 | 2 | } | |
| 513 |