aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/resourceresolver.cpp97
-rw-r--r--lib/resourceresolver.h117
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
+
+