Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix migration manager #657

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/base/Algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ auto transform(const InputVector &input, Converter convert)
return output;
}

template<typename OutputVector, typename InputVector, typename Converter>
auto filtered(const InputVector &input, Converter convert)
{
OutputVector output;
if constexpr (std::ranges::sized_range<InputVector>) {
output.reserve(input.size());
}
for (const auto &value : input) {
if (const std::optional<std::decay_t<decltype(value)>> result = std::invoke(convert, value)) {
output.push_back(*result);
}
}
return output;
}

template<typename Vec, typename T>
auto contains(const Vec &vec, const T &value)
{
Expand Down
22 changes: 20 additions & 2 deletions src/client/QXmppAccountMigrationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,28 @@ std::variant<QXmppExportData, QXmppError> QXmppExportData::fromDom(const QDomEle

void QXmppExportData::toXml(QXmlStreamWriter *writer) const
{
// We need to generate the xml file with nodes always in the same order.
// This is needed for our unit tests which are based on xml generation.
const auto sortedExtensionsKeys = [this]() {
const auto key_selector = [](auto pair) { return pair.first; };
std::vector<std::type_index> keys(d->extensions.size(), std::type_index(typeid(std::nullptr_t)));
std::transform(d->extensions.begin(), d->extensions.end(), keys.begin(), key_selector);
std::stable_sort(keys.begin(), keys.end());
return keys;
}();

writer->writeStartDocument();
writer->writeStartElement(QSL65("account-data"));
writer->writeDefaultNamespace(toString65(ns_qxmpp_export));
writer->writeAttribute(QSL65("jid"), d->accountJid);

const auto &serializers = accountDataSerializers();
for (const auto &[typeIndex, extension] : std::as_const(d->extensions)) {
for (const auto &typeIndex: sortedExtensionsKeys) {
const auto serializer = serializers.find(typeIndex);
if (serializer != serializers.end()) {
const auto &[_, serialize] = *serializer;

serialize(extension, *writer);
serialize(d->extensions.at(typeIndex), *writer);
}
}

Expand Down Expand Up @@ -189,6 +199,14 @@ struct QXmppAccountMigrationManagerPrivate {
/// Contains T or QXmppError.
///

///
/// \fn QXmppAccountMigrationManager::errorOccurred(const QXmppError &error)
///
/// Emitted when an error occured during export or import.
///
/// \param error The occured error
///

///
/// \fn QXmppAccountMigrationManager::registerExportData(ImportFunc importFunc, ExportFunc exportFunc)
///
Expand Down
2 changes: 2 additions & 0 deletions src/client/QXmppAccountMigrationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ class QXMPP_EXPORT QXmppAccountMigrationManager : public QXmppClientExtension
template<typename DataType>
void unregisterExportData();

Q_SIGNAL void errorOccurred(const QXmppError &error);

private:
void registerMigrationDataInternal(std::type_index dataType, std::function<QXmppTask<Result<>>(std::any)>, std::function<QXmppTask<Result<std::any>>()>);
void unregisterMigrationDataInternal(std::type_index dataType);
Expand Down
163 changes: 163 additions & 0 deletions src/client/QXmppMixManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

#include "QXmppMixManager.h"

#include "QXmppAccountMigrationManager.h"
#include "QXmppClient.h"
#include "QXmppConstants_p.h"
#include "QXmppDiscoveryIq.h"
#include "QXmppDiscoveryManager.h"
#include "QXmppGlobal.h"
#include "QXmppMessage.h"
#include "QXmppMixInfoItem.h"
#include "QXmppMixInvitation.h"
Expand All @@ -17,11 +19,14 @@
#include "QXmppPubSubManager.h"
#include "QXmppRosterManager.h"
#include "QXmppUtils.h"
#include "QXmppUtils_p.h"

#include "Algorithms.h"
#include "StringLiterals.h"

#include <QDomElement>

using namespace QXmpp;
using namespace QXmpp::Private;

class QXmppMixManagerPrivate
Expand All @@ -34,6 +39,65 @@ class QXmppMixManagerPrivate
QList<QXmppMixManager::Service> services;
};

namespace QXmpp::Private {

struct MixData {
struct Item {
static constexpr QStringView ns = u"mix-temp";

QString jid;
QString nick;

void parse(const QDomElement &element) {
jid = element.attribute(u"jid"_s);
nick = element.attribute(u"nick"_s);
}

void toXml(QXmlStreamWriter *writer) const {
writer->writeStartElement(QSL65("item"));
writer->writeDefaultNamespace(toString65(ns));
writeOptionalXmlAttribute(writer, u"jid", jid);
writeOptionalXmlAttribute(writer, u"nick", nick);
writer->writeEndElement();
}
};

QList<Item> items;

static std::variant<MixData, QXmppError> fromDom(const QDomElement &el)
{
if (el.tagName() != u"mix" && el.namespaceURI() != ns_qxmpp_export) {
return QXmppError { u"Invalid element."_s, {} };
}

MixData d;

for (const auto &itemEl : iterChildElements(el, u"item", Item::ns)) {
Item item;
item.parse(itemEl);
d.items.push_back(std::move(item));
}

return d;
}

void toXml(QXmlStreamWriter &writer) const
{
writer.writeStartElement(QSL65("mix"));
for (const auto &item : items) {
item.toXml(&writer);
}
writer.writeEndElement();
}
};

static void serializeMixData(const MixData &d, QXmlStreamWriter &writer)
{
d.toXml(writer);
}

} // namespace QXmpp::Private

///
/// \class QXmppMixManager
///
Expand Down Expand Up @@ -366,6 +430,7 @@ constexpr QStringView MIX_SERVICE_DISCOVERY_NODE = u"mix";
QXmppMixManager::QXmppMixManager()
: d(new QXmppMixManagerPrivate())
{
QXmppExportData::registerExtension<MixData, MixData::fromDom, serializeMixData>(u"mix", ns_qxmpp_export);
}

QXmppMixManager::~QXmppMixManager() = default;
Expand Down Expand Up @@ -988,13 +1053,111 @@ void QXmppMixManager::onRegistered(QXmppClient *client)

d->pubSubManager = client->findExtension<QXmppPubSubManager>();
Q_ASSERT_X(d->pubSubManager, "QXmppMixManager", "QXmppPubSubManager is missing");

// data import/export
if (auto manager = client->findExtension<QXmppAccountMigrationManager>()) {
auto rosterManager = client->findExtension<QXmppRosterManager>();
Q_ASSERT_X(rosterManager, "QXmppMixManager", "QXmppRosterManager is missing");

using ImportResult = std::variant<Success, QXmppError>;
auto importData = [this, client, manager](const MixData &data) -> QXmppTask<ImportResult> {
if (data.items.isEmpty()) {
return makeReadyTask<ImportResult>(Success());
}

const auto defaultNick = client->configuration().user();
QXmppPromise<ImportResult> promise;
auto counter = std::make_shared<int>(data.items.size());

for (const auto &item : std::as_const(data.items)) {
const auto nick = item.nick.isEmpty() ? defaultNick : item.nick;

joinChannel(item.jid, nick).then(this, [manager, promise, counter](auto &&result) mutable {
if (promise.task().isFinished()) {
return;
}

// We do not break import/export on mix errors, we only notify about it
if (auto error = std::get_if<QXmppError>(&result); error) {
Q_EMIT manager->errorOccurred(std::move(*error));
}

if ((--(*counter)) == 0) {
return promise.finish(Success());
}
});
}

return promise.task();
};

using ExportResult = std::variant<MixData, QXmppError>;
auto exportData = [this, client, manager, rosterManager]() -> QXmppTask<ExportResult> {
QXmppPromise<ExportResult> promise;

rosterManager->requestRoster().then(this, [this, manager, promise](auto &&rosterResult) mutable {
if (auto error = std::get_if<QXmppError>(&rosterResult); error) {
return promise.finish(std::move(*error));
}

const auto iq = std::move(std::get<QXmppRosterIq>(rosterResult));
const auto iqItems = iq.items();
auto result = std::make_shared<MixData>();
auto counter = std::make_shared<int>(iqItems.size());

result->items.reserve(*counter);

for (const auto &item : std::as_const(iqItems)) {
if (!item.isMixChannel()) {
if ((--(*counter)) == 0) {
return promise.finish(*result.get());
}

continue;
}

requestParticipants(item.bareJid()).then(this, [manager, result, promise, counter, channelId = item.bareJid(), participantId = item.mixParticipantId()](auto &&participantsResult) mutable {
if (promise.task().isFinished()) {
return;
}

// We do not break import/export on mix errors, we only notify about it
if (auto error = std::get_if<QXmppError>(&participantsResult); error) {
Q_EMIT manager->errorOccurred(std::move(*error));
} else {
const auto participants = std::get<QVector<QXmppMixParticipantItem>>(participantsResult);

for (const QXmppMixParticipantItem &participant: participants) {
if (participant.id() == participantId) {
result->items.append({ channelId, participant.nick() });
break;
}
}
}

if ((--(*counter)) == 0) {
return promise.finish(*result.get());
}
});
}
});

return promise.task();
};

manager->registerExportData<MixData>(importData, exportData);
}
}

void QXmppMixManager::onUnregistered(QXmppClient *client)
{
disconnect(d->discoveryManager, &QXmppDiscoveryManager::infoReceived, this, &QXmppMixManager::handleDiscoInfo);
resetCachedData();
disconnect(client, &QXmppClient::connected, this, nullptr);

if (auto manager = client->findExtension<QXmppAccountMigrationManager>()) {
manager->unregisterExportData<MixData>();
}
}

bool QXmppMixManager::handlePubSubEvent(const QDomElement &element, const QString &pubSubService, const QString &nodeName)
Expand Down
12 changes: 7 additions & 5 deletions src/client/QXmppMovedManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ bool QXmppMovedItem::isItem(const QDomElement &itemElement)
});
}

