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% 8 / 0 / 8
Functions: 100.0% 5 / 0 / 5
Branches: 50.0% 1 / 0 / 2

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