app/core/DavPushCoordinator.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QHash> | ||
| 4 | #include <QObject> | ||
| 5 | #include <QPointer> | ||
| 6 | #include <QSet> | ||
| 7 | #include <QTimer> | ||
| 8 | #include <functional> | ||
| 9 | #include <memory> | ||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | #include "CalendarAccountSync.h" | ||
| 13 | #include "DavPushAccount.h" | ||
| 14 | #include "DavPushSource.h" | ||
| 15 | #include "data/DavPushModels.h" | ||
| 16 | |||
| 17 | class DavPushStatistics; | ||
| 18 | class DavPushStore; | ||
| 19 | class DavPushSecretStore; | ||
| 20 | class NetworkLifecycleCoordinator; | ||
| 21 | |||
| 22 | // One calendar row of the push status model (presentation input). Only the | ||
| 23 | // separate state dimensions; texts and priority are derived by the | ||
| 24 | // presentation (SPRINT-96.md §5.2). | ||
| 25 | struct DavPushCollectionStatus { | ||
| 26 | QString accountId; | ||
| 27 | QString path; | ||
| 28 | QString kind; // DavPushSource::kind(): "calendar" or "addressbook" | ||
| 29 | int intervalMinutes = 0; // the source's background interval | ||
| 30 | QString displayName; | ||
| 31 | QString accountLabel; | ||
| 32 | bool readOnly = false; | ||
| 33 | DavPushPolicy policy = DavPushPolicy::Automatic; | ||
| 34 | DavPushOffer offer; | ||
| 35 | qint64 checkedAt = 0; | ||
| 36 | DavPushRegistrationState registration = DavPushRegistrationState::None; | ||
| 37 | qint64 expiresAt = 0; | ||
| 38 | QString registrationError; | ||
| 39 | qint64 nextRetryAt = 0; | ||
| 40 | DavPushReceiverState receiver = DavPushReceiverState::NotConfigured; | ||
| 41 | QString receiverDetail; | ||
| 42 | qint64 receiverConnectedSince = 0; // epoch ms | ||
| 43 | qint64 receiverLastActivity = 0; // epoch ms | ||
| 44 | qint64 receiverNextAttempt = 0; // epoch ms | ||
| 45 | QString pauseReason; // "", "offline", "suspended", "background-off", "disabled" | ||
| 46 | qint64 lastPushAt = 0; | ||
| 47 | qint64 lastSyncAt = 0; | ||
| 48 | bool inFallback = false; | ||
| 49 | bool syncRunning = false; | ||
| 50 | }; | ||
| 51 | |||
| 52 | // Sprint 96 S96.2.4–S96.2.6: session-owned DAV-Push automation. Created by | ||
| 53 | // MailSessionCore only in the process that holds the profile lease, so a | ||
| 54 | // second start (which forwards to the owner) registers nothing; opening or | ||
| 55 | // closing windows does not touch subscriptions. | ||
| 56 | // | ||
| 57 | // It computes, per account, the desired push state from the settings | ||
| 58 | // (receiver, policies, application switch, background interval), the | ||
| 59 | // discovered offers and the lifecycle (network gate, suspend) and lets | ||
| 60 | // DavPushAccount converge. Collections come from DavPushSource adapters | ||
| 61 | // (calendars, address books); push notices become targeted read requests | ||
| 62 | // at the source that owns the collection, and push failures switch the | ||
| 63 | // account's sources to their fallback polling. | ||
| 64 | class DavPushCoordinator final : public QObject { | ||
| 65 | Q_OBJECT | ||
| 66 | public: | ||
| 67 | struct Deps { | ||
| 68 | // Owned; at least one. More can follow with addSource() before start(). | ||
| 69 | std::vector<std::unique_ptr<DavPushSource>> sources; | ||
| 70 | std::unique_ptr<DavPushSecretStore> secrets; | ||
| 71 | std::function<bool()> networkAllowed; | ||
| 72 | std::function<PushReceiver *(const QString &provider, QObject *parent)> makeReceiver; | ||
| 73 | // The UnifiedPush distributor on the session bus ("" = none). Default: | ||
| 74 | // UnifiedPushReceiver::findDistributor() plus a bus watcher. | ||
| 75 | std::function<QString()> findSystemDistributor; | ||
| 76 | std::function<void(const QString &provider, const QString &server, const QString &channel)> releaseReceiver; | ||
| 77 | std::function<DavPushClient *(const QString &, const QString &, const QString &)> makeClient; | ||
| 78 | CalendarAccountSync::CredentialResolver resolveCredentials; | ||
| 79 | std::function<qint64()> now; // epoch seconds | ||
| 80 | NetworkLifecycleCoordinator *lifecycle = nullptr; | ||
| 81 | }; | ||
| 82 | |||
| 83 | DavPushCoordinator(const QString &storePath, Deps deps, QObject *parent = nullptr); | ||
| 84 | ~DavPushCoordinator() override; | ||
| 85 | |||
| 86 | bool isOpen() const; | ||
| 87 | // A further collection kind (e.g. address books, whose sync is created | ||
| 88 | // after the calendar's). Only before start(). | ||
| 89 | void addSource(std::unique_ptr<DavPushSource> source); | ||
| 90 | void start(); | ||
| 91 | // Regular quit: close the streams, keep valid registrations for the next start. | ||
| 92 | void shutdown(); | ||
| 93 | // Settings, accounts, selections or the interval changed. | ||
| 94 | void reconfigure(); | ||
| 95 | |||
| 96 | void setPolicy(const QString &accountId, const QString &path, DavPushPolicy policy); | ||
| 97 | void reconnect(const QString &accountId); | ||
| 98 | void detectAgain(const QString &accountId); | ||
| 99 | void syncCollectionNow(const QString &accountId, const QString &path); | ||
| 100 | bool resetStatistics(const QString &accountId, const QString &path); | ||
| 101 | // Optional subscriber token of the push service: keyring only; running | ||
| 102 | // receivers reconnect with it. | ||
| 103 | // `done(ok)` reports whether the token is in the keyring (S106-336); a | ||
| 104 | // coordinator without its store reports false at once. | ||
| 105 | void storeProviderToken(const QByteArray &token, std::function<void(bool)> done = {}); | ||
| 106 | // S106-337 (INV-09): removing the push service deletes its token too. | ||
| 107 | void removeProviderToken(); | ||
| 108 | |||
| 109 | QList<DavPushCollectionStatus> statuses() const; | ||
| 110 | // The push service in use: the system distributor (KUnifiedPush) when one | ||
| 111 | // runs and the user allows it, else the configured ntfy service. | ||
| 112 | DavPushSettings::Receiver effectiveReceiver() const; | ||
| 113 | // The detected system distributor's bus name ("" = none). | ||
| 114 | 69 | QString systemDistributor() const { return m_systemDistributor; } | |
| 115 | 128 | DavPushStatistics *statistics() const { return m_stats.get(); } | |
| 116 | 16 | DavPushStore *store() const { return m_store.get(); } | |
| 117 | // "", "disabled", "background-off", "offline" or "suspended". | ||
| 118 | QString pauseReason() const; | ||
| 119 | 4 | qint64 now() const { return m_deps.now(); } | |
| 120 | // Redacted diagnostics: no channel/endpoint/registration URLs, no keys, | ||
| 121 | // no tokens, no calendar contents. | ||
| 122 | QString diagnosticsReport() const; | ||
| 123 | |||
| 124 | signals: | ||
| 125 | void changed(); | ||
| 126 | |||
| 127 | private: | ||
| 128 | void connectSource(DavPushSource *source); | ||
| 129 | void onDiscovered(DavPushSource *source, const QString &accountId, const QList<DavPushDiscovery> &found); | ||
| 130 | void onSyncFinished(const DavPushSyncReport &report); | ||
| 131 | void onBatchStarted(const QString &accountId, const QStringList &forcedPaths); | ||
| 132 | void onPushHealth(const QString &accountId, bool failing); | ||
| 133 | DavPushAccount *accountFor(const QString &accountId); | ||
| 134 | // The source whose selected collections contain the path (nullptr: none). | ||
| 135 | DavPushSource *sourceFor(const QString &accountId, const QString &path) const; | ||
| 136 | // Selected collection paths of every source for the account. | ||
| 137 | QSet<QString> selectedPaths(const QString &accountId) const; | ||
| 138 | void requestSync(const QString &accountId, const QStringList &paths, unsigned reasons); | ||
| 139 | |||
| 140 | std::unique_ptr<DavPushStore> m_store; | ||
| 141 | std::unique_ptr<DavPushStatistics> m_stats; | ||
| 142 | Deps m_deps; | ||
| 143 | QString m_installationId; | ||
| 144 | QHash<QString, QPointer<DavPushAccount>> m_accounts; | ||
| 145 | QTimer m_reconcileTimer; | ||
| 146 | QString m_systemDistributor; | ||
| 147 | bool m_started = false; | ||
| 148 | bool m_suspended = false; | ||
| 149 | }; | ||
| 150 |