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
12namespace Quotient {
13class Connection;
14class Room;
15class 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 */
28class QUOTIENT_API UriResolverBase {
29public:
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
44protected:
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 */
108QUOTIENT_API UriResolveResult
109visitResource(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 */
116inline 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 */
144class QUOTIENT_API UriDispatcher : public QObject, public UriResolverBase {
145 Q_OBJECT
146public:
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
156Q_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
170private:
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