/// \cond
void QXmppMovedItem::parsePayload(const QDomElement &payloadElement)
{
m_newJid = payloadElement.firstChildElement(u"new-jid"_s).text();
Expand All @@ -57,6 +58,7 @@ void QXmppMovedItem::serializePayload(QXmlStreamWriter *writer) const
writer->writeTextElement(QSL65("new-jid"), m_newJid);
writer->writeEndElement();
}
/// \endcond

class QXmppMovedManagerPrivate
{
Expand Down Expand Up @@ -160,18 +162,18 @@ QXmppTask<QXmppClient::EmptyResult> QXmppMovedManager::publishStatement(const QS
///
/// \return the result of the action
///
QXmppTask<QXmppMovedManager::Result> QXmppMovedManager::verifyStatement(const QString &oldBareJid, const QString &newBareJid)
QXmppTask<QXmppClient::EmptyResult> QXmppMovedManager::verifyStatement(const QString &oldBareJid, const QString &newBareJid)
{
return chain<QXmppClient::EmptyResult>(
client()->findExtension<QXmppPubSubManager>()->requestItem<QXmppMovedItem>(oldBareJid, ns_moved.toString(), u"current"_s),
this,
[=, this](QXmppPubSubManager::ItemResult<QXmppMovedItem> &&result) {
return std::visit(
overloaded {
[newBareJid, this](QXmppMovedItem item) -> Result {
[newBareJid, this](QXmppMovedItem item) -> QXmppClient::EmptyResult {
return movedJidsMatch(newBareJid, item.newJid());
},
[newBareJid, this](QXmppError err) -> Result {
[newBareJid, this](QXmppError err) -> QXmppClient::EmptyResult {
// As a special case, if the attempt to retrieve the moved statement results in an error with the <gone/> condition
// as defined in RFC 6120, and that <gone/> element contains a valid XMPP URI (e.g. xmpp:[email protected]), then the
// error response MUST be handled equivalent to a <moved/> statement containing a <new-jid/> element with the JID
Expand Down Expand Up @@ -281,7 +283,7 @@ std::optional<QXmppTask<bool>> QXmppMovedManager::handleSubscriptionRequest(cons
}

// return verification result
return chain<bool>(verifyStatement(presence.oldJid(), QXmppUtils::jidToBareJid(presence.from())), this, [this](Result &&result) mutable {
return chain<bool>(verifyStatement(presence.oldJid(), QXmppUtils::jidToBareJid(presence.from())), this, [this](auto &&result) mutable {
return std::holds_alternative<Success>(result);
});
}
Expand All @@ -308,7 +310,7 @@ void QXmppMovedManager::handleDiscoInfo(const QXmppDiscoveryIq &iq)
///
/// \return the result of the action
///
QXmppMovedManager::Result QXmppMovedManager::movedJidsMatch(const QString &newBareJid, const QString &pepBareJid) const
QXmppClient::EmptyResult QXmppMovedManager::movedJidsMatch(const QString &newBareJid, const QString &pepBareJid) const
{
if (newBareJid == pepBareJid) {
return Success();
Expand Down
6 changes: 2 additions & 4 deletions src/client/QXmppMovedManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ class QXMPP_EXPORT QXmppMovedManager : public QXmppClientExtension
Q_PROPERTY(bool supportedByServer READ supportedByServer NOTIFY supportedByServerChanged)

public:
using Result = std::variant<QXmpp::Success, QXmppError>;

explicit QXmppMovedManager();
~QXmppMovedManager() override;

Expand All @@ -26,8 +24,8 @@ class QXMPP_EXPORT QXmppMovedManager : public QXmppClientExtension
bool supportedByServer() const;
Q_SIGNAL void supportedByServerChanged();

QXmppTask<Result> publishStatement(const QString &newBareJid);
QXmppTask<Result> verifyStatement(const QString &oldBareJid, const QString &newBareJid);
QXmppTask<QXmppClient::EmptyResult> publishStatement(const QString &newBareJid);
QXmppTask<QXmppClient::EmptyResult> verifyStatement(const QString &oldBareJid, const QString &newBareJid);

QXmppTask<QXmpp::SendResult> notifyContact(const QString &contactBareJid, const QString &oldBareJid, bool sensitive = true, const QString &reason = {});

Expand Down
Loading
Loading