summaryrefslogtreecommitdiff
path: root/src/intent-client-lib/intentclientrequest.cpp
blob: 09836df282caa811e369bf442b5e08df12b2d7c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2019 Luxoft Sweden AB
// Copyright (C) 2018 Pelagicore AG
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "intentclientrequest.h"
#include "intentclient.h"

#include <QQmlEngine>
#include <QQmlInfo>
#include <QThread>
#include <QPointer>
#include <QTimer>

QT_BEGIN_NAMESPACE_AM

/*! \qmltype IntentRequest
    \inqmlmodule QtApplicationManager
    \ingroup common-non-instantiable
    \brief Each instance represents an outgoing or incoming intent request.

    This type is used both in applications as well as within the System UI to represent an intent
    request. This type can not be instantiated directly, but will be returned by
    IntentClient::sendIntentRequest() (for outgoing requests to the system) and
    IntentHandler::requestReceived() (for incoming requests to the application)

    See the IntentClient type for a short example on how to send intent requests to the system.

    The IntentHandler documenatation provides an example showing the use of this type when receiving
    requests from the system.
*/

/*! \qmlproperty uuid IntentRequest::requestId
    \readonly

    Every intent request in the system gets an unique requestId assigned by the server that will be
    used throughout the life-time of the request in every context (requesting application, handling
    application and intent server).
    \note Since this requestId is generated by the server, any IntentRequest object generated by
          IntentClient::sendIntentRequest() will start with a null requestId. The property will
          be updated asynchronously once the server has assigned a new requestId to the
          incoming request.

    \note Constant for requests received by IntentHandlers, valid on both sent and received requests.
*/

/*! \qmlproperty IntentRequest::Direction IntentRequest::direction
    \readonly

    This property describes if this instance is an outgoing or incoming intent request:

    \list
    \li IntentRequest.ToSystem - The request object was generated by IntentClient::sendIntentRequest(),
                                 i.e. this request is sent out to the system side for handling.
    \li IntentRequest.ToApplication - The request object was received by IntentHandler::requestReceived(),
                                      i.e. this request was sent from the system side to the
                                      application for handling.
    \endlist

    \note Constant, valid on both sent and received requests.
*/

/*! \qmlproperty string IntentRequest::intentId
    \readonly

    The requested intent id.

    \note Constant, valid on both sent and received requests.
*/

/*! \qmlproperty string IntentRequest::applicationId
    \readonly

    The id of the application which should be handling this request. Returns an empty string if
    no specific application was requested.

    \note Constant, valid on both sent and received requests.
*/

/*! \qmlproperty string IntentRequest::requestingApplicationId
    \readonly

    The id of the application which created this intent request. Returns an empty string if called
    from within an application context - only the server side has access to this information in
    IntentServerHandler::requestReceived.

    \note Constant, valid on both sent and received requests.
*/

/*! \qmlproperty var IntentRequest::parameters
    \readonly

    All parameters attached to the request as a JavaScript object.

    \note Constant, valid on both sent and received requests.
*/

/*! \qmlproperty bool IntentRequest::succeeded
    \readonly

    As soon as the replyReceived() signal has been emitted, this property will show if the
    intent request was actually successful.

    \note Valid only on sent requests.
*/

/*! \qmlproperty string IntentRequest::errorMessage
    \readonly

    As soon as the replyReceived() signal has been emitted, this property will hold a potential
    error message in case the request failed.

    \note Valid only on sent requests.

    \sa succeeded
*/

/*! \qmlproperty var IntentRequest::result
    \readonly

    As soon as the replyReceived() signal has been emitted, this property will hold the result in
    form of a JavaScript object in case the request succeeded.

    \note Valid only on sent requests.

    \sa succeeded
*/

/*! \qmlsignal IntentRequest::replyReceived()

    This signal gets emitted when a reply to an intent request is available. The signal handler
    needs to check the succeeded property to decided whether errorMessage or result are actually
    valid.

    \note This signal will only ever by emitted for request objects created by
          IntentClient::sendIntentRequest().
*/


IntentClientRequest::Direction IntentClientRequest::direction() const
{
    return m_direction;
}

IntentClientRequest::~IntentClientRequest()
{
    // the incoming request was gc'ed on the JavaScript side, but no reply was sent yet
    if ((direction() == Direction::ToApplication) && !m_finished)
        sendErrorReply(qSL("Request not handled"));
}

QUuid IntentClientRequest::requestId() const
{
    return m_id;
}

QString IntentClientRequest::intentId() const
{
    return m_intentId;
}

QString IntentClientRequest::applicationId() const
{
    return m_applicationId;
}

QString IntentClientRequest::requestingApplicationId() const
{
    return m_requestingApplicationId;
}

QVariantMap IntentClientRequest::parameters() const
{
    return m_parameters;
}

bool IntentClientRequest::succeeded() const
{
    return m_succeeded;
}

const QVariantMap IntentClientRequest::result() const
{
    return m_result;
}

QString IntentClientRequest::errorMessage() const
{
    return m_errorMessage;
}

