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