File indexing completed on 2025-02-16 04:23:12

0001 /*
0002     SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "distributor.h"
0007 #include "distributor1adaptor.h"
0008 #include "managementadaptor.h"
0009 
0010 #include "client.h"
0011 #include "connector1iface.h"
0012 #include "gotifypushprovider.h"
0013 #include "logging.h"
0014 #include "message.h"
0015 #include "mockpushprovider.h"
0016 #include "nextpushprovider.h"
0017 #include "ntfypushprovider.h"
0018 
0019 #include "../shared/unifiedpush-constants.h"
0020 
0021 #include <QDBusConnection>
0022 #include <QSettings>
0023 
0024 #include <QNetworkInformation>
0025 
0026 using namespace KUnifiedPush;
0027 
0028 Distributor::Distributor(QObject *parent)
0029     : QObject(parent)
0030 {
0031     qDBusRegisterMetaType<KUnifiedPush::ClientInfo>();
0032     qDBusRegisterMetaType<QList<KUnifiedPush::ClientInfo>>();
0033 
0034     // setup network status tracking
0035     if (QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability)) {
0036         connect(QNetworkInformation::instance(), &QNetworkInformation::reachabilityChanged, this, &Distributor::processNextCommand);
0037     } else {
0038         qCWarning(Log) << "No network state information available!" << QNetworkInformation::availableBackends();
0039     }
0040 
0041     // register at D-Bus
0042     new Distributor1Adaptor(this);
0043     QDBusConnection::sessionBus().registerObject(QLatin1String(UP_DISTRIBUTOR_PATH), this);
0044 
0045     new ManagementAdaptor(this);
0046     QDBusConnection::sessionBus().registerObject(QLatin1String(KDE_DISTRIBUTOR_MANAGEMENT_PATH), this);
0047 
0048     // create and set up push provider
0049     if (!setupPushProvider()) {
0050         return;
0051     }
0052 
0053     // load previous clients
0054     // TODO what happens to existing clients if the above failed?
0055     QSettings settings;
0056     const auto clientTokens = settings.value(QStringLiteral("Clients/Tokens"), QStringList()).toStringList();
0057     m_clients.reserve(clientTokens.size());
0058     for (const auto &token : clientTokens) {
0059         auto client = Client::load(token, settings);
0060         if (client.isValid()) {
0061             m_clients.push_back(std::move(client));
0062         }
0063     }
0064     qCDebug(Log) << m_clients.size() << "registered clients loaded";
0065 
0066     // purge uninstalled apps
0067     purgeUnavailableClients();
0068 
0069     // connect to push provider if necessary
0070     if (!m_clients.empty())
0071     {
0072         setStatus(DistributorStatus::NoNetwork);
0073         Command cmd;
0074         cmd.type = Command::Connect;
0075         m_commandQueue.push_back(std::move(cmd));
0076     } else {
0077         setStatus(DistributorStatus::Idle);
0078     }
0079 
0080     processNextCommand();
0081 }
0082 
0083 Distributor::~Distributor() = default;
0084 
0085 QString Distributor::Register(const QString& serviceName, const QString& token, const QString &description, QString& registrationResultReason)
0086 {
0087     qCDebug(Log) << serviceName << token;
0088     const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&token](const auto &client) {
0089         return client.token == token;
0090     });
0091     if (it == m_clients.end()) {
0092         qCDebug(Log) << "Registering new client";
0093 
0094         // if this is the first client, connect to the push provider first
0095         // this can involve first-time device registration that is a prerequisite for registering clients
0096         if (m_clients.empty()) {
0097             Command cmd;
0098             cmd.type = Command::Connect;
0099             m_commandQueue.push_back(std::move(cmd));
0100         }
0101 
0102         Command cmd;
0103         cmd.type = Command::Register;
0104         cmd.client.token = token;
0105         cmd.client.serviceName = serviceName;
0106         cmd.client.description = description;
0107         setDelayedReply(true);
0108         cmd.reply = message().createReply();
0109         m_commandQueue.push_back(std::move(cmd));
0110 
0111         processNextCommand();
0112         return {};
0113     }
0114 
0115     qCDebug(Log) << "Registering known client";
0116     (*it).activate();
0117     (*it).connector().NewEndpoint((*it).token, (*it).endpoint);
0118     registrationResultReason.clear();
0119     return QLatin1String(UP_REGISTER_RESULT_SUCCESS);
0120 }
0121 
0122 void Distributor::Unregister(const QString& token)
0123 {
0124     qCDebug(Log) << token;
0125     const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&token](const auto &client) {
0126         return client.token == token;
0127     });
0128     if (it == m_clients.end()) {
0129         qCWarning(Log) << "Unregistration request for unknown client.";
0130         return;
0131     }
0132 
0133     Command cmd;
0134     cmd.type = Command::Unregister;
0135     cmd.client = (*it);
0136     m_commandQueue.push_back(std::move(cmd));
0137     processNextCommand();
0138 }
0139 
0140 void Distributor::messageReceived(const Message &msg) const
0141 {
0142     qCDebug(Log) << msg.clientRemoteId << msg.content;
0143     const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&msg](const auto &client) {
0144         return client.remoteId == msg.clientRemoteId;
0145     });
0146     if (it == m_clients.end()) {
0147         qCWarning(Log) << "Received message for unknown client";
0148         return;
0149     }
0150 
0151     (*it).activate();
0152     (*it).connector().Message((*it).token, msg.content, {});
0153 }
0154 
0155 void Distributor::clientRegistered(const Client &client, AbstractPushProvider::Error error, const QString &errorMsg)
0156 {
0157     qCDebug(Log) << client.token << client.remoteId << client.serviceName << error << errorMsg;
0158     switch (error) {
0159     case AbstractPushProvider::NoError:
0160     {
0161         // TODO check whether we got an endpoint, otherwise report an error
0162         m_clients.push_back(client);
0163 
0164         QSettings settings;
0165         client.store(settings);
0166         settings.setValue(QStringLiteral("Clients/Tokens"), clientTokens());
0167         Q_EMIT registeredClientsChanged();
0168 
0169         client.connector().NewEndpoint(client.token, client.endpoint);
0170 
0171         if (m_currentCommand.reply.type() != QDBusMessage::InvalidMessage) {
0172             m_currentCommand.reply << QString::fromLatin1(UP_REGISTER_RESULT_SUCCESS) << QString();
0173             QDBusConnection::sessionBus().send(m_currentCommand.reply);
0174         }
0175         break;
0176     }
0177     case AbstractPushProvider::TransientNetworkError:
0178         // retry
0179         m_commandQueue.push_front(std::move(m_currentCommand));
0180         break;
0181     case AbstractPushProvider::ProviderRejected:
0182         m_currentCommand.reply << QString::fromLatin1(UP_REGISTER_RESULT_FAILURE) << errorMsg;
0183         QDBusConnection::sessionBus().send(m_currentCommand.reply);
0184         break;
0185     }
0186 
0187     m_currentCommand = {};
0188     processNextCommand();
0189 }
0190 
0191 void Distributor::clientUnregistered(const Client &client, AbstractPushProvider::Error error)
0192 {
0193     qCDebug(Log) << client.token << client.remoteId << client.serviceName << error;
0194     switch (error) {
0195     case AbstractPushProvider::NoError:
0196         client.connector().Unregistered(m_currentCommand.type == Command::Unregister ? QString() : client.token);
0197         [[fallthrough]];
0198     case AbstractPushProvider::ProviderRejected:
0199     {
0200         QSettings settings;
0201         settings.remove(client.token);
0202         const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&client](const auto &c) {
0203             return c.token == client.token;
0204         });
0205         if (it != m_clients.end()) {
0206             m_clients.erase(it);
0207 
0208             // if this was the last client, also disconnect from the push provider
0209             if (m_clients.empty()) {
0210                 Command cmd;
0211                 cmd.type = Command::Disconnect;
0212                 m_commandQueue.push_back(std::move(cmd));
0213             }
0214         }
0215         settings.setValue(QStringLiteral("Clients/Tokens"), clientTokens());
0216         Q_EMIT registeredClientsChanged();
0217         break;
0218     }
0219     case AbstractPushProvider::TransientNetworkError:
0220         // retry
0221         m_commandQueue.push_front(std::move(m_currentCommand));
0222         break;
0223     }
0224 
0225     m_currentCommand = {};
0226     processNextCommand();
0227 }
0228 
0229 void Distributor::providerConnected()
0230 {
0231     qCDebug(Log);
0232     setStatus(DistributorStatus::Connected);
0233     m_currentCommand = {};
0234     processNextCommand();
0235 }
0236 
0237 void Distributor::providerDisconnected(AbstractPushProvider::Error error, const QString &errorMsg)
0238 {
0239     qCDebug(Log) << error << errorMsg;
0240     if (m_currentCommand.type == Command::Disconnect) {
0241         m_currentCommand = {};
0242         setStatus(m_clients.empty() ? DistributorStatus::Idle : DistributorStatus::NoNetwork);
0243     } else {
0244         setStatus(DistributorStatus::NoNetwork);
0245     }
0246     processNextCommand();
0247 }
0248 
0249 QStringList Distributor::clientTokens() const
0250 {
0251     QStringList l;
0252     l.reserve(m_clients.size());
0253     std::transform(m_clients.begin(), m_clients.end(), std::back_inserter(l), [](const auto &client) { return client.token; });
0254     return l;
0255 }
0256 
0257 bool Distributor::setupPushProvider()
0258 {
0259     // determine push provider
0260     const auto pushProviderName = pushProviderId();
0261     if (pushProviderName == QLatin1String(GotifyPushProvider::Id)) {
0262         m_pushProvider.reset(new GotifyPushProvider);
0263     } else if (pushProviderName == QLatin1String(NextPushProvider::Id)) {
0264         m_pushProvider.reset(new NextPushProvider);
0265     } else if (pushProviderName == QLatin1String(NtfyPushProvider::Id)) {
0266         m_pushProvider.reset(new NtfyPushProvider);
0267     } else if (pushProviderName == QLatin1String(MockPushProvider::Id)) {
0268         m_pushProvider.reset(new MockPushProvider);
0269     } else {
0270         qCWarning(Log) << "Unknown push provider:" << pushProviderName;
0271         m_pushProvider.reset();
0272         setStatus(DistributorStatus::NoSetup);
0273         return false;
0274     }
0275 
0276     QSettings settings;
0277     settings.beginGroup(pushProviderName);
0278     if (!m_pushProvider->loadSettings(settings)) {
0279         qCWarning(Log) << "Invalid push provider settings!";
0280         setStatus(DistributorStatus::NoSetup);
0281         return false;
0282     }
0283     settings.endGroup();
0284 
0285     connect(m_pushProvider.get(), &AbstractPushProvider::messageReceived, this, &Distributor::messageReceived);
0286     connect(m_pushProvider.get(), &AbstractPushProvider::clientRegistered, this, &Distributor::clientRegistered);
0287     connect(m_pushProvider.get(), &AbstractPushProvider::clientUnregistered, this, &Distributor::clientUnregistered);
0288     connect(m_pushProvider.get(), &AbstractPushProvider::connected, this, &Distributor::providerConnected);
0289     connect(m_pushProvider.get(), &AbstractPushProvider::disconnected, this, &Distributor::providerDisconnected);
0290     return true;
0291 }
0292 
0293 void Distributor::purgeUnavailableClients()
0294 {
0295     QStringList activatableServiceNames = QDBusConnection::sessionBus().interface()->activatableServiceNames();
0296     std::sort(activatableServiceNames.begin(), activatableServiceNames.end());
0297 
0298     // collect clients to unregister first, so m_clients doesn't change underneath us
0299     QStringList tokensToUnregister;
0300     for (const auto &client : m_clients) {
0301         if (!std::binary_search(activatableServiceNames.begin(), activatableServiceNames.end(), client.serviceName)) {
0302             tokensToUnregister.push_back(client.token);
0303         }
0304     }
0305 
0306     for (const auto &token : tokensToUnregister) {
0307         Unregister(token);
0308     }
0309 }
0310 
0311 bool Distributor::hasCurrentCommand() const
0312 {
0313     return m_currentCommand.type != Command::NoCommand;
0314 }
0315 
0316 void Distributor::processNextCommand()
0317 {
0318     if (hasCurrentCommand() || m_commandQueue.empty() || !isNetworkAvailable()) {
0319         return;
0320     }
0321 
0322     m_currentCommand = m_commandQueue.front();
0323     m_commandQueue.pop_front();
0324     switch (m_currentCommand.type) {
0325         case Command::NoCommand:
0326             Q_ASSERT(false);
0327             processNextCommand();
0328             break;
0329         case Command::Register:
0330             m_pushProvider->registerClient(m_currentCommand.client);
0331             break;
0332         case Command::Unregister:
0333         case Command::ForceUnregister:
0334             m_pushProvider->unregisterClient(m_currentCommand.client);
0335             break;
0336         case Command::Connect:
0337             m_pushProvider->connectToProvider();
0338             break;
0339         case Command::Disconnect:
0340             m_pushProvider->disconnectFromProvider();
0341             break;
0342         case Command::ChangePushProvider:
0343         {
0344             QSettings settings;
0345             settings.setValue(QLatin1String("PushProvider/Type"), m_currentCommand.pushProvider);
0346             m_currentCommand = {};
0347             if (setupPushProvider()) {
0348                 processNextCommand();
0349             }
0350             break;
0351         }
0352     }
0353 }
0354 
0355 int Distributor::status() const
0356 {
0357     return m_status;
0358 }
0359 
0360 void Distributor::setStatus(DistributorStatus::Status status)
0361 {
0362     if (m_status == status) {
0363         return;
0364     }
0365 
0366     m_status = status;
0367     Q_EMIT statusChanged();
0368 }
0369 
0370 QString Distributor::pushProviderId() const
0371 {
0372     QSettings settings;
0373     return settings.value(QStringLiteral("PushProvider/Type"), QString()).toString();
0374 }
0375 
0376 QVariantMap Distributor::pushProviderConfiguration(const QString &pushProviderId) const
0377 {
0378     if (pushProviderId.isEmpty()) {
0379         return {};
0380     }
0381 
0382     QSettings settings;
0383     settings.beginGroup(pushProviderId);
0384     const auto keys = settings.allKeys();
0385 
0386     QVariantMap config;
0387     for (const auto &key : keys) {
0388         const auto v = settings.value(key);
0389         if (v.isValid()) {
0390             config.insert(key, settings.value(key));
0391         }
0392     }
0393 
0394     return config;
0395 }
0396 
0397 void Distributor::setPushProvider(const QString &pushProviderId, const QVariantMap &config)
0398 {
0399     // store push provider config and check for changes
0400     bool configChanged = false;
0401     QSettings settings;
0402     settings.beginGroup(pushProviderId);
0403     for (auto it = config.begin(); it != config.end(); ++it) {
0404         const auto oldValue = settings.value(it.key());
0405         configChanged |= oldValue != it.value();
0406         settings.setValue(it.key(), it.value());
0407     }
0408     settings.endGroup();
0409     if (!configChanged && pushProviderId == this->pushProviderId()) {
0410         return; // nothing changed
0411     }
0412 
0413     // if push provider or config changed: unregister all clients, create new push provider backend, re-register all clients
0414     if (m_status != DistributorStatus::NoSetup) {
0415         for (const auto &client : m_clients) {
0416             forceUnregisterClient(client.token);
0417         }
0418         if (m_status == DistributorStatus::Connected) {
0419             Command cmd;
0420             cmd.type = Command::Disconnect;
0421             m_commandQueue.push_back(std::move(cmd));
0422         }
0423         {
0424             Command cmd;
0425             cmd.type = Command::ChangePushProvider;
0426             cmd.pushProvider = pushProviderId;
0427             m_commandQueue.push_back(std::move(cmd));
0428         }
0429 
0430         // reconnect if there are clients
0431         if (!m_clients.empty()) {
0432             Command cmd;
0433             cmd.type = Command::Connect;
0434             m_commandQueue.push_back(std::move(cmd));
0435         }
0436 
0437         // re-register clients
0438         for (const auto &client : m_clients) {
0439             Command cmd;
0440             cmd.type = Command::Register;
0441             cmd.client = client;
0442             m_commandQueue.push_back(std::move(cmd));
0443         }
0444     } else {
0445         // recover from a previously failed attempt to change push providers
0446 
0447         // reconnect if there are clients
0448         if (!m_commandQueue.empty()) {
0449             Command cmd;
0450             cmd.type = Command::Connect;
0451             m_commandQueue.push_front(std::move(cmd));
0452         }
0453 
0454         Command cmd;
0455         cmd.type = Command::ChangePushProvider;
0456         cmd.pushProvider = pushProviderId;
0457         m_commandQueue.push_front(std::move(cmd));
0458     }
0459 
0460     processNextCommand();
0461 }
0462 
0463 QList<KUnifiedPush::ClientInfo> Distributor::registeredClients() const
0464 {
0465     QList<KUnifiedPush::ClientInfo> result;
0466     result.reserve(m_clients.size());
0467 
0468     for (const auto &client : m_clients) {
0469         ClientInfo info;
0470         info.token = client.token;
0471         info.serviceName = client.serviceName;
0472         info.description = client.description;
0473         result.push_back(std::move(info));
0474     }
0475 
0476     return result;
0477 }
0478 
0479 void Distributor::forceUnregisterClient(const QString &token)
0480 {
0481     qCDebug(Log) << token;
0482     const auto it = std::find_if(m_clients.begin(), m_clients.end(), [&token](const auto &client) {
0483         return client.token == token;
0484     });
0485     if (it == m_clients.end()) {
0486         qCWarning(Log) << "Unregistration request for unknown client.";
0487         return;
0488     }
0489 
0490     Command cmd;
0491     cmd.type = Command::ForceUnregister;
0492     cmd.client = (*it);
0493     m_commandQueue.push_back(std::move(cmd));
0494     processNextCommand();
0495 }
0496 
0497 bool Distributor::isNetworkAvailable() const
0498 {
0499     // if in doubt assume we have network and try to connect
0500     if (QNetworkInformation::instance()) {
0501         const auto reachability = QNetworkInformation::instance()->reachability();
0502         return reachability == QNetworkInformation::Reachability::Online || reachability == QNetworkInformation::Reachability::Unknown;
0503     }
0504     return true;
0505 }