source: trunk/src/dbus/qdbuspendingcall.cpp@ 769

Last change on this file since 769 was 769, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.3 sources from branches/vendor/nokia/qt.

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