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 | |