Branch data Line data Source code
1 : : // Copyright (C) 2021 The Qt Company Ltd.
2 : : // Copyright (C) 2019 Luxoft Sweden AB
3 : : // Copyright (C) 2018 Pelagicore AG
4 : : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
5 : :
6 : : #include "intentclientrequest.h"
7 : : #include "intentclient.h"
8 : :
9 : : #include <QQmlEngine>
10 : : #include <QQmlInfo>
11 : : #include <QThread>
12 : : #include <QPointer>
13 : : #include <QTimer>
14 : :
15 : : using namespace Qt::StringLiterals;
16 : :
17 : :
18 : : QT_BEGIN_NAMESPACE_AM
19 : :
20 : : /*! \qmltype IntentRequest
21 : : \inqmlmodule QtApplicationManager
22 : : \ingroup common-non-instantiable
23 : : \brief Each instance represents an outgoing or incoming intent request.
24 : :
25 : : This type is used both in applications as well as within the System UI to represent an intent
26 : : request. This type can not be instantiated directly, but will be returned by
27 : : IntentClient::sendIntentRequest() (for outgoing requests to the system) and
28 : : IntentHandler::requestReceived() (for incoming requests to the application)
29 : :
30 : : See the IntentClient type for a short example on how to send intent requests to the system.
31 : :
32 : : The IntentHandler documenatation provides an example showing the use of this type when receiving
33 : : requests from the system.
34 : : */
35 : :
36 : : /*! \qmlproperty uuid IntentRequest::requestId
37 : : \readonly
38 : :
39 : : Every intent request in the system gets an unique requestId assigned by the server that will be
40 : : used throughout the life-time of the request in every context (requesting application, handling
41 : : application and intent server).
42 : : \note Since this requestId is generated by the server, any IntentRequest object generated by
43 : : IntentClient::sendIntentRequest() will start with a null requestId. The property will
44 : : be updated asynchronously once the server has assigned a new requestId to the
45 : : incoming request.
46 : :
47 : : \note Constant for requests received by IntentHandlers, valid on both sent and received requests.
48 : : */
49 : :
50 : : /*! \qmlproperty IntentRequest::Direction IntentRequest::direction
51 : : \readonly
52 : :
53 : : This property describes if this instance is an outgoing or incoming intent request:
54 : :
55 : : \list
56 : : \li IntentRequest.ToSystem - The request object was generated by IntentClient::sendIntentRequest(),
57 : : i.e. this request is sent out to the system side for handling.
58 : : \li IntentRequest.ToApplication - The request object was received by IntentHandler::requestReceived(),
59 : : i.e. this request was sent from the system side to the
60 : : application for handling.
61 : : \endlist
62 : :
63 : : \note Constant, valid on both sent and received requests.
64 : : */
65 : :
66 : : /*! \qmlproperty string IntentRequest::intentId
67 : : \readonly
68 : :
69 : : The requested intent id.
70 : :
71 : : \note Constant, valid on both sent and received requests.
72 : : */
73 : :
74 : : /*! \qmlproperty string IntentRequest::applicationId
75 : : \readonly
76 : :
77 : : The id of the application which should be handling this request. Returns an empty string if
78 : : no specific application was requested.
79 : :
80 : : \note Constant, valid on both sent and received requests.
81 : : */
82 : :
83 : : /*! \qmlproperty string IntentRequest::requestingApplicationId
84 : : \readonly
85 : :
86 : : The id of the application which created this intent request. Returns an empty string if called
87 : : from within an application context - only the server side has access to this information in
88 : : IntentServerHandler::requestReceived.
89 : :
90 : : \note Constant, valid on both sent and received requests.
91 : : */
92 : :
93 : : /*! \qmlproperty var IntentRequest::parameters
94 : : \readonly
95 : :
96 : : All parameters attached to the request as a JavaScript object.
97 : :
98 : : \note Constant, valid on both sent and received requests.
99 : : */
100 : :
101 : : /*! \qmlproperty bool IntentRequest::succeeded
102 : : \readonly
103 : :
104 : : As soon as the replyReceived() signal has been emitted, this property will show if the
105 : : intent request was actually successful.
106 : :
107 : : \note Valid only on sent requests.
108 : : */
109 : :
110 : : /*! \qmlproperty string IntentRequest::errorMessage
111 : : \readonly
112 : :
113 : : As soon as the replyReceived() signal has been emitted, this property will hold a potential
114 : : error message in case the request failed.
115 : :
116 : : \note Valid only on sent requests.
117 : :
118 : : \sa succeeded
119 : : */
120 : :
121 : : /*! \qmlproperty var IntentRequest::result
122 : : \readonly
123 : :
124 : : As soon as the replyReceived() signal has been emitted, this property will hold the result in
125 : : form of a JavaScript object in case the request succeeded.
126 : :
127 : : \note Valid only on sent requests.
128 : :
129 : : \sa succeeded
130 : : */
131 : :
132 : : /*! \qmlproperty bool IntentRequest::broadcast
133 : : \readonly
134 : : \since 6.5
135 : :
136 : : Only Set to \c true, if the received request is a broadcast.
137 : :
138 : : \note Valid only on received requests.
139 : : */
140 : :
141 : : /*! \qmlsignal IntentRequest::replyReceived()
142 : :
143 : : This signal gets emitted when a reply to an intent request is available. The signal handler
144 : : needs to check the succeeded property to decided whether errorMessage or result are actually
145 : : valid.
146 : :
147 : : \note This signal will only ever by emitted for request objects created by
148 : : IntentClient::sendIntentRequest().
149 : : */
150 : :
151 : :
152 : 240 : IntentClientRequest::Direction IntentClientRequest::direction() const
153 : : {
154 : 0 : return m_direction;
155 : : }
156 : :
157 : 374 : IntentClientRequest::~IntentClientRequest()
158 : : {
159 : : // the incoming request was gc'ed on the JavaScript side, but no reply was sent yet
160 [ + + + + : 187 : if ((direction() == Direction::ToApplication) && !m_finished && !m_broadcast)
+ + ]
161 : 10 : sendErrorReply(u"Request not handled"_s);
162 : 374 : }
163 : :
164 : 125 : QUuid IntentClientRequest::requestId() const
165 : : {
166 : 0 : return m_id;
167 : : }
168 : :
169 : 112 : QString IntentClientRequest::intentId() const
170 : : {
171 : 112 : return m_intentId;
172 : : }
173 : :
174 : 152 : QString IntentClientRequest::applicationId() const
175 : : {
176 : 152 : return m_applicationId;
177 : : }
178 : :
179 : 94 : QString IntentClientRequest::requestingApplicationId() const
180 : : {
181 : 94 : return m_requestingApplicationId;
182 : : }
183 : :
184 : 183 : QVariantMap IntentClientRequest::parameters() const
185 : : {
186 [ + + + + ]: 243 : return m_parameters;
187 : : }
188 : :
189 : 111 : bool IntentClientRequest::isBroadcast() const
190 : : {
191 : 0 : return m_broadcast;
192 : : }
193 : :
194 : 228 : bool IntentClientRequest::succeeded() const
195 : : {
196 : 171 : return m_succeeded;
197 : : }
198 : :
199 : 115 : const QVariantMap IntentClientRequest::result() const
200 : : {
201 [ + + + + ]: 164 : return m_result;
202 : : }
203 : :
204 : 39 : QString IntentClientRequest::errorMessage() const
205 : : {
206 : 39 : return m_errorMessage;
207 : : }
208 : :
209 : : /*! \qmlmethod IntentRequest::sendReply(var result)
210 : :
211 : : An IntentHandler needs to call this function to send its \a result back to the system in reply
212 : : to an request received via IntentHandler::requestReceived().
213 : :
214 : : Only either sendReply() or sendErrorReply() can be used on a single IntentRequest.
215 : :
216 : : \note This function only works for request objects received from IntentHandler::requestReceived().
217 : : It will simply do nothing on requests created by IntentClient::sendIntentRequest().
218 : :
219 : : \sa sendErrorReply
220 : : */
221 : 44 : void IntentClientRequest::sendReply(const QVariantMap &result)
222 : : {
223 : : //TODO: check that result only contains basic datatypes. convertFromJSVariant() does most of
224 : : // this already, but doesn't bail out on unconvertible types (yet)
225 : :
226 [ - + ]: 44 : if (m_direction == Direction::ToSystem) {
227 [ # # ]: 0 : qmlWarning(this) << "Calling IntentRequest::sendReply on requests originating from this application is a no-op.";
228 : 0 : return;
229 : : }
230 [ - + ]: 44 : if (m_broadcast) {
231 [ # # ]: 0 : qmlWarning(this) << "Calling IntentRequest::sendReply on broadcast requests is a no-op.";
232 : 0 : return;
233 : : }
234 : :
235 : 44 : IntentClient *ic = IntentClient::instance();
236 : :
237 [ - + ]: 44 : if (QThread::currentThread() != ic->thread()) {
238 : 0 : QPointer<IntentClientRequest> that(this);
239 : :
240 [ # # # # : 0 : ic->metaObject()->invokeMethod(ic, [that, ic, result]()
# # # # ]
241 [ # # ]: 0 : { if (that) ic->replyFromApplication(that.data(), result); },
242 : : Qt::QueuedConnection);
243 : 0 : } else {
244 : 44 : ic->replyFromApplication(this, result);
245 : : }
246 : : }
247 : :
248 : : /*! \qmlmethod IntentRequest::sendErrorReply(string errorMessage)
249 : :
250 : : IntentHandlers can use this function to indicate that they are unable to handle a request that
251 : : they received via IntentHandler::requestReceived(), stating the reason in \a errorMessage.
252 : :
253 : : Only either sendReply() or sendErrorReply() can be used on a single IntentRequest.
254 : :
255 : : \note This function only works for request objects received from IntentHandler::requestReceived().
256 : : It will simply do nothing on requests created by IntentClient::sendIntentRequest().
257 : :
258 : : \sa sendReply
259 : : */
260 : 11 : void IntentClientRequest::sendErrorReply(const QString &errorMessage)
261 : : {
262 [ - + ]: 11 : if (m_direction == Direction::ToSystem) {
263 [ # # ]: 0 : qmlWarning(this) << "Calling IntentRequest::sendErrorReply on requests originating from this application is a no-op.";
264 : 0 : return;
265 : : }
266 [ - + ]: 11 : if (m_broadcast) {
267 [ # # ]: 0 : qmlWarning(this) << "Calling IntentRequest::sendErrorReply on broadcast requests is a no-op.";
268 : 0 : return;
269 : : }
270 : 11 : IntentClient *ic = IntentClient::instance();
271 : :
272 [ - + ]: 11 : if (QThread::currentThread() != ic->thread()) {
273 : 0 : QPointer<IntentClientRequest> that(this);
274 : :
275 [ # # # # : 0 : ic->metaObject()->invokeMethod(ic, [that, ic, errorMessage]()
# # ]
276 [ # # ]: 0 : { if (that) ic->errorReplyFromApplication(that.data(), errorMessage); },
277 : : Qt::QueuedConnection);
278 : 0 : } else {
279 : 11 : ic->errorReplyFromApplication(this, errorMessage);
280 : : }
281 : : }
282 : :
283 : 149 : void IntentClientRequest::startTimeout(int timeout)
284 : : {
285 [ + + ]: 149 : if (timeout <= 0)
286 : : return;
287 : :
288 [ - + - - : 246 : QTimer::singleShot(timeout, this, [this, timeout]() {
- - ]
289 [ - + ]: 15 : if (!m_finished) {
290 [ # # ]: 0 : if (direction() == Direction::ToApplication)
291 [ # # # # ]: 0 : sendErrorReply(u"Intent request to application timed out after %1 ms"_s.arg(timeout));
292 : : else
293 [ # # # # ]: 0 : setErrorMessage(u"No reply received from Intent server after %1 ms"_s.arg(timeout));
294 : : }
295 : 15 : });
296 : : }
297 : :
298 : 53 : void IntentClientRequest::connectNotify(const QMetaMethod &signal)
299 : : {
300 : : // take care of connects happening after the request is already finished:
301 : : // re-emit the finished signal in this case (this shouldn't happen in practice, but better be
302 : : // safe than sorry)
303 [ + - ]: 53 : if (signal == QMetaMethod::fromSignal(&IntentClientRequest::replyReceived)) {
304 [ - + ]: 53 : if (direction() == Direction::ToApplication) {
305 [ # # ]: 0 : qmlWarning(this) << "Connecting to IntentRequest::replyReceived on requests received "
306 : 0 : "by IntentHandlers is a no-op.";
307 [ - + ]: 53 : } else if (m_finished) {
308 : 0 : QMetaObject::invokeMethod(this, &IntentClientRequest::doFinish, Qt::QueuedConnection);
309 : : }
310 : : }
311 : 53 : }
312 : :
313 : 187 : IntentClientRequest::IntentClientRequest(Direction direction, const QString &requestingApplicationId,
314 : : const QUuid &id, const QString &intentId,
315 : : const QString &applicationId, const QVariantMap ¶meters,
316 : 187 : bool broadcast)
317 : : : QObject()
318 : 187 : , m_direction(direction)
319 : 187 : , m_id(id)
320 : 374 : , m_intentId(intentId)
321 : 187 : , m_requestingApplicationId(requestingApplicationId)
322 : 187 : , m_applicationId(applicationId)
323 [ + + ]: 187 : , m_parameters(parameters)
324 : 187 : , m_broadcast(broadcast)
325 : 187 : { }
326 : :
327 : 79 : void IntentClientRequest::setRequestId(const QUuid &requestId)
328 : : {
329 [ + - ]: 79 : if (m_id != requestId) {
330 : 79 : m_id = requestId;
331 : 79 : emit requestIdChanged();
332 : : }
333 : 79 : }
334 : :
335 : 51 : void IntentClientRequest::setResult(const QVariantMap &result)
336 : : {
337 [ + + ]: 51 : if (m_result != result)
338 : 36 : m_result = result;
339 : 51 : m_succeeded = true;
340 : 51 : doFinish();
341 : 51 : }
342 : :
343 : 33 : void IntentClientRequest::setErrorMessage(const QString &errorMessage)
344 : : {
345 [ + - ]: 33 : if (m_errorMessage != errorMessage)
346 : 33 : m_errorMessage = errorMessage;
347 : 33 : m_succeeded = false;
348 : 33 : doFinish();
349 : 33 : }
350 : :
351 : 84 : void IntentClientRequest::doFinish()
352 : : {
353 : 84 : m_finished = true;
354 : 84 : emit replyReceived();
355 : : // We need to disconnect all JS handlers now, because otherwise the request object would
356 : : // never be garbage collected (the signal connections increase the use-counter).
357 : 84 : disconnect(this, &IntentClientRequest::replyReceived, nullptr, nullptr);
358 : 84 : }
359 : :
360 : : QT_END_NAMESPACE_AM
361 : :
362 : : #include "moc_intentclientrequest.cpp"
|