diff options
| author | Linus Jahn <lnj@kaidan.im> | 2021-09-29 13:15:53 +0200 |
|---|---|---|
| committer | Linus Jahn <lnj@kaidan.im> | 2021-09-29 16:37:41 +0200 |
| commit | 9b21bef379d94876908cbb706c9ba2438779bf54 (patch) | |
| tree | 38a7499f3e7bfc27339b2de2ef82775d6f92cde4 | |
| parent | bb861994bdf79cb783a07cc79ee18e8711295a91 (diff) | |
| download | qxmpp-9b21bef379d94876908cbb706c9ba2438779bf54.tar.gz | |
PubSubManager: Add node configuration requests
| -rw-r--r-- | src/base/QXmppPubSubIq.cpp | 13 | ||||
| -rw-r--r-- | src/client/QXmppPubSubManager.cpp | 146 | ||||
| -rw-r--r-- | src/client/QXmppPubSubManager.h | 7 | ||||
| -rw-r--r-- | tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp | 116 |
4 files changed, 272 insertions, 10 deletions
diff --git a/src/base/QXmppPubSubIq.cpp b/src/base/QXmppPubSubIq.cpp index 367b0ec0..5ba2615b 100644 --- a/src/base/QXmppPubSubIq.cpp +++ b/src/base/QXmppPubSubIq.cpp @@ -607,8 +607,17 @@ void QXmppPubSubIqBase::toXmlElementFromChild(QXmlStreamWriter *writer) const case OwnerDefault: case Options: if (auto form = d->dataForm) { - // make sure data form type is submit - form->setType(QXmppDataForm::Submit); + // make sure data form type is correct + switch (type()) { + case QXmppIq::Result: + form->setType(QXmppDataForm::Result); + break; + default: + if (form->type() != QXmppDataForm::Cancel) { + form->setType(QXmppDataForm::Submit); + } + break; + } form->toXml(writer); } break; diff --git a/src/client/QXmppPubSubManager.cpp b/src/client/QXmppPubSubManager.cpp index c28df7f3..08fe75e7 100644 --- a/src/client/QXmppPubSubManager.cpp +++ b/src/client/QXmppPubSubManager.cpp @@ -24,19 +24,19 @@ #include "QXmppPubSubManager.h" -#include <QDomElement> -#include <QFutureInterface> - #include "QXmppClient.h" #include "QXmppConstants_p.h" -#include "QXmppPubSubItem.h" -#include "QXmppPubSubEventManager.h" -#include "QXmppPubSubSubscription.h" #include "QXmppPubSubAffiliation.h" +#include "QXmppPubSubEventManager.h" +#include "QXmppPubSubItem.h" #include "QXmppPubSubSubscribeOptions.h" +#include "QXmppPubSubSubscription.h" #include "QXmppStanza.h" #include "QXmppUtils.h" +#include <QDomElement> +#include <QFutureInterface> + using namespace QXmpp::Private; /// @@ -159,6 +159,13 @@ using namespace QXmpp::Private; /// /// +/// \typedef QXmppPubSubManager::NodeConfigResult +/// +/// Contains a node configuration (QXmppPubSubNodeConfig) or the returned IQ +/// error (QXmppStanza::Error). +/// + +/// /// Default constructor. /// QXmppPubSubManager::QXmppPubSubManager() @@ -280,7 +287,7 @@ auto QXmppPubSubManager::retractItem(const QString &jid, const QString &nodeName request.setType(QXmppIq::Set); request.setQueryType(QXmppPubSubIq<>::Retract); request.setQueryNode(nodeName); - request.setItems({QXmppPubSubItem(itemId)}); + request.setItems({ QXmppPubSubItem(itemId) }); request.setTo(jid); return client()->sendGenericIq(std::move(request)); @@ -473,6 +480,87 @@ QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::setSubscribeOptions(cons } /// +/// Requests the node configuration and starts the configuration process. +/// +/// Requires owner privileges. If the result is successful (a node config form +/// has been returned) this starts the configuration process. The next step is +/// to call configureNode() or cancelNodeConfiguration(). +/// +/// \param service JID of the pubsub service +/// \param nodeName Name of the pubsub node on the service +/// \return +/// +/// \sa configureNode() +/// \sa cancelNodeConfiguration() +/// +QFuture<QXmppPubSubManager::NodeConfigResult> QXmppPubSubManager::requestNodeConfiguration(const QString &service, const QString &nodeName) +{ + using Error = QXmppStanza::Error; + + QXmppPubSubIq request; + request.setType(QXmppIq::Get); + request.setTo(service); + request.setQueryNode(nodeName); + request.setQueryType(QXmppPubSubIq<>::Configure); + + return chainIq(client()->sendIq(std::move(request)), this, + [](QXmppPubSubIq<> &&iq) -> NodeConfigResult { + if (const auto dataForm = iq.dataForm()) { + if (const auto config = QXmppPubSubNodeConfig::fromDataForm(*dataForm)) { + return *config; + } + return Error(Error::Cancel, Error::UndefinedCondition, QStringLiteral("Server returned invalid data form.")); + } + return Error(Error::Cancel, Error::UndefinedCondition, QStringLiteral("Server returned no data form.")); + }); +} + +/// +/// Sets a node configuration. +/// +/// Requires owner privileges. You can use requestNodeConfiguration() to receive +/// a data form with all valid options and default values. +/// +/// \param service JID of the pubsub service +/// \param nodeName Name of the pubsub node on the service +/// \param config +/// \return +/// +/// \sa requestNodeConfiguration() +/// +QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::configureNode(const QString &service, const QString &nodeName, const QXmppPubSubNodeConfig &config) +{ + QXmppPubSubIq request; + request.setType(QXmppIq::Set); + request.setTo(service); + request.setQueryNode(nodeName); + request.setQueryType(QXmppPubSubIq<>::Configure); + request.setDataForm(config); + return client()->sendGenericIq(std::move(request)); +} + +/// +/// Cancels the configuration process and uses the default or exisiting +/// configuration. +/// +/// \param service JID of the pubsub service +/// \param nodeName Name of the pubsub node on the service +/// \return +/// +/// \sa requestNodeConfiguration() +/// +QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::cancelNodeConfiguration(const QString &service, const QString &nodeName) +{ + QXmppPubSubIq request; + request.setType(QXmppIq::Set); + request.setTo(service); + request.setQueryNode(nodeName); + request.setQueryType(QXmppPubSubIq<>::Configure); + request.setDataForm(QXmppDataForm(QXmppDataForm::Cancel)); + return client()->sendGenericIq(std::move(request)); +} + +/// /// \fn QXmppPubSubManager::createPepNode /// /// Creates an empty PEP node with the default configuration. @@ -527,6 +615,48 @@ QFuture<QXmppPubSubManager::Result> QXmppPubSubManager::setSubscribeOptions(cons /// \return /// +/// +/// \fn QXmppPubSubManager::requestPepNodeConfiguration +/// +/// Requests the node configuration and starts the configuration process. +/// +/// This is a convenience method equivalent to calling +/// requestNodeConfiguration() the current account's bare JID. +/// +/// \param nodeName Name of the pubsub node on the service +/// \return +/// +/// \sa configurePepNode() +/// \sa cancelPepNodeConfiguration() +/// + +/// +/// \fn QXmppPubSubManager::configurePepNode +/// +/// Sets a node configuration. +/// +/// This is a convenience method equivalent to calling configureNode() the +/// current account's bare JID. +/// +/// \param nodeName Name of the pubsub node on the service +/// \param config +/// \return +/// +/// \sa requestPepNodeConfiguration() +/// + +/// +/// \fn QXmppPubSubManager::cancelPepNodeConfiguration +/// +/// This is a convenience method equivalent to calling cancelNodeConfiguration() +/// the current account's bare JID. +/// +/// \param nodeName Name of the pubsub node on the service +/// \return +/// +/// \sa requestPepNodeConfiguration() +/// + /// \cond QStringList QXmppPubSubManager::discoveryFeatures() const { @@ -549,7 +679,7 @@ bool QXmppPubSubManager::handleStanza(const QDomElement &element) const auto extensions = client()->extensions(); for (auto *extension : extensions) { - if (auto *eventManager = qobject_cast<QXmppPubSubEventManager*>(extension)) { + if (auto *eventManager = qobject_cast<QXmppPubSubEventManager *>(extension)) { if (eventManager->handlePubSubEvent(element, service, node)) { return true; } diff --git a/src/client/QXmppPubSubManager.h b/src/client/QXmppPubSubManager.h index 26ff7aaf..f3798a3f 100644 --- a/src/client/QXmppPubSubManager.h +++ b/src/client/QXmppPubSubManager.h @@ -62,6 +62,7 @@ public: using SubscriptionsResult = std::variant<QVector<QXmppPubSubSubscription>, QXmppStanza::Error>; using AffiliationsResult = std::variant<QVector<QXmppPubSubAffiliation>, QXmppStanza::Error>; using OptionsResult = std::variant<QXmppPubSubSubscribeOptions, QXmppStanza::Error>; + using NodeConfigResult = std::variant<QXmppPubSubNodeConfig, QXmppStanza::Error>; QXmppPubSubManager(); ~QXmppPubSubManager(); @@ -96,6 +97,9 @@ public: QFuture<OptionsResult> requestSubscribeOptions(const QString &service, const QString &nodeName, const QString &subscriberJid); QFuture<Result> setSubscribeOptions(const QString &service, const QString &nodeName, const QXmppPubSubSubscribeOptions &options); QFuture<Result> setSubscribeOptions(const QString &service, const QString &nodeName, const QXmppPubSubSubscribeOptions &options, const QString &subscriberJid); + QFuture<NodeConfigResult> requestNodeConfiguration(const QString &service, const QString &nodeName); + QFuture<Result> configureNode(const QString &service, const QString &nodeName, const QXmppPubSubNodeConfig &config); + QFuture<Result> cancelNodeConfiguration(const QString &service, const QString &nodeName); // PEP-specific (the PubSub service is the current account) inline QFuture<Result> createPepNode(const QString &nodeName) { return createNode(client()->configuration().jidBare(), nodeName); } @@ -110,6 +114,9 @@ public: QFuture<PublishItemsResult> publishPepItems(const QString &nodeName, const QVector<T> &items); inline QFuture<Result> retractPepItem(const QString &nodeName, const QString &itemId) { return retractItem(client()->configuration().jidBare(), nodeName, itemId); } inline QFuture<Result> purgePepItems(const QString &nodeName) { return purgeItems(client()->configuration().jidBare(), nodeName); } + inline QFuture<NodeConfigResult> requestPepNodeConfiguration(const QString &nodeName) { return requestNodeConfiguration(client()->configuration().jidBare(), nodeName); } + inline QFuture<Result> configurePepNode(const QString &nodeName, const QXmppPubSubNodeConfig &config) { return configureNode(client()->configuration().jidBare(), nodeName, config); } + inline QFuture<Result> cancelPepNodeConfiguration(const QString &nodeName) { return cancelNodeConfiguration(client()->configuration().jidBare(), nodeName); } /// \cond QStringList discoveryFeatures() const override; diff --git a/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp b/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp index 9da709d8..9c80a1b8 100644 --- a/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp +++ b/tests/qxmpppubsubmanager/tst_qxmpppubsubmanager.cpp @@ -67,6 +67,12 @@ public: uint m_events = 0; }; +struct Client +{ + TestClient test; + PSManager *psManager = test.addNewExtension<PSManager>(); +}; + class tst_QXmppPubSubManager : public QObject { Q_OBJECT @@ -94,6 +100,9 @@ private: Q_SLOT void testRequestOptions(); Q_SLOT void testRequestOptionsError(); Q_SLOT void testSetOptions(); + Q_SLOT void testRequestNodeConfig(); + Q_SLOT void testConfigureNode(); + Q_SLOT void testCancelConfig(); Q_SLOT void testEventNotifications_data(); Q_SLOT void testEventNotifications(); }; @@ -763,6 +772,113 @@ void tst_QXmppPubSubManager::testSetOptions() expectFutureVariant<QXmpp::Success>(future); } +void tst_QXmppPubSubManager::testRequestNodeConfig() +{ + auto [test, psManager] = Client(); + + auto future = psManager->requestNodeConfiguration("pubsub.qxmpp.org", "tune"); + test.expect("<iq id='qxmpp1' to='pubsub.qxmpp.org' type='get'>" + "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>" + "<configure node='tune'/>" + "</pubsub>" + "</iq>"); + test.inject<QString>("<iq id='qxmpp1' type='result'>" + "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>" + "<configure node='princely_musings'>" + "<x xmlns='jabber:x:data' type='form'>" + "<field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field>" + "<field var='pubsub#title' type='text-single' label='A friendly name for the node'/>" + "<field var='pubsub#deliver_notifications' type='boolean' label='Whether to deliver event notifications'><value>true</value></field>" + "<field var='pubsub#deliver_payloads' type='boolean' label='Whether to deliver payloads with event notifications'><value>true</value></field>" + "<field var='pubsub#dataform_xslt' type='text-single' label='Payload XSLT'/>" + "</x>" + "</configure>" + "</pubsub>" + "</iq>"); + + const auto form = expectFutureVariant<QXmppPubSubNodeConfig>(future); + QCOMPARE(form.title(), QString()); + QVERIFY(form.notificationsEnabled().has_value()); + QCOMPARE(form.notificationsEnabled().value_or(false), true); + QVERIFY(form.dataFormXslt().isNull()); +} + +void tst_QXmppPubSubManager::testConfigureNode() +{ + auto [test, psManager] = Client(); + + QXmppPubSubNodeConfig config; + config.setTitle("Princely Musings (Atom)"); + config.setNotificationsEnabled(true); + config.setIncludePayloads(true); + config.setPersistItems(true); + config.setMaxItems(10ULL); + config.setItemExpiry(604800); + config.setAccessModel(QXmppPubSubNodeConfig::Roster); + config.setAllowedRosterGroups({ "friends", "servants", "courtiers" }); + config.setPublishModel(QXmppPubSubNodeConfig::Publishers); + config.setPurgeWhenOffline(false); + config.setSendLastItem(QXmppPubSubNodeConfig::Never); + config.setPresenceBasedNotifications(false); + config.setNotificationType(QXmppPubSubNodeConfig::Headline); + config.setConfigNotificationsEnabled(false); + config.setDeleteNotificationsEnabled(false); + config.setRetractNotificationsEnabled(false); + config.setSubNotificationsEnabled(false); + config.setMaxPayloadSize(1028); + config.setPayloadType("http://www.w3.org/2005/Atom"); + config.setBodyXslt("http://jabxslt.jabberstudio.org/atom_body.xslt"); + + auto future = psManager->configureNode("pubsub.qxmpp.org", "princely_musings", config); + test.expect("<iq id='qxmpp1' to='pubsub.qxmpp.org' type='set'>" + "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>" + "<configure node='princely_musings'>" + "<x xmlns='jabber:x:data' type='submit'>" + "<field type='hidden' var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#node_config</value></field>" + "<field type=\"list-single\" var='pubsub#access_model'><value>roster</value></field>" + "<field type=\"text-single\" var='pubsub#body_xslt'><value>http://jabxslt.jabberstudio.org/atom_body.xslt</value></field>" + "<field type=\"boolean\" var='pubsub#deliver_notifications'><value>1</value></field>" + "<field type=\"boolean\" var='pubsub#deliver_payloads'><value>1</value></field>" + "<field type=\"text-single\" var='pubsub#item_expire'><value>604800</value></field>" + "<field type=\"text-single\" var='pubsub#max_items'><value>10</value></field>" + "<field type=\"text-single\" var='pubsub#max_payload_size'><value>1028</value></field>" + "<field type=\"list-single\" var='pubsub#notification_type'><value>headline</value></field>" + "<field type=\"boolean\" var='pubsub#notify_config'><value>0</value></field>" + "<field type=\"boolean\" var='pubsub#notify_delete'><value>0</value></field>" + "<field type=\"boolean\" var='pubsub#notify_retract'><value>0</value></field>" + "<field type=\"boolean\" var='pubsub#notify_sub'><value>0</value></field>" + "<field type=\"boolean\" var='pubsub#persist_items'><value>1</value></field>" + "<field type=\"boolean\" var='pubsub#presence_based_delivery'><value>0</value></field>" + "<field type=\"list-single\" var='pubsub#publish_model'><value>publishers</value></field>" + "<field type=\"boolean\" var='pubsub#purge_offline'><value>0</value></field>" + "<field type=\"list-multi\" var='pubsub#roster_groups_allowed'><value>friends</value><value>servants</value><value>courtiers</value></field>" + "<field type=\"list-single\" var='pubsub#send_last_published_item'><value>never</value></field>" + "<field type=\"text-single\" var='pubsub#title'><value>Princely Musings (Atom)</value></field>" + "<field type=\"text-single\" var='pubsub#type'><value>http://www.w3.org/2005/Atom</value></field>" + "</x>" + "</configure>" + "</pubsub>" + "</iq>"); + test.inject<QString>("<iq type='result' from='pubsub.shakespeare.lit' id='qxmpp1'/>"); + + expectFutureVariant<QXmpp::Success>(future); +} + +void tst_QXmppPubSubManager::testCancelConfig() +{ + auto [test, psManager] = Client(); + auto future = psManager->cancelNodeConfiguration("pubsub.qxmpp.org", "nodeee"); + test.expect("<iq id='qxmpp1' to='pubsub.qxmpp.org' type='set'>" + "<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>" + "<configure node='nodeee'>" + "<x xmlns='jabber:x:data' type='cancel'/>" + "</configure>" + "</pubsub>" + "</iq>"); + test.inject<QString>("<iq id='qxmpp1' type='result'/>"); + expectFutureVariant<QXmpp::Success>(future); +} + void tst_QXmppPubSubManager::testEventNotifications_data() { QTest::addColumn<QString>("xml"); |
