1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qdbuspendingcall.h"
6#include "qdbuspendingcall_p.h"
7
8#include "qdbusconnection_p.h"
9#include "qdbusmetatype_p.h"
10#include "qdbusutil_p.h"
11#include "qcoreapplication.h"
12#include "qcoreevent.h"
13#include <private/qobject_p.h>
14#include <private/qlocking_p.h>
15
16#ifndef QT_NO_DBUS
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22/*!
23 \class QDBusPendingCall
24 \inmodule QtDBus
25 \ingroup shared
26 \since 4.5
27
28 \brief The QDBusPendingCall class refers to one pending asynchronous call.
29
30 A QDBusPendingCall object is a reference to a method call that was
31 sent over D-Bus without waiting for a reply. QDBusPendingCall is an
32 opaque type, meant to be used as a handle for a pending reply.
33
34 In most programs, the QDBusPendingCall class will not be used
35 directly. It can be safely replaced with the template-based
36 QDBusPendingReply, in order to access the contents of the reply or
37 wait for it to be complete.
38
39 The QDBusPendingCallWatcher class allows one to connect to a signal
40 that will indicate when the reply has arrived or if the call has
41 timed out. It also provides the
42 QDBusPendingCallWatcher::waitForFinished() method which will suspend
43 the execution of the program until the reply has arrived.
44
45 \note If you create a copy of a QDBusPendingCall object, all
46 information will be shared among the many copies. Therefore,
47 QDBusPendingCall is an explicitly-shared object and does not
48 provide a method of detaching the copies (since they refer
49 to the same pending call)
50
51 \sa QDBusPendingReply, QDBusPendingCallWatcher
52*/
53
54/*!
55 \class QDBusPendingCallWatcher
56 \inmodule QtDBus
57 \since 4.5
58
59 \brief The QDBusPendingCallWatcher class provides a convenient way for
60 waiting for asynchronous replies.
61
62 The QDBusPendingCallWatcher provides the finished() signal that will be
63 emitted when a reply arrives.
64
65 It is usually used like the following example:
66
67 \snippet code/src_qdbus_qdbuspendingcall.cpp 0
68
69 Note that it is not necessary to keep the original QDBusPendingCall
70 object around since QDBusPendingCallWatcher inherits from that class
71 too.
72
73 The slot connected to by the above code could be something similar
74 to the following:
75
76 \snippet code/src_qdbus_qdbuspendingcall.cpp 1
77
78 Note the use of QDBusPendingReply to validate the argument types in
79 the reply. If the reply did not contain exactly two arguments
80 (one string and one QByteArray), QDBusPendingReply::isError() will
81 return true.
82
83 \sa QDBusPendingReply
84*/
85
86/*!
87 \fn void QDBusPendingCallWatcher::finished(QDBusPendingCallWatcher *self)
88
89 This signal is emitted when the pending call has finished and its
90 reply is available. The \a self parameter is a pointer to the
91 object itself, passed for convenience so that the slot can access
92 the properties and determine the contents of the reply.
93*/
94
95void QDBusPendingCallWatcherHelper::add(QDBusPendingCallWatcher *watcher)
96{
97 connect(sender: this, SIGNAL(finished()), receiver: watcher, SLOT(_q_finished()), Qt::QueuedConnection);
98}
99
100QDBusPendingCallPrivate::~QDBusPendingCallPrivate()
101{
102 if (pending) {
103 q_dbus_pending_call_cancel(pending);
104 q_dbus_pending_call_unref(pending);
105 }
106 delete watcherHelper;
107}
108
109bool QDBusPendingCallPrivate::setReplyCallback(QObject *target, const char *member)
110{
111 receiver = target;
112 metaTypes.clear();
113 methodIdx = -1;
114 if (!target)
115 return true;; // unsetting
116
117 if (!member || !*member) {
118 // would not be able to deliver a reply
119 qWarning(msg: "QDBusPendingCall::setReplyCallback: error: cannot deliver a reply to %s::%s (%s)",
120 target ? target->metaObject()->className() : "(null)",
121 member ? member + 1 : "(null)",
122 target ? qPrintable(target->objectName()) : "no name");
123 return false;
124 }
125
126 QString errorMsg;
127 methodIdx = QDBusConnectionPrivate::findSlot(obj: target, normalizedName: member + 1, params&: metaTypes, errorMsg);
128 if (methodIdx == -1) {
129 QByteArray normalizedName = QMetaObject::normalizedSignature(method: member + 1);
130 methodIdx = QDBusConnectionPrivate::findSlot(obj: target, normalizedName, params&: metaTypes, errorMsg);
131 }
132 if (methodIdx == -1) {
133 // would not be able to deliver a reply
134 qWarning(msg: "QDBusPendingCall::setReplyCallback: error: cannot deliver a reply to %s::%s (%s) "
135 "because %s",
136 target->metaObject()->className(), member + 1, qPrintable(target->objectName()),
137 qPrintable(errorMsg));
138 return false;
139 }
140
141 // success
142 // construct the expected signature
143 int count = metaTypes.size() - 1;
144 if (count == 1 && metaTypes.at(i: 1) == QDBusMetaTypeId::message()) {
145 // wildcard slot, can receive anything, so don't set the signature
146 return true;
147 }
148
149 if (metaTypes.at(i: count) == QDBusMetaTypeId::message())
150 --count;
151
152 setMetaTypes(count, types: count ? metaTypes.constData() + 1 : nullptr);
153 return true;
154}
155
156void QDBusPendingCallPrivate::setMetaTypes(int count, const QMetaType *types)
157{
158 if (count == 0) {
159 expectedReplySignature = ""_L1; // not null
160 return;
161 }
162
163 QByteArray sig;
164 sig.reserve(asize: count + count / 2);
165 for (int i = 0; i < count; ++i) {
166 const char *typeSig = QDBusMetaType::typeToSignature(type: types[i]);
167 if (Q_UNLIKELY(!typeSig))
168 qFatal(msg: "QDBusPendingReply: type %s is not registered with QtDBus", types[i].name());
169 sig += typeSig;
170 }
171
172 expectedReplySignature = QString::fromLatin1(ba: sig);
173}
174
175void QDBusPendingCallPrivate::checkReceivedSignature()
176{
177 // MUST BE CALLED WITH A LOCKED MUTEX!
178
179 if (replyMessage.type() == QDBusMessage::InvalidMessage)
180 return; // not yet finished - no message to
181 // validate against
182 if (replyMessage.type() == QDBusMessage::ErrorMessage)
183 return; // we don't have to check the signature of an error reply
184
185 if (expectedReplySignature.isNull())
186 return; // no signature to validate against
187
188 // can't use startsWith here because a null string doesn't start or end with an empty string
189 if (replyMessage.signature().indexOf(s: expectedReplySignature) != 0) {
190 const auto errorMsg = "Unexpected reply signature: got \"%1\", expected \"%2\""_L1;
191 replyMessage = QDBusMessage::createError(
192 type: QDBusError::InvalidSignature,
193 msg: errorMsg.arg(args: replyMessage.signature(), args&: expectedReplySignature));
194
195 }
196}
197
198void QDBusPendingCallPrivate::waitForFinished()
199{
200 const auto locker = qt_scoped_lock(mutex);
201
202 if (replyMessage.type() != QDBusMessage::InvalidMessage)
203 return; // already finished
204
205 waitForFinishedCondition.wait(lockedMutex: &mutex);
206}
207
208/*!
209 Creates a copy of the \a other pending asynchronous call. Note
210 that both objects will refer to the same pending call.
211*/
212QDBusPendingCall::QDBusPendingCall(const QDBusPendingCall &other)
213 : d(other.d)
214{
215}
216
217/*!
218 \internal
219*/
220QDBusPendingCall::QDBusPendingCall(QDBusPendingCallPrivate *dd)
221 : d(dd)
222{
223 if (dd) {
224 bool r = dd->ref.deref();
225 Q_ASSERT(r);
226 Q_UNUSED(r);
227 }
228}
229
230/*!
231 Destroys this copy of the QDBusPendingCall object. If this copy is
232 also the last copy of a pending asynchronous call, the call will
233 be canceled and no further notifications will be received. There
234 will be no way of accessing the reply's contents when it arrives.
235*/
236QDBusPendingCall::~QDBusPendingCall()
237{
238 // d deleted by QExplicitlySharedDataPointer
239}
240
241
242/*!
243 Creates a copy of the \a other pending asynchronous call and drops
244 the reference to the previously-referenced call. Note that both
245 objects will refer to the same pending call after this function.
246
247 If this object contained the last reference of a pending
248 asynchronous call, the call will be canceled and no further
249 notifications will be received. There will be no way of accessing
250 the reply's contents when it arrives.
251*/
252QDBusPendingCall &QDBusPendingCall::operator=(const QDBusPendingCall &other)
253{
254 d = other.d;
255 return *this;
256}
257
258/*!
259 \fn void QDBusPendingCall::swap(QDBusPendingCall &other)
260 \since 5.0
261
262 Swaps this pending call instance with \a other. This function is
263 very fast and never fails.
264*/
265
266/*!
267 \fn bool QDBusPendingCallWatcher::isFinished() const
268
269 Returns \c true if the pending call has finished processing and the
270 reply has been received.
271
272 Note that this function only changes state if you call
273 waitForFinished() or if an external D-Bus event happens, which in
274 general only happens if you return to the event loop execution.
275
276 \sa QDBusPendingReply::isFinished()
277*/
278
279/*!
280 \fn template <typename... Types> bool QDBusPendingReply<Types...>::isFinished() const
281
282 Returns \c true if the pending call has finished processing and the
283 reply has been received. If this function returns \c true, the
284 isError(), error() and reply() methods should return valid
285 information.
286
287 Note that this function only changes state if you call
288 waitForFinished() or if an external D-Bus event happens, which in
289 general only happens if you return to the event loop execution.
290
291 \sa QDBusPendingCallWatcher::isFinished()
292*/
293
294bool QDBusPendingCall::isFinished() const
295{
296 if (!d)
297 return true; // considered finished
298
299 const auto locker = qt_scoped_lock(mutex&: d->mutex);
300 return d->replyMessage.type() != QDBusMessage::InvalidMessage;
301}
302
303void QDBusPendingCall::waitForFinished()
304{
305 if (d) d->waitForFinished();
306}
307
308/*!
309 \fn template <typename... Types> bool QDBusPendingReply<Types...>::isValid() const
310
311 Returns \c true if the reply contains a normal reply message, false
312 if it contains anything else.
313
314 If the pending call has not finished processing, this function
315 return false.
316*/
317bool QDBusPendingCall::isValid() const
318{
319 if (!d)
320 return false;
321 const auto locker = qt_scoped_lock(mutex&: d->mutex);
322 return d->replyMessage.type() == QDBusMessage::ReplyMessage;
323}
324
325/*!
326 \fn template <typename... Types> bool QDBusPendingReply<Types...>::isError() const
327
328 Returns \c true if the reply contains an error message, false if it
329 contains a normal method reply.
330
331 If the pending call has not finished processing, this function
332 also returns \c true.
333*/
334bool QDBusPendingCall::isError() const
335{
336 if (!d)
337 return true; // considered finished and an error
338 const auto locker = qt_scoped_lock(mutex&: d->mutex);
339 return d->replyMessage.type() == QDBusMessage::ErrorMessage;
340}
341
342/*!
343 \fn template <typename... Types> QDBusError QDBusPendingReply<Types...>::error() const
344
345 Retrieves the error content of the reply message, if it has
346 finished processing. If the reply message has not finished
347 processing or if it contains a normal reply message (non-error),
348 this function returns an invalid QDBusError.
349*/
350QDBusError QDBusPendingCall::error() const
351{
352 if (d) {
353 const auto locker = qt_scoped_lock(mutex&: d->mutex);
354 return QDBusError(d->replyMessage);
355 }
356
357 // not connected, return an error
358 QDBusError err = QDBusError(QDBusError::Disconnected,
359 QDBusUtil::disconnectedErrorMessage());
360 return err;
361}
362
363/*!
364 \fn template <typename... Types> QDBusMessage QDBusPendingReply<Types...>::reply() const
365
366 Retrieves the reply message received for the asynchronous call
367 that was sent, if it has finished processing. If the pending call
368 is not finished, this function returns a QDBusMessage of type
369 QDBusMessage::InvalidMessage.
370
371 After it has finished processing, the message type will either be
372 an error message or a normal method reply message.
373*/
374QDBusMessage QDBusPendingCall::reply() const
375{
376 if (!d)
377 return QDBusMessage::createError(err: error());
378 const auto locker = qt_scoped_lock(mutex&: d->mutex);
379 return d->replyMessage;
380}
381
382#if 0
383/*
384 Sets the slot \a member in object \a target to be called when the
385 reply arrives. The slot's parameter list must match the reply
386 message's arguments for it to be called.
387
388 It may, optionally, contain a QDBusMessage final parameter. If it
389 is present, the parameter will contain the reply message object.
390
391 The callback will not be called if the reply is an error message.
392
393 This function returns \c true if it could set the callback, false
394 otherwise. It is not a guarantee that the callback will be
395 called.
396
397 \warning QDBusPendingCall only supports one callback per pending
398 asynchronous call, even if multiple QDBusPendingCall
399 objects are referencing the same pending call.
400*/
401bool QDBusPendingCall::setReplyCallback(QObject *target, const char *member)
402{
403 if (!d)
404 return false;
405
406 return d->setReplyCallback(target, member);
407}
408#endif
409
410/*!
411 \since 4.6
412 Creates a QDBusPendingCall object based on the error condition
413 \a error. The resulting pending call object will be in the
414 "finished" state and QDBusPendingReply<Types...>::isError() will return true.
415
416 \sa fromCompletedCall()
417*/
418QDBusPendingCall QDBusPendingCall::fromError(const QDBusError &error)
419{
420 return fromCompletedCall(message: QDBusMessage::createError(err: error));
421}
422
423/*!
424 \since 4.6
425 Creates a QDBusPendingCall object based on the message \a msg.
426 The message must be of type QDBusMessage::ErrorMessage or
427 QDBusMessage::ReplyMessage (that is, a message that is typical
428 of a completed call).
429
430 This function is useful for code that requires simulating a pending
431 call, but that has already finished.
432
433 \sa fromError()
434*/
435QDBusPendingCall QDBusPendingCall::fromCompletedCall(const QDBusMessage &msg)
436{
437 QDBusPendingCallPrivate *d = nullptr;
438 if (msg.type() == QDBusMessage::ErrorMessage ||
439 msg.type() == QDBusMessage::ReplyMessage) {
440 d = new QDBusPendingCallPrivate(QDBusMessage(), nullptr);
441 d->replyMessage = msg;
442 d->ref.storeRelaxed(newValue: 1);
443 }
444
445 return QDBusPendingCall(d);
446}
447
448
449class QDBusPendingCallWatcherPrivate: public QObjectPrivate
450{
451public:
452 void _q_finished();
453
454 Q_DECLARE_PUBLIC(QDBusPendingCallWatcher)
455};
456
457inline void QDBusPendingCallWatcherPrivate::_q_finished()
458{
459 Q_Q(QDBusPendingCallWatcher);
460 emit q->finished(self: q);
461}
462
463/*!
464 Creates a QDBusPendingCallWatcher object to watch for replies on the
465 asynchronous pending call \a call and sets this object's parent
466 to \a parent.
467*/
468QDBusPendingCallWatcher::QDBusPendingCallWatcher(const QDBusPendingCall &call, QObject *parent)
469 : QObject(*new QDBusPendingCallWatcherPrivate, parent), QDBusPendingCall(call)
470{
471 if (d) { // QDBusPendingCall::d
472 const auto locker = qt_scoped_lock(mutex&: d->mutex);
473 if (!d->watcherHelper) {
474 d->watcherHelper = new QDBusPendingCallWatcherHelper;
475 if (d->replyMessage.type() != QDBusMessage::InvalidMessage) {
476 // cause a signal emission anyways
477 QMetaObject::invokeMethod(object: d->watcherHelper,
478 function: &QDBusPendingCallWatcherHelper::finished,
479 type: Qt::QueuedConnection);
480 }
481 }
482 d->watcherHelper->add(watcher: this);
483 }
484}
485
486/*!
487 Destroys this object. If this QDBusPendingCallWatcher object was the
488 last reference to the unfinished pending call, the call will be
489 canceled.
490*/
491QDBusPendingCallWatcher::~QDBusPendingCallWatcher()
492{
493}
494
495/*!
496 \fn void QDBusPendingCallWatcher::waitForFinished()
497
498 Suspends the execution of the calling thread until the reply is
499 received and processed. After this function returns, isFinished()
500 should return true, indicating the reply's contents are ready to
501 be processed.
502
503 \sa QDBusPendingReply::waitForFinished()
504*/
505void QDBusPendingCallWatcher::waitForFinished()
506{
507 if (d) {
508 d->waitForFinished();
509
510 // our signals were queued, so deliver them
511 QCoreApplication::sendPostedEvents(receiver: d->watcherHelper, event_type: QEvent::MetaCall);
512 QCoreApplication::sendPostedEvents(receiver: this, event_type: QEvent::MetaCall);
513 }
514}
515QT_END_NAMESPACE
516
517#include "moc_qdbuspendingcall_p.cpp"
518
519#endif // QT_NO_DBUS
520
521#include "moc_qdbuspendingcall.cpp"
522

source code of qtbase/src/dbus/qdbuspendingcall.cpp