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
39using namespace SignOn;
40
41namespace SignOn {
42
43class Connection
44{
45public:
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
61PendingCall::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
72PendingCall::~PendingCall()
73{
74}
75
76bool 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
86void 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
99void PendingCall::fail(const QDBusError &err)
100{
101 Q_EMIT error(err);
102 Q_EMIT finished(0);
103}
104
105void 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
128void 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
139AsyncDBusProxy::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
151AsyncDBusProxy::~AsyncDBusProxy()
152{
153 qDeleteAll(m_connectionsQueue);
154 m_connectionsQueue.clear();
155
156 delete m_connection;
157}
158
159void 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
184void 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
209void AsyncDBusProxy::setConnection(const QDBusConnection &connection)
210{
211 delete m_connection;
212 m_connection = new QDBusConnection(connection);
213 update();
214}
215
216void 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
226void AsyncDBusProxy::setObjectPath(const QDBusObjectPath &objectPath)
227{
228 Q_ASSERT(m_path.isEmpty() || objectPath.path().isEmpty());
229 m_path = objectPath.path();
230 update();
231}
232
233void AsyncDBusProxy::setError(const QDBusError &error)
234{
235 TRACE() << error;
236 m_lastError = error;
237 setStatus(Invalid);
238}
239
240PendingCall *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
248PendingCall *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
284bool 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
299void 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
310void 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
318void AsyncDBusProxy::onRequeueRequested()
319{
320 PendingCall *call = qobject_cast<PendingCall*>(sender());
321 enqueue(call);
322}
323
324SignondAsyncDBusProxy::SignondAsyncDBusProxy(const char *interface,
325 QObject *clientObject):
326 AsyncDBusProxy(SIGNOND_SERVICE, interface, clientObject)
327{
328 setupConnection();
329}
330
331SignondAsyncDBusProxy::~SignondAsyncDBusProxy()
332{
333}
334
335void 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}