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

0001 /*
0002     SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "ntfypushprovider.h"
0007 #include "client.h"
0008 #include "logging.h"
0009 #include "message.h"
0010 
0011 #include <QJsonDocument>
0012 #include <QJsonObject>
0013 #include <QNetworkReply>
0014 #include <QSettings>
0015 #include <QUrlQuery>
0016 #include <QUuid>
0017 
0018 using namespace KUnifiedPush;
0019 
0020 NtfyPushProvider::NtfyPushProvider(QObject *parent)
0021     : AbstractPushProvider(Id, parent)
0022 {
0023     connect(&m_sseStream, &ServerSentEventsStream::messageReceived, this, [this](const SSEMessage &sse) {
0024         qCDebug(Log) << sse.event << sse.data;
0025         if (sse.event.isEmpty()) {
0026             QJsonObject msgObj = QJsonDocument::fromJson(sse.data).object();
0027             Message msg;
0028             msg.clientRemoteId = msgObj.value(QLatin1String("topic")).toString();
0029             msg.content = msgObj.value(QLatin1String("message")).toString().toUtf8();
0030             if (msgObj.value(QLatin1String("encoding")).toString() == QLatin1String("base64")) {
0031                 msg.content = QByteArray::fromBase64(msg.content);
0032             }
0033             m_lastMessageId = msgObj.value(QLatin1String("id")).toString();
0034             Q_EMIT messageReceived(msg);
0035             storeState();
0036         }
0037     });
0038 }
0039 
0040 NtfyPushProvider::~NtfyPushProvider() = default;
0041 
0042 bool NtfyPushProvider::loadSettings(const QSettings &settings)
0043 {
0044     m_url = settings.value(QStringLiteral("Url"), QUrl()).toUrl();
0045 
0046     QSettings internal;
0047     internal.beginGroup(QLatin1String(providerId()) + QLatin1String("-internal"));
0048     m_topics = internal.value(QStringLiteral("Topics"), QStringList()).toStringList();
0049     m_lastMessageId = internal.value(QStringLiteral("LastMessageId"), QString()).toString();
0050 
0051     return m_url.isValid();
0052 }
0053 
0054 void NtfyPushProvider::connectToProvider()
0055 {
0056     doConnectToProvider();
0057     Q_EMIT connected();
0058 }
0059 
0060 void NtfyPushProvider::disconnectFromProvider()
0061 {
0062     if (m_sseReply) {
0063         m_sseReply->abort();
0064     }
0065     Q_EMIT disconnected(NoError);
0066 }
0067 
0068 void NtfyPushProvider::registerClient(const Client &client)
0069 {
0070     const QString topic = QLatin1String("upk") + QUuid::createUuid().toString(QUuid::Id128);
0071     auto newClient = client;
0072     newClient.remoteId = topic;
0073 
0074     QUrl endpoint = m_url;
0075     auto path = endpoint.path();
0076     path  += QLatin1Char('/') + topic;
0077     endpoint.setPath(path);
0078     newClient.endpoint = endpoint.toString();
0079 
0080     m_topics.push_back(topic);
0081     storeState();
0082     doConnectToProvider();
0083     Q_EMIT clientRegistered(newClient);
0084 }
0085 
0086 void NtfyPushProvider::unregisterClient(const Client &client)
0087 {
0088     m_topics.removeAll(client.remoteId);
0089     storeState();
0090     doConnectToProvider();
0091     Q_EMIT clientUnregistered(client);
0092 }
0093 
0094 void NtfyPushProvider::doConnectToProvider()
0095 {
0096     if (m_sseReply) {
0097         m_sseReply->abort();
0098     }
0099 
0100     if (m_topics.empty()) {
0101         return;
0102     }
0103 
0104     QUrl url = m_url;
0105     QString path = url.path();
0106     path += QLatin1Char('/') + m_topics.join(QLatin1Char(',')) + QLatin1String("/sse");
0107     url.setPath(path);
0108     QUrlQuery query;
0109     query.addQueryItem(QStringLiteral("up"), QStringLiteral("1"));
0110     query.addQueryItem(QStringLiteral("since"), m_lastMessageId.isEmpty() ? QStringLiteral("all") : m_lastMessageId);
0111     url.setQuery(query);
0112     qCDebug(Log) << url;
0113 
0114     auto reply = nam()->get(QNetworkRequest(url));
0115     connect(reply, &QNetworkReply::finished, this, [reply, this]() {
0116         reply->deleteLater();
0117         if (reply->error() == QNetworkReply::OperationCanceledError) {
0118             return; // we triggered this ourselves
0119         }
0120         qCDebug(Log) << reply->error() << reply->errorString();
0121         Q_EMIT disconnected(TransientNetworkError, reply->errorString());
0122     });
0123 
0124     m_sseReply = reply;
0125     m_sseStream.read(reply);
0126 }
0127 
0128 void NtfyPushProvider::storeState()
0129 {
0130     QSettings settings;
0131     settings.beginGroup(QLatin1String(providerId()) + QLatin1String("-internal"));
0132     settings.setValue(QStringLiteral("Topics"), m_topics);
0133     settings.setValue(QStringLiteral("LastMessageId"), m_lastMessageId);
0134 }