6#include "distributor.h"
7#include "distributor1adaptor.h"
8#include "distributor2adaptor.h"
9#include "managementadaptor.h"
11#include "autopushprovider.h"
13#include "gotifypushprovider.h"
16#include "mockpushprovider.h"
17#include "nextpushprovider.h"
18#include "ntfypushprovider.h"
20#include "../shared/unifiedpush-constants.h"
22#include <Solid/Battery>
23#include <Solid/Device>
24#include <Solid/DeviceNotifier>
26#include <QDBusConnection>
29#include <QNetworkInformation>
33constexpr inline auto RETRY_BACKOFF_FACTOR = 1.5;
34constexpr inline auto RETRY_INITIAL_MIN = 15;
35constexpr inline auto RETRY_INITIAL_MAX = 40;
37Distributor::Distributor(
QObject *parent)
40 qDBusRegisterMetaType<KUnifiedPush::ClientInfo>();
41 qDBusRegisterMetaType<QList<KUnifiedPush::ClientInfo>>();
53 addBattery(batteryDevice);
59 m_urgency = determineUrgency();
63 m_retryTimer.setSingleShot(
true);
64 m_retryTimer.setInterval(0);
68 new Distributor1Adaptor(
this);
69 new Distributor2Adaptor(
this);
72 new ManagementAdaptor(
this);
76 if (!setupPushProvider()) {
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));
91 qCDebug(
Log) << m_clients.size() <<
"registered clients loaded";
94 if (!m_clients.empty())
96 setStatus(DistributorStatus::NoNetwork);
98 cmd.type = Command::Connect;
99 m_commandQueue.push_back(std::move(cmd));
101 setStatus(DistributorStatus::Idle);
105 purgeUnavailableClients();
107 processNextCommand();
110Distributor::~Distributor() =
default;
114 const auto it = std::ranges::find_if(m_clients, [&token](
const auto &client) {
115 return client.token == token;
117 if (it == m_clients.end()) {
118 qCDebug(Log) <<
"Registering new client (v1)" << serviceName << token;
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;
129 m_commandQueue.push_back(std::move(cmd));
131 processNextCommand();
135 qCDebug(Log) <<
"Registering known client (v1)" << serviceName << token;
138 registrationResultReason.
clear();
139 return UP_REGISTER_RESULT_SUCCESS;
142QVariantMap Distributor::Register(
const QVariantMap &args)
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;
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;
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;
163 m_commandQueue.push_back(std::move(cmd));
165 processNextCommand();
169 qCDebug(Log) <<
"Registering known client (v2)" << (*it).serviceName << token;
178void Distributor::connectOnDemand()
182 if (m_clients.empty()) {
184 cmd.type = Command::Connect;
185 m_commandQueue.push_back(std::move(cmd));
189void Distributor::Unregister(
const QString& token)
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;
195 if (it == m_clients.end()) {
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);
201 qCWarning(Log) <<
"Unregistration request for unknown client.";
209 m_commandQueue.push_back(std::move(cmd));
210 processNextCommand();
213QVariantMap Distributor::Unregister(
const QVariantMap &args)
215 const auto token = args.value(UP_ARG_TOKEN).toString();
220void Distributor::messageReceived(
const Message &msg)
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;
226 if (it == m_clients.end()) {
227 qCWarning(Log) <<
"Received message for unknown client";
232 (*it).message(
this, msg.content, msg.messageId);
237 qCDebug(Log) << client.token << client.remoteId << client.serviceName <<
error << errorMsg;
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);
250 client.store(settings);
251 settings.
setValue(QStringLiteral(
"Clients/Tokens"), clientTokens());
252 Q_EMIT registeredClientsChanged();
254 client.newEndpoint();
257 switch (client.version) {
258 case Client::UnifiedPushVersion::v1:
261 case Client::UnifiedPushVersion::v2:
265 m_currentCommand.reply << args;
275 m_commandQueue.push_front(std::move(m_currentCommand));
277 cmd.type = Command::Wait;
278 m_commandQueue.push_front(std::move(cmd));
283 switch (client.version) {
284 case Client::UnifiedPushVersion::v1:
287 case Client::UnifiedPushVersion::v2:
294 m_currentCommand.reply << args;
303 m_currentCommand = {};
304 processNextCommand();
309 qCDebug(Log) << client.token << client.remoteId << client.serviceName <<
error;
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;
323 if (it != m_clients.end()) {
327 if (m_clients.empty() && m_commandQueue.empty()) {
329 cmd.type = Command::Disconnect;
330 m_commandQueue.push_back(std::move(cmd));
333 settings.
setValue(QStringLiteral(
"Clients/Tokens"), clientTokens());
334 Q_EMIT registeredClientsChanged();
345 m_commandQueue.push_front(std::move(m_currentCommand));
349 m_currentCommand = {};
350 processNextCommand();
353void Distributor::providerConnected()
356 setStatus(DistributorStatus::Connected);
358 if (m_currentCommand.type == Command::Connect) {
359 m_currentCommand = {};
360 m_retryTimer.setInterval(0);
363 if (m_urgency != m_pushProvider->urgency()) {
364 if (std::ranges::none_of(m_commandQueue, [](
const auto &cmd) {
return cmd.type == Command::ChangeUrgency; })) {
366 cmd.type = Command::ChangeUrgency;
367 m_commandQueue.push_back(std::move(cmd));
372 processNextCommand();
377 qCDebug(Log) <<
error << errorMsg;
378 if (m_currentCommand.type == Command::Disconnect) {
379 m_currentCommand = {};
380 setStatus(m_clients.empty() ? DistributorStatus::Idle : DistributorStatus::NoNetwork);
383 setStatus(DistributorStatus::NoNetwork);
384 setErrorMessage(errorMsg);
387 if (hasCurrentCommand() && m_currentCommand.type != Command::Connect) {
388 m_commandQueue.push_front(std::move(m_currentCommand));
390 m_currentCommand = {};
393 if (!m_clients.empty()) {
395 cmd.type = Command::Connect;
396 m_commandQueue.push_front(std::move(cmd));
399 cmd.type = Command::Wait;
400 m_commandQueue.push_front(std::move(cmd));
404 processNextCommand();
407void Distributor::providerMessageAcknowledged(
const Client &client,
const QString &messageIdentifier)
409 qCDebug(Log) << client.serviceName << messageIdentifier;
410 if (m_currentCommand.type == Command::MessageAck && m_currentCommand.value == messageIdentifier) {
411 m_currentCommand = {};
413 processNextCommand();
416void Distributor::providerUrgencyChanged()
419 if (m_currentCommand.type == Command::ChangeUrgency) {
420 m_currentCommand = {};
422 processNextCommand();
425QStringList Distributor::clientTokens()
const
429 std::transform(m_clients.begin(), m_clients.end(), std::back_inserter(l), [](
const auto &client) { return client.token; });
433bool Distributor::setupPushProvider(
bool newSetup)
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);
448 qCWarning(Log) <<
"Unknown push provider:" << pushProviderName;
449 m_pushProvider.reset();
450 setStatus(DistributorStatus::NoSetup);
457 m_pushProvider->resetSettings(settings);
459 if (!m_pushProvider->loadSettings(settings)) {
460 qCWarning(Log) <<
"Invalid push provider settings!";
461 setStatus(DistributorStatus::NoSetup);
476void Distributor::purgeUnavailableClients()
479 std::sort(activatableServiceNames.
begin(), activatableServiceNames.
end());
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);
491 if (m_pushProvider->metaObject() == &MockPushProvider::staticMetaObject) [[unlikely]] {
495 for (
const auto &token : tokensToUnregister) {
500bool Distributor::hasCurrentCommand()
const
502 return m_currentCommand.type != Command::NoCommand;
505void Distributor::processNextCommand()
513void Distributor::doProcessNextCommand()
515 if (hasCurrentCommand() || m_commandQueue.empty() || !isNetworkAvailable()) {
519 m_currentCommand = m_commandQueue.front();
520 m_commandQueue.pop_front();
521 switch (m_currentCommand.type) {
522 case Command::NoCommand:
524 processNextCommand();
526 case Command::Register:
527 m_pushProvider->registerClient(m_currentCommand.client);
532 m_pushProvider->unregisterClient(m_currentCommand.client);
534 case Command::Connect:
535 m_pushProvider->connectToProvider(m_urgency);
537 case Command::Disconnect:
538 m_pushProvider->disconnectFromProvider();
540 case Command::ChangePushProvider:
543 settings.
setValue(QLatin1String(
"PushProvider/Type"), m_currentCommand.value);
544 m_currentCommand = {};
545 if (setupPushProvider(
true )) {
546 processNextCommand();
550 case Command::MessageAck:
551 m_pushProvider->acknowledgeMessage(m_currentCommand.client, m_currentCommand.value);
553 case Command::ChangeUrgency:
554 m_pushProvider->changeUrgency(m_urgency);
557 if (m_retryTimer.interval() == 0) {
558 m_retryTimer.setInterval(std::chrono::seconds(
QRandomGenerator::global()->bounded(RETRY_INITIAL_MIN, RETRY_INITIAL_MAX)));
560 qCDebug(Log) <<
"retry backoff time:" << m_retryTimer.interval();
561 m_retryTimer.start();
566int Distributor::status()
const
571QString Distributor::errorMessage()
const
573 return m_errorMessage;
576void Distributor::setStatus(DistributorStatus::Status
status)
578 if (m_status == status) {
586void Distributor::setErrorMessage(
const QString &errMsg)
588 if (m_errorMessage == errMsg) {
592 m_errorMessage = errMsg;
593 Q_EMIT errorMessageChanged();
596QString Distributor::pushProviderId()
const
599 return settings.
value(QStringLiteral(
"PushProvider/Type"), QString()).
toString();
602QVariantMap Distributor::pushProviderConfiguration(
const QString &pushProviderId)
const
604 if (pushProviderId.isEmpty()) {
610 const auto keys = settings.
allKeys();
613 for (
const auto &key : keys) {
614 const auto v = settings.
value(key);
616 config.insert(key, settings.
value(key));
623void Distributor::setPushProvider(
const QString &pushProviderId,
const QVariantMap &config)
626 bool configChanged =
false;
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());
635 if (!configChanged && pushProviderId == this->pushProviderId()) {
640 if (m_currentCommand.type == Command::Wait) {
641 qCDebug(Log) <<
"stopping retry timer";
642 m_currentCommand = {};
644 m_retryTimer.setInterval(0);
648 if (m_status != DistributorStatus::NoSetup) {
649 for (
const auto &client : m_clients) {
653 m_commandQueue.push_back(std::move(cmd));
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);
665 if (m_status == DistributorStatus::Connected) {
667 cmd.type = Command::Disconnect;
668 m_commandQueue.push_back(std::move(cmd));
672 cmd.type = Command::ChangePushProvider;
673 cmd.value = pushProviderId;
674 m_commandQueue.push_back(std::move(cmd));
678 if (!m_clients.empty() || !pendingClients.empty()) {
680 cmd.type = Command::Connect;
681 m_commandQueue.push_back(std::move(cmd));
685 for (
const auto &client : m_clients) {
687 cmd.type = Command::Register;
689 m_commandQueue.push_back(std::move(cmd));
691 std::ranges::move(pendingClients, std::back_inserter(m_commandQueue));
696 if (!m_commandQueue.empty()) {
698 cmd.type = Command::Connect;
699 m_commandQueue.push_front(std::move(cmd));
703 cmd.type = Command::ChangePushProvider;
704 cmd.value = pushProviderId;
705 m_commandQueue.push_front(std::move(cmd));
708 processNextCommand();
711QList<KUnifiedPush::ClientInfo> Distributor::registeredClients()
const
713 QList<KUnifiedPush::ClientInfo> result;
714 result.
reserve(m_clients.size());
716 for (
const auto &client : m_clients) {
718 info.token = client.token;
719 info.serviceName = client.serviceName;
720 info.description = client.description;
727void Distributor::forceUnregisterClient(
const QString &token)
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;
733 if (it == m_clients.end()) {
734 qCWarning(Log) <<
"Unregistration request for unknown client.";
741 m_commandQueue.push_back(std::move(cmd));
742 processNextCommand();
745void Distributor::messageAcknowledged(
const Client &client,
const QString &messageIdentifier)
747 if (messageIdentifier.
isEmpty()) {
751 qCDebug(Log) << client.serviceName <<messageIdentifier;
753 cmd.type = Command::MessageAck;
755 cmd.value = messageIdentifier;
756 m_commandQueue.
push_back(std::move(cmd));
757 processNextCommand();
760bool Distributor::isNetworkAvailable()
const
765 return reachability == QNetworkInformation::Reachability::Online || reachability == QNetworkInformation::Reachability::Unknown;
770Urgency Distributor::determineUrgency()
const
772 bool isNotDischarging =
false;
773 bool foundBattery =
false;
774 int maxChargePercent = 0;
776 const auto battery = batteryDevice.as<Solid::Battery>();
777 if (battery->type() != Solid::Battery::PrimaryBattery || !battery->isPresent()) {
780 if (battery->chargeState() != Solid::Battery::Discharging) {
781 isNotDischarging =
true;
783 maxChargePercent = std::max(battery->chargePercent(), maxChargePercent);
786 const auto isDischarging = foundBattery && !isNotDischarging;
787 qCDebug(Log) << isDischarging << foundBattery << isNotDischarging << maxChargePercent;
789 if (maxChargePercent > 0 && maxChargePercent <= 15) {
790 return Urgency::High;
794 qCDebug(Log) << isMetered;
795 if (isDischarging && isMetered) {
796 return Urgency::Normal;
799 if (isDischarging || isMetered) {
803 return Urgency::VeryLow;
806void Distributor::setUrgency(Urgency urgency)
808 if (m_urgency == urgency) {
812 qCDebug(Log) << qToUnderlying(urgency);
814 if (std::ranges::any_of(m_commandQueue, [](
const auto &cmd) {
return cmd.type == Command::ChangeUrgency; })) {
818 cmd.type = Command::ChangeUrgency;
819 m_commandQueue.push_back(std::move(cmd));
822void Distributor::addBattery(
const Solid::Device &batteryDevice)
824 const auto battery = batteryDevice.
as<Solid::Battery>();
825 if (!battery || battery->type() != Solid::Battery::PrimaryBattery) {
829 const auto updateUrgency = [
this]() { setUrgency(determineUrgency()); };
835void Distributor::retryTimeout()
837 if (m_currentCommand.type == Command::Wait) {
838 m_currentCommand = {};
839 m_retryTimer.setInterval(m_retryTimer.interval() * RETRY_BACKOFF_FACTOR);
841 processNextCommand();
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
@ NoError
operation succeeded
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.
@ SilentUnregister
unregistration for moving to a different push provider
@ Unregister
unregistration requested by client
@ ForceUnregister
unregistration triggered by distributor
A received push notification message.
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())
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.
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
void push_back(parameter_type value)
void reserve(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QRandomGenerator * global()
QStringList allKeys() const const
void beginGroup(QAnyStringView prefix)
void remove(QAnyStringView key)
void setValue(QAnyStringView key, const QVariant &value)
QVariant value(QAnyStringView key) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
QStringList toStringList() const const