/** * This file is part of TelepathyQt * * @copyright Copyright (C) 2010 Collabora Ltd. * @copyright Copyright (C) 2010 Nokia Corporation * @license LGPL 2.1 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "TelepathyQt/dbus-proxy-factory-internal.h" #include "TelepathyQt/_gen/dbus-proxy-factory.moc.hpp" #include "TelepathyQt/_gen/dbus-proxy-factory-internal.moc.hpp" #include "TelepathyQt/debug-internal.h" #include #include #include #include namespace Tp { struct TP_QT_NO_EXPORT DBusProxyFactory::Private { Private(const QDBusConnection &bus) : bus(bus), cache(new Cache) { } ~Private() { delete cache; } QDBusConnection bus; Cache *cache; }; /** * \class DBusProxyFactory * \ingroup utils * \headerfile TelepathyQt/dbus-proxy-factory.h * * \brief The DBusProxyFactory class is a base class for all D-Bus proxy factory * classes. Handles proxy caching and making them ready as appropriate. */ /** * Construct a new DBusProxyFactory object. * * The intention for storing the bus here is that it generally doesn't make sense to construct * proxies for multiple buses in the same context. Allowing that would lead to more complex keying * needs in the cache, as well. * * \param bus The D-Bus bus connection for the objects constructed using this factory. */ DBusProxyFactory::DBusProxyFactory(const QDBusConnection &bus) : mPriv(new Private(bus)) { } /** * Class destructor. */ DBusProxyFactory::~DBusProxyFactory() { delete mPriv; } /** * Return the D-Bus connection all of the proxies from this factory communicate with. * * \return A QDBusConnection object. */ const QDBusConnection &DBusProxyFactory::dbusConnection() const { return mPriv->bus; } /** * Return a cached proxy with the given \a busName and \a objectPath. * * If a proxy has not been previously put into the cache by nowHaveProxy for those identifying * attributes, or a previously cached proxy has since been invalidated and/or destroyed, a \c Null * shared pointer is returned instead. * * \param busName Bus name of the proxy to return. * \param objectPath Object path of the proxy to return. * \return A pointer to the DBusProxy object, if any. */ DBusProxyPtr DBusProxyFactory::cachedProxy(const QString &busName, const QString &objectPath) const { QString finalName = finalBusNameFrom(busName); return mPriv->cache->get(Cache::Key(finalName, objectPath)); } /** * Should be called by subclasses when they have a proxy, be it a newly-constructed one or one from * the cache. * * This function will then do the rest of the factory work, including caching the proxy if it's not * cached already, doing any initialPrepare()/readyPrepare() work if appropriate, and making the * features from featuresFor() ready if they aren't already. * * The returned PendingReady only finishes when the initialPrepare() and readyPrepare() operations * for the proxy has completed, and the requested features have all been made ready (or found unable * to be made ready). Note that this might have happened already before calling this function, if * the proxy was not a newly created one, but was looked up from the cache. DBusProxyFactory handles * the necessary subleties for this to work. * * Access to the proxy instance is allowed as soon as this method returns through * PendingReady::proxy(), if the proxy is needed in a context where it's not required to be ready. * * \param proxy The proxy which the factory should now make sure is prepared and made ready. * \return A PendingReady operation which will emit PendingReady::finished * when the proxy is usable. */ PendingReady *DBusProxyFactory::nowHaveProxy(const DBusProxyPtr &proxy) const { Q_ASSERT(!proxy.isNull()); mPriv->cache->put(proxy); return new PendingReady(SharedPtr((DBusProxyFactory*) this), proxy, featuresFor(proxy)); } /** * \fn QString DBusProxyFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const * * "Normalize" a bus name according to the rules for the proxy class to construct. * * Should be implemented by subclasses to transform the application-specified name \a * uniqueOrWellKnown to whatever the proxy constructed for that name would have in its * DBusProxy::busName() in the end. * * For StatelessDBusProxy sub-classes this should mostly be an identity transform, while for * StatefulDBusProxy sub-classes StatefulDBusProxy::uniqueNameFrom() or an equivalent thereof should * be used in most cases. * * If this is not implemented correctly, caching won't work properly. * * \param uniqueOrWellKnown Any valid D-Bus service name, either unique or well-known. * \return Whatever that name would turn to, when a proxy is constructed for it. */ /** * Allows subclasses to do arbitrary manipulation on the proxy before it is attempted to be made * ready. * * If a non-\c NULL operation is returned, the completion of that operation is waited for before * starting to make the object ready whenever nowHaveProxy() is called the first time around for a * given proxy. * * \todo FIXME actually implement this... :) Currently just a vtable placeholder. * \param proxy The just-constructed proxy to be prepared. * \return \c NULL ie. nothing to do. */ PendingOperation *DBusProxyFactory::initialPrepare(const DBusProxyPtr &proxy) const { // Nothing we could think about needs doing return NULL; } /** * Allows subclasses to do arbitrary manipulation on the proxy after it has been made ready. * * If a non-\c NULL operation is returned, the completion of that operation is waited for before * signaling that the object is ready for use after ReadyObject::becomeReady() for it has finished * whenever nowHaveProxy() is called the first time around for a given proxy. * * \todo FIXME actually implement this... :) Currently just a vtable placeholder. * \param proxy The just-readified proxy to be prepared. * \return \c NULL ie. nothing to do. */ PendingOperation *DBusProxyFactory::readyPrepare(const DBusProxyPtr &proxy) const { // Nothing we could think about needs doing return NULL; } /** * \fn Features DBusProxyFactory::featuresFor(const SharedPtr &proxy) const * * Return the features which should be made ready on a given proxy. * * This can be used to implement instance-specific features based on arbitrary criteria. * FixedFeatureFactory implements this as a fixed set of features independent of the instance, * however. * * It should be noted that if an empty set of features is returned, ReadyObject::becomeReady() is * not called at all. In other words, any "core feature" is not automatically added to the requested * features. This is to enable setting a factory to not make proxies ready at all, which is useful * eg. in the case of account editing UIs which aren't interested in the state of Connection objects * for the Account objects they're editing. * * \param proxy The proxy on which the returned features will be made ready. * \return A list of Feature objects. */ DBusProxyFactory::Cache::Cache() { } DBusProxyFactory::Cache::~Cache() { } DBusProxyPtr DBusProxyFactory::Cache::get(const Key &key) const { DBusProxyPtr proxy(proxies.value(key)); if (proxy.isNull() || !proxy->isValid()) { // Weak pointer invalidated or proxy invalidated during this mainloop iteration and we still // haven't got the invalidated() signal for it return DBusProxyPtr(); } return proxy; } void DBusProxyFactory::Cache::put(const DBusProxyPtr &proxy) { if (proxy->busName().isEmpty()) { debug() << "Not inserting proxy" << proxy.data() << "with no bus name to factory cache"; return; } else if (!proxy->isValid()) { debug() << "Not inserting to factory cache invalid proxy - proxy is for" << proxy->busName() << ',' << proxy->objectPath(); return; } Key key(proxy->busName(), proxy->objectPath()); DBusProxyPtr existingProxy(proxies.value(key)); if (!existingProxy || existingProxy != proxy) { // Disconnect the invalidated signal from the proxy we're replacing, so it won't uselessly // cause the new (hopefully valid) proxy to be dropped from the cache if it arrives late. // // The window in which this makes a difference is very slim but existent; namely, somebody // must request a proxy from the factory in the same mainloop iteration as an otherwise // matching proxy has invalidated itself. The invalidation signal would be delivered and // processed only during the next mainloop iteration. if (existingProxy) { Q_ASSERT(!existingProxy->isValid()); existingProxy->disconnect( SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), this, SLOT(onProxyInvalidated(Tp::DBusProxy*))); debug() << "Replacing invalidated proxy" << existingProxy.data() << "in cache for name" << existingProxy->busName() << ',' << existingProxy->objectPath(); } connect(proxy.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), SLOT(onProxyInvalidated(Tp::DBusProxy*))); debug() << "Inserting to factory cache proxy for" << key; proxies.insert(key, proxy); } } void DBusProxyFactory::Cache::onProxyInvalidated(Tp::DBusProxy *proxy) { Key key(proxy->busName(), proxy->objectPath()); // Not having it would indicate invalidated() signaled twice for the same proxy, or us having // connected to two proxies with the same key, neither of which should happen Q_ASSERT(proxies.contains(key)); debug() << "Removing from factory cache invalidated proxy for" << key; proxies.remove(key); } }