1 /**
2 * This file is part of TelepathyQt
3 *
4 * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
5 * @copyright Copyright (C) 2008-2010 Nokia Corporation
6 * @license LGPL 2.1
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but 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 02110-1301 USA
21 */
22
23 #include "config.h"
24
25 #include <TelepathyQt/DBusProxy>
26
27 #include "TelepathyQt/_gen/dbus-proxy.moc.hpp"
28
29 #include "TelepathyQt/debug-internal.h"
30
31 #include <TelepathyQt/Constants>
32
33 #include <QDBusConnection>
34 #include <QDBusConnectionInterface>
35 #include <QDBusError>
36 #include <QDBusServiceWatcher>
37 #include <QTimer>
38
39 namespace Tp
40 {
41
42 // ==== DBusProxy ======================================================
43
44 // Features in TpProxy but not here:
45 // * tracking which interfaces we have (in tpqt, subclasses do that)
46 // * being Introspectable, a Peer and a Properties implementation
47 // * disconnecting from signals when invalidated (probably has to be in the
48 // generated code)
49 // * making methods always raise an error when called after invalidated
50 // (has to be in the generated code)
51
52 struct TP_QT_NO_EXPORT DBusProxy::Private
53 {
54 Private(const QDBusConnection &dbusConnection, const QString &busName,
55 const QString &objectPath);
56
57 QDBusConnection dbusConnection;
58 QString busName;
59 QString objectPath;
60 QString invalidationReason;
61 QString invalidationMessage;
62 };
63
Private(const QDBusConnection & dbusConnection,const QString & busName,const QString & objectPath)64 DBusProxy::Private::Private(const QDBusConnection &dbusConnection,
65 const QString &busName, const QString &objectPath)
66 : dbusConnection(dbusConnection),
67 busName(busName),
68 objectPath(objectPath)
69 {
70 debug() << "Creating new DBusProxy";
71 }
72
73 /**
74 * \class DBusProxy
75 * \ingroup clientproxies
76 * \headerfile TelepathyQt/dbus-proxy.h <TelepathyQt/DBusProxy>
77 *
78 * \brief The DBusProxy class is a base class representing a remote object available over D-Bus.
79 *
80 * All Telepathy-Qt client convenience classes that wrap Telepathy interfaces
81 * inherit from this class in order to provide basic D-Bus interface
82 * information.
83 */
84
85 /**
86 * Construct a new DBusProxy object.
87 *
88 * \param dbusConnection QDBusConnection to use.
89 * \param busName D-Bus bus name of the service that provides the remote object.
90 * \param objectPath The object path.
91 * \param featureCore The object core feature.
92 */
DBusProxy(const QDBusConnection & dbusConnection,const QString & busName,const QString & objectPath,const Feature & featureCore)93 DBusProxy::DBusProxy(const QDBusConnection &dbusConnection,
94 const QString &busName, const QString &objectPath, const Feature &featureCore)
95 : Object(),
96 ReadyObject(this, featureCore),
97 mPriv(new Private(dbusConnection, busName, objectPath))
98 {
99 if (!dbusConnection.isConnected()) {
100 invalidate(TP_QT_ERROR_DISCONNECTED,
101 QLatin1String("DBus connection disconnected"));
102 }
103 }
104
105 /**
106 * Class destructor.
107 */
~DBusProxy()108 DBusProxy::~DBusProxy()
109 {
110 delete mPriv;
111 }
112
113 /**
114 * Return the D-Bus connection through which the remote object is
115 * accessed.
116 *
117 * \return A QDBusConnection object.
118 */
dbusConnection() const119 QDBusConnection DBusProxy::dbusConnection() const
120 {
121 return mPriv->dbusConnection;
122 }
123
124 /**
125 * Return the D-Bus object path of the remote object within the service.
126 *
127 * \return The D-Bus object path.
128 */
objectPath() const129 QString DBusProxy::objectPath() const
130 {
131 return mPriv->objectPath;
132 }
133
134 /**
135 * Return the D-Bus bus name (either a unique name or a well-known
136 * name) of the service that provides the remote object.
137 *
138 * \return The D-Bus bus name.
139 */
busName() const140 QString DBusProxy::busName() const
141 {
142 return mPriv->busName;
143 }
144
145 /**
146 * Sets the D-Bus bus name. This is used by subclasses after converting
147 * well-known names to unique names.
148 *
149 * \param busName The D-Bus bus name to set.
150 */
setBusName(const QString & busName)151 void DBusProxy::setBusName(const QString &busName)
152 {
153 mPriv->busName = busName;
154 }
155
156 /**
157 * Return whether this proxy is still valid (has not emitted invalidated()).
158 *
159 * \return \c true if still valid, \c false otherwise.
160 */
isValid() const161 bool DBusProxy::isValid() const
162 {
163 return mPriv->invalidationReason.isEmpty();
164 }
165
166 /**
167 * Return the error name indicating the reason this proxy became invalid.
168 *
169 * \return A D-Bus error name, or QString() if this object is still valid.
170 */
invalidationReason() const171 QString DBusProxy::invalidationReason() const
172 {
173 return mPriv->invalidationReason;
174 }
175
176 /**
177 * Return a debugging message indicating the reason this proxy became invalid.
178 *
179 * \return A debugging message, or QString() if this object is still valid.
180 */
invalidationMessage() const181 QString DBusProxy::invalidationMessage() const
182 {
183 return mPriv->invalidationMessage;
184 }
185
186 /**
187 * Called by subclasses when the DBusProxy should become invalid.
188 *
189 * This method takes care of setting the invalidationReason,
190 * invalidationMessage, and emitting the invalidated signal.
191 *
192 * \param reason A D-Bus error name (a string in a subset of ASCII,
193 * prefixed with a reversed domain name)
194 * \param message A debugging message associated with the error
195 */
invalidate(const QString & reason,const QString & message)196 void DBusProxy::invalidate(const QString &reason, const QString &message)
197 {
198 if (!isValid()) {
199 debug().nospace() << "Already invalidated by "
200 << mPriv->invalidationReason
201 << ", not replacing with " << reason
202 << " \"" << message << "\"";
203 return;
204 }
205
206 Q_ASSERT(!reason.isEmpty());
207
208 debug().nospace() << "proxy invalidated: " << reason
209 << ": " << message;
210
211 mPriv->invalidationReason = reason;
212 mPriv->invalidationMessage = message;
213
214 Q_ASSERT(!isValid());
215
216 // Defer emitting the invalidated signal until we next
217 // return to the mainloop.
218 QTimer::singleShot(0, this, SLOT(emitInvalidated()));
219 }
220
invalidate(const QDBusError & error)221 void DBusProxy::invalidate(const QDBusError &error)
222 {
223 invalidate(error.name(), error.message());
224 }
225
emitInvalidated()226 void DBusProxy::emitInvalidated()
227 {
228 Q_ASSERT(!isValid());
229
230 emit invalidated(this, mPriv->invalidationReason, mPriv->invalidationMessage);
231 }
232
233 /**
234 * \fn void DBusProxy::invalidated(Tp::DBusProxy *proxy,
235 * const QString &errorName, const QString &errorMessage)
236 *
237 * Emitted when this object is no longer usable.
238 *
239 * After this signal is emitted, any D-Bus method calls on the object
240 * will fail, but it may be possible to retrieve information that has
241 * already been retrieved and cached.
242 *
243 * \param proxy This proxy.
244 * \param errorName The name of a D-Bus error describing the reason for the invalidation.
245 * \param errorMessage A debugging message associated with the error.
246 */
247
248 // ==== StatefulDBusProxy ==============================================
249
250 struct TP_QT_NO_EXPORT StatefulDBusProxy::Private
251 {
PrivateTp::StatefulDBusProxy::Private252 Private(const QString &originalName)
253 : originalName(originalName) {}
254
255 QString originalName;
256 };
257
258 /**
259 * \class StatefulDBusProxy
260 * \ingroup clientproxies
261 * \headerfile TelepathyQt/dbus-proxy.h <TelepathyQt/StatefulDBusProxy>
262 *
263 * \brief The StatefulDBusProxy class is a base class representing a remote object whose API is
264 * stateful.
265 *
266 * These objects do not remain useful if the service providing them exits or
267 * crashes, so they emit invalidated() if this happens.
268 *
269 * Examples include the Connection and Channel classes.
270 */
271
272 /**
273 * Construct a new StatefulDBusProxy object.
274 *
275 * \param dbusConnection QDBusConnection to use.
276 * \param busName D-Bus bus name of the service that provides the remote object.
277 * \param objectPath The object path.
278 * \param featureCore The object core feature.
279 */
StatefulDBusProxy(const QDBusConnection & dbusConnection,const QString & busName,const QString & objectPath,const Feature & featureCore)280 StatefulDBusProxy::StatefulDBusProxy(const QDBusConnection &dbusConnection,
281 const QString &busName, const QString &objectPath, const Feature &featureCore)
282 : DBusProxy(dbusConnection, busName, objectPath, featureCore),
283 mPriv(new Private(busName))
284 {
285 QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher(busName,
286 dbusConnection, QDBusServiceWatcher::WatchForUnregistration, this);
287 connect(serviceWatcher,
288 SIGNAL(serviceOwnerChanged(QString,QString,QString)),
289 SLOT(onServiceOwnerChanged(QString,QString,QString)));
290
291 QString error, message;
292 QString uniqueName = uniqueNameFrom(dbusConnection, busName, error, message);
293
294 if (uniqueName.isEmpty()) {
295 invalidate(error, message);
296 return;
297 }
298
299 setBusName(uniqueName);
300 }
301
302 /**
303 * Class destructor.
304 */
~StatefulDBusProxy()305 StatefulDBusProxy::~StatefulDBusProxy()
306 {
307 delete mPriv;
308 }
309
uniqueNameFrom(const QDBusConnection & bus,const QString & name)310 QString StatefulDBusProxy::uniqueNameFrom(const QDBusConnection &bus, const QString &name)
311 {
312 QString error, message;
313 QString uniqueName = uniqueNameFrom(bus, name, error, message);
314 if (uniqueName.isEmpty()) {
315 warning() << "StatefulDBusProxy::uniqueNameFrom(): Failed to get unique name of" << name;
316 warning() << " error:" << error << "message:" << message;
317 }
318
319 return uniqueName;
320 }
321
uniqueNameFrom(const QDBusConnection & bus,const QString & name,QString & error,QString & message)322 QString StatefulDBusProxy::uniqueNameFrom(const QDBusConnection &bus, const QString &name,
323 QString &error, QString &message)
324 {
325 if (name.startsWith(QLatin1String(":"))) {
326 return name;
327 }
328
329 // For a stateful interface, it makes no sense to follow name-owner
330 // changes, so we want to bind to the unique name.
331 QDBusReply<QString> reply = bus.interface()->serviceOwner(name);
332 if (reply.isValid()) {
333 return reply.value();
334 } else {
335 error = reply.error().name();
336 message = reply.error().message();
337 return QString();
338 }
339 }
340
onServiceOwnerChanged(const QString & name,const QString & oldOwner,const QString & newOwner)341 void StatefulDBusProxy::onServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
342 {
343 // We only want to invalidate this object if it is not already invalidated,
344 // and its (not any other object's) name owner changed signal is emitted.
345 if (isValid() && name == mPriv->originalName && newOwner.isEmpty()) {
346 invalidate(TP_QT_DBUS_ERROR_NAME_HAS_NO_OWNER,
347 QLatin1String("Name owner lost (service crashed?)"));
348 }
349 }
350
351 // ==== StatelessDBusProxy =============================================
352
353 /**
354 * \class StatelessDBusProxy
355 * \ingroup clientproxies
356 * \headerfile TelepathyQt/dbus-proxy.h <TelepathyQt/DBusProxy>
357 *
358 * \brief The StatelessDBusProxy class is a base class representing a remote object whose API is
359 * basically stateless.
360 *
361 * These objects can remain valid even if the service providing them exits
362 * and is restarted.
363 *
364 * Examples include the AccountManager, Account and ConnectionManager.
365 */
366
367 /**
368 * Construct a new StatelessDBusProxy object.
369 *
370 * \param dbusConnection QDBusConnection to use.
371 * \param busName D-Bus bus name of the service that provides the remote object.
372 * \param objectPath The object path.
373 * \param featureCore The object core feature.
374 */
StatelessDBusProxy(const QDBusConnection & dbusConnection,const QString & busName,const QString & objectPath,const Feature & featureCore)375 StatelessDBusProxy::StatelessDBusProxy(const QDBusConnection &dbusConnection,
376 const QString &busName, const QString &objectPath, const Feature &featureCore)
377 : DBusProxy(dbusConnection, busName, objectPath, featureCore),
378 mPriv(0)
379 {
380 if (busName.startsWith(QLatin1String(":"))) {
381 warning() <<
382 "Using StatelessDBusProxy for a unique name does not make sense";
383 }
384 }
385
386 /**
387 * Class destructor.
388 */
~StatelessDBusProxy()389 StatelessDBusProxy::~StatelessDBusProxy()
390 {
391 }
392
393 } // Tp
394