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 }