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 |