/** * This file is part of TelepathyQt4 * * @copyright Copyright (C) 2008-2010 Collabora Ltd. * @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 #include #include "TelepathyQt4/connection-internal.h" #include "TelepathyQt4/_gen/cli-connection.moc.hpp" #include "TelepathyQt4/_gen/cli-connection-body.hpp" #include "TelepathyQt4/_gen/connection.moc.hpp" #include "TelepathyQt4/_gen/connection-internal.moc.hpp" #include "TelepathyQt4/_gen/connection-lowlevel.moc.hpp" #include "TelepathyQt4/debug-internal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Tp { struct TELEPATHY_QT4_NO_EXPORT Connection::Private { Private(Connection *parent, const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory); ~Private(); void init(); static void introspectMain(Private *self); void introspectMainFallbackStatus(); void introspectMainFallbackInterfaces(); void introspectMainFallbackSelfHandle(); void introspectCapabilities(); void introspectContactAttributeInterfaces(); static void introspectSelfContact(Private *self); static void introspectSimplePresence(Private *self); static void introspectRoster(Private *self); static void introspectRosterGroups(Private *self); static void introspectBalance(Private *self); static void introspectConnected(Private *self); void continueMainIntrospection(); void setCurrentStatus(uint status); void forceCurrentStatus(uint status); void setInterfaces(const QStringList &interfaces); // Should always be used instead of directly using baseclass invalidate() void invalidateResetCaps(const QString &errorName, const QString &errorMessage); struct HandleContext; // Public object Connection *parent; ConnectionLowlevelPtr lowlevel; // Factories ChannelFactoryConstPtr chanFactory; ContactFactoryConstPtr contactFactory; // Instance of generated interface class Client::ConnectionInterface *baseInterface; // Mandatory properties interface proxy Client::DBus::PropertiesInterface *properties; // Optional interface proxies Client::ConnectionInterfaceSimplePresenceInterface *simplePresence; ReadinessHelper *readinessHelper; // Introspection QQueue introspectMainQueue; // FeatureCore // keep pendingStatus and pendingStatusReason until we emit statusChanged // so Connection::status() and Connection::statusReason() are consistent bool introspectingConnected; uint pendingStatus; uint pendingStatusReason; uint status; uint statusReason; ErrorDetails errorDetails; uint selfHandle; bool immortalHandles; ConnectionCapabilities caps; ContactManagerPtr contactManager; // FeatureSelfContact bool introspectingSelfContact; bool reintrospectSelfContactRequired; ContactPtr selfContact; QStringList contactAttributeInterfaces; // FeatureSimplePresence SimpleStatusSpecMap simplePresenceStatuses; uint maxPresenceStatusMessageLength; // FeatureAccountBalance CurrencyAmount accountBalance; // misc // (Bus connection name, service name) -> HandleContext static QMap, HandleContext *> handleContexts; static QMutex handleContextsLock; HandleContext *handleContext; QString cmName; QString protocolName; }; struct TELEPATHY_QT4_NO_EXPORT ConnectionLowlevel::Private { Private(Connection *conn) : conn(QWeakPointer(conn)) { } QWeakPointer conn; HandleIdentifierMap contactsIds; }; // Handle tracking struct TELEPATHY_QT4_NO_EXPORT Connection::Private::HandleContext { struct Type { QMap refcounts; QSet toRelease; uint requestsInFlight; bool releaseScheduled; Type() : requestsInFlight(0), releaseScheduled(false) { } }; HandleContext() : refcount(0) { } int refcount; QMutex lock; QMap types; }; Connection::Private::Private(Connection *parent, const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory) : parent(parent), lowlevel(ConnectionLowlevelPtr(new ConnectionLowlevel(parent))), chanFactory(chanFactory), contactFactory(contactFactory), baseInterface(new Client::ConnectionInterface(parent)), properties(parent->interface()), simplePresence(0), readinessHelper(parent->readinessHelper()), introspectingConnected(false), pendingStatus((uint) -1), pendingStatusReason(ConnectionStatusReasonNoneSpecified), status((uint) -1), statusReason(ConnectionStatusReasonNoneSpecified), selfHandle(0), immortalHandles(false), contactManager(ContactManagerPtr(new ContactManager(parent))), introspectingSelfContact(false), reintrospectSelfContactRequired(false), maxPresenceStatusMessageLength(0), handleContext(0) { accountBalance.amount = 0; accountBalance.scale = 0; Q_ASSERT(properties != 0); if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) { warning() << " The D-Bus connection in the channel factory is not the proxy connection"; } init(); ReadinessHelper::Introspectables introspectables; ReadinessHelper::Introspectable introspectableCore( QSet() << (uint) -1 << ConnectionStatusDisconnected << ConnectionStatusConnected, // makesSenseForStatuses Features(), // dependsOnFeatures (none) QStringList(), // dependsOnInterfaces (none) (ReadinessHelper::IntrospectFunc) &Private::introspectMain, this); introspectables[FeatureCore] = introspectableCore; ReadinessHelper::Introspectable introspectableSelfContact( QSet() << ConnectionStatusConnected, // makesSenseForStatuses Features() << FeatureCore, // dependsOnFeatures (core) QStringList(), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectSelfContact, this); introspectables[FeatureSelfContact] = introspectableSelfContact; ReadinessHelper::Introspectable introspectableSimplePresence( QSet() << ConnectionStatusDisconnected << ConnectionStatusConnected, // makesSenseForStatuses Features() << FeatureCore, // dependsOnFeatures (core) QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectSimplePresence, this); introspectables[FeatureSimplePresence] = introspectableSimplePresence; ReadinessHelper::Introspectable introspectableRoster( QSet() << ConnectionStatusConnected, // makesSenseForStatuses Features() << FeatureCore, // dependsOnFeatures (core) QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectRoster, this); introspectables[FeatureRoster] = introspectableRoster; ReadinessHelper::Introspectable introspectableRosterGroups( QSet() << ConnectionStatusConnected, // makesSenseForStatuses Features() << FeatureRoster, // dependsOnFeatures (core) QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectRosterGroups, this); introspectables[FeatureRosterGroups] = introspectableRosterGroups; ReadinessHelper::Introspectable introspectableBalance( QSet() << ConnectionStatusConnected, // makesSenseForStatuses Features() << FeatureCore, // dependsOnFeatures (core) QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_BALANCE), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectBalance, this); introspectables[FeatureAccountBalance] = introspectableBalance; ReadinessHelper::Introspectable introspectableConnected( QSet() << (uint) -1 << ConnectionStatusDisconnected << ConnectionStatusConnecting << ConnectionStatusConnected, // makesSenseForStatuses Features() << FeatureCore, // dependsOnFeatures (none) QStringList(), // dependsOnInterfaces (none) (ReadinessHelper::IntrospectFunc) &Private::introspectConnected, this); introspectables[FeatureConnected] = introspectableConnected; readinessHelper->addIntrospectables(introspectables); readinessHelper->setCurrentStatus(status); parent->connect(readinessHelper, SIGNAL(statusReady(uint)), SLOT(onStatusReady(uint))); // FIXME: QRegExp probably isn't the most efficient possible way to parse // this :-) QRegExp rx(QLatin1String("^" TELEPATHY_CONNECTION_OBJECT_PATH_BASE "([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol "/([_A-Za-z][_A-Za-z0-9]*)" // account-specific part )); if (rx.exactMatch(parent->objectPath())) { cmName = rx.cap(1); protocolName = rx.cap(2); } else { warning() << "Connection object path is not spec-compliant, " "trying again with a different account-specific part check"; rx = QRegExp(QLatin1String("^" TELEPATHY_CONNECTION_OBJECT_PATH_BASE "([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol "/([_A-Za-z0-9]*)" // account-specific part )); if (rx.exactMatch(parent->objectPath())) { cmName = rx.cap(1); protocolName = rx.cap(2); } else { warning() << "Not a valid Connection object path:" << parent->objectPath(); } } } Connection::Private::~Private() { contactManager->resetRoster(); // Clear selfContact so its handle will be released cleanly before the // handleContext selfContact.reset(); QMutexLocker locker(&handleContextsLock); // All handle contexts locked, so safe if (!--handleContext->refcount) { if (!immortalHandles) { debug() << "Destroying HandleContext"; foreach (uint handleType, handleContext->types.keys()) { HandleContext::Type type = handleContext->types[handleType]; if (!type.refcounts.empty()) { debug() << " Still had references to" << type.refcounts.size() << "handles, releasing now"; baseInterface->ReleaseHandles(handleType, type.refcounts.keys()); } if (!type.toRelease.empty()) { debug() << " Was going to release" << type.toRelease.size() << "handles, doing that now"; baseInterface->ReleaseHandles(handleType, type.toRelease.toList()); } } } handleContexts.remove(qMakePair(baseInterface->connection().name(), parent->objectPath())); delete handleContext; } else { Q_ASSERT(handleContext->refcount > 0); } } void Connection::Private::init() { debug() << "Connecting to ConnectionError()"; parent->connect(baseInterface, SIGNAL(ConnectionError(QString,QVariantMap)), SLOT(onConnectionError(QString,QVariantMap))); debug() << "Connecting to StatusChanged()"; parent->connect(baseInterface, SIGNAL(StatusChanged(uint,uint)), SLOT(onStatusChanged(uint,uint))); debug() << "Connecting to SelfHandleChanged()"; parent->connect(baseInterface, SIGNAL(SelfHandleChanged(uint)), SLOT(onSelfHandleChanged(uint))); QMutexLocker locker(&handleContextsLock); QString busConnectionName = baseInterface->connection().name(); if (handleContexts.contains(qMakePair(busConnectionName, parent->objectPath()))) { debug() << "Reusing existing HandleContext for" << parent->objectPath(); handleContext = handleContexts[ qMakePair(busConnectionName, parent->objectPath())]; } else { debug() << "Creating new HandleContext for" << parent->objectPath(); handleContext = new HandleContext; handleContexts[ qMakePair(busConnectionName, parent->objectPath())] = handleContext; } // All handle contexts locked, so safe ++handleContext->refcount; } void Connection::Private::introspectMain(Connection::Private *self) { debug() << "Calling Properties::GetAll(Connection)"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CONNECTION)), self->parent); self->parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotMainProperties(QDBusPendingCallWatcher*))); } void Connection::Private::introspectMainFallbackStatus() { debug() << "Calling GetStatus()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(baseInterface->GetStatus(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotStatus(QDBusPendingCallWatcher*))); } void Connection::Private::introspectMainFallbackInterfaces() { debug() << "Calling GetInterfaces()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(baseInterface->GetInterfaces(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotInterfaces(QDBusPendingCallWatcher*))); } void Connection::Private::introspectMainFallbackSelfHandle() { debug() << "Calling GetSelfHandle()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(baseInterface->GetSelfHandle(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotSelfHandle(QDBusPendingCallWatcher*))); } void Connection::Private::introspectCapabilities() { debug() << "Retrieving capabilities"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( properties->Get( QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), QLatin1String("RequestableChannelClasses")), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotCapabilities(QDBusPendingCallWatcher*))); } void Connection::Private::introspectContactAttributeInterfaces() { debug() << "Retrieving contact attribute interfaces"; QDBusPendingCall call = properties->Get( QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS), QLatin1String("ContactAttributeInterfaces")); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotContactAttributeInterfaces(QDBusPendingCallWatcher*))); } void Connection::Private::introspectSelfContact(Connection::Private *self) { debug() << "Building self contact"; Q_ASSERT(!self->introspectingSelfContact); self->introspectingSelfContact = true; self->reintrospectSelfContactRequired = false; PendingContacts *contacts = self->contactManager->contactsForHandles( UIntList() << self->selfHandle); self->parent->connect(contacts, SIGNAL(finished(Tp::PendingOperation*)), SLOT(gotSelfContact(Tp::PendingOperation*))); } void Connection::Private::introspectSimplePresence(Connection::Private *self) { Q_ASSERT(self->properties != 0); debug() << "Calling Properties::Get(" "Connection.I.SimplePresence.Statuses)"; QDBusPendingCall call = self->properties->GetAll( QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE)); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, self->parent); self->parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotSimpleStatuses(QDBusPendingCallWatcher*))); } void Connection::Private::introspectRoster(Connection::Private *self) { debug() << "Introspecting roster"; PendingOperation *op = self->contactManager->introspectRoster(); self->parent->connect(op, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onIntrospectRosterFinished(Tp::PendingOperation*))); } void Connection::Private::introspectRosterGroups(Connection::Private *self) { debug() << "Introspecting roster groups"; PendingOperation *op = self->contactManager->introspectRosterGroups(); self->parent->connect(op, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onIntrospectRosterGroupsFinished(Tp::PendingOperation*))); } void Connection::Private::introspectBalance(Connection::Private *self) { debug() << "Introspecting balance"; // we already checked if balance interface exists, so bypass requests // interface checking Client::ConnectionInterfaceBalanceInterface *iface = self->parent->interface(); debug() << "Connecting to Balance.BalanceChanged"; self->parent->connect(iface, SIGNAL(BalanceChanged(Tp::CurrencyAmount)), SLOT(onBalanceChanged(Tp::CurrencyAmount))); debug() << "Retrieving balance"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( self->properties->Get( QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_BALANCE), QLatin1String("AccountBalance")), self->parent); self->parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotBalance(QDBusPendingCallWatcher*))); } void Connection::Private::introspectConnected(Connection::Private *self) { Q_ASSERT(!self->introspectingConnected); self->introspectingConnected = true; if (self->pendingStatus == ConnectionStatusConnected) { self->readinessHelper->setIntrospectCompleted(FeatureConnected, true); self->introspectingConnected = false; } } void Connection::Private::continueMainIntrospection() { if (!parent->isValid()) { debug() << parent << "stopping main introspection, as it has been invalidated"; return; } if (introspectMainQueue.isEmpty()) { readinessHelper->setIntrospectCompleted(FeatureCore, true); } else { (this->*(introspectMainQueue.dequeue()))(); } } void Connection::Private::setCurrentStatus(uint status) { // ReadinessHelper waits for all in-flight introspection ops to finish for the current status // before proceeding to a new one. // // Therefore we don't need any safeguarding here to prevent finishing introspection when there // is a pending status change. However, we can speed up the process slightly by canceling any // pending introspect ops from our local introspection queue when it's waiting for us. introspectMainQueue.clear(); if (introspectingConnected) { // On the other hand, we have to finish the Connected introspection for now, as // ReadinessHelper would otherwise wait indefinitely for it to land debug() << "Finishing FeatureConnected for status" << this->status << "to allow ReadinessHelper to introspect new status" << status; readinessHelper->setIntrospectCompleted(FeatureConnected, true); introspectingConnected = false; } readinessHelper->setCurrentStatus(status); } void Connection::Private::forceCurrentStatus(uint status) { // only update the status if we did not get it from StatusChanged if (pendingStatus == (uint) -1) { debug() << "Got status:" << status; pendingStatus = status; // No need to re-run introspection as we just received the status. Let // the introspection continue normally but update readinessHelper with // the correct status. readinessHelper->forceCurrentStatus(status); } } void Connection::Private::setInterfaces(const QStringList &interfaces) { debug() << "Got interfaces:" << interfaces; parent->setInterfaces(interfaces); readinessHelper->setInterfaces(interfaces); } void Connection::Private::invalidateResetCaps(const QString &error, const QString &message) { caps.updateRequestableChannelClasses(RequestableChannelClassList()); parent->invalidate(error, message); } /** * \class ConnectionLowlevel * \ingroup clientconn * \headerfile TelepathyQt4/connection-lowlevel.h * * \brief The ConnectionLowlevel class extends Connection with support to * low-level features. */ ConnectionLowlevel::ConnectionLowlevel(Connection *conn) : mPriv(new Private(conn)) { } ConnectionLowlevel::~ConnectionLowlevel() { delete mPriv; } bool ConnectionLowlevel::isValid() const { return !mPriv->conn.isNull(); } ConnectionPtr ConnectionLowlevel::connection() const { return ConnectionPtr(mPriv->conn); } Connection::PendingConnect::PendingConnect(const ConnectionPtr &connection, const Features &requestedFeatures) : PendingReady(connection, requestedFeatures) { if (!connection) { // Called when the connection had already been destroyed return; } QDBusPendingCall call = connection->baseInterface()->Connect(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(connection.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), this, SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onConnectReply(QDBusPendingCallWatcher*))); } void Connection::PendingConnect::onConnectReply(QDBusPendingCallWatcher *watcher) { ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy()); if (watcher->isError()) { debug() << "Connect failed with" << watcher->error().name() << ": " << watcher->error().message(); setFinishedWithError(watcher->error()); connection->disconnect( this, SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); } else { if (connection->status() == ConnectionStatusConnected) { onStatusChanged(ConnectionStatusConnected); } else { // Wait for statusChanged()! Connect returning just means that the connection has // started to connect - spec quoted for truth: // // Connect () -> nothing // Request that the connection be established. This will be done asynchronously and // errors will be returned by emitting StatusChanged signals. // // Which should actually say progress and/or errors IMO, but anyway... connect(connection.data(), SIGNAL(statusChanged(Tp::ConnectionStatus)), SLOT(onStatusChanged(Tp::ConnectionStatus))); } } watcher->deleteLater(); } void Connection::PendingConnect::onStatusChanged(ConnectionStatus newStatus) { ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy()); if (newStatus == ConnectionStatusDisconnected) { debug() << "Connection became disconnected while a PendingConnect was underway"; setFinishedWithError(connection->invalidationReason(), connection->invalidationMessage()); connection->disconnect(this, SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); return; } if (newStatus == ConnectionStatusConnected) { // OK, the connection is Connected now - finally, we'll get down to business connect(connection->becomeReady(requestedFeatures()), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onBecomeReadyReply(Tp::PendingOperation*))); } } void Connection::PendingConnect::onBecomeReadyReply(Tp::PendingOperation *op) { ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy()); // We don't care about future disconnects even if they happen before we are destroyed // (which happens two mainloop iterations from now) connection->disconnect(this, SLOT(onStatusChanged(Tp::ConnectionStatus))); connection->disconnect(this, SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); if (op->isError()) { debug() << "Connection->becomeReady failed with" << op->errorName() << ": " << op->errorMessage(); setFinishedWithError(op->errorName(), op->errorMessage()); } else { debug() << "Connected"; if (connection->isValid()) { setFinished(); } else { debug() << " ... but the Connection was immediately invalidated!"; setFinishedWithError(connection->invalidationReason(), connection->invalidationMessage()); } } } void Connection::PendingConnect::onConnInvalidated(Tp::DBusProxy *proxy, const QString &error, const QString &message) { ConnectionPtr connection = ConnectionPtr::qObjectCast(this->proxy()); Q_ASSERT(proxy == connection.data()); if (!isFinished()) { debug() << "Unable to connect. Connection invalidated"; setFinishedWithError(error, message); } connection->disconnect(this, SLOT(onStatusChanged(Tp::ConnectionStatus))); } QMap, Connection::Private::HandleContext*> Connection::Private::handleContexts; QMutex Connection::Private::handleContextsLock; /** * \class Connection * \ingroup clientconn * \headerfile TelepathyQt4/connection.h * * \brief The Connection class represents a Telepathy connection. * * This models a connection to a single user account on a communication service. * * Contacts, and server-stored lists (such as subscribed contacts, * block lists, or allow lists) on a service are all represented using the * ContactManager object on the connection, which is valid only for the lifetime * of the connection object. * * The remote object accessor functions on this object (status(), * statusReason(), and so on) don't make any D-Bus calls; instead, they return/use * values cached from a previous introspection run. The introspection process * populates their values in the most efficient way possible based on what the * service implements. * * To avoid unnecessary D-Bus traffic, some accessors only return valid * information after specific features have been enabled. * For instance, to retrieve the connection self contact, it is necessary to * enable the feature Connection::FeatureSelfContact. * See the individual methods descriptions for more details. * * Connection features can be enabled by constructing a ConnectionFactory and enabling * the desired features, and passing it to AccountManager, Account or ClientRegistrar * when creating them as appropriate. However, if a particular * feature is only ever used in a specific circumstance, such as an user opening * some settings dialog separate from the general view of the application, * features can be later enabled as needed by calling becomeReady() with the additional * features, and waiting for the resulting PendingOperation to finish. * * As an addition to accessors, signals are emitted to indicate that properties have changed, * for example statusChanged()(), selfContactChanged(), etc. * * \section conn_usage_sec Usage * * \subsection conn_create_sec Creating a connection object * * The easiest way to create connection objects is through Account. One can * just use the Account::connection method to get an account active connection. * * If you already know the object path, you can just call create(). * For example: * * \code ConnectionPtr conn = Connection::create(busName, objectPath); \endcode * * A ConnectionPtr object is returned, which will automatically keep * track of object lifetime. * * You can also provide a D-Bus connection as a QDBusConnection: * * \code * * ConnectionPtr conn = Connection::create(QDBusConnection::sessionBus(), * busName, objectPath); * * \endcode * * \subsection conn_ready_sec Making connection ready to use * * A Connection object needs to become ready before usage, meaning that the * introspection process finished and the object accessors can be used. * * To make the object ready, use becomeReady() and wait for the * PendingOperation::finished() signal to be emitted. * * \code * * class MyClass : public QObject * { * QOBJECT * * public: * MyClass(QObject *parent = 0); * ~MyClass() { } * * private Q_SLOTS: * void onConnectionReady(Tp::PendingOperation*); * * private: * ConnectionPtr conn; * }; * * MyClass::MyClass(const QString &busName, const QString &objectPath, * QObject *parent) * : QObject(parent) * conn(Connection::create(busName, objectPath)) * { * // connect and become ready * connect(conn->requestConnect(), * SIGNAL(finished(Tp::PendingOperation*)), * SLOT(onConnectionReady(Tp::PendingOperation*))); * } * * void MyClass::onConnectionReady(Tp::PendingOperation *op) * { * if (op->isError()) { * qWarning() << "Account cannot become ready:" << * op->errorName() << "-" << op->errorMessage(); * return; * } * * // Connection is now ready * } * * \endcode * * See \ref async_model, \ref shared_ptr */ /** * Feature representing the core that needs to become ready to make the * Connection object usable. * * Note that this feature must be enabled in order to use most Connection * methods. * See specific methods documentation for more details. * * When calling isReady(), becomeReady(), this feature is implicitly added * to the requested features. */ const Feature Connection::FeatureCore = Feature(QLatin1String(Connection::staticMetaObject.className()), 0, true); /** * Feature used to retrieve the connection self contact. * * See self contact specific methods' documentation for more details. * * \sa selfContact(), selfContactChanged() */ const Feature Connection::FeatureSelfContact = Feature(QLatin1String(Connection::staticMetaObject.className()), 1); /** * Feature used to retrieve/keep track of the connection self presence. * * See simple presence specific methods' documentation for more details. */ const Feature Connection::FeatureSimplePresence = Feature(QLatin1String(Connection::staticMetaObject.className()), 2); /** * Feature used to enable roster support on Connection::contactManager. * * See ContactManager roster specific methods' documentation for more details. * * \sa ContactManager::allKnownContacts() */ const Feature Connection::FeatureRoster = Feature(QLatin1String(Connection::staticMetaObject.className()), 4); /** * Feature used to enable roster groups support on Connection::contactManager. * * See ContactManager roster groups specific methods' documentation for more * details. * * \sa ContactManager::allKnownGroups() */ const Feature Connection::FeatureRosterGroups = Feature(QLatin1String(Connection::staticMetaObject.className()), 5); /** * Feature used to retrieve/keep track of the connection account balance. * * See account balance specific methods' documentation for more details. * * \sa accountBalance(), accountBalanceChanged() */ const Feature Connection::FeatureAccountBalance = Feature(QLatin1String(Connection::staticMetaObject.className()), 6); /** * When this feature is prepared, it means that the connection status() is * ConnectionStatusConnected. * * Note that if ConnectionFactory is being used with FeatureConnected set, Connection objects will * only be signalled by the library when the corresponding connection is in status() * ConnectionStatusConnected. */ const Feature Connection::FeatureConnected = Feature(QLatin1String(Connection::staticMetaObject.className()), 7); /** * Create a new connection object using QDBusConnection::sessionBus(). * * A warning is printed if the factories are not for QDBusConnection::sessionBus(). * * \param busName The connection well-known bus name (sometimes called a * "service name"). * \param objectPath The connection object path. * \param channelFactory The channel factory to use. * \param contactFactory The contact factory to use. * \return A ConnectionPtr object pointing to the newly created Connection object. */ ConnectionPtr Connection::create(const QString &busName, const QString &objectPath, const ChannelFactoryConstPtr &channelFactory, const ContactFactoryConstPtr &contactFactory) { return ConnectionPtr(new Connection(QDBusConnection::sessionBus(), busName, objectPath, channelFactory, contactFactory, Connection::FeatureCore)); } /** * Create a new connection object using the given \a bus. * * A warning is printed if the factories are not for \a bus. * * \param bus QDBusConnection to use. * \param busName The connection well-known bus name (sometimes called a * "service name"). * \param objectPath The connection object path. * \param channelFactory The channel factory to use. * \param contactFactory The contact factory to use. * \return A ConnectionPtr object pointing to the newly created Connection object. */ ConnectionPtr Connection::create(const QDBusConnection &bus, const QString &busName, const QString &objectPath, const ChannelFactoryConstPtr &channelFactory, const ContactFactoryConstPtr &contactFactory) { return ConnectionPtr(new Connection(bus, busName, objectPath, channelFactory, contactFactory, Connection::FeatureCore)); } /** * Construct a new connection object using the given \a bus. * * A warning is printed if the factories are not for \a bus. * * \param bus QDBusConnection to use. * \param busName The connection's well-known bus name (sometimes called a * "service name"). * \param objectPath The connection object path. * \param channelFactory The channel factory to use. * \param contactFactory The contact factory to use. * \param coreFeature The core feature of the Connection subclass. The corresponding introspectable * should depend on Connection::FeatureCore. */ Connection::Connection(const QDBusConnection &bus, const QString &busName, const QString &objectPath, const ChannelFactoryConstPtr &channelFactory, const ContactFactoryConstPtr &contactFactory, const Feature &coreFeature) : StatefulDBusProxy(bus, busName, objectPath, coreFeature), OptionalInterfaceFactory(this), mPriv(new Private(this, channelFactory, contactFactory)) { } /** * Class destructor. */ Connection::~Connection() { delete mPriv; } /** * Return the channel factory used by this connection. * * 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 account would have unpredictably * different construction settings (eg. subclass). * * \return A read-only pointer to the ChannelFactory object. */ ChannelFactoryConstPtr Connection::channelFactory() const { return mPriv->chanFactory; } /** * Return the contact factory used by this connection. * * 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 account would have unpredictably * different construction settings (eg. subclass). * * \return A read-only pointer to the ContactFactory object. */ ContactFactoryConstPtr Connection::contactFactory() const { return mPriv->contactFactory; } /** * Return the connection manager name of this connection. * * \return The connection manager name. */ QString Connection::cmName() const { return mPriv->cmName; } /** * Return the protocol name of this connection. * * \return The protocol name. */ QString Connection::protocolName() const { return mPriv->protocolName; } /** * Return the status of this connection. * * Change notification is via the statusChanged() signal. * * This method requires Connection::FeatureCore to be ready. * * \return The status as #ConnectionStatus. * \sa statusChanged(), statusReason(), errorDetails() */ ConnectionStatus Connection::status() const { return (ConnectionStatus) mPriv->status; } /** * Return the reason for this connection status. * * The validity and change rules are the same as for status(). * * The status reason should be only used as a fallback in error handling when the application * doesn't understand an error name given as the invalidation reason, which may in some cases be * domain/UI-specific. * * This method requires Connection::FeatureCore to be ready. * * \return The status reason as #ConnectionStatusReason. * \sa invalidated(), invalidationReason() */ ConnectionStatusReason Connection::statusReason() const { return (ConnectionStatusReason) mPriv->statusReason; } struct TELEPATHY_QT4_NO_EXPORT Connection::ErrorDetails::Private : public QSharedData { Private(const QVariantMap &details) : details(details) {} QVariantMap details; }; /** * \class Connection::ErrorDetails * \ingroup clientconn * \headerfile TelepathyQt4/connection.h * * \brief The Connection::ErrorDetails class represents the details of a connection error. * * It contains detailed information about the reason for the connection going invalidated(). * * Some services may provide additional error information in the ConnectionError D-Bus signal, when * a Connection is disconnected / has become unusable. If the service didn't provide any, or has not * been invalidated yet, the instance will be invalid, as returned by isValid(). * * The information provided by invalidationReason() and this class should always be used in error * handling in preference to statusReason(). The status reason can be used as a fallback, however, * if the client doesn't understand what a particular value returned by invalidationReason() means, * as it may be domain-specific with some services. * * Connection::errorDetails() can be used to return the instance containing the details for * invalidating that connection after invalidated() has been emitted. */ /** * Constructs a new invalid ErrorDetails instance. */ Connection::ErrorDetails::ErrorDetails() : mPriv(0) { } /** * Construct a error details instance with the given details. The instance will indicate that * it is valid. */ Connection::ErrorDetails::ErrorDetails(const QVariantMap &details) : mPriv(new Private(details)) { } /** * Copy constructor. */ Connection::ErrorDetails::ErrorDetails(const ErrorDetails &other) : mPriv(other.mPriv) { } /** * Class destructor. */ Connection::ErrorDetails::~ErrorDetails() { } /** * Assigns all information (validity, details) from other to this. */ Connection::ErrorDetails &Connection::ErrorDetails::operator=( const ErrorDetails &other) { if (this->mPriv.constData() != other.mPriv.constData()) this->mPriv = other.mPriv; return *this; } /** * \fn bool Connection::ErrorDetails::isValid() const * * Return whether or not the details are valid (have actually been received from the service). * * \return \c true if valid, \c false otherwise. */ /** * \fn bool Connection::ErrorDetails::hasDebugMessage() const * * Return whether or not the details specify a debug message. * * If present, the debug message will likely be the same string as the one returned by * invalidationMessage(). * * The debug message is purely informational, offered for display for bug reporting purposes, and * should not be attempted to be parsed. * * \return \c true if debug message is present, \c false otherwise. * \sa debugMessage() */ /** * \fn QString Connection::ErrorDetails::debugMessage() const * * Return the debug message specified by the details, if any. * * If present, the debug message will likely be the same string as the one returned by * invalidationMessage(). * * The debug message is purely informational, offered for display for bug reporting purposes, and * should not be attempted to be parsed. * * \return The debug message, or an empty string if there is none. * \sa hasDebugMessage() */ /** * Return a map containing all details given in the low-level ConnectionError signal. * * This is useful for accessing domain-specific additional details. * * \return The details of the connection error as QVariantMap. */ QVariantMap Connection::ErrorDetails::allDetails() const { return isValid() ? mPriv->details : QVariantMap(); } /** * Return detailed information about the reason for the connection going invalidated(). * * Some services may provide additional error information in the ConnectionError D-Bus signal, when * a Connection is disconnected / has become unusable. If the service didn't provide any, or has not * been invalidated yet, an invalid instance is returned. * * The information provided by invalidationReason() and this method should always be used in error * handling in preference to statusReason(). The status reason can be used as a fallback, however, * if the client doesn't understand what a particular value returned by invalidationReason() means, * as it may be domain-specific with some services. * * \return The error details as a Connection::ErrorDetails object. * \sa status(), statusReason(), invalidationReason() */ const Connection::ErrorDetails &Connection::errorDetails() const { if (isValid()) { warning() << "Connection::errorDetails() used on" << objectPath() << "which is valid"; } return mPriv->errorDetails; } /** * Return the handle representing the user on this connection. * * Note that if the connection is not yet in the ConnectionStatusConnected state, * the value of this property may be zero. * * Change notification is via the selfHandleChanged() signal. * * This method requires Connection::FeatureCore to be ready. * * \return The user handle. * \sa selfHandleChanged(), selfContact() */ uint Connection::selfHandle() const { return mPriv->selfHandle; } /** * Return a dictionary of presence statuses valid for use in this connection. * * The value may have changed arbitrarily during the time the * Connection spends in status ConnectionStatusConnecting, * again staying fixed for the entire time in ConnectionStatusConnected. * * This method requires Connection::FeatureSimplePresence to be ready. * * \return The allowed statuses as a map from string identifiers to SimpleStatusSpec objects. */ SimpleStatusSpecMap ConnectionLowlevel::allowedPresenceStatuses() const { if (!isValid()) { warning() << "ConnectionLowlevel::selfHandle() " "called for a connection which is already destroyed"; return SimpleStatusSpecMap(); } ConnectionPtr conn(mPriv->conn); if (!conn->isReady(Connection::FeatureSimplePresence)) { warning() << "Trying to retrieve allowed presence statuses from connection, but " "simple presence is not supported or was not requested. " "Enable FeatureSimplePresence in this connection"; } return conn->mPriv->simplePresenceStatuses; } /** * Return the maximum length for a presence status message. * * The value may have changed arbitrarily during the time the * Connection spends in status ConnectionStatusConnecting, * again staying fixed for the entire time in ConnectionStatusConnected. * * This method requires Connection::FeatureSimplePresence to be ready. * * \return The maximum length, or 0 if there is no limit. */ uint ConnectionLowlevel::maxPresenceStatusMessageLength() const { if (!isValid()) { warning() << "ConnectionLowlevel::maxPresenceStatusMessageLength() " "called for a connection which is already destroyed"; return 0; } ConnectionPtr conn(mPriv->conn); if (!conn->isReady(Connection::FeatureSimplePresence)) { warning() << "Trying to retrieve max presence status message length connection, but " "simple presence is not supported or was not requested. " "Enable FeatureSimplePresence in this connection"; } return conn->mPriv->maxPresenceStatusMessageLength; } /** * Set the self presence status. * * This should generally only be called by an Account Manager. In typical usage, * Account::setRequestedPresence() should be used instead. * * \a status must be one of the allowed statuses returned by * allowedPresenceStatuses(). * * Note that clients SHOULD set the status message for the local user to the * empty string, unless the user has actually provided a specific message (i.e. * one that conveys more information than the ConnectionStatus). * * \param status The desired status. * \param statusMessage The desired status message. * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. * \sa allowedPresenceStatuses() */ PendingOperation *ConnectionLowlevel::setSelfPresence(const QString &status, const QString &statusMessage) { if (!isValid()) { warning() << "ConnectionLowlevel::selfHandle() called for a connection which is already destroyed"; return new PendingFailure(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("Connection already destroyed"), ConnectionPtr()); } ConnectionPtr conn(mPriv->conn); if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE))) { return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Connection does not support SimplePresence"), conn); } Client::ConnectionInterfaceSimplePresenceInterface *simplePresenceInterface = conn->interface(); return new PendingVoid( simplePresenceInterface->SetPresence(status, statusMessage), conn); } /** * Return the object representing the user on this connection. * * Note that if the connection is not yet in the ConnectionStatusConnected state, the value of this * property may be null. * * Change notification is via the selfContactChanged() signal. * * This method requires Connection::FeatureSelfContact to be ready. * * \return A pointer to the Contact object, or a null ContactPtr if unknown. * \sa selfContactChanged(), selfHandle() */ ContactPtr Connection::selfContact() const { if (!isReady(FeatureSelfContact)) { warning() << "Connection::selfContact() used, but becomeReady(FeatureSelfContact) " "hasn't been completed!"; } return mPriv->selfContact; } /** * Return the user's balance on the account corresponding to this connection. * * A negative amount may be possible on some services, and indicates that the user * owes money to the service provider. * * Change notification is via the accountBalanceChanged() signal. * * This method requires Connection::FeatureAccountBalance to be ready. * * \return The account balance as #CurrencyAmount. * \sa accountBalanceChanged() */ CurrencyAmount Connection::accountBalance() const { if (!isReady(FeatureAccountBalance)) { warning() << "Connection::accountBalance() used before connection " "FeatureAccountBalance is ready"; } return mPriv->accountBalance; } /** * Return the capabilities for this connection. * * User interfaces can use this information to show or hide UI components. * * This property cannot change after the connection has gone to state * ConnectionStatusConnected, so there is no change notification. * * This method requires Connection::FeatureCore to be ready. * * @return The capabilities of this connection. */ ConnectionCapabilities Connection::capabilities() const { if (!isReady(Connection::FeatureCore)) { warning() << "Connection::capabilities() used before connection " "FeatureCore is ready"; } return mPriv->caps; } void Connection::onStatusReady(uint status) { Q_ASSERT(status == mPriv->pendingStatus); if (mPriv->status == status) { return; } mPriv->status = status; mPriv->statusReason = mPriv->pendingStatusReason; if (isValid()) { emit statusChanged((ConnectionStatus) mPriv->status); } else { debug() << this << " not emitting statusChanged because it has been invalidated"; } } void Connection::onStatusChanged(uint status, uint reason) { debug() << "StatusChanged from" << mPriv->pendingStatus << "to" << status << "with reason" << reason; if (mPriv->pendingStatus == status) { warning() << "New status was the same as the old status! Ignoring" "redundant StatusChanged"; return; } uint oldStatus = mPriv->pendingStatus; mPriv->pendingStatus = status; mPriv->pendingStatusReason = reason; switch (status) { case ConnectionStatusConnected: debug() << "Performing introspection for the Connected status"; mPriv->setCurrentStatus(status); break; case ConnectionStatusConnecting: mPriv->setCurrentStatus(status); break; case ConnectionStatusDisconnected: { QString errorName = ConnectionHelper::statusReasonToErrorName( (ConnectionStatusReason) reason, (ConnectionStatus) oldStatus); // TODO should we signal statusChanged to Disconnected here or just // invalidate? // Also none of the pendingOperations will finish. The // user should just consider them to fail as the connection // is invalid onStatusReady(ConnectionStatusDisconnected); mPriv->invalidateResetCaps(errorName, QString(QLatin1String("ConnectionStatusReason = %1")).arg(uint(reason))); } break; default: warning() << "Unknown connection status" << status; break; } } void Connection::onConnectionError(const QString &error, const QVariantMap &details) { debug().nospace() << "Connection(" << objectPath() << ") got ConnectionError(" << error << ") with " << details.size() << " details"; mPriv->errorDetails = details; mPriv->invalidateResetCaps(error, details.value(QLatin1String("debug-message")).toString()); } void Connection::gotMainProperties(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; QVariantMap props; if (!reply.isError()) { props = reply.value(); } else { warning().nospace() << "Properties::GetAll(Connection) failed with " << reply.error().name() << ": " << reply.error().message(); // let's try to fallback first before failing } uint status = static_cast(-1); if (props.contains(QLatin1String("Status")) && ((status = qdbus_cast(props[QLatin1String("Status")])) <= ConnectionStatusDisconnected)) { mPriv->forceCurrentStatus(status); } else { // only introspect status if we did not got it from StatusChanged if (mPriv->pendingStatus == (uint) -1) { mPriv->introspectMainQueue.enqueue( &Private::introspectMainFallbackStatus); } } if (props.contains(QLatin1String("Interfaces"))) { mPriv->setInterfaces(qdbus_cast( props[QLatin1String("Interfaces")])); } else { mPriv->introspectMainQueue.enqueue( &Private::introspectMainFallbackInterfaces); } if (props.contains(QLatin1String("SelfHandle"))) { mPriv->selfHandle = qdbus_cast( props[QLatin1String("SelfHandle")]); } else { mPriv->introspectMainQueue.enqueue( &Private::introspectMainFallbackSelfHandle); } if (props.contains(QLatin1String("HasImmortalHandles"))) { mPriv->immortalHandles = qdbus_cast(props[QLatin1String("HasImmortalHandles")]); } if (hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_REQUESTS)) { mPriv->introspectMainQueue.enqueue( &Private::introspectCapabilities); } if (hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACTS)) { mPriv->introspectMainQueue.enqueue( &Private::introspectContactAttributeInterfaces); } mPriv->continueMainIntrospection(); watcher->deleteLater(); } void Connection::gotStatus(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { mPriv->forceCurrentStatus(reply.value()); mPriv->continueMainIntrospection(); } else { warning().nospace() << "GetStatus() failed with " << reply.error().name() << ": " << reply.error().message(); mPriv->invalidateResetCaps(reply.error().name(), reply.error().message()); } watcher->deleteLater(); } void Connection::gotInterfaces(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { mPriv->setInterfaces(reply.value()); } else { warning().nospace() << "GetInterfaces() failed with " << reply.error().name() << ": " << reply.error().message() << " - assuming no new interfaces"; // let's not fail if GetInterfaces fail } mPriv->continueMainIntrospection(); watcher->deleteLater(); } void Connection::gotSelfHandle(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { mPriv->selfHandle = reply.value(); debug() << "Got self handle:" << mPriv->selfHandle; mPriv->continueMainIntrospection(); } else { warning().nospace() << "GetSelfHandle() failed with " << reply.error().name() << ": " << reply.error().message(); mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); } watcher->deleteLater(); } void Connection::gotCapabilities(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { debug() << "Got capabilities"; mPriv->caps.updateRequestableChannelClasses( qdbus_cast(reply.value().variant())); } else { warning().nospace() << "Getting capabilities failed with " << reply.error().name() << ": " << reply.error().message(); // let's not fail if retrieving capabilities fail } mPriv->continueMainIntrospection(); watcher->deleteLater(); } void Connection::gotContactAttributeInterfaces(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { debug() << "Got contact attribute interfaces"; mPriv->contactAttributeInterfaces = qdbus_cast(reply.value().variant()); } else { warning().nospace() << "Getting contact attribute interfaces failed with " << reply.error().name() << ": " << reply.error().message(); // let's not fail if retrieving contact attribute interfaces fail // TODO should we remove Contacts interface from interfaces? } mPriv->continueMainIntrospection(); watcher->deleteLater(); } void Connection::gotSimpleStatuses(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { QVariantMap props = reply.value(); mPriv->simplePresenceStatuses = qdbus_cast( props[QLatin1String("Statuses")]); mPriv->maxPresenceStatusMessageLength = qdbus_cast( props[QLatin1String("MaximumStatusMessageLength")]); debug() << "Got" << mPriv->simplePresenceStatuses.size() << "simple presence statuses - max status message length is" << mPriv->maxPresenceStatusMessageLength; mPriv->readinessHelper->setIntrospectCompleted(FeatureSimplePresence, true); } else { warning().nospace() << "Getting simple presence statuses failed with " << reply.error().name() << ":" << reply.error().message(); mPriv->readinessHelper->setIntrospectCompleted(FeatureSimplePresence, false, reply.error()); } watcher->deleteLater(); } void Connection::gotSelfContact(PendingOperation *op) { PendingContacts *pending = qobject_cast(op); if (pending->isValid()) { Q_ASSERT(pending->contacts().size() == 1); ContactPtr contact = pending->contacts()[0]; if (mPriv->selfContact != contact) { mPriv->selfContact = contact; if (!isReady(FeatureSelfContact)) { mPriv->readinessHelper->setIntrospectCompleted(FeatureSelfContact, true); } emit selfContactChanged(); } } else { warning().nospace() << "Getting self contact failed with " << pending->errorName() << ":" << pending->errorMessage(); // check if the feature is already there, and for some reason introspectSelfContact // failed when called the second time if (!isReady(FeatureSelfContact)) { mPriv->readinessHelper->setIntrospectCompleted(FeatureSelfContact, false, op->errorName(), op->errorMessage()); } if (mPriv->selfContact) { mPriv->selfContact.reset(); emit selfContactChanged(); } } mPriv->introspectingSelfContact = false; if (mPriv->reintrospectSelfContactRequired) { mPriv->introspectSelfContact(mPriv); } } void Connection::onIntrospectRosterFinished(PendingOperation *op) { if (op->isError()) { warning().nospace() << "Introspecting roster failed with " << op->errorName() << ": " << op->errorMessage(); mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, false, op->errorName(), op->errorMessage()); return; } debug() << "Introspecting roster finished"; mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, true); } void Connection::onIntrospectRosterGroupsFinished(PendingOperation *op) { if (op->isError()) { warning().nospace() << "Introspecting roster groups failed with " << op->errorName() << ": " << op->errorMessage(); mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, false, op->errorName(), op->errorMessage()); return; } debug() << "Introspecting roster groups finished"; mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, true); } void Connection::gotBalance(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { debug() << "Got balance"; mPriv->accountBalance = qdbus_cast(reply.value()); mPriv->readinessHelper->setIntrospectCompleted(FeatureAccountBalance, true); } else { warning().nospace() << "Getting balance failed with " << reply.error().name() << ":" << reply.error().message(); mPriv->readinessHelper->setIntrospectCompleted(FeatureAccountBalance, false, reply.error().name(), reply.error().message()); } watcher->deleteLater(); } /** * Return the Client::ConnectionInterface interface proxy object for this connection. * 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::ConnectionInterface object for this * Connection object. */ Client::ConnectionInterface *Connection::baseInterface() const { return mPriv->baseInterface; } /** * Same as \c createChannel(request, -1) */ PendingChannel *ConnectionLowlevel::createChannel(const QVariantMap &request) { return createChannel(request, -1); } /** * Asynchronously creates a channel satisfying the given request. * * In typical usage, only the Channel Dispatcher should call this. Ordinary * applications should use the Account::createChannel() family of methods * (which invoke the Channel Dispatcher's services). * * The request MUST contain the following keys: * org.freedesktop.Telepathy.Channel.ChannelType * org.freedesktop.Telepathy.Channel.TargetHandleType * * Upon completion, the reply to the request can be retrieved through the * returned PendingChannel object. The object also provides access to the * parameters with which the call was made and a signal to connect to get * notification of the request finishing processing. See the documentation * for that class for more info. * * \param request A dictionary containing the desirable properties. * \param timeout The D-Bus timeout in milliseconds used for the method call. * If timeout is -1, a default implementation-defined value that * is suitable for inter-process communications (generally, * 25 seconds) will be used. * \return A PendingChannel which will emit PendingChannel::finished * when the channel has been created, or an error occurred. * \sa PendingChannel, ensureChannel(), * Account::createChannel(), Account::createAndHandleChannel(), * Account::ensureChannel(), Account::ensureAndHandleChannel() */ PendingChannel *ConnectionLowlevel::createChannel(const QVariantMap &request, int timeout) { if (!isValid()) { return new PendingChannel(ConnectionPtr(), TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed")); } ConnectionPtr conn(mPriv->conn); if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { warning() << "Calling createChannel with connection not yet connected"; return new PendingChannel(conn, QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("Connection not yet connected")); } if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS))) { warning() << "Requests interface is not support by this connection"; return new PendingChannel(conn, QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Connection does not support Requests Interface")); } if (!request.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) { return new PendingChannel(conn, QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("Invalid 'request' argument")); } debug() << "Creating a Channel"; PendingChannel *channel = new PendingChannel(conn, request, true, timeout); return channel; } /** * Same as \c ensureChannel(request, -1) */ PendingChannel *ConnectionLowlevel::ensureChannel(const QVariantMap &request) { return ensureChannel(request, -1); } /** * Asynchronously ensures a channel exists satisfying the given request. * * In typical usage, only the Channel Dispatcher should call this. Ordinary * applications should use the Account::ensureChannel() family of methods * (which invoke the Channel Dispatcher's services). * * The request MUST contain the following keys: * org.freedesktop.Telepathy.Channel.ChannelType * org.freedesktop.Telepathy.Channel.TargetHandleType * * Upon completion, the reply to the request can be retrieved through the * returned PendingChannel object. The object also provides access to the * parameters with which the call was made and a signal to connect to get * notification of the request finishing processing. See the documentation * for that class for more info. * * \param request A dictionary containing the desirable properties. * \param timeout The D-Bus timeout in milliseconds used for the method call. * If timeout is -1, a default implementation-defined value that * is suitable for inter-process communications (generally, * 25 seconds) will be used. * \return A PendingChannel which will emit PendingChannel::finished * when the channel is ensured to exist, or an error occurred. * \sa PendingChannel, createChannel(), * Account::createChannel(), Account::createAndHandleChannel(), * Account::ensureChannel(), Account::ensureAndHandleChannel() */ PendingChannel *ConnectionLowlevel::ensureChannel(const QVariantMap &request, int timeout) { if (!isValid()) { return new PendingChannel(ConnectionPtr(), TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed")); } ConnectionPtr conn(mPriv->conn); if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { warning() << "Calling ensureChannel with connection not yet connected"; return new PendingChannel(conn, QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("Connection not yet connected")); } if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS))) { warning() << "Requests interface is not support by this connection"; return new PendingChannel(conn, QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Connection does not support Requests Interface")); } if (!request.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) { return new PendingChannel(conn, QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("Invalid 'request' argument")); } debug() << "Creating a Channel"; PendingChannel *channel = new PendingChannel(conn, request, false, timeout); return channel; } /** * Request handles of the given type for the given entities (contacts, * rooms, lists, etc.). * * Typically one doesn't need to request and use handles directly; instead, string identifiers * and/or Contact objects are used in most APIs. File a bug for APIs in which there is no * alternative to using handles. In particular however using low-level DBus interfaces for which * there is no corresponding high-level (or one is implementing that abstraction) functionality does * and will always require using bare handles. * * Upon completion, the reply to the request can be retrieved through the * returned PendingHandles object. The object also provides access to the * parameters with which the call was made and a signal to connect to to get * notification of the request finishing processing. See the documentation * for that class for more info. * * The returned PendingHandles object should be freed using * its QObject::deleteLater() method after it is no longer used. However, * all PendingHandles objects resulting from requests to a particular * Connection will be freed when the Connection itself is freed. Conversely, * this means that the PendingHandles object should not be used after the * Connection is destroyed. * * \param handleType Type for the handles to request, as specified in * #HandleType. * \param names Names of the entities to request handles for. * \return A PendingHandles which will emit PendingHandles::finished * when the handles have been requested, or an error occurred. * \sa PendingHandles */ PendingHandles *ConnectionLowlevel::requestHandles(HandleType handleType, const QStringList &names) { debug() << "Request for" << names.length() << "handles of type" << handleType; if (!isValid()) { return new PendingHandles(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed")); } ConnectionPtr conn(mPriv->conn); if (!hasImmortalHandles()) { Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext; QMutexLocker locker(&handleContext->lock); handleContext->types[handleType].requestsInFlight++; } PendingHandles *pending = new PendingHandles(conn, handleType, names); return pending; } /** * Request a reference to the given handles. Handles not explicitly * requested (via requestHandles()) but eg. observed in a signal need to be * referenced to guarantee them staying valid. * * Typically one doesn't need to reference and use handles directly; instead, string identifiers * and/or Contact objects are used in most APIs. File a bug for APIs in which there is no * alternative to using handles. In particular however using low-level DBus interfaces for which * there is no corresponding high-level (or one is implementing that abstraction) functionality does * and will always require using bare handles. * * Upon completion, the reply to the operation can be retrieved through the * returned PendingHandles object. The object also provides access to the * parameters with which the call was made and a signal to connect to to get * notification of the request finishing processing. See the documentation * for that class for more info. * * The returned PendingHandles object should be freed using * its QObject::deleteLater() method after it is no longer used. However, * all PendingHandles objects resulting from requests to a particular * Connection will be freed when the Connection itself is freed. Conversely, * this means that the PendingHandles object should not be used after the * Connection is destroyed. * * \sa PendingHandles * * \param handleType Type of the handles given, as specified in #HandleType. * \param handles Handles to request a reference to. * \return A PendingHandles which will emit PendingHandles::finished * when the handles have been referenced, or an error occurred. */ PendingHandles *ConnectionLowlevel::referenceHandles(HandleType handleType, const UIntList &handles) { debug() << "Reference of" << handles.length() << "handles of type" << handleType; if (!isValid()) { return new PendingHandles(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed")); } ConnectionPtr conn(mPriv->conn); UIntList alreadyHeld; UIntList notYetHeld; if (!hasImmortalHandles()) { Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext; QMutexLocker locker(&handleContext->lock); foreach (uint handle, handles) { if (handleContext->types[handleType].refcounts.contains(handle) || handleContext->types[handleType].toRelease.contains(handle)) { alreadyHeld.push_back(handle); } else { notYetHeld.push_back(handle); } } debug() << " Already holding" << alreadyHeld.size() << "of the handles -" << notYetHeld.size() << "to go"; } else { alreadyHeld = handles; } PendingHandles *pending = new PendingHandles(conn, handleType, handles, alreadyHeld, notYetHeld); return pending; } /** * Start an asynchronous request that the connection be connected. * * When using a full-fledged Telepathy setup with an Account Manager service, the Account methods * Account::setRequestedPresence() and Account::reconnect() must be used instead. * * The returned PendingOperation will finish successfully when the connection * has reached ConnectionStatusConnected and the requested \a features are all ready, or * finish with an error if a fatal error occurs during that process. * * \param requestedFeatures The features which should be enabled * \return A PendingReady which will emit PendingReady::finished * when the Connection has reached #ConnectionStatusConnected, and initial setup * for basic functionality, plus the given features, has succeeded or * failed. */ PendingReady *ConnectionLowlevel::requestConnect(const Features &requestedFeatures) { if (!isValid()) { Connection::PendingConnect *pending = new Connection::PendingConnect(ConnectionPtr(), requestedFeatures); pending->setFinishedWithError(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed")); return pending; } return new Connection::PendingConnect(ConnectionPtr(mPriv->conn), requestedFeatures); } /** * Start an asynchronous request that the connection be disconnected. * The returned PendingOperation object will signal the success or failure * of this request; under normal circumstances, it can be expected to * succeed. * * When using a full-fledged Telepathy setup with an Account Manager service, * Account::setRequestedPresence() with Presence::offline() as an argument should generally be used * instead. * * \return A PendingOperation which will emit PendingOperation::finished * when the request has been made. */ PendingOperation *ConnectionLowlevel::requestDisconnect() { if (!isValid()) { return new PendingFailure(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed"), ConnectionPtr()); } ConnectionPtr conn(mPriv->conn); return new PendingVoid(conn->baseInterface()->Disconnect(), conn); } /** * Requests attributes for contacts. Optionally, the handles of the contacts * will be referenced automatically. Essentially, this method wraps * ConnectionInterfaceContactsInterface::GetContactAttributes(), integrating it * with the rest of the handle-referencing machinery. * * This is very low-level API the Contact/ContactManager API provides a higher level of abstraction * for the same functionality. * * Upon completion, the reply to the request can be retrieved through the * returned PendingContactAttributes object. The object also provides access to * the parameters with which the call was made and a signal to connect to to get * notification of the request finishing processing. See the documentation for * that class for more info. * * If the remote object doesn't support the Contacts interface (as signified by * the list returned by interfaces() not containing * #TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACTS), the returned * PendingContactAttributes instance will fail instantly with the error * #TP_QT4_ERROR_NOT_IMPLEMENTED. * * Similarly, if the connection isn't both connected and ready * (status() == ConnectionStatusConnected && isReady(Connection::FeatureCore)), * the returned PendingContactAttributes instance will fail instantly with the * error #TP_QT4_ERROR_NOT_AVAILABLE. * * This method requires Connection::FeatureCore to be ready. * * \sa PendingContactAttributes * * \param handles A list of handles of type HandleTypeContact * \param interfaces D-Bus interfaces for which the client requires information * \param reference Whether the handles should additionally be referenced. * \return A PendingContactAttributes which will emit PendingContactAttributes::fininshed * when the contact attributes have been retrieved, or an error occurred. */ PendingContactAttributes *ConnectionLowlevel::contactAttributes(const UIntList &handles, const QStringList &interfaces, bool reference) { debug() << "Request for attributes for" << handles.size() << "contacts"; if (!isValid()) { PendingContactAttributes *pending = new PendingContactAttributes(ConnectionPtr(), handles, interfaces, reference); pending->failImmediately(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("The connection has been destroyed")); return pending; } ConnectionPtr conn(mPriv->conn); PendingContactAttributes *pending = new PendingContactAttributes(conn, handles, interfaces, reference); if (!conn->isReady(Connection::FeatureCore)) { warning() << "ConnectionLowlevel::contactAttributes() used when not ready"; pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("The connection isn't ready")); return pending; } else if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { warning() << "ConnectionLowlevel::contactAttributes() used with status" << conn->status() << "!= ConnectionStatusConnected"; pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("The connection isn't Connected")); return pending; } else if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) { warning() << "ConnectionLowlevel::contactAttributes() used without the remote object supporting" << "the Contacts interface"; pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("The connection doesn't support the Contacts interface")); return pending; } if (!hasImmortalHandles()) { Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext; QMutexLocker locker(&handleContext->lock); handleContext->types[HandleTypeContact].requestsInFlight++; } Client::ConnectionInterfaceContactsInterface *contactsInterface = conn->interface(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(contactsInterface->GetContactAttributes(handles, interfaces, reference)); pending->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onCallFinished(QDBusPendingCallWatcher*))); return pending; } QStringList ConnectionLowlevel::contactAttributeInterfaces() const { if (!isValid()) { warning() << "ConnectionLowlevel::contactAttributeInterfaces() called for a destroyed Connection"; return QStringList(); } ConnectionPtr conn(mPriv->conn); if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { warning() << "ConnectionLowlevel::contactAttributeInterfaces() used with status" << conn->status() << "!= ConnectionStatusConnected"; } else if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) { warning() << "ConnectionLowlevel::contactAttributeInterfaces() used without the remote object supporting" << "the Contacts interface"; } return conn->mPriv->contactAttributeInterfaces; } void ConnectionLowlevel::injectContactIds(const HandleIdentifierMap &contactIds) { if (!hasImmortalHandles()) { return; } for (HandleIdentifierMap::const_iterator i = contactIds.constBegin(); i != contactIds.constEnd(); ++i) { uint handle = i.key(); QString id = i.value(); if (!id.isEmpty()) { QString currentId = mPriv->contactsIds.value(handle); if (!currentId.isEmpty() && id != currentId) { warning() << "Trying to overwrite contact id from" << currentId << "to" << id << "for the same handle" << handle << ", ignoring"; } else { mPriv->contactsIds.insert(handle, id); } } } } void ConnectionLowlevel::injectContactId(uint handle, const QString &contactId) { HandleIdentifierMap contactIds; contactIds.insert(handle, contactId); injectContactIds(contactIds); } bool ConnectionLowlevel::hasContactId(uint handle) const { return mPriv->contactsIds.contains(handle); } QString ConnectionLowlevel::contactId(uint handle) const { return mPriv->contactsIds.value(handle); } /** * Return whether the handles last for the whole lifetime of the connection. * * \return \c true if handles are immortal, \c false otherwise. */ bool ConnectionLowlevel::hasImmortalHandles() const { ConnectionPtr conn(mPriv->conn); return conn->mPriv->immortalHandles; } /** * Return the ContactManager object for this connection. * * The contact manager is responsible for all contact handling in this * connection, including adding, removing, authorizing, etc. * * \return A pointer to the ContactManager object. */ ContactManagerPtr Connection::contactManager() const { return mPriv->contactManager; } ConnectionLowlevelPtr Connection::lowlevel() { return mPriv->lowlevel; } ConnectionLowlevelConstPtr Connection::lowlevel() const { return mPriv->lowlevel; } void Connection::refHandle(HandleType handleType, uint handle) { if (mPriv->immortalHandles) { return; } Private::HandleContext *handleContext = mPriv->handleContext; QMutexLocker locker(&handleContext->lock); if (handleContext->types[handleType].toRelease.contains(handle)) { handleContext->types[handleType].toRelease.remove(handle); } handleContext->types[handleType].refcounts[handle]++; } void Connection::unrefHandle(HandleType handleType, uint handle) { if (mPriv->immortalHandles) { return; } Private::HandleContext *handleContext = mPriv->handleContext; QMutexLocker locker(&handleContext->lock); Q_ASSERT(handleContext->types.contains(handleType)); Q_ASSERT(handleContext->types[handleType].refcounts.contains(handle)); if (!--handleContext->types[handleType].refcounts[handle]) { handleContext->types[handleType].refcounts.remove(handle); handleContext->types[handleType].toRelease.insert(handle); if (!handleContext->types[handleType].releaseScheduled) { if (!handleContext->types[handleType].requestsInFlight) { debug() << "Lost last reference to at least one handle of type" << handleType << "and no requests in flight for that type - scheduling a release sweep"; QMetaObject::invokeMethod(this, "doReleaseSweep", Qt::QueuedConnection, Q_ARG(uint, handleType)); handleContext->types[handleType].releaseScheduled = true; } } } } void Connection::doReleaseSweep(uint handleType) { if (mPriv->immortalHandles) { return; } Private::HandleContext *handleContext = mPriv->handleContext; QMutexLocker locker(&handleContext->lock); Q_ASSERT(handleContext->types.contains(handleType)); Q_ASSERT(handleContext->types[handleType].releaseScheduled); debug() << "Entering handle release sweep for type" << handleType; handleContext->types[handleType].releaseScheduled = false; if (handleContext->types[handleType].requestsInFlight > 0) { debug() << " There are requests in flight, deferring sweep to when they have been completed"; return; } if (handleContext->types[handleType].toRelease.isEmpty()) { debug() << " No handles to release - every one has been resurrected"; return; } debug() << " Releasing" << handleContext->types[handleType].toRelease.size() << "handles"; mPriv->baseInterface->ReleaseHandles(handleType, handleContext->types[handleType].toRelease.toList()); handleContext->types[handleType].toRelease.clear(); } void Connection::handleRequestLanded(HandleType handleType) { if (mPriv->immortalHandles) { return; } Private::HandleContext *handleContext = mPriv->handleContext; QMutexLocker locker(&handleContext->lock); Q_ASSERT(handleContext->types.contains(handleType)); Q_ASSERT(handleContext->types[handleType].requestsInFlight > 0); if (!--handleContext->types[handleType].requestsInFlight && !handleContext->types[handleType].toRelease.isEmpty() && !handleContext->types[handleType].releaseScheduled) { debug() << "All handle requests for type" << handleType << "landed and there are handles of that type to release - scheduling a release sweep"; QMetaObject::invokeMethod(this, "doReleaseSweep", Qt::QueuedConnection, Q_ARG(uint, handleType)); handleContext->types[handleType].releaseScheduled = true; } } void Connection::onSelfHandleChanged(uint handle) { if (mPriv->selfHandle == handle) { return; } if (mPriv->pendingStatus != ConnectionStatusConnected || !mPriv->selfHandle) { debug() << "Got a self handle change before we have the initial self handle, ignoring"; return; } debug() << "Connection self handle changed to" << handle; mPriv->selfHandle = handle; emit selfHandleChanged(handle); if (mPriv->introspectingSelfContact) { // We're currently introspecting the SelfContact feature, but have started building the // contact with the old handle, so we need to do it again with the new handle. debug() << "The self contact is being built, will rebuild with the new handle shortly"; mPriv->reintrospectSelfContactRequired = true; } else if (isReady(FeatureSelfContact)) { // We've already introspected the SelfContact feature, so we can reinvoke the introspection // logic directly to rebuild with the new handle. debug() << "Re-building self contact for handle" << handle; Private::introspectSelfContact(mPriv); } // If ReadinessHelper hasn't started introspecting SelfContact yet for the Connected state, we // don't need to do anything. When it does start the introspection, it will do so using the // correct handle. } void Connection::onBalanceChanged(const Tp::CurrencyAmount &value) { mPriv->accountBalance = value; emit accountBalanceChanged(value); } QString ConnectionHelper::statusReasonToErrorName(Tp::ConnectionStatusReason reason, Tp::ConnectionStatus oldStatus) { const char *errorName; switch (reason) { case ConnectionStatusReasonNoneSpecified: errorName = TELEPATHY_ERROR_DISCONNECTED; break; case ConnectionStatusReasonRequested: errorName = TELEPATHY_ERROR_CANCELLED; break; case ConnectionStatusReasonNetworkError: errorName = TELEPATHY_ERROR_NETWORK_ERROR; break; case ConnectionStatusReasonAuthenticationFailed: errorName = TELEPATHY_ERROR_AUTHENTICATION_FAILED; break; case ConnectionStatusReasonEncryptionError: errorName = TELEPATHY_ERROR_ENCRYPTION_ERROR; break; case ConnectionStatusReasonNameInUse: if (oldStatus == ConnectionStatusConnected) { errorName = TELEPATHY_ERROR_CONNECTION_REPLACED; } else { errorName = TELEPATHY_ERROR_ALREADY_CONNECTED; } break; case ConnectionStatusReasonCertNotProvided: errorName = TELEPATHY_ERROR_CERT_NOT_PROVIDED; break; case ConnectionStatusReasonCertUntrusted: errorName = TELEPATHY_ERROR_CERT_UNTRUSTED; break; case ConnectionStatusReasonCertExpired: errorName = TELEPATHY_ERROR_CERT_EXPIRED; break; case ConnectionStatusReasonCertNotActivated: errorName = TELEPATHY_ERROR_CERT_NOT_ACTIVATED; break; case ConnectionStatusReasonCertHostnameMismatch: errorName = TELEPATHY_ERROR_CERT_HOSTNAME_MISMATCH; break; case ConnectionStatusReasonCertFingerprintMismatch: errorName = TELEPATHY_ERROR_CERT_FINGERPRINT_MISMATCH; break; case ConnectionStatusReasonCertSelfSigned: errorName = TELEPATHY_ERROR_CERT_SELF_SIGNED; break; case ConnectionStatusReasonCertOtherError: errorName = TELEPATHY_ERROR_CERT_INVALID; break; default: errorName = TELEPATHY_ERROR_DISCONNECTED; break; } return QLatin1String(errorName); } /** * \fn void Connection::statusChanged(Tp::ConnectionStatus newStatus) * * Indicates that the connection's status has changed and that all previously requested features are * now ready to use for the new status. * * Legitimate uses for this signal, apart from waiting for a given connection status to be ready, * include updating an animation based on the connection being in ConnectionStatusConnecting, * ConnectionStatusConnected and ConnectionStatusDisconnected, and otherwise showing progress * indication to the user. It should, however, NEVER be used for error handling: * * This signal doesn't contain the status reason as an argument, because statusChanged() shouldn't * be used for error-handling. There are numerous cases in which a Connection may become unusable * without there being a status change to ConnectionStatusDisconnected. All of these cases, and * being disconnected itself, are signaled by invalidated() with appropriate error names. On the * other hand, the reason for the status going to ConnectionStatusConnecting or * ConnectionStatusConnected will always be ConnectionStatusReasonRequested, so signaling that would * be useless. * * The status reason, as returned by statusReason(), may however be used as a fallback for error * handling in slots connected to the invalidated() signal, if the client doesn't understand a * particular (likely domain-specific if so) error name given by invalidateReason(). * * \param newStatus The new status of the connection, as would be returned by status(). */ /** * \fn void Connection::selfHandleChanged(uint newHandle) * * Emitted when the value of selfHandle() changes. * * \param newHandle The new connection self handle. * \sa selfHandle() */ /** * \fn void Connection::selfContactChanged() * * Emitted when the value of selfContact() changes. * * \sa selfContact() */ /** * \fn void Connection::accountBalanceChanged(const Tp::CurrencyAmount &accountBalance) * * Emitted when the value of accountBalance() changes. * * \param accountBalance The new user's balance of this connection. * \sa accountBalance() */ } // Tp