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