| 1 | // SPDX-FileCopyrightText: 2020 Kitsune Ral <kitsune-ral@users.sf.net> |
| 2 | // SPDX-License-Identifier: LGPL-2.1-or-later |
| 3 | |
| 4 | #pragma once |
| 5 | |
| 6 | #include "uri.h" |
| 7 | |
| 8 | #include <QtCore/QObject> |
| 9 | |
| 10 | #include <functional> |
| 11 | |
| 12 | namespace Quotient { |
| 13 | class Connection; |
| 14 | class Room; |
| 15 | class User; |
| 16 | |
| 17 | /*! \brief Abstract class to resolve the resource and act on it |
| 18 | * |
| 19 | * This class encapsulates the logic of resolving a Matrix identifier or URI |
| 20 | * into a Quotient object (or objects) and calling an appropriate handler on it. |
| 21 | * It is a type-safe way of handling a URI with no prior context on its type |
| 22 | * in cases like, e.g., when a user clicks on a URI in the application. |
| 23 | * |
| 24 | * This class provides empty "handlers" for each type of URI to facilitate |
| 25 | * gradual implementation. Derived classes are encouraged to override as many |
| 26 | * of them as possible. |
| 27 | */ |
| 28 | class QUOTIENT_API UriResolverBase { |
| 29 | public: |
| 30 | /*! \brief Resolve the resource and dispatch an action depending on its type |
| 31 | * |
| 32 | * This method: |
| 33 | * 1. Resolves \p uri into an actual object (e.g., Room or User), |
| 34 | * with possible additional data such as event id, in the context of |
| 35 | * \p account. |
| 36 | * 2. If the resolving is successful, depending on the type of the object, |
| 37 | * calls the appropriate virtual function (defined in a derived |
| 38 | * concrete class) to perform an action on the resource (open a room, |
| 39 | * mention a user etc.). |
| 40 | * 3. Returns the result of resolving the resource. |
| 41 | */ |
| 42 | UriResolveResult visitResource(Connection* account, const Uri& uri); |
| 43 | |
| 44 | protected: |
| 45 | virtual ~UriResolverBase() = 0; |
| 46 | |
| 47 | /// Called by visitResource() when the passed URI identifies a Matrix user |
| 48 | /*! |
| 49 | * \return IncorrectAction if the action is not correct or not supported; |
| 50 | * UriResolved if it is accepted; other values are disallowed |
| 51 | */ |
| 52 | virtual UriResolveResult visitUser(User* user [[maybe_unused]], |
| 53 | const QString& action [[maybe_unused]]) |
| 54 | { |
| 55 | return IncorrectAction; |
| 56 | } |
| 57 | /// Called by visitResource() when the passed URI identifies a room or |
| 58 | /// an event in a room |
| 59 | virtual void visitRoom(Room* room [[maybe_unused]], |
| 60 | const QString& eventId [[maybe_unused]]) |
| 61 | {} |
| 62 | /// Called by visitResource() when the passed URI has `action() == "join"` |
| 63 | /// and identifies a room that the user defined by the Connection argument |
| 64 | /// is not a member of |
| 65 | virtual void joinRoom(Connection* account [[maybe_unused]], |
| 66 | const QString& roomAliasOrId [[maybe_unused]], |
| 67 | const QStringList& viaServers [[maybe_unused]] = {}) |
| 68 | {} |
| 69 | /// Called by visitResource() when the passed URI has `type() == NonMatrix` |
| 70 | /*! |
| 71 | * Should return true if the URI is considered resolved, false otherwise. |
| 72 | * A basic implementation in a graphical client can look like |
| 73 | * `return QDesktopServices::openUrl(url);` but it's strongly advised to |
| 74 | * ask for a user confirmation beforehand. |
| 75 | */ |
| 76 | virtual bool visitNonMatrix(const QUrl& url [[maybe_unused]]) |
| 77 | { |
| 78 | return false; |
| 79 | } |
| 80 | }; |
| 81 | |
| 82 | /*! \brief Resolve the resource and invoke an action on it, via function objects |
| 83 | * |
| 84 | * This function encapsulates the logic of resolving a Matrix identifier or URI |
| 85 | * into a Quotient object (or objects) and calling an appropriate handler on it. |
| 86 | * Unlike UriResolverBase it accepts the list of handlers from |
| 87 | * the caller; internally it's uses a minimal UriResolverBase class |
| 88 | * |
| 89 | * \param account The connection used as a context to resolve the identifier |
| 90 | * |
| 91 | * \param uri A URI that can represent a Matrix entity |
| 92 | * |
| 93 | * \param userHandler Called when the passed URI identifies a Matrix user |
| 94 | * |
| 95 | * \param roomEventHandler Called when the passed URI identifies a room or |
| 96 | * an event in a room |
| 97 | * |
| 98 | * \param joinHandler Called when the passed URI has `action() == "join"` and |
| 99 | * identifies a room that the user defined by |
| 100 | * the Connection argument is not a member of |
| 101 | * |
| 102 | * \param nonMatrixHandler Called when the passed URI has `type() == NonMatrix`; |
| 103 | * should return true if the URI is considered resolved, |
| 104 | * false otherwise |
| 105 | * |
| 106 | * \sa UriResolverBase, UriDispatcher |
| 107 | */ |
| 108 | QUOTIENT_API UriResolveResult |
| 109 | visitResource(Connection* account, const Uri& uri, |
| 110 | std::function<UriResolveResult(User*, QString)> userHandler, |
| 111 | std::function<void(Room*, QString)> roomEventHandler, |
| 112 | std::function<void(Connection*, QString, QStringList)> joinHandler, |
| 113 | std::function<bool(const QUrl&)> nonMatrixHandler); |
| 114 | |
| 115 | /*! \brief Check that the resource is resolvable with no action on it */ |
| 116 | inline UriResolveResult checkResource(Connection* account, const Uri& uri) |
| 117 | { |
| 118 | return visitResource( |
| 119 | account, uri, userHandler: [](auto, auto) { return UriResolved; }, roomEventHandler: [](auto, auto) {}, |
| 120 | joinHandler: [](auto, auto, auto) {}, nonMatrixHandler: [](auto) { return false; }); |
| 121 | } |
| 122 | |
| 123 | /*! \brief Resolve the resource and invoke an action on it, via Qt signals |
| 124 | * |
| 125 | * This is an implementation of UriResolverBase that is based on |
| 126 | * QObject and uses Qt signals instead of virtual functions to provide an |
| 127 | * open-ended interface for visitors. |
| 128 | * |
| 129 | * This class is aimed primarily at clients where invoking the resolving/action |
| 130 | * and handling the action are happening in decoupled parts of the code; it's |
| 131 | * also useful to operate on Matrix identifiers and URIs from QML/JS code |
| 132 | * that cannot call resolveResource() due to QML/C++ interface limitations. |
| 133 | * |
| 134 | * This class does not restrain the client code to a certain type of |
| 135 | * connections: both direct and queued (or a mix) will work fine. One limitation |
| 136 | * caused by that is there's no way to indicate if a non-Matrix URI has been |
| 137 | * successfully resolved - a signal always returns void. |
| 138 | * |
| 139 | * Note that in case of using (non-blocking) queued connections the code that |
| 140 | * calls resolveResource() should not expect the action to be performed |
| 141 | * synchronously - the returned value is the result of resolving the URI, |
| 142 | * not acting on it. |
| 143 | */ |
| 144 | class QUOTIENT_API UriDispatcher : public QObject, public UriResolverBase { |
| 145 | Q_OBJECT |
| 146 | public: |
| 147 | explicit UriDispatcher(QObject* parent = nullptr) : QObject(parent) {} |
| 148 | |
| 149 | // It's actually UriResolverBase::visitResource() but with Q_INVOKABLE |
| 150 | Q_INVOKABLE UriResolveResult resolveResource(Connection* account, |
| 151 | const Uri& uri) |
| 152 | { |
| 153 | return UriResolverBase::visitResource(account, uri); |
| 154 | } |
| 155 | |
| 156 | Q_SIGNALS: |
| 157 | /// An action on a user has been requested |
| 158 | void userAction(Quotient::User* user, QString action); |
| 159 | |
| 160 | /// An action on a room has been requested, with optional event id |
| 161 | void roomAction(Quotient::Room* room, QString eventId); |
| 162 | |
| 163 | /// A join action has been requested, with optional 'via' servers |
| 164 | void joinAction(Quotient::Connection* account, QString roomAliasOrId, |
| 165 | QStringList viaServers); |
| 166 | |
| 167 | /// An action on a non-Matrix URL has been requested |
| 168 | void nonMatrixAction(QUrl url); |
| 169 | |
| 170 | private: |
| 171 | UriResolveResult visitUser(User* user, const QString& action) override; |
| 172 | void visitRoom(Room* room, const QString& eventId) override; |
| 173 | void joinRoom(Connection* account, const QString& roomAliasOrId, |
| 174 | const QStringList& viaServers = {}) override; |
| 175 | bool visitNonMatrix(const QUrl& url) override; |
| 176 | }; |
| 177 | |
| 178 | } // namespace Quotient |
| 179 | |
| 180 | |
| 181 | |