diff options
Diffstat (limited to 'qt4/TelepathyQt4/connection-manager.cpp')
-rw-r--r-- | qt4/TelepathyQt4/connection-manager.cpp | 1078 |
1 files changed, 1078 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/connection-manager.cpp b/qt4/TelepathyQt4/connection-manager.cpp new file mode 100644 index 000000000..6b081c9e8 --- /dev/null +++ b/qt4/TelepathyQt4/connection-manager.cpp @@ -0,0 +1,1078 @@ +/** + * This file is part of TelepathyQt4 + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-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 <TelepathyQt4/ConnectionManager> +#include <TelepathyQt4/ConnectionManagerLowlevel> +#include "TelepathyQt4/connection-manager-internal.h" + +#include "TelepathyQt4/_gen/cli-connection-manager-body.hpp" +#include "TelepathyQt4/_gen/cli-connection-manager.moc.hpp" +#include "TelepathyQt4/_gen/connection-manager.moc.hpp" +#include "TelepathyQt4/_gen/connection-manager-internal.moc.hpp" +#include "TelepathyQt4/_gen/connection-manager-lowlevel.moc.hpp" + +#include "TelepathyQt4/debug-internal.h" + +#include <TelepathyQt4/ConnectionCapabilities> +#include <TelepathyQt4/DBus> +#include <TelepathyQt4/PendingConnection> +#include <TelepathyQt4/PendingReady> +#include <TelepathyQt4/Constants> +#include <TelepathyQt4/ManagerFile> +#include <TelepathyQt4/Types> + +#include <QDBusConnectionInterface> +#include <QQueue> +#include <QStringList> +#include <QTimer> + +namespace +{ + +bool checkValidProtocolName(const QString &protocolName) +{ + if (!protocolName[0].isLetter()) { + return false; + } + + int length = protocolName.length(); + if (length <= 1) { + return true; + } + + QChar ch; + for (int i = 1; i < length; ++i) { + ch = protocolName[i]; + if (!ch.isLetterOrNumber() && ch != QLatin1Char('-')) { + return false; + } + } + + return true; +} + +} + +namespace Tp +{ + +ConnectionManager::Private::PendingNames::PendingNames(const QDBusConnection &bus) + : PendingStringList(SharedPtr<RefCounted>()), + mBus(bus) +{ + mMethodsQueue.enqueue(QLatin1String("ListNames")); + mMethodsQueue.enqueue(QLatin1String("ListActivatableNames")); + QTimer::singleShot(0, this, SLOT(continueProcessing())); +} + +void ConnectionManager::Private::PendingNames::onCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + + if (!reply.isError()) { + parseResult(reply.value()); + continueProcessing(); + } else { + warning() << "Failure: error " << reply.error().name() << + ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void ConnectionManager::Private::PendingNames::continueProcessing() +{ + if (!mMethodsQueue.isEmpty()) { + QLatin1String method = mMethodsQueue.dequeue(); + invokeMethod(method); + } + else { + debug() << "Success: list" << mResult; + setResult(mResult.toList()); + setFinished(); + } +} + +void ConnectionManager::Private::PendingNames::invokeMethod(const QLatin1String &method) +{ + QDBusPendingCall call = mBus.interface()->asyncCallWithArgumentList( + method, QList<QVariant>()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onCallFinished(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::PendingNames::parseResult(const QStringList &names) +{ + foreach (const QString name, names) { + if (name.startsWith(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_MANAGER "."))) { + mResult << name.right(name.length() - 44); + } + } +} + +const Feature ConnectionManager::Private::ProtocolWrapper::FeatureCore = + Feature(QLatin1String(ConnectionManager::Private::ProtocolWrapper::staticMetaObject.className()), + 0, true); + +ConnectionManager::Private::ProtocolWrapper::ProtocolWrapper( + const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const QString &cmName, const QString &name, const QVariantMap &props) + : StatelessDBusProxy(bus, busName, objectPath, FeatureCore), + OptionalInterfaceFactory<ProtocolWrapper>(this), + mReadinessHelper(readinessHelper()), + mInfo(ProtocolInfo(cmName, name)), + mImmutableProps(props) +{ + fillRCCs(); + + ReadinessHelper::Introspectables introspectables; + + // As Protocol does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &ProtocolWrapper::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + mReadinessHelper->addIntrospectables(introspectables); +} + +ConnectionManager::Private::ProtocolWrapper::~ProtocolWrapper() +{ +} + +void ConnectionManager::Private::ProtocolWrapper::introspectMain( + ConnectionManager::Private::ProtocolWrapper *self) +{ + Client::DBus::PropertiesInterface *properties = self->propertiesInterface(); + Q_ASSERT(properties != 0); + + if (self->receiveProperties(self->mImmutableProps)) { + debug() << "Got everything we want from the immutable props for" << + self->info().name(); + + if (self->hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_AVATARS)) { + self->introspectQueue.enqueue(&ProtocolWrapper::introspectAvatars); + } else { + debug() << "Full functionality requires CM support for the Protocol.Avatars interface"; + } + + if (self->hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_PRESENCE)) { + self->introspectQueue.enqueue(&ProtocolWrapper::introspectPresence); + } else { + debug() << "Full functionality requires CM support for the Protocol.Presence interface"; + } + + self->continueIntrospection(); + return; + } + + debug() << "Not enough immutable properties, calling Properties::GetAll(Protocol) for" << + self->info().name(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->GetAll(TP_QT4_IFACE_PROTOCOL), + self); + self->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::ProtocolWrapper::introspectAvatars() +{ + Client::DBus::PropertiesInterface *properties = propertiesInterface(); + Q_ASSERT(properties != 0); + + debug() << "Calling Properties::GetAll(Protocol.Avatars)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->GetAll(TP_QT4_IFACE_PROTOCOL_INTERFACE_AVATARS), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotAvatarsProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::ProtocolWrapper::introspectPresence() +{ + Client::DBus::PropertiesInterface *properties = propertiesInterface(); + Q_ASSERT(properties != 0); + + debug() << "Calling Properties::GetAll(Protocol.Presence)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->GetAll(TP_QT4_IFACE_PROTOCOL_INTERFACE_PRESENCE), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotPresenceProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::ProtocolWrapper::continueIntrospection() +{ + if (introspectQueue.isEmpty()) { + mReadinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + (this->*(introspectQueue.dequeue()))(); + } +} + +void ConnectionManager::Private::ProtocolWrapper::gotMainProperties( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Protocol)"; + + QVariantMap unqualifiedProps = reply.value(); + QVariantMap qualifiedProps; + foreach (QString unqualified, unqualifiedProps.keys()) { + qualifiedProps.insert( + QString(QLatin1String("%1.%2")). + arg(TP_QT4_IFACE_PROTOCOL). + arg(unqualified), + unqualifiedProps.value(unqualified)); + } + receiveProperties(qualifiedProps); + } else { + warning().nospace() << + "Properties.GetAll(Protocol) failed: " << + reply.error().name() << ": " << reply.error().message(); + warning() << " Full functionality requires CM support for the Protocol interface"; + } + + if (hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_AVATARS)) { + introspectQueue.enqueue(&ProtocolWrapper::introspectAvatars); + } else { + debug() << "Full functionality requires CM support for the Protocol.Avatars interface"; + } + + if (hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_PRESENCE)) { + introspectQueue.enqueue(&ProtocolWrapper::introspectPresence); + } else { + debug() << "Full functionality requires CM support for the Protocol.Presence interface"; + } + + continueIntrospection(); + + watcher->deleteLater(); +} + +void ConnectionManager::Private::ProtocolWrapper::gotAvatarsProperties( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Protocol.Avatars)"; + props = reply.value(); + + QStringList supportedMimeTypes = qdbus_cast<QStringList>( + props[QLatin1String("SupportedAvatarMIMETypes")]); + uint minHeight = qdbus_cast<uint>(props[QLatin1String("MinimumAvatarHeight")]); + uint maxHeight = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarHeight")]); + uint recommendedHeight = qdbus_cast<uint>( + props[QLatin1String("RecommendedAvatarHeight")]); + uint minWidth = qdbus_cast<uint>(props[QLatin1String("MinimumAvatarWidth")]); + uint maxWidth = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarWidth")]); + uint recommendedWidth = qdbus_cast<uint>( + props[QLatin1String("RecommendedAvatarWidth")]); + uint maxBytes = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarBytes")]); + mInfo.setAvatarRequirements(AvatarSpec(supportedMimeTypes, + minHeight, maxHeight, recommendedHeight, + minWidth, maxWidth, recommendedWidth, + maxBytes)); + } else { + warning().nospace() << + "Properties.GetAll(Protocol.Avatars) failed: " << + reply.error().name() << ": " << reply.error().message(); + warning() << " Full functionality requires CM support for the Protocol.Avatars interface"; + } + + continueIntrospection(); + + watcher->deleteLater(); +} + +void ConnectionManager::Private::ProtocolWrapper::gotPresenceProperties( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Protocol.Presence)"; + props = reply.value(); + mInfo.setAllowedPresenceStatuses(PresenceSpecList(qdbus_cast<SimpleStatusSpecMap>( + props[QLatin1String("Statuses")]))); + } else { + warning().nospace() << + "Properties.GetAll(Protocol.Presence) failed: " << + reply.error().name() << ": " << reply.error().message(); + warning() << " Full functionality requires CM support for the Protocol.Presence interface"; + } + + continueIntrospection(); + + watcher->deleteLater(); +} + +void ConnectionManager::Private::ProtocolWrapper::fillRCCs() +{ + RequestableChannelClassList classes; + + QVariantMap fixedProps; + QStringList allowedProps; + + // Text chatrooms + fixedProps.insert( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT)); + fixedProps.insert( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + static_cast<uint>(HandleTypeRoom)); + + RequestableChannelClass textChatroom = {fixedProps, allowedProps}; + classes.append(textChatroom); + + // 1-1 text chats + fixedProps[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")] = + static_cast<uint>(HandleTypeContact); + + RequestableChannelClass text = {fixedProps, allowedProps}; + classes.append(text); + + // Media calls + fixedProps[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")] = + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA); + + RequestableChannelClass media = {fixedProps, allowedProps}; + classes.append(media); + + // Initially audio-only calls + allowedProps.push_back( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio")); + + RequestableChannelClass initialAudio = {fixedProps, allowedProps}; + classes.append(initialAudio); + + // Initially AV calls + allowedProps.push_back( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo")); + + RequestableChannelClass initialAV = {fixedProps, allowedProps}; + classes.append(initialAV); + + // Initially video-only calls + allowedProps.removeAll( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio")); + + RequestableChannelClass initialVideo = {fixedProps, allowedProps}; + classes.append(initialVideo); + + // That also settles upgrading calls, because the media classes don't have ImmutableStreams + + mInfo.setRequestableChannelClasses(classes); +} + +bool ConnectionManager::Private::ProtocolWrapper::receiveProperties(const QVariantMap &props) +{ + bool gotEverything = + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Interfaces")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Parameters")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".ConnectionInterfaces")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".VCardField")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".EnglishName")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Icon")); + + setInterfaces(qdbus_cast<QStringList>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Interfaces")])); + mReadinessHelper->setInterfaces(interfaces()); + + ParamSpecList parameters = qdbus_cast<ParamSpecList>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Parameters")]); + foreach (const ParamSpec &spec, parameters) { + mInfo.addParameter(spec); + } + + mInfo.setVCardField(qdbus_cast<QString>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".VCardField")])); + QString englishName = qdbus_cast<QString>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".EnglishName")]); + if (englishName.isEmpty()) { + QStringList words = mInfo.name().split(QLatin1Char('-')); + for (int i = 0; i < words.size(); ++i) { + words[i][0] = words[i].at(0).toUpper(); + } + englishName = words.join(QLatin1String(" ")); + } + mInfo.setEnglishName(englishName); + QString iconName = qdbus_cast<QString>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Icon")]); + if (iconName.isEmpty()) { + iconName = QString(QLatin1String("im-%1")).arg(mInfo.name()); + } + mInfo.setIconName(iconName); + + // Don't overwrite the everything-is-possible RCCs with an empty list if there is no RCCs key + if (props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses"))) { + mInfo.setRequestableChannelClasses(qdbus_cast<RequestableChannelClassList>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses")])); + } + + return gotEverything; +} + +ConnectionManager::Private::Private(ConnectionManager *parent, const QString &name, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) + : parent(parent), + lowlevel(ConnectionManagerLowlevelPtr(new ConnectionManagerLowlevel(parent))), + name(name), + baseInterface(new Client::ConnectionManagerInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + connFactory(connFactory), + chanFactory(chanFactory), + contactFactory(contactFactory) +{ + debug() << "Creating new ConnectionManager:" << parent->busName(); + + if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + + ReadinessHelper::Introspectables introspectables; + + // As ConnectionManager does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); +} + +ConnectionManager::Private::~Private() +{ + delete baseInterface; +} + +bool ConnectionManager::Private::parseConfigFile() +{ + ManagerFile f(name); + if (!f.isValid()) { + return false; + } + + foreach (const QString &protocol, f.protocols()) { + ProtocolInfo info(name, protocol); + + foreach (const ParamSpec &spec, f.parameters(protocol)) { + info.addParameter(spec); + } + info.setRequestableChannelClasses( + f.requestableChannelClasses(protocol)); + info.setVCardField(f.vcardField(protocol)); + info.setEnglishName(f.englishName(protocol)); + info.setIconName(f.iconName(protocol)); + info.setAllowedPresenceStatuses(f.allowedPresenceStatuses(protocol)); + info.setAvatarRequirements(f.avatarRequirements(protocol)); + + protocols.append(info); + } + + return true; +} + +void ConnectionManager::Private::introspectMain(ConnectionManager::Private *self) +{ + if (self->parseConfigFile()) { + self->readinessHelper->setIntrospectCompleted(FeatureCore, true); + return; + } + + warning() << "Error parsing config file for connection manager" + << self->name << "- introspecting"; + + debug() << "Calling Properties::GetAll(ConnectionManager)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_MANAGER)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::introspectProtocolsLegacy() +{ + debug() << "Calling ConnectionManager::ListProtocols"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + baseInterface->ListProtocols(), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotProtocolsLegacy(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::introspectParametersLegacy() +{ + foreach (const QString &protocolName, parametersQueue) { + debug() << "Calling ConnectionManager::GetParameters(" << protocolName << ")"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + baseInterface->GetParameters(protocolName), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotParametersLegacy(QDBusPendingCallWatcher*))); + } +} + +QString ConnectionManager::Private::makeBusName(const QString &name) +{ + return QString(QLatin1String( + TELEPATHY_CONNECTION_MANAGER_BUS_NAME_BASE)).append(name); +} + +QString ConnectionManager::Private::makeObjectPath(const QString &name) +{ + return QString(QLatin1String( + TELEPATHY_CONNECTION_MANAGER_OBJECT_PATH_BASE)).append(name); +} + +/** + * \class ConnectionManagerLowlevel + * \ingroup clientcm + * \headerfile TelepathyQt4/connection-manager-lowlevel.h <TelepathyQt4/ConnectionManagerLowlevel> + * + * \brief The ConnectionManagerLowlevel class extends ConnectionManager with + * support to low-level features. + */ + +ConnectionManagerLowlevel::ConnectionManagerLowlevel(ConnectionManager *cm) + : mPriv(new Private(cm)) +{ +} + +ConnectionManagerLowlevel::~ConnectionManagerLowlevel() +{ + delete mPriv; +} + +bool ConnectionManagerLowlevel::isValid() const +{ + return !mPriv->cm.isNull(); +} + +ConnectionManagerPtr ConnectionManagerLowlevel::connectionManager() const +{ + return ConnectionManagerPtr(mPriv->cm); +} + +/** + * \class ConnectionManager + * \ingroup clientcm + * \headerfile TelepathyQt4/connection-manager.h <TelepathyQt4/ConnectionManager> + * + * \brief The ConnectionManager class represents a Telepathy connection manager. + * + * Connection managers allow connections to be made on one or more protocols. + * + * Most client applications should use this functionality via the + * AccountManager, to allow connections to be shared between client + * applications. + */ + +/** + * Feature representing the core that needs to become ready to make the + * ConnectionManager object usable. + * + * Note that this feature must be enabled in order to use most ConnectionManager + * methods. + * See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature ConnectionManager::FeatureCore = Feature(QLatin1String(ConnectionManager::staticMetaObject.className()), 0, true); + +/** + * Create a new ConnectionManager object. + * + * The instance will use a connection factory creating Tp::Connection objects with no features + * ready, and a channel factory creating stock Telepathy-Qt4 channel subclasses, as appropriate, + * with no features ready. + * + * \param bus QDBusConnection to use. + * \param name Name of the connection manager. + * \return A ConnectionManagerPtr object pointing to the newly created + * ConnectionManager object. + */ +ConnectionManagerPtr ConnectionManager::create(const QDBusConnection &bus, const QString &name) +{ + return ConnectionManagerPtr(new ConnectionManager(QDBusConnection::sessionBus(), name, + ConnectionFactory::create(bus), ChannelFactory::create(bus), + ContactFactory::create())); +} + +/** + * Create a new ConnectionManager using QDBusConnection::sessionBus() and the given factories. + * + * The channel factory is passed to any Connection objects created by this manager + * object. In fact, they're not used directly by ConnectionManager at all. + * + * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus(). + * + * \param name Name of the connection manager. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ConnectionManagerPtr object pointing to the newly created + * ConnectionManager object. + */ +ConnectionManagerPtr ConnectionManager::create(const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ConnectionManagerPtr(new ConnectionManager(QDBusConnection::sessionBus(), name, + connectionFactory, channelFactory, contactFactory)); +} + +/** + * Create a new ConnectionManager using the given \a bus and the given factories. + * + * The channel factory is passed to any Connection objects created by this manager + * object. In fact, they're not used directly by ConnectionManager at all. + * + * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus(). + * + * \param bus QDBusConnection to use. + * \param name Name of the connection manager. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ConnectionManagerPtr object pointing to the newly created + * ConnectionManager object. + */ +ConnectionManagerPtr ConnectionManager::create(const QDBusConnection &bus, + const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ConnectionManagerPtr(new ConnectionManager(bus, name, + connectionFactory, channelFactory, contactFactory)); +} + +/** + * Construct a new ConnectionManager object using the given \a bus. + * + * \param bus QDBusConnection to use. + * \param name Name of the connection manager. + */ +ConnectionManager::ConnectionManager(const QDBusConnection &bus, + const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) + : StatelessDBusProxy(bus, Private::makeBusName(name), + Private::makeObjectPath(name), FeatureCore), + OptionalInterfaceFactory<ConnectionManager>(this), + mPriv(new Private(this, name, connectionFactory, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +ConnectionManager::~ConnectionManager() +{ + delete mPriv; +} + +/** + * Return the short name of the connection manager (e.g. "gabble"). + * + * \return The name of the connection manager. + */ +QString ConnectionManager::name() const +{ + return mPriv->name; +} + +/** + * Return the connection factory used by this manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ConnectionFactory object. + */ +ConnectionFactoryConstPtr ConnectionManager::connectionFactory() const +{ + return mPriv->connFactory; +} + +/** + * Return the channel factory used by this manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr ConnectionManager::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Return the contact factory used by this manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr ConnectionManager::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return a list of strings identifying the protocols supported by this + * connection manager, as described in the \telepathy_spec (e.g. "jabber"). + * + * These identifiers are not intended to be displayed to users directly; user + * interfaces are responsible for mapping them to localized strings. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \return A list of supported protocols. + */ +QStringList ConnectionManager::supportedProtocols() const +{ + QStringList protocols; + foreach (const ProtocolInfo &info, mPriv->protocols) { + protocols.append(info.name()); + } + return protocols; +} + +/** + * Return a list of protocols info for this connection manager. + * + * Note that the returned ProtocolInfoList contents should not be freed. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \return A list of á¹”rotocolInfo. + */ +const ProtocolInfoList &ConnectionManager::protocols() const +{ + return mPriv->protocols; +} + +/** + * Return whether this connection manager implements the protocol specified by + * \a protocolName. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \return \c true if the protocol is supported, \c false otherwise. + * \sa protocol(), protocols() + */ +bool ConnectionManager::hasProtocol(const QString &protocolName) const +{ + foreach (const ProtocolInfo &info, mPriv->protocols) { + if (info.name() == protocolName) { + return true; + } + } + return false; +} + +/** + * Return the ProtocolInfo object for the protocol specified by + * \a protocolName. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \param protocolName The name of the protocol. + * \return A ProtocolInfo object which will return \c for ProtocolInfo::isValid() if the protocol + * specified by \a protocolName is not supported. + * \sa hasProtocol() + */ +ProtocolInfo ConnectionManager::protocol(const QString &protocolName) const +{ + foreach (const ProtocolInfo &info, mPriv->protocols) { + if (info.name() == protocolName) { + return info; + } + } + return ProtocolInfo(); +} + +/** + * Request a Connection object representing a given account on a given protocol + * with the given parameters. + * + * Return a pending operation representing the Connection object which will + * succeed when the connection has been created or fail if an error occurred. + * + * \param protocol Name of the protocol to create the account for. + * \param parameters Account parameters. + * \return A PendingOperation which will emit PendingConnection::finished when + * the account has been created of failed its creation process. + */ +PendingConnection *ConnectionManagerLowlevel::requestConnection(const QString &protocol, + const QVariantMap ¶meters) +{ + if (!isValid()) { + return new PendingConnection(TP_QT4_ERROR_NOT_AVAILABLE, + QLatin1String("The connection manager has been destroyed already")); + } + + return new PendingConnection(ConnectionManagerPtr(mPriv->cm), + protocol, parameters); +} + +/** + * Return a pending operation from which a list of all installed connection + * manager short names (such as "gabble" or "haze") can be retrieved if it + * succeeds. + * + * \return A PendingStringList which will emit PendingStringList::finished + * when this object has finished or failed getting the connection + * manager names. + */ +PendingStringList *ConnectionManager::listNames(const QDBusConnection &bus) +{ + return new ConnectionManager::Private::PendingNames(bus); +} + +ConnectionManagerLowlevelPtr ConnectionManager::lowlevel() +{ + return mPriv->lowlevel; +} + +ConnectionManagerLowlevelConstPtr ConnectionManager::lowlevel() const +{ + return mPriv->lowlevel; +} + +/** + * Return the Client::ConnectionManagerInterface for this ConnectionManager. + * This method is protected since the convenience methods provided by this + * class should generally be used instead of calling D-Bus methods + * directly. + * + * \return A pointer to the existing Client::ConnectionManagerInterface for this + * ConnectionManager object. + */ +Client::ConnectionManagerInterface *ConnectionManager::baseInterface() const +{ + return mPriv->baseInterface; +} + +/**** Private ****/ +void ConnectionManager::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(ConnectionManager)"; + props = reply.value(); + + // If Interfaces is not supported, the spec says to assume it's + // empty, so keep the empty list mPriv was initialized with + if (props.contains(QLatin1String("Interfaces"))) { + setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")])); + mPriv->readinessHelper->setInterfaces(interfaces()); + } + } else { + warning().nospace() << + "Properties.GetAll(ConnectionManager) failed: " << + reply.error().name() << ": " << reply.error().message(); + + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + watcher->deleteLater(); + return; + } + + ProtocolPropertiesMap protocolsMap = + qdbus_cast<ProtocolPropertiesMap>(props[QLatin1String("Protocols")]); + if (!protocolsMap.isEmpty()) { + ProtocolPropertiesMap::const_iterator i = protocolsMap.constBegin(); + ProtocolPropertiesMap::const_iterator end = protocolsMap.constEnd(); + while (i != end) { + QString protocolName = i.key(); + if (!checkValidProtocolName(protocolName)) { + warning() << "Protocol has an invalid name" << protocolName << "- ignoring"; + continue; + } + + QString escapedProtocolName = protocolName; + escapedProtocolName.replace(QLatin1Char('-'), QLatin1Char('_')); + QString protocolPath = QString( + QLatin1String("%1/%2")).arg(objectPath()).arg(escapedProtocolName); + SharedPtr<Private::ProtocolWrapper> wrapper = SharedPtr<Private::ProtocolWrapper>( + new Private::ProtocolWrapper( + dbusConnection(), busName(), protocolPath, + mPriv->name, protocolName, i.value())); + connect(wrapper->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onProtocolReady(Tp::PendingOperation*))); + mPriv->wrappers.insert(wrapper); + ++i; + } + } else { + mPriv->introspectProtocolsLegacy(); + } + + watcher->deleteLater(); +} + +void ConnectionManager::gotProtocolsLegacy(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + QStringList protocolsNames; + + if (!reply.isError()) { + debug() << "Got reply to ConnectionManager.ListProtocols"; + protocolsNames = reply.value(); + + foreach (const QString &protocolName, protocolsNames) { + mPriv->protocols.append(ProtocolInfo(mPriv->name, protocolName)); + mPriv->parametersQueue.enqueue(protocolName); + } + + mPriv->introspectParametersLegacy(); + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + + warning().nospace() << + "ConnectionManager.ListProtocols failed: " << + reply.error().name() << ": " << reply.error().message(); + + // FIXME shouldn't this invalidate the CM? + } + + watcher->deleteLater(); +} + +void ConnectionManager::gotParametersLegacy(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<ParamSpecList> reply = *watcher; + QString protocolName = mPriv->parametersQueue.dequeue(); + bool found = false; + int pos = 0; + foreach (const ProtocolInfo &info, mPriv->protocols) { + if (info.name() == protocolName) { + found = true; + break; + } + ++pos; + } + Q_ASSERT(found); + + if (!reply.isError()) { + debug() << QString(QLatin1String("Got reply to ConnectionManager.GetParameters(%1)")).arg(protocolName); + ParamSpecList parameters = reply.value(); + ProtocolInfo &info = mPriv->protocols[pos]; + foreach (const ParamSpec &spec, parameters) { + debug() << "Parameter" << spec.name << "has flags" << spec.flags + << "and signature" << spec.signature; + + info.addParameter(spec); + } + } else { + // let's remove this protocol as we can't get the params + mPriv->protocols.removeAt(pos); + + warning().nospace() << + QString(QLatin1String("ConnectionManager.GetParameters(%1) failed: ")).arg(protocolName) << + reply.error().name() << ": " << reply.error().message(); + } + + if (mPriv->parametersQueue.isEmpty()) { + if (!mPriv->protocols.isEmpty()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + // we could not retrieve the params for any protocol, fail core. + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + } + } + + watcher->deleteLater(); +} + +void ConnectionManager::onProtocolReady(Tp::PendingOperation *op) +{ + PendingReady *pr = qobject_cast<PendingReady*>(op); + SharedPtr<Private::ProtocolWrapper> wrapper = + SharedPtr<Private::ProtocolWrapper>::qObjectCast(pr->proxy()); + ProtocolInfo info = wrapper->info(); + + mPriv->wrappers.remove(wrapper); + + if (!op->isError()) { + mPriv->protocols.append(info); + } else { + warning().nospace() << "Protocol(" << info.name() << ")::becomeReady " + "failed: " << op->errorName() << ": " << op->errorMessage(); + } + + if (mPriv->wrappers.isEmpty()) { + if (!mPriv->protocols.isEmpty()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + // we could not make any Protocol objects ready, fail core. + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + op->errorName(), op->errorMessage()); + } + } +} + +} // Tp |