diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/resourceresolver.cpp | 97 | ||||
-rw-r--r-- | lib/resourceresolver.h | 117 |
2 files changed, 214 insertions, 0 deletions
diff --git a/lib/resourceresolver.cpp b/lib/resourceresolver.cpp new file mode 100644 index 00000000..f910d640 --- /dev/null +++ b/lib/resourceresolver.cpp @@ -0,0 +1,97 @@ +#include "resourceresolver.h" + +#include "settings.h" + +#include <QtCore/QRegularExpression> + +using namespace Quotient; + +QString ResourceResolver::toMatrixId(const QString& uriOrId, + QStringList uriServers) +{ + auto id = QUrl::fromPercentEncoding(uriOrId.toUtf8()); + const auto MatrixScheme = "matrix:"_ls; + if (id.startsWith(MatrixScheme)) { + id.remove(0, MatrixScheme.size()); + for (const auto& p: { std::pair { "user/"_ls, '@' }, + { "roomid/"_ls, '!' }, + { "room/"_ls, '#' } }) + if (id.startsWith(p.first)) { + id.replace(0, p.first.size(), p.second); + break; + } + // The below assumes that /event/ cannot show up in normal Matrix ids. + id.replace("/event/"_ls, "/$"_ls); + } else { + const auto MatrixTo_ServerName = QStringLiteral("matrix.to"); + if (!uriServers.contains(MatrixTo_ServerName)) + uriServers.push_back(MatrixTo_ServerName); + id.remove( + QRegularExpression("^https://(" + uriServers.join('|') + ")/?#/")); + } + return id; +} + +ResourceResolver::Result ResourceResolver::visitResource( + Connection* account, const QString& identifier, + std::function<void(User*)> userHandler, + std::function<void(Room*, QString)> roomEventHandler) +{ + const auto& normalizedId = toMatrixId(identifier); + auto&& [sigil, mainId, secondaryId] = parseIdentifier(normalizedId); + Room* room = nullptr; + switch (sigil) { + case char(-1): + return MalformedMatrixId; + case char(0): + return EmptyMatrixId; + case '@': + if (auto* user = account->user(mainId)) { + userHandler(user); + return Success; + } + return MalformedMatrixId; + case '!': + if ((room = account->room(mainId))) + break; + return UnknownMatrixId; + case '#': + if ((room = account->roomByAlias(mainId))) + break; + [[fallthrough]]; + default: + return UnknownMatrixId; + } + roomEventHandler(room, secondaryId); + return Success; +} + +ResourceResolver::IdentifierParts +ResourceResolver::parseIdentifier(const QString& identifier) +{ + if (identifier.isEmpty()) + return {}; + + // The regex is quick and dirty, only intending to triage the id. + static const QRegularExpression IdRE { + "^(?<main>(?<sigil>.)([^/]+))(/(?<sec>[^?]+))?" + }; + auto dissectedId = IdRE.match(identifier); + if (!dissectedId.hasMatch()) + return { char(-1) }; + + const auto sigil = dissectedId.captured("sigil"); + return { sigil.size() != 1 ? char(-1) : sigil.front().toLatin1(), + dissectedId.captured("main"), dissectedId.captured("sec") }; +} + +ResourceResolver::Result +ResourceResolver::openResource(Connection* account, const QString& identifier, + const QString& action) +{ + return visitResource(account, identifier, + [this, &action](User* u) { emit userAction(u, action); }, + [this, &action](Room* room, const QString& eventId) { + emit roomAction(room, eventId, action); + }); +} diff --git a/lib/resourceresolver.h b/lib/resourceresolver.h new file mode 100644 index 00000000..794b7796 --- /dev/null +++ b/lib/resourceresolver.h @@ -0,0 +1,117 @@ +#pragma once + +#include "connection.h" + +#include <functional> + +namespace Quotient { + +/*! \brief Matrix resource resolver + * TODO: rewrite + * Similar to visitResource(), this class encapsulates the logic of resolving + * a Matrix identifier or a URI into Quotient object(s) and applying an action + * to the resolved object(s). Instead of using a C++ visitor pattern, it + * announces the request through Qt's signals passing the resolved object(s) + * through those (still in a typesafe way). + * + * This class is aimed primarily at clients where invoking the resolving/action + * and handling the action are happening in decoupled parts of the code; it's + * also useful to operate on Matrix identifiers and URIs from QML/JS code + * that cannot call visitResource due to QML/C++ interface limitations. + */ +class ResourceResolver : public QObject { + Q_OBJECT +public: + enum Result : short { + StillResolving = -1, + Success = 0, + UnknownMatrixId, + MalformedMatrixId, + NoAccount, + EmptyMatrixId + }; + Q_ENUM(Result) + + explicit ResourceResolver(QObject* parent = nullptr) : QObject(parent) + { } + + /*! \brief Decode a URI to a Matrix identifier (or a room/event pair) + * + * This accepts plain Matrix ids, MSC2312 URIs (aka matrix: URIs) and + * matrix.to URIs. + * + * \return a Matrix identifier as defined by the common identifier grammars + * or a slash separated pair of Matrix identifiers if the original + * uri/id pointed to an event in a room + */ + static QString toMatrixId(const QString& uriOrId, + QStringList uriServers = {}); + + /*! \brief Resolve the resource and invoke an action on it, visitor style + * + * This template function encapsulates the logic of resolving a Matrix + * identifier or URI into a Quotient object (or objects) and applying an + * appropriate action handler from the set provided by the caller to it. + * A typical use case for that is opening a room or mentioning a user in + * response to clicking on a Matrix URI or identifier. + * + * \param account The connection used as a context to resolve the identifier + * + * \param identifier The Matrix identifier or URI. MSC2312 URIs and classic + * Matrix ID scheme are supported. + * + * \sa ResourceResolver + */ + static Result + visitResource(Connection* account, const QString& identifier, + std::function<void(User*)> userHandler, + std::function<void(Room*, QString)> roomEventHandler); + + /*! \brief Resolve the resource and request an action on it, signal style + * + * This method: + * 1. Resolves \p identifier into an actual object (Room or User), with + * possible additional data such as event id, in the context of + * \p account. + * 2. If the resolving is successful, depending on the type of the object, + * emits the respective signal to which the client must connect in order + * to apply the action to the resource (open a room, mention a user etc.). + * 3. Returns the result of resolving the resource. + * + * Note that the action can be applied either synchronously or entirely + * asynchronously; ResourceResolver does not restrain the client code + * to use either method. The resource resolving part is entirely synchronous + * though. If the synchronous operation is chosen, only + * direct connections to ResourceResolver signals must be used, and + * the caller should check the future's state immediately after calling + * openResource() to process any feedback from the resolver and/or action + * handler. If asynchronous operation is needed then either direct or queued + * connections to ResourceResolver's signals can be used and the caller + * must both check the ResourceFuture state right after calling openResource + * and also connect to ResourceFuture::ready() signal in order to process + * the result of resolving and action. + */ + Q_INVOKABLE Result openResource(Connection* account, + const QString& identifier, + const QString& action = {}); + +signals: + /// An action on a user has been requested + void userAction(Quotient::User* user, QString action); + + /// An action on a room has been requested, with optional event id + void roomAction(Quotient::Room* room, QString eventId, QString action); + +private: + struct IdentifierParts { + char sigil; + QString mainId {}; + QString secondaryId = {}; + }; + + static IdentifierParts parseIdentifier(const QString& identifier); +}; + +} // namespace Quotient + + |