service/UnifiedPushReceiver.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QDBusAbstractAdaptor> | ||
| 4 | #include <QDBusContext> | ||
| 5 | #include <QDBusMessage> | ||
| 6 | #include <QHash> | ||
| 7 | #include <QPointer> | ||
| 8 | #include <QStringList> | ||
| 9 | #include <QTimer> | ||
| 10 | #include <QVariantMap> | ||
| 11 | #include <functional> | ||
| 12 | |||
| 13 | #include "PushReceiver.h" | ||
| 14 | |||
| 15 | class QDBusPendingCallWatcher; | ||
| 16 | class QDBusServiceWatcher; | ||
| 17 | |||
| 18 | // Sprint 96 (DAVPUSH-KUP): push reception through a UnifiedPush D-Bus | ||
| 19 | // distributor on the session bus, e.g. KDE's KUnifiedPush | ||
| 20 | // ("org.unifiedpush.Distributor.kde"). Optional at runtime: nothing is | ||
| 21 | // linked against KUnifiedPush; when no distributor is on the bus MailJD uses | ||
| 22 | // its own ntfy receiver. | ||
| 23 | // | ||
| 24 | // Protocol (UnifiedPush D-Bus spec DBUS_0.3.0): MailJD owns the connector | ||
| 25 | // name connectorService() and serves org.unifiedpush.Connector2 (and the | ||
| 26 | // older Connector1) at /org/unifiedpush/Connector. A receiver calls | ||
| 27 | // Distributor2.Register {service, token, description, vapid} (Distributor1 | ||
| 28 | // for older distributors); the distributor answers asynchronously and | ||
| 29 | // sends the push endpoint through NewEndpoint, later every push message | ||
| 30 | // (the RFC 8291 ciphertext the DAV server posted) through Message. | ||
| 31 | // | ||
| 32 | // Config: server = the distributor's bus name, channel = the registration | ||
| 33 | // token (secret, per account), vapidKey = the DAV server's VAPID key | ||
| 34 | // (base64url, optional). stop() keeps the registration (restart = same | ||
| 35 | // endpoint); unregisterToken() ends it when a receiver is replaced. | ||
| 36 | class UnifiedPushReceiver final : public PushReceiver { | ||
| 37 | Q_OBJECT | ||
| 38 | public: | ||
| 39 | static inline const QString kDistributorPrefix = QStringLiteral("org.unifiedpush.Distributor."); | ||
| 40 | // The distributor to use ("" = none): $UNIFIEDPUSH_DISTRIBUTOR when it is | ||
| 41 | // a distributor name on the bus, else KDE's when present, else the first | ||
| 42 | // registered or activatable one in name order. | ||
| 43 | static QString findDistributor(); | ||
| 44 | // The connector bus name MailJD owns while a receiver runs. | ||
| 45 | static QString connectorService(); | ||
| 46 | // Ends the registration of `token` at `distributor` (best effort). | ||
| 47 | static void unregisterToken(const QString &distributor, const QString &token); | ||
| 48 | // Calls `changed` whenever a distributor appears on or leaves the bus. | ||
| 49 | static QObject *watchDistributors(QObject *parent, std::function<void()> changed); | ||
| 50 | |||
| 51 | explicit UnifiedPushReceiver(QObject *parent = nullptr); | ||
| 52 | ~UnifiedPushReceiver() override; | ||
| 53 | // The distributor bus name this receiver registered with. | ||
| 54 | 24 | QString distributorService() const { return m_config.server; } | |
| 55 | |||
| 56 | void start(const Config &config) override; | ||
| 57 | void stop() override; | ||
| 58 | 6 | QString endpoint() const override { return m_endpoint; } | |
| 59 | |||
| 60 | // From the connector adaptor (token already matched). | ||
| 61 | void deliverMessage(const QByteArray &message, const QString &id); | ||
| 62 | void deliverEndpoint(const QString &endpoint); | ||
| 63 | void deliverUnregistered(); | ||
| 64 | |||
| 65 | private: | ||
| 66 | void registerWithDistributor(); | ||
| 67 | void registerVersion1(); | ||
| 68 | void onRegistered(bool success, const QString &reason); | ||
| 69 | void onDistributorOwner(const QString &newOwner); | ||
| 70 | void retryLater(Failure failure, const QString &detail); | ||
| 71 | |||
| 72 | Config m_config; | ||
| 73 | QString m_endpoint; | ||
| 74 | bool m_running = false; | ||
| 75 | bool m_registered = false; | ||
| 76 | bool m_connectedOnce = false; | ||
| 77 | bool m_lostDistributor = false; | ||
| 78 | int m_attempts = 0; | ||
| 79 | QTimer m_retry; | ||
| 80 | QDBusServiceWatcher *m_watcher = nullptr; | ||
| 81 | QStringList m_recentIds; | ||
| 82 | }; | ||
| 83 | |||
| 84 | // The connector side MailJD exports on the session bus: one object for all | ||
| 85 | // accounts, dispatching by token. Registered on first use. | ||
| 86 | // QDBusContext: Qt sets the call context on the exported object, not on | ||
| 87 | // its adaptors. | ||
| 88 | class UnifiedPushConnector final : public QObject, protected QDBusContext { | ||
| 89 | Q_OBJECT | ||
| 90 | public: | ||
| 91 | static UnifiedPushConnector *instance(); | ||
| 92 | // False when another process owns the connector name (another profile). | ||
| 93 | 9 | bool available() const { return m_available; } | |
| 94 | void attach(const QString &token, UnifiedPushReceiver *receiver); | ||
| 95 | void detach(const QString &token, UnifiedPushReceiver *receiver); | ||
| 96 | UnifiedPushReceiver *receiver(const QString &token) const { return m_receivers.value(token); } | ||
| 97 | // Sprint 102 S102.5.5: the receiver of `token` when `caller` (the unique | ||
| 98 | // bus name of the D-Bus message sender) owns that receiver's distributor | ||
| 99 | // name; any other session-bus client gets nullptr. | ||
| 100 | UnifiedPushReceiver *receiverFrom(const QString &token, const QString &caller) const; | ||
| 101 | // The bus owner of a well-known name; a blocking session-bus round trip by | ||
| 102 | // default (its nested loop can delete receivers — S106-065). Tests replace it. | ||
| 103 | using OwnerLookup = std::function<QString(const QString &service)>; | ||
| 104 | 5 | void setOwnerLookup(OwnerLookup lookup) { | |
| 105 | 5 | m_ownerLookup = std::move(lookup); | |
| 106 | 5 | m_owners.clear(); | |
| 107 | 5 | } | |
| 108 | // The cached owner of a distributor name (S106-066). | ||
| 109 | QString ownerOf(const QString &service) const; | ||
| 110 | // The unique bus name of the D-Bus call being delivered ("" outside one). | ||
| 111 |
1/2✓ Branch 3 → 4 taken 21 times.
✗ Branch 3 → 6 not taken.
|
21 | QString callingService() const { return calledFromDBus() ? message().service() : QString(); } |
| 112 | |||
| 113 | private: | ||
| 114 | UnifiedPushConnector(); | ||
| 115 | bool m_available = false; | ||
| 116 | QHash<QString, QPointer<UnifiedPushReceiver>> m_receivers; | ||
| 117 | OwnerLookup m_ownerLookup; | ||
| 118 | mutable QHash<QString, QString> m_owners; // distributor name → unique bus name | ||
| 119 | void onNameOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); | ||
| 120 | }; | ||
| 121 | |||
| 122 | // org.unifiedpush.Connector2 (dictionary arguments). | ||
| 123 | class UnifiedPushConnector2Adaptor final : public QDBusAbstractAdaptor { | ||
| 124 | Q_OBJECT | ||
| 125 | Q_CLASSINFO("D-Bus Interface", "org.unifiedpush.Connector2") | ||
| 126 | public: | ||
| 127 | explicit UnifiedPushConnector2Adaptor(UnifiedPushConnector *connector); | ||
| 128 | public slots: | ||
| 129 | QVariantMap Message(const QVariantMap &args); | ||
| 130 | QVariantMap NewEndpoint(const QVariantMap &args); | ||
| 131 | QVariantMap Unregistered(const QVariantMap &args); | ||
| 132 | |||
| 133 | private: | ||
| 134 | UnifiedPushConnector *m_connector; | ||
| 135 | }; | ||
| 136 | |||
| 137 | // org.unifiedpush.Connector1 (positional arguments; older distributors). | ||
| 138 | class UnifiedPushConnector1Adaptor final : public QDBusAbstractAdaptor { | ||
| 139 | Q_OBJECT | ||
| 140 | Q_CLASSINFO("D-Bus Interface", "org.unifiedpush.Connector1") | ||
| 141 | public: | ||
| 142 | explicit UnifiedPushConnector1Adaptor(UnifiedPushConnector *connector); | ||
| 143 | public slots: | ||
| 144 | void Message(const QString &token, const QByteArray &message, const QString &id); | ||
| 145 | void NewEndpoint(const QString &token, const QString &endpoint); | ||
| 146 | void Unregistered(const QString &token); | ||
| 147 | |||
| 148 | private: | ||
| 149 | UnifiedPushConnector *m_connector; | ||
| 150 | }; | ||
| 151 |