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: &QDBusPendingCallWatcherHelper::finished, context: watcher,
98 slot: [watcher] { Q_EMIT watcher->finished(self: watcher); }, type: Qt::QueuedConnection);
99}
100
101QDBusPendingCallPrivate::~QDBusPendingCallPrivate()
102{
103 if (pending) {
104 q_dbus_pending_call_cancel(pending);
105 q_dbus_pending_call_unref(pending);
106 }
107 delete watcherHelper;
108}
109
110bool QDBusPendingCallPrivate::setReplyCallback(QObject *target, const char *member)
111{
112 receiver = target;
113 metaTypes.clear();
114 methodIdx = -1;
115 if (!target)
116 return true;; // unsetting
117
118 if (!member || !*member) {
119 // would not be able to deliver a reply
120 qWarning(msg: "QDBusPendingCall::setReplyCallback: error: cannot deliver a reply to %s::%s (%s)",
121 target ? target->metaObject()->className() : "(null)",
122 member ? member + 1 : "(null)",
123 target ? qPrintable(target->objectName()) : "no name");
124 return false;
125 }
126
127 QString errorMsg;
128 methodIdx = QDBusConnectionPrivate::findSlot(obj: target, normalizedName: member + 1, params&: metaTypes, errorMsg);
129 if (methodIdx == -1) {
130 QByteArray normalizedName = QMetaObject::normalizedSignature(method: member + 1);
131 methodIdx = QDBusConnectionPrivate::findSlot(obj: target, normalizedName, params&: metaTypes, errorMsg);
132 }
133 if (methodIdx == -1) {
134 // would not be able to deliver a reply
135 qWarning(msg: "QDBusPendingCall::setReplyCallback: error: cannot deliver a reply to %s::%s (%s) "
136 "because %s",
137 target->metaObject()->className(), member + 1, qPrintable(target->objectName()),
138 qPrintable(errorMsg));
139 return false;
140 }
141
142 // success
143 // construct the expected signature
144 int count = metaTypes.size() - 1;
145 if (count == 1 && metaTypes.at(i: 1) == QDBusMetaTypeId::message()) {
146 // wildcard slot, can receive anything, so don't set the signature
147 return true;
148 }
149
150 if (metaTypes.at(i: count) == QDBusMetaTypeId::message())
151 --count;
152
153 setMetaTypes(count, types: count ? metaTypes.constData() + 1 : nullptr);
154 return true;
155}
156
157void QDBusPendingCallPrivate::setMetaTypes(int count, const QMetaType *types)
158{
159 if (count == 0) {
160 expectedReplySignature = ""_L1; // not null
161 return;
162 }
163
164 QByteArray sig;
165 sig.reserve(asize: count + count / 2);
166 for (int i = 0; i < count; ++i) {
167 const char *typeSig = QDBusMetaType::typeToSignature(type: types[i]);
168 if (Q_UNLIKELY(!typeSig))
169 qFatal(msg: "QDBusPendingReply: type %s is not registered with QtDBus", types[i].name());
170 sig += typeSig;
171 }
172
173 expectedReplySignature = QString::fromLatin1(ba: sig);
174}
175
176void QDBusPendingCallPrivate::checkReceivedSignature()
177{
178 // MUST BE CALLED WITH A LOCKED MUTEX!
179
180 if (replyMessage.type() == QDBusMessage::InvalidMessage)
181 return; // not yet finished - no message to
182 // validate against
183 if (replyMessage.type() == QDBusMessage::ErrorMessage)
184 return; // we don't have to check the signature of an error reply
185
186 if (expectedReplySignature.isNull())
187 return; // no signature to validate against
188
189 // can't use startsWith here because a null string doesn't start or end with an empty string
190 if (replyMessage.signature().indexOf(s: expectedReplySignature) != 0) {
191 const auto errorMsg = "Unexpected reply signature: got \"%1\", expected \"%2\""_L1;
192 replyMessage = QDBusMessage::createError(
193 type: QDBusError::InvalidSignature,
194 msg: errorMsg.arg(args: replyMessage.signature(), args&: expectedReplySignature));
195
196 }
197}
198
199void QDBusPendingCallPrivate::waitForFinished()
200{
201 const auto locker = qt_scoped_lock(mutex);
202
203 if (replyMessage.type() != QDBusMessage::InvalidMessage)
204 return; // already finished
205
206 waitForFinishedCondition.wait(lockedMutex: &mutex);
207}
208
209void QDBusPendingCallPrivate::waitForFinishedWithGui()
210{
211 QEventLoop loop;
212
213 {
214 const auto locker = qt_scoped_lock(mutex);
215 if (replyMessage.type() != QDBusMessage::InvalidMessage)
216 return; // already finished
217
218 Q_ASSERT(!watcherHelper);
219 watcherHelper = new QDBusPendingCallWatcherHelper;
220 loop.connect(sender: watcherHelper, signal: &QDBusPendingCallWatcherHelper::reply, context: &loop,
221 slot: &QEventLoop::quit);
222 loop.connect(sender: watcherHelper, signal: &QDBusPendingCallWatcherHelper::error, context: &loop,
223 slot: &QEventLoop::quit);
224 }
225
226 loop.exec(flags: QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents);
227}
228
229/*!
230 Creates a copy of the \a other pending asynchronous call. Note
231 that both objects will refer to the same pending call.
232*/
233QDBusPendingCall::QDBusPendingCall(const QDBusPendingCall &other)
234 : d(other.d)
235{
236}
237
238/*!
239 \internal
240*/
241QDBusPendingCall::QDBusPendingCall(QDBusPendingCallPrivate *dd)
242 : d(dd)
243{
244 if (dd) {
245 bool r = dd->ref.deref();
246 Q_ASSERT(r);
247 }
248}
249
250/*!
251 Destroys this copy of the QDBusPendingCall object. If this copy is
252 also the last copy of a pending asynchronous call, the call will
253 be canceled and no further notifications will be received. There
254 will be no way of accessing the reply's contents when it arrives.
255*/
256QDBusPendingCall::~QDBusPendingCall()
257{
258 // d deleted by QExplicitlySharedDataPointer
259}
260
261
262/*!
263 Creates a copy of the \a other pending asynchronous call and drops
264 the reference to the previously-referenced call. Note that both
265 objects will refer to the same pending call after this function.
266
267 If this object contained the last reference of a pending
268 asynchronous call, the call will be canceled and no further
269 notifications will be received. There will be no way of accessing
270 the reply's contents when it arrives.
271*/
272QDBusPendingCall &QDBusPendingCall::operator=(const QDBusPendingCall &other)
273{
274 d = other.d;
275 return *this;
276}
277
278/*!
279 \fn void QDBusPendingCall::swap(QDBusPendingCall &other)
280 \since 5.0
281
282 Swaps this pending call instance with \a other. This function is
283 very fast and never fails.
284*/
285
286/*!
287 \fn bool QDBusPendingCallWatcher::isFinished() const
288
289 Returns \c true if the pending call has finished processing and the
290 reply has been received.
291
292 Note that this function only changes state if you call
293 waitForFinished() or if an external D-Bus event happens, which in
294 general only happens if you return to the event loop execution.
295
296 \sa QDBusPendingReply::isFinished()
297*/
298
299/*!
300 \fn template <typename... Types> bool QDBusPendingReply<Types...>::isFinished() const
301
302 Returns \c true if the pending call has finished processing and the
303 reply has been received. If this function returns \c true, the
304 isError(), error() and reply() methods should return valid
305 information.
306
307 Note that this function only changes state if you call
308 waitForFinished() or if an external D-Bus event happens, which in
309 general only happens if you return to the event loop execution.
310
311 \sa QDBusPendingCallWatcher::isFinished()
312*/
313
314bool QDBusPendingCall::isFinished() const
315{
316 if (!d)
317 return true; // considered finished
318
319 const auto locker = qt_scoped_lock(mutex&: d->mutex);
320 return d->replyMessage.type() != QDBusMessage::InvalidMessage;
321}
322
323void QDBusPendingCall::waitForFinished()
324{
325 if (d) d->waitForFinished();
326}
327
328/*!
329 \fn template <typename... Types> bool QDBusPendingReply<Types...>::isValid() const
330
331 Returns \c true if the reply contains a normal reply message, false
332 if it contains anything else.
333
334 If the pending call has not finished processing, this function
335 return false.
336*/
337bool QDBusPendingCall::isValid() const
338{
339 if (!d)
340 return false;
341 const auto locker = qt_scoped_lock(mutex&: d->mutex);
342 return d->replyMessage.type() == QDBusMessage::ReplyMessage;
343}
344
345/*!
346 \fn template <typename... Types> bool QDBusPendingReply<Types...>::isError() const
347
348 Returns \c true if the reply contains an error message, false if it
349 contains a normal method reply.
350
351 If the pending call has not finished processing, this function
352 also returns \c true.
353*/
354bool QDBusPendingCall::isError() const
355{
356 if (!d)
357 return true; // considered finished and an error
358 const auto locker = qt_scoped_lock(mutex&: d->mutex);
359 return d->replyMessage.type() == QDBusMessage::ErrorMessage;
360}
361
362/*!
363 \fn template <typename... Types> QDBusError QDBusPendingReply<Types...>::error() const
364
365 Retrieves the error content of the reply message, if it has
366 finished processing. If the reply message has not finished
367 processing or if it contains a normal reply message (non-error),
368 this function returns an invalid QDBusError.
369*/
370QDBusError QDBusPendingCall::error() const
371{
372 if (d) {
373 const auto locker = qt_scoped_lock(mutex&: d->mutex);
374 return QDBusError(d->replyMessage);
375 }
376
377 // not connected, return an error
378 QDBusError err = QDBusError(QDBusError::Disconnected,
379 QDBusUtil::disconnectedErrorMessage());
380 return err;
381}
382
383/*!
384 \fn template <typename... Types> QDBusMessage QDBusPendingReply<Types...>::reply() const
385
386 Retrieves the reply message received for the asynchronous call
387 that was sent, if it has finished processing. If the pending call
388 is not finished, this function returns a QDBusMessage of type
389 QDBusMessage::InvalidMessage.
390
391 After it has finished processing, the message type will either be
392 an error message or a normal method reply message.
393*/
394QDBusMessage QDBusPendingCall::reply() const
395{
396 if (!d)
397 return QDBusMessage::createError(err: error());
398 const auto locker = qt_scoped_lock(mutex&: d->mutex);
399 return d->replyMessage;
400}
401
402#if 0
403/*
404 Sets the slot \a member in object \a target to be called when the
405 reply arrives. The slot's parameter list must match the reply
406 message's arguments for it to be called.
407
408 It may, optionally, contain a QDBusMessage final parameter. If it
409 is present, the parameter will contain the reply message object.
410
411 The callback will not be called if the reply is an error message.
412
413 This function returns \c true if it could set the callback, false
414 otherwise. It is not a guarantee that the callback will be
415 called.
416
417 \warning QDBusPendingCall only supports one callback per pending
418 asynchronous call, even if multiple QDBusPendingCall
419 objects are referencing the same pending call.
420*/
421bool QDBusPendingCall::setReplyCallback(QObject *target, const char *member)
422{
423 if (!d)
424 return false;
425
426 return d->setReplyCallback(target, member);
427}
428#endif
429
430/*!
431 \since 4.6
432 Creates a QDBusPendingCall object based on the error condition
433 \a error. The resulting pending call object will be in the
434 "finished" state and QDBusPendingReply<Types...>::isError() will return true.
435
436 \sa fromCompletedCall()
437*/
438QDBusPendingCall QDBusPendingCall::fromError(const QDBusError &error)
439{
440 return fromCompletedCall(message: QDBusMessage::createError(err: error));
441}
442
443/*!
444 \since 4.6
445 Creates a QDBusPendingCall object based on the message \a msg.
446 The message must be of type QDBusMessage::ErrorMessage or
447 QDBusMessage::ReplyMessage (that is, a message that is typical
448 of a completed call).
449
450 This function is useful for code that requires simulating a pending
451 call, but that has already finished.
452
453 \sa fromError()
454*/
455QDBusPendingCall QDBusPendingCall::fromCompletedCall(const QDBusMessage &msg)
456{
457 QDBusPendingCallPrivate *d = nullptr;
458 if (msg.type() == QDBusMessage::ErrorMessage ||
459 msg.type() == QDBusMessage::ReplyMessage) {
460 d = new QDBusPendingCallPrivate(QDBusMessage(), nullptr);
461 d->replyMessage = msg;
462 d->ref.storeRelaxed(newValue: 1);
463 }
464
465 return QDBusPendingCall(d);
466}
467
468/*!
469 Creates a QDBusPendingCallWatcher object to watch for replies on the
470 asynchronous pending call \a call and sets this object's parent
471 to \a parent.
472*/
473QDBusPendingCallWatcher::QDBusPendingCallWatcher(const QDBusPendingCall &call, QObject *parent)
474 : QObject(parent), QDBusPendingCall(call)
475{
476 if (d) { // QDBusPendingCall::d
477 const auto locker = qt_scoped_lock(mutex&: d->mutex);
478 if (!d->watcherHelper) {
479 d->watcherHelper = new QDBusPendingCallWatcherHelper;
480 if (d->replyMessage.type() != QDBusMessage::InvalidMessage) {
481 // cause a signal emission anyways
482 QMetaObject::invokeMethod(object: d->watcherHelper,
483 function: &QDBusPendingCallWatcherHelper::finished,
484 type: Qt::QueuedConnection);
485 }
486 }
487 d->watcherHelper->add(watcher: this);
488 }
489}
490
491/*!
492 Destroys this object. If this QDBusPendingCallWatcher object was the
493 last reference to the unfinished pending call, the call will be
494 canceled.
495*/
496QDBusPendingCallWatcher::~QDBusPendingCallWatcher()
497{
498}
499
500/*!
501 \fn void QDBusPendingCallWatcher::waitForFinished()
502
503 Suspends the execution of the calling thread until the reply is
504 received and processed. After this function returns, isFinished()
505 should return true, indicating the reply's contents are ready to
506 be processed.
507
508 \sa QDBusPendingReply::waitForFinished()
509*/
510void QDBusPendingCallWatcher::waitForFinished()
511{
512 if (d) {
513 d->waitForFinished();
514
515 // our signals were queued, so deliver them
516 QCoreApplication::sendPostedEvents(receiver: d->watcherHelper, event_type: QEvent::MetaCall);
517 QCoreApplication::sendPostedEvents(receiver: this, event_type: QEvent::MetaCall);
518 }
519}
520QT_END_NAMESPACE
521
522#include "moc_qdbuspendingcall_p.cpp"
523
524#endif // QT_NO_DBUS
525
526#include "moc_qdbuspendingcall.cpp"
527

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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