/*! \qmlmethod IntentRequest::sendReply(var result)

    An IntentHandler needs to call this function to send its \a result back to the system in reply
    to an request received via IntentHandler::requestReceived().

    Only either sendReply() or sendErrorReply() can be used on a single IntentRequest.

    \note This function only works for request objects received from IntentHandler::requestReceived().
          It will simply do nothing on requests created by IntentClient::sendIntentRequest().

    \sa sendErrorReply
*/
void IntentClientRequest::sendReply(const QVariantMap &result)
{
    //TODO: check that result only contains basic datatypes. convertFromJSVariant() does most of
    //      this already, but doesn't bail out on unconvertible types (yet)

    if (m_direction == Direction::ToSystem) {
        qmlWarning(this) << "Calling IntentRequest::sendReply on requests originating from this application is a no-op.";
        return;
    }
    IntentClient *ic = IntentClient::instance();

    if (QThread::currentThread() != ic->thread()) {
        QPointer<IntentClientRequest> that(this);

        ic->metaObject()->invokeMethod(ic, [that, ic, result]()
        { if (that) ic->replyFromApplication(that.data(), result); },
        Qt::QueuedConnection);
    } else {
        ic->replyFromApplication(this, result);
    }
}

/*! \qmlmethod IntentRequest::sendErrorReply(string errorMessage)

    IntentHandlers can use this function to indicate that they are unable to handle a request that
    they received via IntentHandler::requestReceived(), stating the reason in \a errorMessage.

    Only either sendReply() or sendErrorReply() can be used on a single IntentRequest.

    \note This function only works for request objects received from IntentHandler::requestReceived().
          It will simply do nothing on requests created by IntentClient::sendIntentRequest().

    \sa sendReply
*/
void IntentClientRequest::sendErrorReply(const QString &errorMessage)
{
    if (m_direction == Direction::ToSystem) {
        qmlWarning(this) << "Calling IntentRequest::sendErrorReply on requests originating from this application is a no-op.";
        return;
    }
    IntentClient *ic = IntentClient::instance();

    if (QThread::currentThread() != ic->thread()) {
        QPointer<IntentClientRequest> that(this);

        ic->metaObject()->invokeMethod(ic, [that, ic, errorMessage]()
        { if (that) ic->errorReplyFromApplication(that.data(), errorMessage); },
        Qt::QueuedConnection);
    } else {
        ic->errorReplyFromApplication(this, errorMessage);
    }
}

void IntentClientRequest::startTimeout(int timeout)
{
    if (timeout <= 0)
        return;

    QTimer::singleShot(timeout, this, [this, timeout]() {
        if (!m_finished) {
            if (direction() == Direction::ToApplication)
                sendErrorReply(qSL("Intent request to application timed out after %1 ms").arg(timeout));
            else
                setErrorMessage(qSL("No reply received from Intent server after %1 ms").arg(timeout));
        }
    });
}

void IntentClientRequest::connectNotify(const QMetaMethod &signal)
{
    // take care of connects happening after the request is already finished:
    // re-emit the finished signal in this case (this shouldn't happen in practice, but better be
    // safe than sorry)
    if (signal == QMetaMethod::fromSignal(&IntentClientRequest::replyReceived)) {
        if (direction() == Direction::ToApplication) {
            qmlWarning(this) << "Connecting to IntentRequest::replyReceived on requests received "
                                "by IntentHandlers is a no-op.";
        } else if (m_finished) {
            QMetaObject::invokeMethod(this, &IntentClientRequest::doFinish, Qt::QueuedConnection);
        }
    }
}

IntentClientRequest::IntentClientRequest(Direction direction, const QString &requestingApplicationId,
                                         const QUuid &id, const QString &intentId,
                                         const QString &applicationId, const QVariantMap &parameters)
    : QObject()
    , m_direction(direction)
    , m_id(id)
    , m_intentId(intentId)
    , m_requestingApplicationId(requestingApplicationId)
    , m_applicationId(applicationId)
    , m_parameters(parameters)
{ }

void IntentClientRequest::setRequestId(const QUuid &requestId)
{
    if (m_id != requestId) {
        m_id = requestId;
        emit requestIdChanged();
    }
}

void IntentClientRequest::setResult(const QVariantMap &result)
{
    if (m_result != result)
        m_result = result;
    m_succeeded = true;
    doFinish();
}

void IntentClientRequest::setErrorMessage(const QString &errorMessage)
{
    if (m_errorMessage != errorMessage)
        m_errorMessage = errorMessage;
    m_succeeded = false;
    doFinish();
}

void IntentClientRequest::doFinish()
{
    m_finished = true;
    emit replyReceived();
    // We need to disconnect all JS handlers now, because otherwise the request object would
    // never be garbage collected (the signal connections increase the use-counter).
    disconnect(this, &IntentClientRequest::replyReceived, nullptr, nullptr);
}

QT_END_NAMESPACE_AM

#include "moc_intentclientrequest.cpp"