async-dbus-proxy.cpp
00001 /*
00002  * This file is part of signon
00003  *
00004  * Copyright (C) 2009-2010 Nokia Corporation.
00005  * Copyright (C) 2013-2015 Canonical Ltd.
00006  *
00007  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
00008  *
00009  * This library is free software; you can redistribute it and/or
00010  * modify it under the terms of the GNU Lesser General Public License
00011  * version 2.1 as published by the Free Software Foundation.
00012  *
00013  * This library is distributed in the hope that it will be useful, but
00014  * WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
00021  * 02110-1301 USA
00022  */
00023 
00024 #include "async-dbus-proxy.h"
00025 
00026 #include <QDBusConnection>
00027 #include <QDBusObjectPath>
00028 #include <QDBusPendingCallWatcher>
00029 #include <QDebug>
00030 #include <QMetaMethod>
00031 #include <QMetaType>
00032 
00033 #include "connection-manager.h"
00034 #include "dbusinterface.h"
00035 #include "debug.h"
00036 #include "libsignoncommon.h"
00037 #include "signond/signoncommon.h"
00038 
00039 using namespace SignOn;
00040 
00041 namespace SignOn {
00042 
00043 class Connection
00044 {
00045 public:
00046     Connection(const char *name, QObject *receiver, const char *slot):
00047         m_name(name),
00048         m_receiver(receiver),
00049         m_slot(slot)
00050     {
00051     }
00052     ~Connection() {}
00053 
00054     const char *m_name;
00055     QObject *m_receiver;
00056     const char *m_slot;
00057 };
00058 
00059 } // namespace
00060 
00061 PendingCall::PendingCall(const QString &method,
00062                          const QList<QVariant> &args,
00063                          QObject *parent):
00064     QObject(parent),
00065     m_method(method),
00066     m_args(args),
00067     m_watcher(0),
00068     m_interfaceWasDestroyed(false)
00069 {
00070 }
00071 
00072 PendingCall::~PendingCall()
00073 {
00074 }
00075 
00076 bool PendingCall::cancel()
00077 {
00078     if (m_watcher) {
00079         // Too late, can't cancel
00080         return false;
00081     }
00082     Q_EMIT finished(0);
00083     return true;
00084 }
00085 
00086 void PendingCall::doCall(QDBusAbstractInterface *interface)
00087 {
00088     QDBusPendingCall call =
00089         interface->asyncCallWithArgumentList(m_method, m_args);
00090     m_watcher = new QDBusPendingCallWatcher(call, this);
00091     QObject::connect(m_watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
00092                      this, SLOT(onFinished(QDBusPendingCallWatcher*)));
00093     /* Check if the interface gets destroyed while our call executes */
00094     m_interfaceWasDestroyed = false;
00095     QObject::connect(interface, SIGNAL(destroyed()),
00096                      this, SLOT(onInterfaceDestroyed()));
00097 }
00098 
00099 void PendingCall::fail(const QDBusError &err)
00100 {
00101     Q_EMIT error(err);
00102     Q_EMIT finished(0);
00103 }
00104 
00105 void PendingCall::onFinished(QDBusPendingCallWatcher *watcher)
00106 {
00107     /* Check if the call failed because the interface became invalid; if
00108      * so, emit a signal to instruct the AsyncDBusProxy to re-queue this
00109      * operation. */
00110     if (m_interfaceWasDestroyed && watcher->isError()) {
00111         QDBusError::ErrorType type = watcher->error().type();
00112         if (type == QDBusError::Disconnected ||
00113             type == QDBusError::UnknownObject) {
00114             TRACE() << "emitting retry signal";
00115             Q_EMIT requeueRequested();
00116             return;
00117         }
00118     }
00119 
00120     if (watcher->isError()) {
00121         Q_EMIT error(watcher->error());
00122     } else {
00123         Q_EMIT success(watcher);
00124     }
00125     Q_EMIT finished(watcher);
00126 }
00127 
00128 void PendingCall::onInterfaceDestroyed()
00129 {
00130     /* If the interface is destroyed during the lifetime of the call, this can
00131      * be because the remote object got destroyed or the D-Bus connection
00132      * dropped. In either case, we might have to re-queue our method call.
00133      *
00134      * This is done in the onFinished() slot; here we just record the event.
00135      */
00136     m_interfaceWasDestroyed = true;
00137 }
00138 
00139 AsyncDBusProxy::AsyncDBusProxy(const QString &service,
00140                                const char *interface,
00141                                QObject *clientObject):
00142     m_serviceName(service),
00143     m_interfaceName(interface),
00144     m_connection(NULL),
00145     m_clientObject(clientObject),
00146     m_interface(NULL),
00147     m_status(Incomplete)
00148 {
00149 }
00150 
00151 AsyncDBusProxy::~AsyncDBusProxy()
00152 {
00153     qDeleteAll(m_connectionsQueue);
00154     m_connectionsQueue.clear();
00155 
00156     delete m_connection;
00157 }
00158 
00159 void AsyncDBusProxy::setStatus(Status status)
00160 {
00161     m_status = status;
00162 
00163     if (status == Ready) {
00164         /* connect the signals and execute all pending methods */
00165         Q_FOREACH(Connection *connection, m_connectionsQueue) {
00166             m_interface->connect(connection->m_name,
00167                                  connection->m_receiver,
00168                                  connection->m_slot);
00169         }
00170 
00171         Q_FOREACH(PendingCall *call, m_operationsQueue) {
00172             call->doCall(m_interface);
00173         }
00174         m_operationsQueue.clear();
00175     } else if (status == Invalid) {
00176         /* signal error on all operations */
00177         Q_FOREACH(PendingCall *call, m_operationsQueue) {
00178             call->fail(m_lastError);
00179         }
00180         m_operationsQueue.clear();
00181     }
00182 }
00183 
00184 void AsyncDBusProxy::update()
00185 {
00186     if (m_interface != NULL) {
00187         delete m_interface;
00188         m_interface = 0;
00189     }
00190 
00191     if (m_connection == NULL || m_path.isEmpty()) {
00192         setStatus(Incomplete);
00193         return;
00194     }
00195 
00196     if (!m_connection->isConnected()) {
00197         setError(m_connection->lastError());
00198         return;
00199     }
00200 
00201     m_interface = new DBusInterface(m_serviceName,
00202                                     m_path,
00203                                     m_interfaceName,
00204                                     *m_connection,
00205                                     this);
00206     setStatus(Ready);
00207 }
00208 
00209 void AsyncDBusProxy::setConnection(const QDBusConnection &connection)
00210 {
00211     delete m_connection;
00212     m_connection = new QDBusConnection(connection);
00213     update();
00214 }
00215 
00216 void AsyncDBusProxy::setDisconnected()
00217 {
00218     TRACE();
00219     delete m_connection;
00220     m_connection = 0;
00221     /* The daemon is dead, so certainly the object paths are also invalid */
00222     m_path = QString();
00223     update();
00224 }
00225 
00226 void AsyncDBusProxy::setObjectPath(const QDBusObjectPath &objectPath)
00227 {
00228     Q_ASSERT(m_path.isEmpty() || objectPath.path().isEmpty());
00229     m_path = objectPath.path();
00230     update();
00231 }
00232 
00233 void AsyncDBusProxy::setError(const QDBusError &error)
00234 {
00235     TRACE() << error;
00236     m_lastError = error;
00237     setStatus(Invalid);
00238 }
00239 
00240 PendingCall *AsyncDBusProxy::queueCall(const QString &method,
00241                                        const QList<QVariant> &args,
00242                                        const char *replySlot,
00243                                        const char *errorSlot)
00244 {
00245     return queueCall(method, args, m_clientObject, replySlot, errorSlot);
00246 }
00247 
00248 PendingCall *AsyncDBusProxy::queueCall(const QString &method,
00249                                        const QList<QVariant> &args,
00250                                        QObject *receiver,
00251                                        const char *replySlot,
00252                                        const char *errorSlot)
00253 {
00254     PendingCall *call = new PendingCall(method, args, this);
00255     QObject::connect(call, SIGNAL(finished(QDBusPendingCallWatcher*)),
00256                      this, SLOT(onCallFinished(QDBusPendingCallWatcher*)));
00257     QObject::connect(call, SIGNAL(requeueRequested()),
00258                      this, SLOT(onRequeueRequested()));
00259 
00260     if (errorSlot) {
00261         QObject::connect(call, SIGNAL(error(const QDBusError&)),
00262                          receiver, errorSlot);
00263         if (replySlot) {
00264             QObject::connect(call, SIGNAL(success(QDBusPendingCallWatcher*)),
00265                              receiver, replySlot);
00266         }
00267     } else if (replySlot) {
00268         QObject::connect(call, SIGNAL(finished(QDBusPendingCallWatcher*)),
00269                          receiver, replySlot);
00270     }
00271 
00272     if (m_status == Ready) {
00273         call->doCall(m_interface);
00274     } else if (m_status == Incomplete) {
00275         enqueue(call);
00276     } else {
00277         QMetaObject::invokeMethod(call, "fail",
00278                                   Qt::QueuedConnection,
00279                                   Q_ARG(QDBusError, m_lastError));
00280     }
00281     return call;
00282 }
00283 
00284 bool AsyncDBusProxy::connect(const char *name,
00285                              QObject *receiver,
00286                              const char *slot)
00287 {
00288     /* Remember all the connections anyway, because we'll re-play them if we
00289      * disconnect and reconnect again */
00290     Connection *connection = new Connection(name, receiver, slot);
00291     m_connectionsQueue.enqueue(connection);
00292 
00293     if (m_status == Ready) {
00294         return m_interface->connect(name, receiver, slot);
00295     }
00296     return true;
00297 }
00298 
00299 void AsyncDBusProxy::enqueue(PendingCall *call)
00300 {
00301     m_operationsQueue.enqueue(call);
00302     if (!m_connection) {
00303         Q_EMIT connectionNeeded();
00304     }
00305     if (m_path.isEmpty()) {
00306         Q_EMIT objectPathNeeded();
00307     }
00308 }
00309 
00310 void AsyncDBusProxy::onCallFinished(QDBusPendingCallWatcher *watcher)
00311 {
00312     Q_UNUSED(watcher);
00313     PendingCall *call = qobject_cast<PendingCall*>(sender());
00314     m_operationsQueue.removeOne(call);
00315     call->deleteLater();
00316 }
00317 
00318 void AsyncDBusProxy::onRequeueRequested()
00319 {
00320     PendingCall *call = qobject_cast<PendingCall*>(sender());
00321     enqueue(call);
00322 }
00323 
00324 SignondAsyncDBusProxy::SignondAsyncDBusProxy(const char *interface,
00325                                              QObject *clientObject):
00326     AsyncDBusProxy(SIGNOND_SERVICE, interface, clientObject)
00327 {
00328     setupConnection();
00329 }
00330 
00331 SignondAsyncDBusProxy::~SignondAsyncDBusProxy()
00332 {
00333 }
00334 
00335 void SignondAsyncDBusProxy::setupConnection()
00336 {
00337     ConnectionManager *connManager = ConnectionManager::instance();
00338     QObject::connect(connManager, SIGNAL(connected(const QDBusConnection&)),
00339                      this, SLOT(setConnection(const QDBusConnection&)));
00340     QObject::connect(connManager, SIGNAL(disconnected()),
00341                      this, SLOT(setDisconnected()));
00342     QObject::connect(this, SIGNAL(connectionNeeded()),
00343                      connManager, SLOT(connect()));
00344     if (connManager->hasConnection()) {
00345         setConnection(connManager->connection());
00346     }
00347 }