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