KUnifiedPush

distributor.cpp
1/*
2 SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "distributor.h"
7#include "distributor1adaptor.h"
8#include "distributor2adaptor.h"
9#include "managementadaptor.h"
10
11#include "autopushprovider.h"
12#include "client.h"
13#include "gotifypushprovider.h"
14#include "logging.h"
15#include "message.h"
16#include "mockpushprovider.h"
17#include "nextpushprovider.h"
18#include "ntfypushprovider.h"
19
20#include "../shared/unifiedpush-constants.h"
21
22#include <Solid/Battery>
23#include <Solid/Device>
24#include <Solid/DeviceNotifier>
25
26#include <QDBusConnection>
27#include <QSettings>
28
29#include <QNetworkInformation>
30
31using namespace KUnifiedPush;
32
33constexpr inline auto RETRY_BACKOFF_FACTOR = 1.5;
34constexpr inline auto RETRY_INITIAL_MIN = 15;
35constexpr inline auto RETRY_INITIAL_MAX = 40;
36
37Distributor::Distributor(QObject *parent)
38 : QObject(parent)
39{
40 qDBusRegisterMetaType<KUnifiedPush::ClientInfo>();
41 qDBusRegisterMetaType<QList<KUnifiedPush::ClientInfo>>();
42
43 // setup network status tracking
44 if (QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability)) {
45 connect(QNetworkInformation::instance(), &QNetworkInformation::reachabilityChanged, this, &Distributor::processNextCommand);
46 connect(QNetworkInformation::instance(), &QNetworkInformation::isMeteredChanged, this, [this]{ setUrgency(determineUrgency()); });
47 } else {
48 qCWarning(Log) << "No network state information available!" << QNetworkInformation::availableBackends();
49 }
50
51 // setup battery monitoring
52 for (const auto &batteryDevice : Solid::Device::listFromType(Solid::DeviceInterface::Battery)) {
53 addBattery(batteryDevice);
54 }
55 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, [this](const auto &id) {
56 addBattery(Solid::Device(id));
57 });
58
59 m_urgency = determineUrgency();
60
61 // exponential backoff retry timer
62 m_retryTimer.setTimerType(Qt::VeryCoarseTimer);
63 m_retryTimer.setSingleShot(true);
64 m_retryTimer.setInterval(0);
65 connect(&m_retryTimer, &QTimer::timeout, this, &Distributor::retryTimeout);
66
67 // register at D-Bus
68 new Distributor1Adaptor(this);
69 new Distributor2Adaptor(this);
70 QDBusConnection::sessionBus().registerObject(UP_DISTRIBUTOR_PATH, this);
71
72 new ManagementAdaptor(this);
73 QDBusConnection::sessionBus().registerObject(KDE_DISTRIBUTOR_MANAGEMENT_PATH, this);
74
75 // create and set up push provider
76 if (!setupPushProvider()) {
77 return;
78 }
79
80 // load previous clients
81 // TODO what happens to existing clients if the above failed?
82 QSettings settings;
83 const auto clientTokens = settings.value(QStringLiteral("Clients/Tokens"), QStringList()).toStringList();
84 m_clients.reserve(clientTokens.size());
85 for (const auto &token : clientTokens) {
86 auto client = Client::load(token, settings);
87 if (client.isValid()) {
88 m_clients.push_back(std::move(client));
89 }
90 }
91 qCDebug(Log) << m_clients.size() << "registered clients loaded";
92
93 // connect to push provider if necessary
94 if (!m_clients.empty())
95 {
96 setStatus(DistributorStatus::NoNetwork);
97 Command cmd;
98 cmd.type = Command::Connect;
99 m_commandQueue.push_back(std::move(cmd));
100 } else {
101 setStatus(DistributorStatus::Idle);
102 }
103
104 // purge uninstalled apps
105 purgeUnavailableClients();
106
107 processNextCommand();
108}
109
110Distributor::~Distributor() = default;
111
112QString Distributor::Register(const QString& serviceName, const QString& token, const QString &description, QString& registrationResultReason)
113{
114 const auto it = std::ranges::find_if(m_clients, [&token](const auto &client) {
115 return client.token == token;
116 });
117 if (it == m_clients.end()) {
118 qCDebug(Log) << "Registering new client (v1)" << serviceName << token;
119 connectOnDemand();
120
121 Command cmd;
122 cmd.type = Command::Register;
123 cmd.client.token = token;
124 cmd.client.serviceName = serviceName;
125 cmd.client.description = description;
126 cmd.client.version = Client::UnifiedPushVersion::v1;
127 setDelayedReply(true);
128 cmd.reply = message().createReply();
129 m_commandQueue.push_back(std::move(cmd));
130
131 processNextCommand();
132 return {};
133 }
134
135 qCDebug(Log) << "Registering known client (v1)" << serviceName << token;
136 (*it).activate();
137 (*it).newEndpoint();
138 registrationResultReason.clear();
139 return UP_REGISTER_RESULT_SUCCESS;
140}
141
142QVariantMap Distributor::Register(const QVariantMap &args)
143{
144 const auto token = args.value(UP_ARG_TOKEN).toString();
145 const auto it = std::ranges::find_if(m_clients, [&token](const auto &client) {
146 return client.token == token;
147 });
148
149 if (it == m_clients.end()) {
150 const auto serviceName = args.value(UP_ARG_SERVICE).toString();
151 qCDebug(Log) << "Registering new client (v2)" << serviceName << token;
152 connectOnDemand();
153
154 Command cmd;
155 cmd.type = Command::Register;
156 cmd.client.token = token;
157 cmd.client.serviceName = serviceName;
158 cmd.client.description = args.value(UP_ARG_DESCRIPTION).toString();
159 cmd.client.vapidKey = args.value(UP_ARG_VAPID).toString();
160 cmd.client.version = Client::UnifiedPushVersion::v2;
161 setDelayedReply(true);
162 cmd.reply = message().createReply();
163 m_commandQueue.push_back(std::move(cmd));
164
165 processNextCommand();
166 return {};
167 }
168
169 qCDebug(Log) << "Registering known client (v2)" << (*it).serviceName << token;
170 (*it).activate();
171 (*it).newEndpoint();
172
173 QVariantMap res;
174 res.insert(UP_ARG_SUCCESS, QString::fromLatin1(UP_REGISTER_RESULT_SUCCESS));
175 return res;
176}
177
178void Distributor::connectOnDemand()
179{
180 // if this is the first client, connect to the push provider first
181 // this can involve first-time device registration that is a prerequisite for registering clients
182 if (m_clients.empty()) {
183 Command cmd;
184 cmd.type = Command::Connect;
185 m_commandQueue.push_back(std::move(cmd));
186 }
187}
188
189void Distributor::Unregister(const QString& token)
190{
191 qCDebug(Log) << token;
192 const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&token](const auto &client) {
193 return client.token == token;
194 });
195 if (it == m_clients.end()) {
196 // might still be in the queue and never completed registration
197 const auto it = std::ranges::find_if(m_commandQueue, [token](const auto &cmd) { return cmd.type == Command::Register && cmd.client.token == token; });
198 if (it != m_commandQueue.end()) {
199 m_commandQueue.erase(it);
200 } else {
201 qCWarning(Log) << "Unregistration request for unknown client.";
202 }
203 return;
204 }
205
206 Command cmd;
207 cmd.type = Command::Unregister;
208 cmd.client = (*it);
209 m_commandQueue.push_back(std::move(cmd));
210 processNextCommand();
211}
212
213QVariantMap Distributor::Unregister(const QVariantMap &args)
214{
215 const auto token = args.value(UP_ARG_TOKEN).toString();
216 Unregister(token);
217 return {};
218}
219
220void Distributor::messageReceived(const Message &msg)
221{
222 qCDebug(Log) << msg.clientRemoteId << msg.content;
223 const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&msg](const auto &client) {
224 return client.remoteId == msg.clientRemoteId;
225 });
226 if (it == m_clients.end()) {
227 qCWarning(Log) << "Received message for unknown client";
228 return;
229 }
230
231 (*it).activate();
232 (*it).message(this, msg.content, msg.messageId);
233}
234
235void Distributor::clientRegistered(const Client &client, AbstractPushProvider::Error error, const QString &errorMsg)
236{
237 qCDebug(Log) << client.token << client.remoteId << client.serviceName << error << errorMsg;
238 switch (error) {
240 {
241 // TODO check whether we got an endpoint, otherwise report an error
242 const auto it = std::ranges::find_if(m_clients, [&client](const auto &c) { return c.token == client.token; });
243 if (it == m_clients.end()) {
244 m_clients.push_back(client);
245 } else {
246 (*it) = client;
247 }
248
249 QSettings settings;
250 client.store(settings);
251 settings.setValue(QStringLiteral("Clients/Tokens"), clientTokens());
252 Q_EMIT registeredClientsChanged();
253
254 client.newEndpoint();
255
256 if (m_currentCommand.reply.type() != QDBusMessage::InvalidMessage) {
257 switch (client.version) {
258 case Client::UnifiedPushVersion::v1:
259 m_currentCommand.reply << QString::fromLatin1(UP_REGISTER_RESULT_SUCCESS) << QString();
260 break;
261 case Client::UnifiedPushVersion::v2:
262 {
263 QVariantMap args;
264 args.insert(UP_ARG_SUCCESS, QString::fromLatin1(UP_REGISTER_RESULT_SUCCESS));
265 m_currentCommand.reply << args;
266 break;
267 }
268 }
269 QDBusConnection::sessionBus().send(m_currentCommand.reply);
270 }
271 break;
272 }
274 {
275 m_commandQueue.push_front(std::move(m_currentCommand));
276 Command cmd;
277 cmd.type = Command::Wait;
278 m_commandQueue.push_front(std::move(cmd));
279 break;
280 }
282 {
283 switch (client.version) {
284 case Client::UnifiedPushVersion::v1:
285 m_currentCommand.reply << QString::fromLatin1(UP_REGISTER_RESULT_FAILURE) << errorMsg;
286 break;
287 case Client::UnifiedPushVersion::v2:
288 {
289 QVariantMap args;
290 args.insert(UP_ARG_SUCCESS, QString::fromLatin1(UP_REGISTER_RESULT_FAILURE));
291 args.insert(UP_ARG_REASON, QString::fromLatin1(UP_REGISTER_FAILURE_INTERNAL_ERROR));
292 // TODO when the client side ever actually uses errorMsg we could return this here in addition
293 // in a custom argument
294 m_currentCommand.reply << args;
295 break;
296 }
297 }
298 QDBusConnection::sessionBus().send(m_currentCommand.reply);
299 break;
300 }
301 }
302
303 m_currentCommand = {};
304 processNextCommand();
305}
306
307void Distributor::clientUnregistered(const Client &client, AbstractPushProvider::Error error)
308{
309 qCDebug(Log) << client.token << client.remoteId << client.serviceName << error;
310 switch (error) {
312 if (m_currentCommand.type != Command::SilentUnregister) {
313 client.unregistered(m_currentCommand.type == Command::Unregister);
314 }
315 [[fallthrough]];
317 if (m_currentCommand.type != Command::SilentUnregister) {
318 QSettings settings;
319 settings.remove(client.token);
320 const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&client](const auto &c) {
321 return c.token == client.token;
322 });
323 if (it != m_clients.end()) {
324 m_clients.erase(it);
325
326 // if this was the last client and nothing else needs to be done, also disconnect from the push provider
327 if (m_clients.empty() && m_commandQueue.empty()) {
328 Command cmd;
329 cmd.type = Command::Disconnect;
330 m_commandQueue.push_back(std::move(cmd));
331 }
332 }
333 settings.setValue(QStringLiteral("Clients/Tokens"), clientTokens());
334 Q_EMIT registeredClientsChanged();
335 } else {
336 auto c = client;
337 c.endpoint.clear();
338 QSettings settings;
339 c.store(settings);
340 c.newEndpoint();
341 }
342 break;
344 // retry
345 m_commandQueue.push_front(std::move(m_currentCommand));
346 break;
347 }
348
349 m_currentCommand = {};
350 processNextCommand();
351}
352
353void Distributor::providerConnected()
354{
355 qCDebug(Log);
356 setStatus(DistributorStatus::Connected);
357 setErrorMessage({});
358 if (m_currentCommand.type == Command::Connect) {
359 m_currentCommand = {};
360 m_retryTimer.setInterval(0); // reset retry backoff timer
361
362 // provider needs separate command to change urgency
363 if (m_urgency != m_pushProvider->urgency()) {
364 if (std::ranges::none_of(m_commandQueue, [](const auto &cmd) { return cmd.type == Command::ChangeUrgency; })) {
365 Command cmd;
366 cmd.type = Command::ChangeUrgency;
367 m_commandQueue.push_back(std::move(cmd));
368 }
369 }
370 }
371
372 processNextCommand();
373}
374
375void Distributor::providerDisconnected(AbstractPushProvider::Error error, const QString &errorMsg)
376{
377 qCDebug(Log) << error << errorMsg;
378 if (m_currentCommand.type == Command::Disconnect) {
379 m_currentCommand = {};
380 setStatus(m_clients.empty() ? DistributorStatus::Idle : DistributorStatus::NoNetwork);
381 setErrorMessage({});
382 } else {
383 setStatus(DistributorStatus::NoNetwork);
384 setErrorMessage(errorMsg);
385
386 // defer what we are doing
387 if (hasCurrentCommand() && m_currentCommand.type != Command::Connect) {
388 m_commandQueue.push_front(std::move(m_currentCommand));
389 }
390 m_currentCommand = {};
391
392 // attempt to reconnect when we have active clients and didn't ask for the disconnect
393 if (!m_clients.empty()) {
394 Command cmd;
395 cmd.type = Command::Connect;
396 m_commandQueue.push_front(std::move(cmd));
397
399 cmd.type = Command::Wait;
400 m_commandQueue.push_front(std::move(cmd));
401 }
402 }
403 }
404 processNextCommand();
405}
406
407void Distributor::providerMessageAcknowledged(const Client &client, const QString &messageIdentifier)
408{
409 qCDebug(Log) << client.serviceName << messageIdentifier;
410 if (m_currentCommand.type == Command::MessageAck && m_currentCommand.value == messageIdentifier) {
411 m_currentCommand = {};
412 }
413 processNextCommand();
414}
415
416void Distributor::providerUrgencyChanged()
417{
418 qCDebug(Log);
419 if (m_currentCommand.type == Command::ChangeUrgency) {
420 m_currentCommand = {};
421 }
422 processNextCommand();
423}
424
425QStringList Distributor::clientTokens() const
426{
427 QStringList l;
428 l.reserve(m_clients.size());
429 std::transform(m_clients.begin(), m_clients.end(), std::back_inserter(l), [](const auto &client) { return client.token; });
430 return l;
431}
432
433bool Distributor::setupPushProvider(bool newSetup)
434{
435 // determine push provider
436 const auto pushProviderName = pushProviderId();
437 if (pushProviderName == GotifyPushProvider::Id) {
438 m_pushProvider.reset(new GotifyPushProvider);
439 } else if (pushProviderName == NextPushProvider::Id) {
440 m_pushProvider.reset(new NextPushProvider);
441 } else if (pushProviderName == NtfyPushProvider::Id) {
442 m_pushProvider.reset(new NtfyPushProvider);
443 } else if (pushProviderName == AutopushProvider::Id) {
444 m_pushProvider = std::make_unique<AutopushProvider>();
445 } else if (pushProviderName == MockPushProvider::Id) {
446 m_pushProvider.reset(new MockPushProvider);
447 } else {
448 qCWarning(Log) << "Unknown push provider:" << pushProviderName;
449 m_pushProvider.reset();
450 setStatus(DistributorStatus::NoSetup);
451 return false;
452 }
453
454 QSettings settings;
455 settings.beginGroup(pushProviderName);
456 if (newSetup) {
457 m_pushProvider->resetSettings(settings);
458 }
459 if (!m_pushProvider->loadSettings(settings)) {
460 qCWarning(Log) << "Invalid push provider settings!";
461 setStatus(DistributorStatus::NoSetup);
462 return false;
463 }
464 settings.endGroup();
465
466 connect(m_pushProvider.get(), &AbstractPushProvider::messageReceived, this, &Distributor::messageReceived);
467 connect(m_pushProvider.get(), &AbstractPushProvider::clientRegistered, this, &Distributor::clientRegistered);
468 connect(m_pushProvider.get(), &AbstractPushProvider::clientUnregistered, this, &Distributor::clientUnregistered);
469 connect(m_pushProvider.get(), &AbstractPushProvider::connected, this, &Distributor::providerConnected);
470 connect(m_pushProvider.get(), &AbstractPushProvider::disconnected, this, &Distributor::providerDisconnected);
471 connect(m_pushProvider.get(), &AbstractPushProvider::messageAcknowledged, this, &Distributor::providerMessageAcknowledged);
472 connect(m_pushProvider.get(), &AbstractPushProvider::urgencyChanged, this, &Distributor::providerUrgencyChanged);
473 return true;
474}
475
476void Distributor::purgeUnavailableClients()
477{
478 QStringList activatableServiceNames = QDBusConnection::sessionBus().interface()->activatableServiceNames();
479 std::sort(activatableServiceNames.begin(), activatableServiceNames.end());
480
481 // collect clients to unregister first, so m_clients doesn't change underneath us
482 QStringList tokensToUnregister;
483 for (const auto &client : m_clients) {
484 if (!std::binary_search(activatableServiceNames.begin(), activatableServiceNames.end(), client.serviceName)) {
485 tokensToUnregister.push_back(client.token);
486 }
487 }
488
489 // in mock mode assume everything is activatable, as unit tests don't install
490 // D-Bus service files
491 if (m_pushProvider->metaObject() == &MockPushProvider::staticMetaObject) [[unlikely]] {
492 return;
493 }
494
495 for (const auto &token : tokensToUnregister) {
496 Unregister(token);
497 }
498}
499
500bool Distributor::hasCurrentCommand() const
501{
502 return m_currentCommand.type != Command::NoCommand;
503}
504
505void Distributor::processNextCommand()
506{
507 // Return to the event loop before processing the next command.
508 // This allows calls into here from within processing a network operation
509 // to fully complete first before we potentially call into that code again.
510 QMetaObject::invokeMethod(this, &Distributor::doProcessNextCommand, Qt::QueuedConnection);
511}
512
513void Distributor::doProcessNextCommand()
514{
515 if (hasCurrentCommand() || m_commandQueue.empty() || !isNetworkAvailable()) {
516 return;
517 }
518
519 m_currentCommand = m_commandQueue.front();
520 m_commandQueue.pop_front();
521 switch (m_currentCommand.type) {
522 case Command::NoCommand:
523 Q_ASSERT(false);
524 processNextCommand();
525 break;
526 case Command::Register:
527 m_pushProvider->registerClient(m_currentCommand.client);
528 break;
532 m_pushProvider->unregisterClient(m_currentCommand.client);
533 break;
534 case Command::Connect:
535 m_pushProvider->connectToProvider(m_urgency);
536 break;
537 case Command::Disconnect:
538 m_pushProvider->disconnectFromProvider();
539 break;
540 case Command::ChangePushProvider:
541 {
542 QSettings settings;
543 settings.setValue(QLatin1String("PushProvider/Type"), m_currentCommand.value);
544 m_currentCommand = {};
545 if (setupPushProvider(true /* new setup */)) {
546 processNextCommand();
547 }
548 break;
549 }
550 case Command::MessageAck:
551 m_pushProvider->acknowledgeMessage(m_currentCommand.client, m_currentCommand.value);
552 break;
553 case Command::ChangeUrgency:
554 m_pushProvider->changeUrgency(m_urgency);
555 break;
556 case Command::Wait:
557 if (m_retryTimer.interval() == 0) {
558 m_retryTimer.setInterval(std::chrono::seconds(QRandomGenerator::global()->bounded(RETRY_INITIAL_MIN, RETRY_INITIAL_MAX)));
559 }
560 qCDebug(Log) << "retry backoff time:" << m_retryTimer.interval();
561 m_retryTimer.start();
562 break;
563 }
564}
565
566int Distributor::status() const
567{
568 return m_status;
569}
570
571QString Distributor::errorMessage() const
572{
573 return m_errorMessage;
574}
575
576void Distributor::setStatus(DistributorStatus::Status status)
577{
578 if (m_status == status) {
579 return;
580 }
581
582 m_status = status;
583 Q_EMIT statusChanged();
584}
585
586void Distributor::setErrorMessage(const QString &errMsg)
587{
588 if (m_errorMessage == errMsg) {
589 return;
590 }
591
592 m_errorMessage = errMsg;
593 Q_EMIT errorMessageChanged();
594}
595
596QString Distributor::pushProviderId() const
597{
598 QSettings settings;
599 return settings.value(QStringLiteral("PushProvider/Type"), QString()).toString();
600}
601
602QVariantMap Distributor::pushProviderConfiguration(const QString &pushProviderId) const
603{
604 if (pushProviderId.isEmpty()) {
605 return {};
606 }
607
608 QSettings settings;
609 settings.beginGroup(pushProviderId);
610 const auto keys = settings.allKeys();
611
612 QVariantMap config;
613 for (const auto &key : keys) {
614 const auto v = settings.value(key);
615 if (v.isValid()) {
616 config.insert(key, settings.value(key));
617 }
618 }
619
620 return config;
621}
622
623void Distributor::setPushProvider(const QString &pushProviderId, const QVariantMap &config)
624{
625 // store push provider config and check for changes
626 bool configChanged = false;
627 QSettings settings;
628 settings.beginGroup(pushProviderId);
629 for (auto it = config.begin(); it != config.end(); ++it) {
630 const auto oldValue = settings.value(it.key());
631 configChanged |= oldValue != it.value();
632 settings.setValue(it.key(), it.value());
633 }
634 settings.endGroup();
635 if (!configChanged && pushProviderId == this->pushProviderId()) {
636 return; // nothing changed
637 }
638
639 // stop retry timer if active
640 if (m_currentCommand.type == Command::Wait) {
641 qCDebug(Log) << "stopping retry timer";
642 m_currentCommand = {};
643 m_retryTimer.stop();
644 m_retryTimer.setInterval(0);
645 }
646
647 // if push provider or config changed: unregister all clients, create new push provider backend, re-register all clients
648 if (m_status != DistributorStatus::NoSetup) {
649 for (const auto &client : m_clients) {
650 Command cmd;
651 cmd.type = Command::SilentUnregister;
652 cmd.client = client;
653 m_commandQueue.push_back(std::move(cmd));
654 }
655 // remaining registration commands are for not yet persisted clients, keep those
656 std::vector<Command> pendingClients;
657 for (auto it = m_commandQueue.begin(); it != m_commandQueue.end();) {
658 if ((*it).type == Command::Register) {
659 pendingClients.push_back(std::move(*it));
660 it = m_commandQueue.erase(it);
661 } else {
662 ++it;
663 }
664 }
665 if (m_status == DistributorStatus::Connected) {
666 Command cmd;
667 cmd.type = Command::Disconnect;
668 m_commandQueue.push_back(std::move(cmd));
669 }
670 {
671 Command cmd;
672 cmd.type = Command::ChangePushProvider;
673 cmd.value = pushProviderId;
674 m_commandQueue.push_back(std::move(cmd));
675 }
676
677 // reconnect if there are clients
678 if (!m_clients.empty() || !pendingClients.empty()) {
679 Command cmd;
680 cmd.type = Command::Connect;
681 m_commandQueue.push_back(std::move(cmd));
682 }
683
684 // re-register clients
685 for (const auto &client : m_clients) {
686 Command cmd;
687 cmd.type = Command::Register;
688 cmd.client = client;
689 m_commandQueue.push_back(std::move(cmd));
690 }
691 std::ranges::move(pendingClients, std::back_inserter(m_commandQueue));
692 } else {
693 // recover from a previously failed attempt to change push providers
694
695 // reconnect if there are clients
696 if (!m_commandQueue.empty()) {
697 Command cmd;
698 cmd.type = Command::Connect;
699 m_commandQueue.push_front(std::move(cmd));
700 }
701
702 Command cmd;
703 cmd.type = Command::ChangePushProvider;
704 cmd.value = pushProviderId;
705 m_commandQueue.push_front(std::move(cmd));
706 }
707
708 processNextCommand();
709}
710
711QList<KUnifiedPush::ClientInfo> Distributor::registeredClients() const
712{
713 QList<KUnifiedPush::ClientInfo> result;
714 result.reserve(m_clients.size());
715
716 for (const auto &client : m_clients) {
717 ClientInfo info;
718 info.token = client.token;
719 info.serviceName = client.serviceName;
720 info.description = client.description;
721 result.push_back(std::move(info));
722 }
723
724 return result;
725}
726
727void Distributor::forceUnregisterClient(const QString &token)
728{
729 qCDebug(Log) << token;
730 const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&token](const auto &client) {
731 return client.token == token;
732 });
733 if (it == m_clients.end()) {
734 qCWarning(Log) << "Unregistration request for unknown client.";
735 return;
736 }
737
738 Command cmd;
739 cmd.type = Command::ForceUnregister;
740 cmd.client = (*it);
741 m_commandQueue.push_back(std::move(cmd));
742 processNextCommand();
743}
744
745void Distributor::messageAcknowledged(const Client &client, const QString &messageIdentifier)
746{
747 if (messageIdentifier.isEmpty()) {
748 return;
749 }
750
751 qCDebug(Log) << client.serviceName <<messageIdentifier;
752 Command cmd;
753 cmd.type = Command::MessageAck;
754 cmd.client = client;
755 cmd.value = messageIdentifier;
756 m_commandQueue.push_back(std::move(cmd));
757 processNextCommand();
758}
759
760bool Distributor::isNetworkAvailable() const
761{
762 // if in doubt assume we have network and try to connect
764 const auto reachability = QNetworkInformation::instance()->reachability();
765 return reachability == QNetworkInformation::Reachability::Online || reachability == QNetworkInformation::Reachability::Unknown;
766 }
767 return true;
768}
769
770Urgency Distributor::determineUrgency() const
771{
772 bool isNotDischarging = false;
773 bool foundBattery = false;
774 int maxChargePercent = 0;
775 for (const auto &batteryDevice : Solid::Device::listFromType(Solid::DeviceInterface::Battery)) {
776 const auto battery = batteryDevice.as<Solid::Battery>();
777 if (battery->type() != Solid::Battery::PrimaryBattery || !battery->isPresent()) {
778 continue;
779 }
780 if (battery->chargeState() != Solid::Battery::Discharging) {
781 isNotDischarging = true;
782 }
783 maxChargePercent = std::max(battery->chargePercent(), maxChargePercent);
784 foundBattery = true;
785 }
786 const auto isDischarging = foundBattery && !isNotDischarging;
787 qCDebug(Log) << isDischarging << foundBattery << isNotDischarging << maxChargePercent;
788
789 if (maxChargePercent > 0 && maxChargePercent <= 15) {
790 return Urgency::High;
791 }
792
793 const auto isMetered = QNetworkInformation::instance() ? QNetworkInformation::instance()->isMetered() : false;
794 qCDebug(Log) << isMetered;
795 if (isDischarging && isMetered) {
796 return Urgency::Normal;
797 }
798
799 if (isDischarging || isMetered) {
800 return Urgency::Low;
801 }
802
803 return Urgency::VeryLow;
804}
805
806void Distributor::setUrgency(Urgency urgency)
807{
808 if (m_urgency == urgency) {
809 return;
810 }
811
812 qCDebug(Log) << qToUnderlying(urgency);
813 m_urgency = urgency;
814 if (std::ranges::any_of(m_commandQueue, [](const auto &cmd) { return cmd.type == Command::ChangeUrgency; })) {
815 return;
816 }
817 Command cmd;
818 cmd.type = Command::ChangeUrgency;
819 m_commandQueue.push_back(std::move(cmd));
820}
821
822void Distributor::addBattery(const Solid::Device &batteryDevice)
823{
824 const auto battery = batteryDevice.as<Solid::Battery>();
825 if (!battery || battery->type() != Solid::Battery::PrimaryBattery) {
826 return;
827 }
828
829 const auto updateUrgency = [this]() { setUrgency(determineUrgency()); };
830 connect(battery, &Solid::Battery::presentStateChanged, this, updateUrgency);
831 connect(battery, &Solid::Battery::chargeStateChanged, this, updateUrgency);
832 connect(battery, &Solid::Battery::chargePercentChanged, this, updateUrgency);
833}
834
835void Distributor::retryTimeout()
836{
837 if (m_currentCommand.type == Command::Wait) {
838 m_currentCommand = {};
839 m_retryTimer.setInterval(m_retryTimer.interval() * RETRY_BACKOFF_FACTOR);
840 }
841 processNextCommand();
842}
843
844#include "moc_distributor.cpp"
void messageReceived(const KUnifiedPush::Message &msg)
Inform about a received push notification.
void connected()
Emitted after the connection to the push provider has been established successfully.
void clientUnregistered(const KUnifiedPush::Client &client, KUnifiedPush::AbstractPushProvider::Error error=NoError)
Emitted after successful client unregistration.
void disconnected(KUnifiedPush::AbstractPushProvider::Error error, const QString &errorMsg={})
Emitted after the connection to the push provider disconnected or failed to be established.
void urgencyChanged()
Emitted when the urgency level change request has been executed.
void clientRegistered(const KUnifiedPush::Client &client, KUnifiedPush::AbstractPushProvider::Error error=NoError, const QString &errorMsg={})
Emitted after successful client registration.
@ ProviderRejected
communication worked, but the provider refused to complete the operation
@ TransientNetworkError
temporary network error, try again
void messageAcknowledged(const KUnifiedPush::Client &client, const QString &messageIdentifier)
Emitted after a message reception has been acknowledge to the push server.
Information about a registered client.
Definition client.h:20
@ SilentUnregister
unregistration for moving to a different push provider
Definition command.h:24
@ Unregister
unregistration requested by client
Definition command.h:22
@ ForceUnregister
unregistration triggered by distributor
Definition command.h:23
A received push notification message.
Definition message.h:15
void presentStateChanged(bool newState, const QString &udi)
void chargePercentChanged(int value, const QString &udi)
void chargeStateChanged(int newState, const QString &udi=QString())
void deviceAdded(const QString &udi)
static QList< Device > listFromType(const DeviceInterface::Type &type, const QString &parentUdi=QString())
DevIface * as()
Q_SCRIPTABLE CaptureState status()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
Client-side integration with UnifiedPush.
Definition connector.h:14
QDBusConnectionInterface * interface() const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
const QDBusMessage & message() const const
void setDelayedReply(bool enable) const const
QDBusMessage createReply(const QList< QVariant > &arguments) const const
iterator begin()
iterator end()
void push_back(parameter_type value)
void reserve(qsizetype size)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QStringList availableBackends()
QNetworkInformation * instance()
void isMeteredChanged(bool isMetered)
bool loadBackendByFeatures(Features features)
void reachabilityChanged(Reachability newReachability)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QRandomGenerator * global()
QStringList allKeys() const const
void beginGroup(QAnyStringView prefix)
void endGroup()
void remove(QAnyStringView key)
void setValue(QAnyStringView key, const QVariant &value)
QVariant value(QAnyStringView key) const const
void clear()
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
void push_back(QChar ch)
QueuedConnection
VeryCoarseTimer
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
QString toString() const const
QStringList toStringList() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 12:05:39 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.