MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 12 / 0 / 12
Functions: 100.0% 10 / 0 / 10
Branches: 100.0% 1 / 0 / 1

app/core/ContactSyncCoordinator.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QHash>
4 #include <QObject>
5 #include <QPointer>
6 #include <QSet>
7 #include <QStringList>
8 #include <QTimer>
9 #include <functional>
10
11 #include "DavPushSource.h"
12 #include "data/CollectedAddressStore.h"
13 #include "data/DavCredentials.h"
14
15 class CardDavClient;
16 class ContactOutbox;
17 class ContactStore;
18
19 // Session-owned contact sync (Q09; rebuilt in Sprint 97 S97.1.4/S97.1.5).
20 //
21 // Keeps contacts.db fresh from the address books selected in the DAV
22 // settings: one run at a time, generation-checked, each account's secret
23 // asked at most once per run (async keyring), then one incremental
24 // ContactBookSync per book. A failed book keeps its cards and does not stop
25 // the others. Books deselected in the settings leave the store.
26 //
27 // Owns the ContactOutbox (local-first writes, retry, merge, conflicts) and
28 // records sent recipients (collected addresses, D-4). Emits per-card change
29 // keys so the list updates rows in place instead of resetting (CB-08).
30 class ContactSyncCoordinator : public QObject {
31 14 Q_OBJECT
32 public:
33 using AsyncCredentialResolver =
34 std::function<void(QObject *owner, const QString &accountId, const QString &serverUrl,
35 const QString &username, DavCredentials::PasswordCallback callback)>;
36 // clientFactory for tests: returns an unstarted CardDavClient.
37 using ClientFactory =
38 std::function<CardDavClient *(const QString &serverUrl, const QString &username, const QString &password)>;
39
40 explicit ContactSyncCoordinator(const QString &contactsDbPath, QObject *parent = nullptr);
41 ~ContactSyncCoordinator() override;
42
43 bool open();
44 20244 ContactStore *store() const { return m_store; }
45 1162 ContactOutbox *outbox() const { return m_outbox; }
46 QString databasePath() const { return m_databasePath; }
47
48 // Reads carddav/syncIntervalMin (legacy shared interval fallback, 0
49 // disables) and arms the periodic timer plus the initial delayed sync.
50 void startBackgroundSync();
51 void stopBackgroundSync();
52 // Offline profiles (--offline, disabled network) must not touch the
53 // keyring or DAV servers. Unset means network is always allowed.
54 823 void setNetworkGate(std::function<bool()> gate) { m_networkGate = std::move(gate); }
55 void setClock(std::function<qint64()> clock) { m_clock = std::move(clock); }
56 Q_INVOKABLE void triggerSync();
57 2455 bool syncing() const { return m_run != nullptr; }
58
59 // ─── Sprint 96 S96.6.2: DAV-Push (ContactPushSource) ───
60 // A run of just these books of one account (empty paths: every selected
61 // book of the account). Requests during a run are merged into one
62 // follow-up run, never dropped. `reasons` are DavSyncReason bits.
63 void syncBooks(const QString &accountId, const QStringList &paths, unsigned reasons);
64 // Every selected book of every account (resume, network back).
65
1/1
✓ Branch 3 → 4 taken 1 time.
1 void syncAll(unsigned reasons) { startRun({}, reasons); }
66 // The book's committed RFC 6578 token ("" when unknown).
67 QString bookSyncToken(const QString &accountId, const QString &path) const;
68 // Own writes name the book's push registration (Push-Dont-Notify).
69 822 void setPushDontNotifyProvider(std::function<QString(const QString &accountId, const QString &path)> provider) {
70 822 m_pushDontNotify = std::move(provider);
71 822 }
72 // Selected books of an account (carddav/accounts selectedBooks).
73 static QStringList selectedBooks(const QString &accountId);
74 void retryPendingWrites();
75
76 32 void setAsyncCredentialResolver(AsyncCredentialResolver resolver) { m_asyncCredentialResolver = std::move(resolver); }
77 32 void setClientFactory(ClientFactory factory) { m_clientFactory = std::move(factory); }
78
79 // ─── collected addresses (S97.1.6, D-4) ───
80 // Called after a mail was sent successfully; honours the setting
81 // "Remember addresses I write to" (contacts/collectAddresses, default on).
82 void recordSentRecipients(const QList<CollectedAddressStore::Recipient> &recipients);
83 // The same for "Name <address>" header values of the To/Cc/Bcc fields.
84 void recordSentHeaders(const QStringList &headers);
85 bool forgetCollectedAddresses();
86 static bool collectAddressesEnabled();
87 static void setCollectAddressesEnabled(bool enabled);
88
89 // ─── contacts that exist only on this computer (D-5) ───
90 int localOnlyCount() const;
91 // Moves them into the given CardDAV book (through the outbox).
92 bool uploadLocalOnly(qint64 bookId);
93 void keepLocalOnly();
94
95 static int configuredIntervalMinutes();
96
97 // Requests and downloaded cards of the last finished run (G5 evidence).
98 int lastRunRequests() const { return m_lastRequests; }
99 6 int lastRunCards() const { return m_lastCards; }
100
101 signals:
102 void contactsChanged(); // reload (bulk change)
103 void cardsChanged(const QStringList &keys); // per-card updates
104 void booksChanged();
105 void statusMessage(const QString &message);
106 void syncStateChanged(bool running);
107 // Sprint 96: per account at the end of a run — push offers read with the
108 // books' properties, and which books were synchronised or failed.
109 void pushOffersRead(const QString &accountId, const QList<DavPushDiscovery> &offers);
110 void booksSynced(const DavPushSyncReport &report);
111 void booksRunStarted(const QString &accountId, const QStringList &paths);
112
113 private:
114 struct SyncAccount {
115 QString accountId, serverUrl, username;
116 QStringList selectedBooks;
117 bool requiresLocalAuthorization = false;
118 };
119 static QList<SyncAccount> readSyncAccounts();
120 // One finished book of the current run (push reports).
121 struct BookResult {
122 QString accountId, path;
123 bool ok = false;
124 DavPushOffer offer;
125 };
126 // Starts a run limited to `filter` (account → books; empty = all books).
127 void startRun(const QHash<QString, QSet<QString>> &filter, unsigned reasons);
128 void reportBooks();
129 void reconcileBooks(const QList<SyncAccount> &accounts);
130 void finishResolution(QObject *operation, quint64 generation);
131 void startBookSyncs(quint64 generation);
132 void bookFinished(quint64 generation, bool ok, const QStringList &keys, bool bulk);
133 void runCompleted();
134 bool runCurrent(quint64 generation) const;
135 void resolveEndpoint(QObject *owner, const QString &accountId, std::function<void(QString, QString, QByteArray)> done);
136
137 ContactStore *m_store = nullptr;
138 ContactOutbox *m_outbox = nullptr;
139 QString m_databasePath;
140 QTimer m_syncTimer;
141 QTimer m_initialSyncTimer;
142 QPointer<QObject> m_run;
143 quint64 m_generation = 0;
144 int m_pendingResolutions = 0;
145 int m_queuedBooks = 0;
146 int m_failedBooks = 0;
147 int m_runRequests = 0;
148 int m_runCards = 0;
149 int m_lastRequests = 0;
150 int m_lastCards = 0;
151 bool m_runBulk = false;
152 QStringList m_runKeys;
153 QHash<QString, QByteArray> m_resolvedPasswords;
154 void wipeResolvedPasswords();
155 QList<SyncAccount> m_runAccounts;
156 QHash<QString, QSet<QString>> m_runFilter; // empty = every book
157 unsigned m_runReasons = 0;
158 QList<BookResult> m_bookResults;
159 QHash<QString, QSet<QString>> m_queuedFilter; // follow-up run
160 bool m_queuedAll = false;
161 unsigned m_queuedReasons = 0;
162 std::function<QString(const QString &, const QString &)> m_pushDontNotify;
163 AsyncCredentialResolver m_asyncCredentialResolver;
164 std::function<bool()> m_networkGate;
165 std::function<qint64()> m_clock;
166 ClientFactory m_clientFactory;
167 };
168