summaryrefslogtreecommitdiffstats
path: root/src/dbus/qdbuspendingcall.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dbus/qdbuspendingcall.cpp')
-rw-r--r--src/dbus/qdbuspendingcall.cpp539
1 files changed, 539 insertions, 0 deletions
diff --git a/src/dbus/qdbuspendingcall.cpp b/src/dbus/qdbuspendingcall.cpp
new file mode 100644
index 0000000000..3ebd149ed3
--- /dev/null
+++ b/src/dbus/qdbuspendingcall.cpp
@@ -0,0 +1,539 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdbuspendingcall.h"
+#include "qdbuspendingcall_p.h"
+
+#include "qdbusconnection_p.h"
+#include "qdbusmetatype_p.h"
+#include "qcoreapplication.h"
+#include "qcoreevent.h"
+#include <private/qobject_p.h>
+
+#ifndef QT_NO_DBUS
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QDBusPendingCall
+ \inmodule QtDBus
+ \since 4.5
+
+ \brief The QDBusPendingCall class refers to one pending asynchronous call
+
+ A QDBusPendingCall object is a reference to a method call that was
+ sent over D-Bus without waiting for a reply. QDBusPendingCall is an
+ opaque type, meant to be used as a handle for a pending reply.
+
+ In most programs, the QDBusPendingCall class will not be used
+ directly. It can be safely replaced with the template-based
+ QDBusPendingReply, in order to access the contents of the reply or
+ wait for it to be complete.
+
+ The QDBusPendingCallWatcher class allows one to connect to a signal
+ that will indicate when the reply has arrived or if the call has
+ timed out. It also provides the
+ QDBusPendingCallWatcher::waitForFinished() method which will suspend
+ the execution of the program until the reply has arrived.
+
+ \note If you create a copy of a QDBusPendingCall object, all
+ information will be shared among the many copies. Therefore,
+ QDBusPendingCall is an explicitly-shared object and does not
+ provide a method of detaching the copies (since they refer
+ to the same pending call)
+
+ \sa QDBusPendingReply, QDBusPendingCallWatcher,
+ QDBusAbstractInterface::asyncCall()
+*/
+
+/*!
+ \class QDBusPendingCallWatcher
+ \inmodule QtDBus
+ \since 4.5
+
+ \brief The QDBusPendingCallWatcher class provides a convenient way for
+ waiting for asynchronous replies
+
+ The QDBusPendingCallWatcher provides the finished() signal that will be
+ emitted when a reply arrives.
+
+ It is usually used like the following example:
+
+ \snippet doc/src/snippets/code/src.qdbus.qdbuspendingcall.cpp 0
+
+ Note that it is not necessary to keep the original QDBusPendingCall
+ object around since QDBusPendingCallWatcher inherits from that class
+ too.
+
+ The slot connected to by the above code could be something similar
+ to the following:
+
+ \snippet doc/src/snippets/code/src.qdbus.qdbuspendingcall.cpp 1
+
+ Note the use of QDBusPendingReply to validate the argument types in
+ the reply. If the reply did not contain exactly two arguments
+ (one string and one QByteArray), QDBusPendingReply::isError() will
+ return true.
+
+ \sa QDBusPendingReply, QDBusAbstractInterface::asyncCall()
+*/
+
+/*!
+ \fn void QDBusPendingCallWatcher::finished(QDBusPendingCallWatcher *self)
+
+ This signal is emitted when the pending call has finished and its
+ reply is available. The \a self parameter is a pointer to the
+ object itself, passed for convenience so that the slot can access
+ the properties and determine the contents of the reply.
+*/
+
+void QDBusPendingCallWatcherHelper::add(QDBusPendingCallWatcher *watcher)
+{
+ connect(this, SIGNAL(finished()), watcher, SLOT(_q_finished()), Qt::QueuedConnection);
+}
+
+QDBusPendingCallPrivate::~QDBusPendingCallPrivate()
+{
+ if (pending) {
+ q_dbus_pending_call_cancel(pending);
+ q_dbus_pending_call_unref(pending);
+ }
+ delete watcherHelper;
+}
+
+bool QDBusPendingCallPrivate::setReplyCallback(QObject *target, const char *member)
+{
+ receiver = target;
+ metaTypes.clear();
+ methodIdx = -1;
+ if (!target)
+ return true;; // unsetting
+
+ if (!member || !*member) {
+ // would not be able to deliver a reply
+ qWarning("QDBusPendingCall::setReplyCallback: error: cannot deliver a reply to %s::%s (%s)",
+ target ? target->metaObject()->className() : "(null)",
+ member ? member + 1 : "(null)",
+ target ? qPrintable(target->objectName()) : "no name");
+ return false;
+ }
+
+ methodIdx = QDBusConnectionPrivate::findSlot(target, member + 1, metaTypes);
+ if (methodIdx == -1) {
+ QByteArray normalizedName = QMetaObject::normalizedSignature(member + 1);
+ methodIdx = QDBusConnectionPrivate::findSlot(target, normalizedName, metaTypes);
+ }
+ if (methodIdx == -1) {
+ // would not be able to deliver a reply
+ qWarning("QDBusPendingCall::setReplyCallback: error: cannot deliver a reply to %s::%s (%s)",
+ target->metaObject()->className(),
+ member + 1, qPrintable(target->objectName()));
+ return false;
+ }
+
+ // success
+ // construct the expected signature
+ int count = metaTypes.count() - 1;
+ if (count == 1 && metaTypes.at(1) == QDBusMetaTypeId::message) {
+ // wildcard slot, can receive anything, so don't set the signature
+ return true;
+ }
+
+ if (metaTypes.at(count) == QDBusMetaTypeId::message)
+ --count;
+
+ // QList<int> is actually a vector
+ // kids, don't try this at home
+ setMetaTypes(count, count ? &metaTypes.at(1) : 0);
+ return true;
+}
+
+void QDBusPendingCallPrivate::setMetaTypes(int count, const int *types)
+{
+ expectedReplyCount = count;
+ if (count == 0) {
+ expectedReplySignature = QLatin1String(""); // not null
+ return;
+ }
+
+ QByteArray sig;
+ sig.reserve(count + count / 2);
+ for (int i = 0; i < count; ++i) {
+ const char *typeSig = QDBusMetaType::typeToSignature(types[i]);
+ if (!typeSig) {
+ qFatal("QDBusPendingReply: type %s is not registered with QtDBus",
+ QMetaType::typeName(types[i]));
+ }
+ sig += typeSig;
+ }
+
+ expectedReplySignature = QString::fromLatin1(sig);
+}
+
+void QDBusPendingCallPrivate::checkReceivedSignature()
+{
+ // MUST BE CALLED WITH A LOCKED MUTEX!
+
+ if (replyMessage.type() == QDBusMessage::InvalidMessage)
+ return; // not yet finished - no message to
+ // validate against
+ if (replyMessage.type() == QDBusMessage::ErrorMessage)
+ return; // we don't have to check the signature of an error reply
+
+ if (expectedReplySignature.isNull())
+ return; // no signature to validate against
+
+ // can't use startsWith here because a null string doesn't start or end with an empty string
+ if (!replyMessage.signature().indexOf(expectedReplySignature) == 0) {
+ QString errorMsg = QLatin1String("Unexpected reply signature: got \"%1\", "
+ "expected \"%2\"");
+ replyMessage = QDBusMessage::createError(
+ QDBusError::InvalidSignature,
+ errorMsg.arg(replyMessage.signature(), expectedReplySignature));
+
+ }
+}
+
+void QDBusPendingCallPrivate::waitForFinished()
+{
+ QMutexLocker locker(&mutex);
+
+ if (replyMessage.type() != QDBusMessage::InvalidMessage)
+ return; // already finished
+
+ connection->waitForFinished(this);
+}
+
+/*!
+ Creates a copy of the \a other pending asynchronous call. Note
+ that both objects will refer to the same pending call.
+*/
+QDBusPendingCall::QDBusPendingCall(const QDBusPendingCall &other)
+ : d(other.d)
+{
+}
+
+/*!
+ \internal
+*/
+QDBusPendingCall::QDBusPendingCall(QDBusPendingCallPrivate *dd)
+ : d(dd)
+{
+}
+
+/*!
+ Destroys this copy of the QDBusPendingCall object. If this copy is
+ also the last copy of a pending asynchronous call, the call will
+ be canceled and no further notifications will be received. There
+ will be no way of accessing the reply's contents when it arrives.
+*/
+QDBusPendingCall::~QDBusPendingCall()
+{
+ // d deleted by QExplicitlySharedDataPointer
+}
+
+
+/*!
+ Creates a copy of the \a other pending asynchronous call and drops
+ the reference to the previously-referenced call. Note that both
+ objects will refer to the same pending call after this function.
+
+ If this object contained the last reference of a pending
+ asynchronous call, the call will be canceled and no further
+ notifications will be received. There will be no way of accessing
+ the reply's contents when it arrives.
+*/
+QDBusPendingCall &QDBusPendingCall::operator=(const QDBusPendingCall &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ \fn bool QDBusPendingCallWatcher::isFinished() const
+
+ Returns true if the pending call has finished processing and the
+ reply has been received.
+
+ Note that this function only changes state if you call
+ waitForFinished() or if an external D-Bus event happens, which in
+ general only happens if you return to the event loop execution.
+
+ \sa QDBusPendingReply::isFinished()
+*/
+/*!
+ \fn bool QDBusPendingReply::isFinished() const
+
+ Returns true if the pending call has finished processing and the
+ reply has been received. If this function returns true, the
+ isError(), error() and reply() methods should return valid
+ information.
+
+ Note that this function only changes state if you call
+ waitForFinished() or if an external D-Bus event happens, which in
+ general only happens if you return to the event loop execution.
+
+ \sa QDBusPendingCallWatcher::isFinished()
+*/
+
+bool QDBusPendingCall::isFinished() const
+{
+ if (!d)
+ return true; // considered finished
+
+ QMutexLocker locker(&d->mutex);
+ return d->replyMessage.type() != QDBusMessage::InvalidMessage;
+}
+
+void QDBusPendingCall::waitForFinished()
+{
+ if (d) d->waitForFinished();
+}
+
+/*!
+ \fn bool QDBusPendingReply::isValid() const
+
+ Returns true if the reply contains a normal reply message, false
+ if it contains anything else.
+
+ If the pending call has not finished processing, this function
+ return false.
+*/
+bool QDBusPendingCall::isValid() const
+{
+ if (!d)
+ return false;
+ QMutexLocker locker(&d->mutex);
+ return d->replyMessage.type() == QDBusMessage::ReplyMessage;
+}
+
+/*!
+ \fn bool QDBusPendingReply::isError() const
+
+ Returns true if the reply contains an error message, false if it
+ contains a normal method reply.
+
+ If the pending call has not finished processing, this function
+ also returns true.
+*/
+bool QDBusPendingCall::isError() const
+{
+ if (!d)
+ return true; // considered finished and an error
+ QMutexLocker locker(&d->mutex);
+ return d->replyMessage.type() == QDBusMessage::ErrorMessage;
+}
+
+/*!
+ \fn QDBusError QDBusPendingReply::error() const
+
+ Retrieves the error content of the reply message, if it has
+ finished processing. If the reply message has not finished
+ processing or if it contains a normal reply message (non-error),
+ this function returns an invalid QDBusError.
+*/
+QDBusError QDBusPendingCall::error() const
+{
+ if (d) {
+ QMutexLocker locker(&d->mutex);
+ return d->replyMessage;
+ }
+
+ // not connected, return an error
+ QDBusError err = QDBusError(QDBusError::Disconnected,
+ QLatin1String("Not connected to D-Bus server"));
+ return err;
+}
+
+/*!
+ \fn QDBusMessage QDBusPendingReply::reply() const
+
+ Retrieves the reply message received for the asynchronous call
+ that was sent, if it has finished processing. If the pending call
+ is not finished, this function returns a QDBusMessage of type
+ QDBusMessage::InvalidMessage.
+
+ After it has finished processing, the message type will either be
+ an error message or a normal method reply message.
+*/
+QDBusMessage QDBusPendingCall::reply() const
+{
+ if (!d)
+ return QDBusMessage::createError(error());
+ QMutexLocker locker(&d->mutex);
+ return d->replyMessage;
+}
+
+#if 0
+/*!
+ Sets the slot \a member in object \a target to be called when the
+ reply arrives. The slot's parameter list must match the reply
+ message's arguments for it to be called.
+
+ It may, optionally, contain a QDBusMessage final parameter. If it
+ is present, the parameter will contain the reply message object.
+
+ The callback will not be called if the reply is an error message.
+
+ This function returns true if it could set the callback, false
+ otherwise. It is not a guarantee that the callback will be
+ called.
+
+ \warning QDBusPendingCall only supports one callback per pending
+ asynchronous call, even if multiple QDBusPendingCall
+ objects are referencing the same pending call.
+*/
+bool QDBusPendingCall::setReplyCallback(QObject *target, const char *member)
+{
+ if (!d)
+ return false;
+
+ return d->setReplyCallback(target, member);
+}
+#endif
+
+/*!
+ \since 4.6
+ Creates a QDBusPendingCall object based on the error condition
+ \a error. The resulting pending call object will be in the
+ "finished" state and QDBusPendingReply::isError() will return true.
+
+ \sa fromCompletedCall()
+*/
+QDBusPendingCall QDBusPendingCall::fromError(const QDBusError &error)
+{
+ return fromCompletedCall(QDBusMessage::createError(error));
+}
+
+/*!
+ \since 4.6
+ Creates a QDBusPendingCall object based on the message \a msg.
+ The message must be of type QDBusMessage::ErrorMessage or
+ QDBusMessage::ReplyMessage (that is, a message that is typical
+ of a completed call).
+
+ This function is useful for code that requires simulating a pending
+ call, but that has already finished.
+
+ \sa fromError()
+*/
+QDBusPendingCall QDBusPendingCall::fromCompletedCall(const QDBusMessage &msg)
+{
+ QDBusPendingCallPrivate *d = 0;
+ if (msg.type() == QDBusMessage::ErrorMessage ||
+ msg.type() == QDBusMessage::ReplyMessage) {
+ d = new QDBusPendingCallPrivate(QDBusMessage(), 0);
+ d->replyMessage = msg;
+ }
+
+ return QDBusPendingCall(d);
+}
+
+
+class QDBusPendingCallWatcherPrivate: public QObjectPrivate
+{
+public:
+ void _q_finished();
+
+ Q_DECLARE_PUBLIC(QDBusPendingCallWatcher)
+};
+
+inline void QDBusPendingCallWatcherPrivate::_q_finished()
+{
+ Q_Q(QDBusPendingCallWatcher);
+ emit q->finished(q);
+}
+
+/*!
+ Creates a QDBusPendingCallWatcher object to watch for replies on the
+ asynchronous pending call \a call and sets this object's parent
+ to \a parent.
+*/
+QDBusPendingCallWatcher::QDBusPendingCallWatcher(const QDBusPendingCall &call, QObject *parent)
+ : QObject(*new QDBusPendingCallWatcherPrivate, parent), QDBusPendingCall(call)
+{
+ if (d) { // QDBusPendingCall::d
+ QMutexLocker locker(&d->mutex);
+ if (!d->watcherHelper) {
+ d->watcherHelper = new QDBusPendingCallWatcherHelper;
+ if (d->replyMessage.type() != QDBusMessage::InvalidMessage) {
+ // cause a signal emission anyways
+ QMetaObject::invokeMethod(d->watcherHelper, "finished", Qt::QueuedConnection);
+ }
+ }
+ d->watcherHelper->add(this);
+ }
+}
+
+/*!
+ Destroys this object. If this QDBusPendingCallWatcher object was the
+ last reference to the unfinished pending call, the call will be
+ canceled.
+*/
+QDBusPendingCallWatcher::~QDBusPendingCallWatcher()
+{
+}
+
+/*!
+ \fn void QDBusPendingCallWatcher::waitForFinished()
+
+ Suspends the execution of the calling thread until the reply is
+ received and processed. After this function returns, isFinished()
+ should return true, indicating the reply's contents are ready to
+ be processed.
+
+ \sa QDBusPendingReply::waitForFinished()
+*/
+void QDBusPendingCallWatcher::waitForFinished()
+{
+ if (d) {
+ d->waitForFinished();
+
+ // our signals were queued, so deliver them
+ QCoreApplication::sendPostedEvents(d->watcherHelper, QEvent::MetaCall);
+ QCoreApplication::sendPostedEvents(this, QEvent::MetaCall);
+ }
+}
+QT_END_NAMESPACE
+
+#endif // QT_NO_DBUS
+
+#include "moc_qdbuspendingcall.cpp"