My Project
async-dbus-proxy.cpp
1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation.
5  * Copyright (C) 2013-2016 Canonical Ltd.
6  *
7  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1 as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  */
23 
24 #include "async-dbus-proxy.h"
25 
26 #include <QDBusConnection>
27 #include <QDBusObjectPath>
28 #include <QDBusPendingCallWatcher>
29 #include <QDebug>
30 #include <QMetaMethod>
31 #include <QMetaType>
32 
33 #include "connection-manager.h"
34 #include "dbusinterface.h"
35 #include "debug.h"
36 #include "libsignoncommon.h"
37 #include "signond/signoncommon.h"
38 
39 using namespace SignOn;
40 
41 namespace SignOn {
42 
43 class Connection
44 {
45 public:
46  Connection(const char *name, QObject *receiver, const char *slot):
47  m_name(name),
48  m_receiver(receiver),
49  m_slot(slot)
50  {
51  }
52  ~Connection() {}
53 
54  const char *m_name;
55  QObject *m_receiver;
56  const char *m_slot;
57 };
58 
59 } // namespace
60 
61 PendingCall::PendingCall(const QString &method,
62  const QList<QVariant> &args,
63  QObject *parent):
64  QObject(parent),
65  m_method(method),
66  m_args(args),
67  m_watcher(0),
68  m_interfaceWasDestroyed(false)
69 {
70 }
71 
72 PendingCall::~PendingCall()
73 {
74 }
75 
76 bool PendingCall::cancel()
77 {
78  if (m_watcher) {
79  // Too late, can't cancel
80  return false;
81  }
82  Q_EMIT finished(0);
83  return true;
84 }
85 
86 void PendingCall::doCall(QDBusAbstractInterface *interface)
87 {
88  QDBusPendingCall call =
89  interface->asyncCallWithArgumentList(m_method, m_args);
90  m_watcher = new QDBusPendingCallWatcher(call, this);
91  QObject::connect(m_watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
92  this, SLOT(onFinished(QDBusPendingCallWatcher*)));
93  /* Check if the interface gets destroyed while our call executes */
94  m_interfaceWasDestroyed = false;
95  QObject::connect(interface, SIGNAL(destroyed()),
96  this, SLOT(onInterfaceDestroyed()));
97 }
98 
99 void PendingCall::fail(const QDBusError &err)
100 {
101  Q_EMIT error(err);
102  Q_EMIT finished(0);
103 }
104 
105 void PendingCall::onFinished(QDBusPendingCallWatcher *watcher)
106 {
107  /* Check if the call failed because the interface became invalid; if
108  * so, emit a signal to instruct the AsyncDBusProxy to re-queue this
109  * operation. */
110  if (m_interfaceWasDestroyed && watcher->isError()) {
111  QDBusError::ErrorType type = watcher->error().type();
112  if (type == QDBusError::Disconnected ||
113  type == QDBusError::UnknownObject) {
114  TRACE() << "emitting retry signal";
115  Q_EMIT requeueRequested();
116  return;
117  }
118  }
119 
120  if (watcher->isError()) {
121  Q_EMIT error(watcher->error());
122  } else {
123  Q_EMIT success(watcher);
124  }
125  Q_EMIT finished(watcher);
126 }
127 
128 void PendingCall::onInterfaceDestroyed()
129 {
130  /* If the interface is destroyed during the lifetime of the call, this can
131  * be because the remote object got destroyed or the D-Bus connection
132  * dropped. In either case, we might have to re-queue our method call.
133  *
134  * This is done in the onFinished() slot; here we just record the event.
135  */
136  m_interfaceWasDestroyed = true;
137 }
138 
139 AsyncDBusProxy::AsyncDBusProxy(const QString &service,
140  const char *interface,
141  QObject *clientObject):
142  m_serviceName(service),
143  m_interfaceName(interface),
144  m_connection(NULL),
145  m_clientObject(clientObject),
146  m_interface(NULL),
147  m_status(Incomplete)
148 {
149 }
150 
151 AsyncDBusProxy::~AsyncDBusProxy()
152 {
153  qDeleteAll(m_connectionsQueue);
154  m_connectionsQueue.clear();
155 
156  delete m_connection;
157 }
158 
159 void AsyncDBusProxy::setStatus(Status status)
160 {
161  m_status = status;
162 
163  if (status == Ready) {
164  /* connect the signals and execute all pending methods */
165  Q_FOREACH(Connection *connection, m_connectionsQueue) {
166  m_interface->connect(connection->m_name,
167  connection->m_receiver,
168  connection->m_slot);
169  }
170 
171  Q_FOREACH(PendingCall *call, m_operationsQueue) {
172  call->doCall(m_interface);
173  }
174  m_operationsQueue.clear();
175  } else if (status == Invalid) {
176  /* signal error on all operations */
177  Q_FOREACH(PendingCall *call, m_operationsQueue) {
178  call->fail(m_lastError);
179  }
180  m_operationsQueue.clear();
181  }
182 }
183 
184 void AsyncDBusProxy::update()
185 {
186  if (m_interface != NULL) {
187  delete m_interface;
188  m_interface = 0;
189  }
190 
191  if (m_connection == NULL || m_path.isEmpty()) {
192  setStatus(Incomplete);
193  return;
194  }
195 
196  if (!m_connection->isConnected()) {
197  setError(m_connection->lastError());
198  return;
199  }
200 
201  m_interface = new DBusInterface(m_serviceName,
202  m_path,
203  m_interfaceName,
204  *m_connection,
205  this);
206  setStatus(Ready);
207 }
208 
209 void AsyncDBusProxy::setConnection(const QDBusConnection &connection)
210 {
211  delete m_connection;
212  m_connection = new QDBusConnection(connection);
213  update();
214 }
215 
216 void AsyncDBusProxy::setDisconnected()
217 {
218  TRACE();
219  delete m_connection;
220  m_connection = 0;
221  /* The daemon is dead, so certainly the object paths are also invalid */
222  m_path = QString();
223  update();
224 }
225 
226 void AsyncDBusProxy::setObjectPath(const QDBusObjectPath &objectPath)
227 {
228  Q_ASSERT(m_path.isEmpty() || objectPath.path().isEmpty());
229  m_path = objectPath.path();
230  update();
231 }
232 
233 void AsyncDBusProxy::setError(const QDBusError &error)
234 {
235  TRACE() << error;
236  m_lastError = error;
237  setStatus(Invalid);
238 }
239 
240 PendingCall *AsyncDBusProxy::queueCall(const QString &method,
241  const QList<QVariant> &args,
242  const char *replySlot,
243  const char *errorSlot)
244 {
245  return queueCall(method, args, m_clientObject, replySlot, errorSlot);
246 }
247 
248 PendingCall *AsyncDBusProxy::queueCall(const QString &method,
249  const QList<QVariant> &args,
250  QObject *receiver,
251  const char *replySlot,
252  const char *errorSlot)
253 {
254  PendingCall *call = new PendingCall(method, args, this);
255  QObject::connect(call, SIGNAL(finished(QDBusPendingCallWatcher*)),
256  this, SLOT(onCallFinished(QDBusPendingCallWatcher*)));
257  QObject::connect(call, SIGNAL(requeueRequested()),
258  this, SLOT(onRequeueRequested()));
259 
260  if (errorSlot) {
261  QObject::connect(call, SIGNAL(error(const QDBusError&)),
262  receiver, errorSlot);
263  if (replySlot) {
264  QObject::connect(call, SIGNAL(success(QDBusPendingCallWatcher*)),
265  receiver, replySlot);
266  }
267  } else if (replySlot) {
268  QObject::connect(call, SIGNAL(finished(QDBusPendingCallWatcher*)),
269  receiver, replySlot);
270  }
271 
272  if (m_status == Ready) {
273  call->doCall(m_interface);
274  } else if (m_status == Incomplete) {
275  enqueue(call);
276  } else {
277  QMetaObject::invokeMethod(call, "fail",
278  Qt::QueuedConnection,
279  Q_ARG(QDBusError, m_lastError));
280  }
281  return call;
282 }
283 
284 bool AsyncDBusProxy::connect(const char *name,
285  QObject *receiver,
286  const char *slot)
287 {
288  /* Remember all the connections anyway, because we'll re-play them if we
289  * disconnect and reconnect again */
290  Connection *connection = new Connection(name, receiver, slot);
291  m_connectionsQueue.enqueue(connection);
292 
293  if (m_status == Ready) {
294  return m_interface->connect(name, receiver, slot);
295  }
296  return true;
297 }
298 
299 void AsyncDBusProxy::enqueue(PendingCall *call)
300 {
301  m_operationsQueue.enqueue(call);
302  if (!m_connection) {
303  Q_EMIT connectionNeeded();
304  }
305  if (m_path.isEmpty()) {
306  Q_EMIT objectPathNeeded();
307  }
308 }
309 
310 void AsyncDBusProxy::onCallFinished(QDBusPendingCallWatcher *watcher)
311 {
312  Q_UNUSED(watcher);
313  PendingCall *call = qobject_cast<PendingCall*>(sender());
314  m_operationsQueue.removeOne(call);
315  call->deleteLater();
316 }
317 
318 void AsyncDBusProxy::onRequeueRequested()
319 {
320  PendingCall *call = qobject_cast<PendingCall*>(sender());
321  enqueue(call);
322 }
323 
324 SignondAsyncDBusProxy::SignondAsyncDBusProxy(const char *interface,
325  QObject *clientObject):
326  AsyncDBusProxy(SIGNOND_SERVICE, interface, clientObject)
327 {
328  setupConnection();
329 }
330 
331 SignondAsyncDBusProxy::~SignondAsyncDBusProxy()
332 {
333 }
334 
335 void SignondAsyncDBusProxy::setupConnection()
336 {
337  ConnectionManager *connManager = ConnectionManager::instance();
338  QObject::connect(connManager, SIGNAL(connected(const QDBusConnection&)),
339  this, SLOT(setConnection(const QDBusConnection&)));
340  QObject::connect(connManager, SIGNAL(disconnected()),
341  this, SLOT(setDisconnected()));
342  QObject::connect(this, SIGNAL(connectionNeeded()),
343  connManager, SLOT(connect()));
344  if (connManager->hasConnection()) {
345  setConnection(connManager->connection());
346  }
347 }