1 /**
2  * This file is part of TelepathyQt
3  *
4  * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
5  * @copyright Copyright (C) 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 <TelepathyQt/DBusProxyFactory>
24 #include "TelepathyQt/dbus-proxy-factory-internal.h"
25 
26 #include "TelepathyQt/_gen/dbus-proxy-factory.moc.hpp"
27 #include "TelepathyQt/_gen/dbus-proxy-factory-internal.moc.hpp"
28 
29 #include "TelepathyQt/debug-internal.h"
30 
31 #include <TelepathyQt/DBusProxy>
32 #include <TelepathyQt/ReadyObject>
33 #include <TelepathyQt/PendingReady>
34 
35 #include <QDBusConnection>
36 
37 namespace Tp
38 {
39 
40 struct TP_QT_NO_EXPORT DBusProxyFactory::Private
41 {
PrivateTp::DBusProxyFactory::Private42     Private(const QDBusConnection &bus)
43         : bus(bus),
44           cache(new Cache)
45     {
46     }
47 
~PrivateTp::DBusProxyFactory::Private48     ~Private()
49     {
50         delete cache;
51     }
52 
53     QDBusConnection bus;
54     Cache *cache;
55 };
56 
57 /**
58  * \class DBusProxyFactory
59  * \ingroup utils
60  * \headerfile TelepathyQt/dbus-proxy-factory.h <TelepathyQt/DBusProxyFactory>
61  *
62  * \brief The DBusProxyFactory class is a base class for all D-Bus proxy factory
63  * classes. Handles proxy caching and making them ready as appropriate.
64  */
65 
66 /**
67  * Construct a new DBusProxyFactory object.
68  *
69  * The intention for storing the bus here is that it generally doesn't make sense to construct
70  * proxies for multiple buses in the same context. Allowing that would lead to more complex keying
71  * needs in the cache, as well.
72  *
73  * \param bus The D-Bus bus connection for the objects constructed using this factory.
74  */
DBusProxyFactory(const QDBusConnection & bus)75 DBusProxyFactory::DBusProxyFactory(const QDBusConnection &bus)
76     : mPriv(new Private(bus))
77 {
78 }
79 
80 /**
81  * Class destructor.
82  */
~DBusProxyFactory()83 DBusProxyFactory::~DBusProxyFactory()
84 {
85     delete mPriv;
86 }
87 
88 /**
89  * Return the D-Bus connection all of the proxies from this factory communicate with.
90  *
91  * \return A QDBusConnection object.
92  */
dbusConnection() const93 const QDBusConnection &DBusProxyFactory::dbusConnection() const
94 {
95     return mPriv->bus;
96 }
97 
98 /**
99  * Return a cached proxy with the given \a busName and \a objectPath.
100  *
101  * If a proxy has not been previously put into the cache by nowHaveProxy for those identifying
102  * attributes, or a previously cached proxy has since been invalidated and/or destroyed, a \c Null
103  * shared pointer is returned instead.
104  *
105  * \param busName Bus name of the proxy to return.
106  * \param objectPath Object path of the proxy to return.
107  * \return A pointer to the DBusProxy object, if any.
108  */
cachedProxy(const QString & busName,const QString & objectPath) const109 DBusProxyPtr DBusProxyFactory::cachedProxy(const QString &busName,
110         const QString &objectPath) const
111 {
112     QString finalName = finalBusNameFrom(busName);
113     return mPriv->cache->get(Cache::Key(finalName, objectPath));
114 }
115 
116 /**
117  * Should be called by subclasses when they have a proxy, be it a newly-constructed one or one from
118  * the cache.
119  *
120  * This function will then do the rest of the factory work, including caching the proxy if it's not
121  * cached already, doing any initialPrepare()/readyPrepare() work if appropriate, and making the
122  * features from featuresFor() ready if they aren't already.
123  *
124  * The returned PendingReady only finishes when the initialPrepare() and readyPrepare() operations
125  * for the proxy has completed, and the requested features have all been made ready (or found unable
126  * to be made ready). Note that this might have happened already before calling this function, if
127  * the proxy was not a newly created one, but was looked up from the cache. DBusProxyFactory handles
128  * the necessary subleties for this to work.
129  *
130  * Access to the proxy instance is allowed as soon as this method returns through
131  * PendingReady::proxy(), if the proxy is needed in a context where it's not required to be ready.
132  *
133  * \param proxy The proxy which the factory should now make sure is prepared and made ready.
134  * \return A PendingReady operation which will emit PendingReady::finished
135  *         when the proxy is usable.
136  */
nowHaveProxy(const DBusProxyPtr & proxy) const137 PendingReady *DBusProxyFactory::nowHaveProxy(const DBusProxyPtr &proxy) const
138 {
139     Q_ASSERT(!proxy.isNull());
140 
141     mPriv->cache->put(proxy);
142     return new PendingReady(SharedPtr<DBusProxyFactory>((DBusProxyFactory*) this),
143            proxy, featuresFor(proxy));
144 }
145 
146 /**
147  * \fn QString DBusProxyFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const
148  *
149  * "Normalize" a bus name according to the rules for the proxy class to construct.
150  *
151  * Should be implemented by subclasses to transform the application-specified name \a
152  * uniqueOrWellKnown to whatever the proxy constructed for that name would have in its
153  * DBusProxy::busName() in the end.
154  *
155  * For StatelessDBusProxy sub-classes this should mostly be an identity transform, while for
156  * StatefulDBusProxy sub-classes StatefulDBusProxy::uniqueNameFrom() or an equivalent thereof should
157  * be used in most cases.
158  *
159  * If this is not implemented correctly, caching won't work properly.
160  *
161  * \param uniqueOrWellKnown Any valid D-Bus service name, either unique or well-known.
162  * \return Whatever that name would turn to, when a proxy is constructed for it.
163  */
164 
165 /**
166  * Allows subclasses to do arbitrary manipulation on the proxy before it is attempted to be made
167  * ready.
168  *
169  * If a non-\c NULL operation is returned, the completion of that operation is waited for before
170  * starting to make the object ready whenever nowHaveProxy() is called the first time around for a
171  * given proxy.
172  *
173  * \todo FIXME actually implement this... :) Currently just a vtable placeholder.
174  * \param proxy The just-constructed proxy to be prepared.
175  * \return \c NULL ie. nothing to do.
176  */
initialPrepare(const DBusProxyPtr & proxy) const177 PendingOperation *DBusProxyFactory::initialPrepare(const DBusProxyPtr &proxy) const
178 {
179     // Nothing we could think about needs doing
180     return NULL;
181 }
182 
183 /**
184  * Allows subclasses to do arbitrary manipulation on the proxy after it has been made ready.
185  *
186  * If a non-\c NULL operation is returned, the completion of that operation is waited for before
187  * signaling that the object is ready for use after ReadyObject::becomeReady() for it has finished
188  * whenever nowHaveProxy() is called the first time around for a given proxy.
189  *
190  * \todo FIXME actually implement this... :) Currently just a vtable placeholder.
191  * \param proxy The just-readified proxy to be prepared.
192  * \return \c NULL ie. nothing to do.
193  */
readyPrepare(const DBusProxyPtr & proxy) const194 PendingOperation *DBusProxyFactory::readyPrepare(const DBusProxyPtr &proxy) const
195 {
196     // Nothing we could think about needs doing
197     return NULL;
198 }
199 
200 /**
201  * \fn Features DBusProxyFactory::featuresFor(const SharedPtr<RefCounted> &proxy) const
202  *
203  * Return the features which should be made ready on a given proxy.
204  *
205  * This can be used to implement instance-specific features based on arbitrary criteria.
206  * FixedFeatureFactory implements this as a fixed set of features independent of the instance,
207  * however.
208  *
209  * It should be noted that if an empty set of features is returned, ReadyObject::becomeReady() is
210  * not called at all. In other words, any "core feature" is not automatically added to the requested
211  * features. This is to enable setting a factory to not make proxies ready at all, which is useful
212  * eg. in the case of account editing UIs which aren't interested in the state of Connection objects
213  * for the Account objects they're editing.
214  *
215  * \param proxy The proxy on which the returned features will be made ready.
216  * \return A list of Feature objects.
217  */
218 
Cache()219 DBusProxyFactory::Cache::Cache()
220 {
221 }
222 
~Cache()223 DBusProxyFactory::Cache::~Cache()
224 {
225 }
226 
get(const Key & key) const227 DBusProxyPtr DBusProxyFactory::Cache::get(const Key &key) const
228 {
229     DBusProxyPtr proxy(proxies.value(key));
230 
231     if (proxy.isNull() || !proxy->isValid()) {
232         // Weak pointer invalidated or proxy invalidated during this mainloop iteration and we still
233         // haven't got the invalidated() signal for it
234         return DBusProxyPtr();
235     }
236 
237     return proxy;
238 }
239 
put(const DBusProxyPtr & proxy)240 void DBusProxyFactory::Cache::put(const DBusProxyPtr &proxy)
241 {
242     if (proxy->busName().isEmpty()) {
243         debug() << "Not inserting proxy" << proxy.data() << "with no bus name to factory cache";
244         return;
245     } else if (!proxy->isValid()) {
246         debug() << "Not inserting to factory cache invalid proxy - proxy is for" <<
247             proxy->busName() << ',' << proxy->objectPath();
248         return;
249     }
250 
251     Key key(proxy->busName(), proxy->objectPath());
252 
253     DBusProxyPtr existingProxy(proxies.value(key));
254     if (!existingProxy || existingProxy != proxy) {
255         // Disconnect the invalidated signal from the proxy we're replacing, so it won't uselessly
256         // cause the new (hopefully valid) proxy to be dropped from the cache if it arrives late.
257         //
258         // The window in which this makes a difference is very slim but existent; namely, somebody
259         // must request a proxy from the factory in the same mainloop iteration as an otherwise
260         // matching proxy has invalidated itself. The invalidation signal would be delivered and
261         // processed only during the next mainloop iteration.
262         if (existingProxy) {
263             Q_ASSERT(!existingProxy->isValid());
264             existingProxy->disconnect(
265                     SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
266                     this,
267                     SLOT(onProxyInvalidated(Tp::DBusProxy*)));
268 
269             debug() << "Replacing invalidated proxy" << existingProxy.data() << "in cache for name"
270                 << existingProxy->busName() << ',' << existingProxy->objectPath();
271         }
272 
273         connect(proxy.data(),
274                 SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
275                 SLOT(onProxyInvalidated(Tp::DBusProxy*)));
276 
277         debug() << "Inserting to factory cache proxy for" << key;
278         proxies.insert(key, proxy);
279     }
280 }
281 
onProxyInvalidated(Tp::DBusProxy * proxy)282 void DBusProxyFactory::Cache::onProxyInvalidated(Tp::DBusProxy *proxy)
283 {
284     Key key(proxy->busName(), proxy->objectPath());
285 
286     // Not having it would indicate invalidated() signaled twice for the same proxy, or us having
287     // connected to two proxies with the same key, neither of which should happen
288     Q_ASSERT(proxies.contains(key));
289 
290     debug() << "Removing from factory cache invalidated proxy for" << key;
291 
292     proxies.remove(key);
293 }
294 
295 }
296