summaryrefslogtreecommitdiff
path: root/qt4/TelepathyQt4/connection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qt4/TelepathyQt4/connection.cpp')
-rw-r--r--qt4/TelepathyQt4/connection.cpp2578
1 files changed, 2578 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/connection.cpp b/qt4/TelepathyQt4/connection.cpp
new file mode 100644
index 000000000..4bec14bde
--- /dev/null
+++ b/qt4/TelepathyQt4/connection.cpp
@@ -0,0 +1,2578 @@
+/**
+ * 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/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#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 <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContactAttributes>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include <QMap>
+#include <QMetaObject>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QPair>
+#include <QQueue>
+#include <QString>
+#include <QTimer>
+#include <QtGlobal>
+
+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<void (Private::*)()> 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<QPair<QString, QString>, HandleContext *> handleContexts;
+ static QMutex handleContextsLock;
+ HandleContext *handleContext;
+
+ QString cmName;
+ QString protocolName;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ConnectionLowlevel::Private
+{
+ Private(Connection *conn)
+ : conn(QWeakPointer<Connection>(conn))
+ {
+ }
+
+ QWeakPointer<Connection> conn;
+ HandleIdentifierMap contactsIds;
+};
+
+// Handle tracking
+struct TELEPATHY_QT4_NO_EXPORT Connection::Private::HandleContext
+{
+ struct Type
+ {
+ QMap<uint, uint> refcounts;
+ QSet<uint> toRelease;
+ uint requestsInFlight;
+ bool releaseScheduled;
+
+ Type()
+ : requestsInFlight(0),
+ releaseScheduled(false)
+ {
+ }
+ };
+
+ HandleContext()
+ : refcount(0)
+ {
+ }
+
+ int refcount;
+ QMutex lock;
+ QMap<uint, Type> 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<Client::DBus::PropertiesInterface>()),
+ 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>() << (uint) -1 <<
+ ConnectionStatusDisconnected <<
+ ConnectionStatusConnected, // makesSenseForStatuses
+ Features(), // dependsOnFeatures (none)
+ QStringList(), // dependsOnInterfaces (none)
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ ReadinessHelper::Introspectable introspectableSelfContact(
+ QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectSelfContact,
+ this);
+ introspectables[FeatureSelfContact] = introspectableSelfContact;
+
+ ReadinessHelper::Introspectable introspectableSimplePresence(
+ QSet<uint>() << 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<uint>() << 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<uint>() << 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<uint>() << 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>() << (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<Client::ConnectionInterfaceBalanceInterface>();
+
+ 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 <TelepathyQt4/ConnectionLowlevel>
+ *
+ * \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<QPair<QString, QString>, Connection::Private::HandleContext*> Connection::Private::handleContexts;
+QMutex Connection::Private::handleContextsLock;
+
+/**
+ * \class Connection
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/connection.h <TelepathyQt4/Connection>
+ *
+ * \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<Connection>(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 <TelepathyQt4/Connection>
+ *
+ * \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<Client::ConnectionInterfaceSimplePresenceInterface>();
+ 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<QVariantMap> 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<uint>(-1);
+ if (props.contains(QLatin1String("Status"))
+ && ((status = qdbus_cast<uint>(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<QStringList>(
+ props[QLatin1String("Interfaces")]));
+ } else {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectMainFallbackInterfaces);
+ }
+
+ if (props.contains(QLatin1String("SelfHandle"))) {
+ mPriv->selfHandle = qdbus_cast<uint>(
+ props[QLatin1String("SelfHandle")]);
+ } else {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectMainFallbackSelfHandle);
+ }
+
+ if (props.contains(QLatin1String("HasImmortalHandles"))) {
+ mPriv->immortalHandles = qdbus_cast<bool>(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<uint> 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<QStringList> 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<uint> 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<QDBusVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got capabilities";
+ mPriv->caps.updateRequestableChannelClasses(
+ qdbus_cast<RequestableChannelClassList>(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<QDBusVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got contact attribute interfaces";
+ mPriv->contactAttributeInterfaces = qdbus_cast<QStringList>(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<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ QVariantMap props = reply.value();
+
+ mPriv->simplePresenceStatuses = qdbus_cast<SimpleStatusSpecMap>(
+ props[QLatin1String("Statuses")]);
+ mPriv->maxPresenceStatusMessageLength = qdbus_cast<uint>(
+ 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<PendingContacts *>(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<QVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got balance";
+ mPriv->accountBalance = qdbus_cast<CurrencyAmount>(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
+ * (<code>status() == ConnectionStatusConnected && isReady(Connection::FeatureCore)</code>),
+ * 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<Client::ConnectionInterfaceContactsInterface>();
+ 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