summaryrefslogtreecommitdiff
path: root/qt4/TelepathyQt4/contact-manager-roster.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qt4/TelepathyQt4/contact-manager-roster.cpp')
-rw-r--r--qt4/TelepathyQt4/contact-manager-roster.cpp2217
1 files changed, 2217 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/contact-manager-roster.cpp b/qt4/TelepathyQt4/contact-manager-roster.cpp
new file mode 100644
index 000000000..5c1dbda58
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-manager-roster.cpp
@@ -0,0 +1,2217 @@
+/**
+ * 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/contact-manager-internal.h"
+
+#include "TelepathyQt4/_gen/contact-manager-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReferencedHandles>
+
+namespace Tp
+{
+
+ContactManager::Roster::Roster(ContactManager *contactManager)
+ : QObject(),
+ contactManager(contactManager),
+ usingFallbackContactList(false),
+ hasContactBlockingInterface(false),
+ introspectPendingOp(0),
+ introspectGroupsPendingOp(0),
+ pendingContactListState((uint) -1),
+ contactListState((uint) -1),
+ canReportAbusive(false),
+ gotContactBlockingInitialBlockedContacts(false),
+ canChangeContactList(false),
+ contactListRequestUsesMessage(false),
+ gotContactListInitialContacts(false),
+ gotContactListContactsChangedWithId(false),
+ groupsReintrospectionRequired(false),
+ contactListGroupPropertiesReceived(false),
+ processingContactListChanges(false),
+ contactListChannelsReady(0),
+ featureContactListGroupsTodo(0),
+ groupsSetSuccess(false)
+{
+}
+
+ContactManager::Roster::~Roster()
+{
+}
+
+ContactListState ContactManager::Roster::state() const
+{
+ return (Tp::ContactListState) contactListState;
+}
+
+PendingOperation *ContactManager::Roster::introspect()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) {
+ debug() << "Connection.ContactList found, using it";
+
+ usingFallbackContactList = false;
+
+ if (conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING)) {
+ debug() << "Connection.ContactBlocking found. using it";
+ hasContactBlockingInterface = true;
+ introspectContactBlocking();
+ } else {
+ debug() << "Connection.ContactBlocking not found, falling back "
+ "to contact list deny channel";
+
+ debug() << "Requesting handle for deny channel";
+
+ contactListChannels.insert(ChannelInfo::TypeDeny,
+ ChannelInfo(ChannelInfo::TypeDeny));
+
+ PendingHandles *ph = conn->lowlevel()->requestHandles(HandleTypeList,
+ QStringList() << ChannelInfo::identifierForType(
+ ChannelInfo::TypeDeny));
+ connect(ph,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListChannelHandle(Tp::PendingOperation*)));
+ }
+ } else {
+ debug() << "Connection.ContactList not found, falling back to contact list channels";
+
+ usingFallbackContactList = true;
+
+ for (uint i = 0; i < ChannelInfo::LastType; ++i) {
+ QString channelId = ChannelInfo::identifierForType(
+ (ChannelInfo::Type) i);
+
+ debug() << "Requesting handle for" << channelId << "channel";
+
+ contactListChannels.insert(i,
+ ChannelInfo((ChannelInfo::Type) i));
+
+ PendingHandles *ph = conn->lowlevel()->requestHandles(HandleTypeList,
+ QStringList() << channelId);
+ connect(ph,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListChannelHandle(Tp::PendingOperation*)));
+ }
+ }
+
+ Q_ASSERT(!introspectPendingOp);
+ introspectPendingOp = new PendingOperation(conn);
+ return introspectPendingOp;
+}
+
+PendingOperation *ContactManager::Roster::introspectGroups()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ Q_ASSERT(!introspectGroupsPendingOp);
+
+ if (conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) {
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(TP_QT4_ERROR_NOT_IMPLEMENTED,
+ QLatin1String("Roster groups not supported"), conn);
+ }
+
+ debug() << "Connection.ContactGroups found, using it";
+
+ if (!gotContactListInitialContacts) {
+ debug() << "Initial ContactList contacts not retrieved. Postponing introspection";
+ groupsReintrospectionRequired = true;
+ return new PendingSuccess(conn);
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+
+ connect(iface,
+ SIGNAL(GroupsChanged(Tp::UIntList,QStringList,QStringList)),
+ SLOT(onContactListGroupsChanged(Tp::UIntList,QStringList,QStringList)));
+ connect(iface,
+ SIGNAL(GroupsCreated(QStringList)),
+ SLOT(onContactListGroupsCreated(QStringList)));
+ connect(iface,
+ SIGNAL(GroupRenamed(QString,QString)),
+ SLOT(onContactListGroupRenamed(QString,QString)));
+ connect(iface,
+ SIGNAL(GroupsRemoved(QStringList)),
+ SLOT(onContactListGroupsRemoved(QStringList)));
+
+ PendingVariantMap *pvm = iface->requestAllProperties();
+ connect(pvm,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListGroupsProperties(Tp::PendingOperation*)));
+ } else {
+ debug() << "Connection.ContactGroups not found, falling back to contact list group channels";
+
+ ++featureContactListGroupsTodo; // decremented in gotChannels
+
+ // we already checked if requests interface exists, so bypass requests
+ // interface checking
+ Client::ConnectionInterfaceRequestsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceRequestsInterface>();
+
+ debug() << "Connecting to Requests.NewChannels";
+ connect(iface,
+ SIGNAL(NewChannels(Tp::ChannelDetailsList)),
+ SLOT(onNewChannels(Tp::ChannelDetailsList)));
+
+ debug() << "Retrieving channels";
+ Client::DBus::PropertiesInterface *properties =
+ contactManager->connection()->interface<Client::DBus::PropertiesInterface>();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS),
+ QLatin1String("Channels")), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotChannels(QDBusPendingCallWatcher*)));
+ }
+
+ if (groupsReintrospectionRequired) {
+ return NULL;
+ }
+
+ Q_ASSERT(!introspectGroupsPendingOp);
+ introspectGroupsPendingOp = new PendingOperation(conn);
+ return introspectGroupsPendingOp;
+}
+
+void ContactManager::Roster::reset()
+{
+ contactListChannels.clear();
+ subscribeChannel.reset();
+ publishChannel.reset();
+ storedChannel.reset();
+ denyChannel.reset();
+ contactListGroupChannels.clear();
+ removedContactListGroupChannels.clear();
+}
+
+Contacts ContactManager::Roster::allKnownContacts() const
+{
+ return cachedAllKnownContacts;
+}
+
+QStringList ContactManager::Roster::allKnownGroups() const
+{
+ if (usingFallbackContactList) {
+ return contactListGroupChannels.keys();
+ }
+
+ return cachedAllKnownGroups.toList();
+}
+
+PendingOperation *ContactManager::Roster::addGroup(const QString &group)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeGroup);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ group);
+ return conn->lowlevel()->ensureChannel(request);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->AddToGroup(group, UIntList()));
+}
+
+PendingOperation *ContactManager::Roster::removeGroup(const QString &group)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid group"),
+ conn);
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return new RemoveGroupOp(channel);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RemoveGroup(group));
+}
+
+Contacts ContactManager::Roster::groupContacts(const QString &group) const
+{
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return Contacts();
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return channel->groupContacts();
+ }
+
+ Contacts ret;
+ foreach (const ContactPtr &contact, allKnownContacts()) {
+ if (contact->groups().contains(group))
+ ret << contact;
+ }
+ return ret;
+}
+
+PendingOperation *ContactManager::Roster::addContactsToGroup(const QString &group,
+ const QList<ContactPtr> &contacts)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid group"),
+ conn);
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return channel->groupAddContacts(contacts);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->AddToGroup(group, handles));
+}
+
+PendingOperation *ContactManager::Roster::removeContactsFromGroup(const QString &group,
+ const QList<ContactPtr> &contacts)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid group"),
+ conn);
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return channel->groupRemoveContacts(contacts);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RemoveFromGroup(group, handles));
+}
+
+bool ContactManager::Roster::canRequestPresenceSubscription() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel && subscribeChannel->groupCanAddContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::subscriptionRequestHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageAdd);
+ }
+
+ return contactListRequestUsesMessage;
+}
+
+PendingOperation *ContactManager::Roster::requestPresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!subscribeChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot subscribe to contacts' presence on this protocol"),
+ conn);
+ }
+
+ return subscribeChannel->groupAddContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RequestSubscription(handles, message));
+}
+
+bool ContactManager::Roster::canRemovePresenceSubscription() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel && subscribeChannel->groupCanRemoveContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::subscriptionRemovalHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRemove);
+ }
+
+ return false;
+}
+
+bool ContactManager::Roster::canRescindPresenceSubscriptionRequest() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel && subscribeChannel->groupCanRescindContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::subscriptionRescindingHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRescind);
+ }
+
+ return false;
+}
+
+PendingOperation *ContactManager::Roster::removePresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!subscribeChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot subscribe to contacts' presence on this protocol"),
+ conn);
+ }
+
+ return subscribeChannel->groupRemoveContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->Unsubscribe(handles));
+}
+
+bool ContactManager::Roster::canAuthorizePresencePublication() const
+{
+ if (usingFallbackContactList) {
+ // do not check for Channel::groupCanAddContacts as all contacts in local
+ // pending can be added, even if the Channel::groupFlags() does not contain
+ // the flag CanAdd
+ return (bool) publishChannel;
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::publicationAuthorizationHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageAccept);
+ }
+
+ return false;
+}
+
+PendingOperation *ContactManager::Roster::authorizePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!publishChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot control publication of presence on this protocol"),
+ conn);
+ }
+
+ return publishChannel->groupAddContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->AuthorizePublication(handles));
+}
+
+bool ContactManager::Roster::publicationRejectionHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageReject);
+ }
+
+ return false;
+}
+
+bool ContactManager::Roster::canRemovePresencePublication() const
+{
+ if (usingFallbackContactList) {
+ return publishChannel && publishChannel->groupCanRemoveContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::publicationRemovalHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRemove);
+ }
+
+ return false;
+}
+
+PendingOperation *ContactManager::Roster::removePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!publishChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot control publication of presence on this protocol"),
+ conn);
+ }
+
+ return publishChannel->groupRemoveContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->Unpublish(handles));
+}
+
+PendingOperation *ContactManager::Roster::removeContacts(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ /* If the CM implements stored channel correctly, it should have the
+ * wanted behaviour. Otherwise we have to to remove from publish
+ * and subscribe channels.
+ */
+
+ if (storedChannel && storedChannel->groupCanRemoveContacts()) {
+ debug() << "Removing contacts from stored list";
+ return storedChannel->groupRemoveContacts(contacts, message);
+ }
+
+ QList<PendingOperation*> operations;
+
+ if (canRemovePresenceSubscription()) {
+ debug() << "Removing contacts from subscribe list";
+ operations << removePresenceSubscription(contacts, message);
+ }
+
+ if (canRemovePresencePublication()) {
+ debug() << "Removing contacts from publish list";
+ operations << removePresencePublication(contacts, message);
+ }
+
+ if (operations.isEmpty()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot remove contacts on this protocol"),
+ conn);
+ }
+
+ return new PendingComposite(operations, conn);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RemoveContacts(handles));
+}
+
+bool ContactManager::Roster::canBlockContacts() const
+{
+ if (!usingFallbackContactList && hasContactBlockingInterface) {
+ return true;
+ } else {
+ return (bool) denyChannel;
+ }
+}
+
+bool ContactManager::Roster::canReportAbuse() const
+{
+ return canReportAbusive;
+}
+
+PendingOperation *ContactManager::Roster::blockContacts(
+ const QList<ContactPtr> &contacts, bool value, bool reportAbuse)
+{
+ if (!contactManager->connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ contactManager->connection());
+ } else if (!contactManager->connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ contactManager->connection());
+ }
+
+ if (!usingFallbackContactList && hasContactBlockingInterface) {
+ ConnectionPtr conn(contactManager->connection());
+ Client::ConnectionInterfaceContactBlockingInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactBlockingInterface>();
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Q_ASSERT(iface);
+ if(value) {
+ return queuedFinishVoid(iface->BlockContacts(handles, reportAbuse));
+ } else {
+ return queuedFinishVoid(iface->UnblockContacts(handles));
+ }
+
+ } else {
+ ConnectionPtr conn(contactManager->connection());
+
+ if (!denyChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot block contacts on this protocol"),
+ conn);
+ }
+
+ if (value) {
+ return denyChannel->groupAddContacts(contacts);
+ } else {
+ return denyChannel->groupRemoveContacts(contacts);
+ }
+ }
+}
+
+void ContactManager::Roster::gotContactBlockingCapabilities(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Getting ContactBlockingCapabilities property failed with" <<
+ op->errorName() << ":" << op->errorMessage();
+ introspectContactList();
+ return;
+ }
+
+ debug() << "Got ContactBlockingCapabilities property";
+
+ PendingVariant *pv = qobject_cast<PendingVariant*>(op);
+
+ uint contactBlockingCaps = pv->result().toUInt();
+ canReportAbusive =
+ contactBlockingCaps & ContactBlockingCapabilityCanReportAbusive;
+
+ introspectContactBlockingBlockedContacts();
+}
+
+void ContactManager::Roster::gotContactBlockingBlockedContacts(
+ QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<HandleIdentifierMap> reply = *watcher;
+
+ if (watcher->isError()) {
+ warning() << "Getting initial ContactBlocking blocked "
+ "contacts failed with" <<
+ watcher->error().name() << ":" << watcher->error().message();
+ introspectContactList();
+ return;
+ }
+
+ debug() << "Got initial ContactBlocking blocked contacts";
+
+ gotContactBlockingInitialBlockedContacts = true;
+
+ ConnectionPtr conn(contactManager->connection());
+ HandleIdentifierMap contactIds = reply.value();
+
+ if (!contactIds.isEmpty()) {
+ conn->lowlevel()->injectContactIds(contactIds);
+
+ //fake change event where all the contacts are added
+ contactListBlockedContactsChangedQueue.enqueue(
+ BlockedContactsChangedInfo(contactIds, HandleIdentifierMap(), true));
+ contactListChangesQueue.enqueue(
+ &ContactManager::Roster::processContactListBlockedContactsChanged);
+ processContactListChanges();
+ } else {
+ introspectContactList();
+ }
+}
+
+void ContactManager::Roster::onContactBlockingBlockedContactsChanged(
+ const HandleIdentifierMap &added,
+ const HandleIdentifierMap &removed)
+{
+ if (!gotContactBlockingInitialBlockedContacts) {
+ return;
+ }
+
+ ConnectionPtr conn(contactManager->connection());
+ conn->lowlevel()->injectContactIds(added);
+ conn->lowlevel()->injectContactIds(removed);
+
+ contactListBlockedContactsChangedQueue.enqueue(
+ BlockedContactsChangedInfo(added, removed));
+ contactListChangesQueue.enqueue(
+ &ContactManager::Roster::processContactListBlockedContactsChanged);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::gotContactListProperties(PendingOperation *op)
+{
+ if (op->isError()) {
+ // We may have been in state Failure and then Success, and FeatureRoster is already ready
+ if (introspectPendingOp) {
+ introspectPendingOp->setFinishedWithError(
+ op->errorName(), op->errorMessage());
+ introspectPendingOp = 0;
+ }
+ return;
+ }
+
+ debug() << "Got ContactList properties";
+
+ PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op);
+
+ QVariantMap props = pvm->result();
+
+ canChangeContactList = qdbus_cast<uint>(props[QLatin1String("CanChangeContactList")]);
+ contactListRequestUsesMessage = qdbus_cast<uint>(props[QLatin1String("RequestUsesMessage")]);
+
+ // only update the status if we did not get it from ContactListStateChanged
+ if (pendingContactListState == (uint) -1) {
+ uint state = qdbus_cast<uint>(props[QLatin1String("ContactListState")]);
+ onContactListStateChanged(state);
+ }
+}
+
+void ContactManager::Roster::gotContactListContacts(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<ContactAttributesMap> reply = *watcher;
+
+ if (watcher->isError()) {
+ warning() << "Failed introspecting ContactList contacts";
+
+ contactListState = ContactListStateFailure;
+ debug() << "Setting state to failure";
+ emit contactManager->stateChanged((Tp::ContactListState) contactListState);
+
+ // We may have been in state Failure and then Success, and FeatureRoster is already ready
+ if (introspectPendingOp) {
+ introspectPendingOp->setFinishedWithError(
+ reply.error());
+ introspectPendingOp = 0;
+ }
+ return;
+ }
+
+ debug() << "Got initial ContactList contacts";
+
+ gotContactListInitialContacts = true;
+
+ ConnectionPtr conn(contactManager->connection());
+ ContactAttributesMap attrsMap = reply.value();
+ ContactAttributesMap::const_iterator begin = attrsMap.constBegin();
+ ContactAttributesMap::const_iterator end = attrsMap.constEnd();
+ for (ContactAttributesMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ QVariantMap attrs = i.value();
+
+ ContactPtr contact = contactManager->ensureContact(ReferencedHandles(conn,
+ HandleTypeContact, UIntList() << bareHandle),
+ conn->contactFactory()->features(), attrs);
+ cachedAllKnownContacts.insert(contact);
+ contactListContacts.insert(contact);
+ }
+
+ if (contactManager->connection()->requestedFeatures().contains(
+ Connection::FeatureRosterGroups)) {
+ groupsSetSuccess = true;
+ }
+
+ // We may have been in state Failure and then Success, and FeatureRoster is already ready
+ // In any case, if we're going to reintrospect Groups, we only advance to state success once
+ // that is finished. We connect to the op finishing already here to catch all the failure finish
+ // cases as well.
+ if (introspectPendingOp) {
+ if (!groupsSetSuccess) {
+ // Will emit stateChanged() signal when the op is finished in idle
+ // callback. This is to ensure FeatureRoster (and Groups) is marked ready.
+ connect(introspectPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ }
+
+ introspectPendingOp->setFinished();
+ introspectPendingOp = 0;
+ } else if (!groupsSetSuccess) {
+ setStateSuccess();
+ } else {
+ // Verify that Groups is actually going to set the state
+ // As far as I can see, this will always be the case.
+ Q_ASSERT(groupsReintrospectionRequired);
+ }
+
+ if (groupsReintrospectionRequired) {
+ introspectGroups();
+ }
+}
+
+void ContactManager::Roster::setStateSuccess()
+{
+ if (contactManager->connection()->isValid()) {
+ debug() << "State is now success";
+ contactListState = ContactListStateSuccess;
+ emit contactManager->stateChanged((Tp::ContactListState) contactListState);
+ }
+}
+
+void ContactManager::Roster::onContactListStateChanged(uint state)
+{
+ if (pendingContactListState == state) {
+ // ignore redundant state changes
+ return;
+ }
+
+ pendingContactListState = state;
+
+ if (state == ContactListStateSuccess) {
+ introspectContactListContacts();
+ return;
+ }
+
+ contactListState = state;
+
+ if (state == ContactListStateFailure) {
+ debug() << "State changed to failure, finishing roster introspection";
+ }
+
+ emit contactManager->stateChanged((Tp::ContactListState) state);
+
+ if (state == ContactListStateFailure) {
+ // Consider it done here as the state may go from Failure to Success afterwards, in which
+ // case the contacts will appear.
+ Q_ASSERT(introspectPendingOp);
+ introspectPendingOp->setFinished();
+ introspectPendingOp = 0;
+ }
+}
+
+void ContactManager::Roster::onContactListContactsChangedWithId(const Tp::ContactSubscriptionMap &changes,
+ const Tp::HandleIdentifierMap &ids, const Tp::HandleIdentifierMap &removals)
+{
+ debug() << "Got ContactList.ContactsChangedWithID with" << changes.size() <<
+ "changes and" << removals.size() << "removals";
+
+ gotContactListContactsChangedWithId = true;
+
+ if (!gotContactListInitialContacts) {
+ debug() << "Ignoring ContactList changes until initial contacts are retrieved";
+ return;
+ }
+
+ ConnectionPtr conn(contactManager->connection());
+ conn->lowlevel()->injectContactIds(ids);
+
+ contactListUpdatesQueue.enqueue(UpdateInfo(changes, ids, removals));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListUpdates);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes,
+ const Tp::UIntList &removals)
+{
+ if (gotContactListContactsChangedWithId) {
+ return;
+ }
+
+ debug() << "Got ContactList.ContactsChanged with" << changes.size() <<
+ "changes and" << removals.size() << "removals";
+
+ if (!gotContactListInitialContacts) {
+ debug() << "Ignoring ContactList changes until initial contacts are retrieved";
+ return;
+ }
+
+ HandleIdentifierMap removalsMap;
+ foreach (uint handle, removals) {
+ removalsMap.insert(handle, QString());
+ }
+
+ contactListUpdatesQueue.enqueue(UpdateInfo(changes, HandleIdentifierMap(), removalsMap));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListUpdates);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListBlockedContactsConstructed(Tp::PendingOperation *op)
+{
+ BlockedContactsChangedInfo info = contactListBlockedContactsChangedQueue.dequeue();
+
+ if (op->isError()) {
+ if (info.continueIntrospectionWhenFinished) {
+ introspectContactList();
+ }
+ processingContactListChanges = false;
+ processContactListChanges();
+ return;
+ }
+
+ Contacts newBlockedContacts;
+ Contacts unblockedContacts;
+
+ HandleIdentifierMap::const_iterator begin = info.added.constBegin();
+ HandleIdentifierMap::const_iterator end = info.added.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to construct contact for handle" << bareHandle;
+ continue;
+ }
+
+ debug() << "Contact" << contact->id() << "is now blocked";
+ blockedContacts.insert(contact);
+ newBlockedContacts.insert(contact);
+ contact->setBlocked(true);
+ }
+
+ begin = info.removed.constBegin();
+ end = info.removed.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to construct contact for handle" << bareHandle;
+ continue;
+ }
+
+ debug() << "Contact" << contact->id() << "is now unblocked";
+ blockedContacts.remove(contact);
+ unblockedContacts.insert(contact);
+ contact->setBlocked(false);
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(newBlockedContacts, Contacts(),
+ Contacts(), unblockedContacts, Channel::GroupMemberChangeDetails());
+
+ if (info.continueIntrospectionWhenFinished) {
+ introspectContactList();
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListNewContactsConstructed(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ contactListUpdatesQueue.dequeue();
+ processingContactListChanges = false;
+ processContactListChanges();
+ return;
+ }
+
+ UpdateInfo info = contactListUpdatesQueue.dequeue();
+
+ Tp::Contacts added;
+ Tp::Contacts removed;
+
+ Tp::Contacts publishRequested;
+
+ ContactSubscriptionMap::const_iterator begin = info.changes.constBegin();
+ ContactSubscriptionMap::const_iterator end = info.changes.constEnd();
+ for (ContactSubscriptionMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ ContactSubscriptions subscriptions = i.value();
+
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to construct contact for handle" << bareHandle;
+ continue;
+ }
+
+ contactListContacts.insert(contact);
+ added << contact;
+
+ Contact::PresenceState oldContactPublishState = contact->publishState();
+ QString oldContactPublishStateMessage = contact->publishStateMessage();
+ contact->setSubscriptionState((SubscriptionState) subscriptions.subscribe);
+ contact->setPublishState((SubscriptionState) subscriptions.publish,
+ subscriptions.publishRequest);
+ if (subscriptions.publish == SubscriptionStateAsk &&
+ (oldContactPublishState != Contact::PresenceStateAsk ||
+ oldContactPublishStateMessage != contact->publishStateMessage())) {
+ Channel::GroupMemberChangeDetails publishRequestDetails;
+ QVariantMap detailsMap;
+ detailsMap.insert(QLatin1String("message"), subscriptions.publishRequest);
+ publishRequestDetails = Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap);
+ // FIXME (API/ABI break) remove both of these signals
+ emit contactManager->presencePublicationRequested(Contacts() << contact,
+ publishRequestDetails);
+ emit contactManager->presencePublicationRequested(Contacts() << contact,
+ subscriptions.publishRequest);
+
+ publishRequested.insert(contact);
+ }
+ }
+
+ if (!publishRequested.empty()) {
+ emit contactManager->presencePublicationRequested(publishRequested);
+ }
+
+ foreach (uint bareHandle, info.removals.keys()) {
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to find removed contact with handle" << bareHandle;
+ continue;
+ }
+
+ if (!contactListContacts.contains(contact)) {
+ warning() << "Contact" << contact->id() << "removed from ContactList "
+ "but it wasn't present, ignoring.";
+ continue;
+ }
+
+ contactListContacts.remove(contact);
+ removed << contact;
+ }
+
+ computeKnownContactsChanges(added, Contacts(), Contacts(),
+ removed, Channel::GroupMemberChangeDetails());
+
+ foreach (const Tp::ContactPtr &contact, removed) {
+ contact->setSubscriptionState(SubscriptionStateNo);
+ contact->setPublishState(SubscriptionStateNo);
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupsChanged(const Tp::UIntList &contacts,
+ const QStringList &added, const QStringList &removed)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupsUpdatesQueue.enqueue(GroupsUpdateInfo(contacts,
+ added, removed));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsUpdates);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupsCreated(const QStringList &names)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupsCreatedQueue.enqueue(names);
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsCreated);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupRenamed(const QString &oldName, const QString &newName)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupRenamedQueue.enqueue(GroupRenamedInfo(oldName, newName));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupRenamed);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupsRemoved(const QStringList &names)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupsRemovedQueue.enqueue(names);
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsRemoved);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onModifyFinished(Tp::PendingOperation *op)
+{
+ ModifyFinishOp *returned = returnedModifyOps.take(op);
+
+ // Finished twice, or we didn't add the returned op at all?
+ Q_ASSERT(returned);
+
+ if (op->isError()) {
+ returned->setError(op->errorName(), op->errorMessage());
+ }
+
+ modifyFinishQueue.enqueue(returned);
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processFinishedModify);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::gotContactListChannelHandle(PendingOperation *op)
+{
+ PendingHandles *ph = qobject_cast<PendingHandles*>(op);
+ Q_ASSERT(ph->namesRequested().size() == 1);
+ QString channelId = ph->namesRequested().first();
+ uint type = ChannelInfo::typeForIdentifier(channelId);
+
+ if (op->isError()) {
+ // let's not fail, because the contact lists are not supported
+ debug() << "Unable to retrieve handle for" << channelId << "channel, ignoring";
+ contactListChannels.remove(type);
+ onContactListChannelReady();
+ return;
+ }
+
+ if (ph->invalidNames().size() == 1) {
+ // let's not fail, because the contact lists are not supported
+ debug() << "Unable to retrieve handle for" << channelId << "channel, ignoring";
+ contactListChannels.remove(type);
+ onContactListChannelReady();
+ return;
+ }
+
+ Q_ASSERT(ph->handles().size() == 1);
+
+ debug() << "Got handle for" << channelId << "channel";
+
+ if (!usingFallbackContactList) {
+ Q_ASSERT(type == ChannelInfo::TypeDeny);
+ } else {
+ Q_ASSERT(type != (uint) -1 && type < ChannelInfo::LastType);
+ }
+
+ ReferencedHandles handle = ph->handles();
+ contactListChannels[type].handle = handle;
+
+ debug() << "Requesting channel for" << channelId << "channel";
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) HandleTypeList);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ handle[0]);
+ ConnectionPtr conn(contactManager->connection());
+ /* Request the channel passing INT_MAX as timeout (meaning no timeout), as
+ * some CMs may take too long to return from ensureChannel when still
+ * loading the contact list */
+ connect(conn->lowlevel()->ensureChannel(request, INT_MAX),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListChannel(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::gotContactListChannel(PendingOperation *op)
+{
+ if (op->isError()) {
+ debug() << "Unable to create channel, ignoring";
+ onContactListChannelReady();
+ return;
+ }
+
+ PendingChannel *pc = qobject_cast<PendingChannel*>(op);
+ ChannelPtr channel = pc->channel();
+ Q_ASSERT(channel);
+ uint handle = pc->targetHandle();
+ Q_ASSERT(handle);
+
+ for (uint i = 0; i < ChannelInfo::LastType; ++i) {
+ if (contactListChannels.contains(i) &&
+ contactListChannels[i].handle.size() > 0 &&
+ contactListChannels[i].handle[0] == handle) {
+ Q_ASSERT(!contactListChannels[i].channel);
+ contactListChannels[i].channel = channel;
+
+ // deref connection refcount here as connection will keep a ref to channel and we don't
+ // want a contact list channel keeping a ref of connection, otherwise connection will
+ // leak, thus the channels.
+ channel->connection()->deref();
+
+ connect(channel->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListChannelReady()));
+ }
+ }
+}
+
+void ContactManager::Roster::onContactListChannelReady()
+{
+ if (!usingFallbackContactList) {
+ setContactListChannelsReady();
+
+ updateContactsBlockState();
+
+ if (denyChannel) {
+ cachedAllKnownContacts.unite(denyChannel->groupContacts());
+ }
+
+ introspectContactList();
+ } else if (++contactListChannelsReady == ChannelInfo::LastType) {
+ if (contactListChannels.isEmpty()) {
+ contactListState = ContactListStateFailure;
+ debug() << "State is failure, roster not supported";
+ emit contactManager->stateChanged((Tp::ContactListState) contactListState);
+
+ Q_ASSERT(introspectPendingOp);
+ introspectPendingOp->setFinishedWithError(TP_QT4_ERROR_NOT_IMPLEMENTED,
+ QLatin1String("Roster not supported"));
+ introspectPendingOp = 0;
+ return;
+ }
+
+ setContactListChannelsReady();
+
+ updateContactsBlockState();
+
+ // Refresh the cache for the current known contacts
+ foreach (const ChannelInfo &contactListChannel, contactListChannels) {
+ ChannelPtr channel = contactListChannel.channel;
+ if (!channel) {
+ continue;
+ }
+ cachedAllKnownContacts.unite(channel->groupContacts());
+ cachedAllKnownContacts.unite(channel->groupLocalPendingContacts());
+ cachedAllKnownContacts.unite(channel->groupRemotePendingContacts());
+ }
+
+ updateContactsPresenceState();
+
+ Q_ASSERT(introspectPendingOp);
+
+ if (!contactManager->connection()->requestedFeatures().contains(
+ Connection::FeatureRosterGroups)) {
+ // Will emit stateChanged() signal when the op is finished in idle
+ // callback. This is to ensure FeatureRoster is marked ready.
+ connect(introspectPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ } else {
+ Q_ASSERT(!groupsSetSuccess);
+ groupsSetSuccess = true;
+ }
+
+ introspectPendingOp->setFinished();
+ introspectPendingOp = 0;
+ }
+}
+
+void ContactManager::Roster::gotContactListGroupsProperties(PendingOperation *op)
+{
+ Q_ASSERT(introspectGroupsPendingOp != NULL);
+
+ if (groupsSetSuccess) {
+ // Connect here, so we catch the following and the other failure cases
+ connect(introspectGroupsPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ }
+
+ if (op->isError()) {
+ warning() << "Getting contact list groups properties failed:" << op->errorName() << '-'
+ << op->errorMessage();
+
+ introspectGroupsPendingOp->setFinishedWithError(
+ op->errorName(), op->errorMessage());
+ introspectGroupsPendingOp = 0;
+
+ return;
+ }
+
+ debug() << "Got contact list groups properties";
+ PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op);
+
+ QVariantMap props = pvm->result();
+
+ cachedAllKnownGroups = qdbus_cast<QStringList>(props[QLatin1String("Groups")]).toSet();
+ contactListGroupPropertiesReceived = true;
+
+ processingContactListChanges = true;
+ PendingContacts *pc = contactManager->upgradeContacts(
+ contactManager->allKnownContacts().toList(),
+ Contact::FeatureRosterGroups);
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListContactsUpgraded(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::onContactListContactsUpgraded(PendingOperation *op)
+{
+ Q_ASSERT(processingContactListChanges);
+ processingContactListChanges = false;
+
+ Q_ASSERT(introspectGroupsPendingOp != NULL);
+
+ if (op->isError()) {
+ warning() << "Upgrading contacts with group membership failed:" << op->errorName() << '-'
+ << op->errorMessage();
+
+ introspectGroupsPendingOp->setFinishedWithError(
+ op->errorName(), op->errorMessage());
+ introspectGroupsPendingOp = 0;
+ processContactListChanges();
+ return;
+ }
+
+ introspectGroupsPendingOp->setFinished();
+ introspectGroupsPendingOp = 0;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onNewChannels(const Tp::ChannelDetailsList &channelDetailsList)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ QString channelType;
+ uint handleType;
+ foreach (const ChannelDetails &channelDetails, channelDetailsList) {
+ channelType = channelDetails.properties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ if (channelType != QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST)) {
+ continue;
+ }
+
+ handleType = channelDetails.properties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
+ if (handleType != Tp::HandleTypeGroup) {
+ continue;
+ }
+
+ ++featureContactListGroupsTodo; // decremented in onContactListGroupChannelReady
+ ChannelPtr channel = Channel::create(conn,
+ channelDetails.channel.path(), channelDetails.properties);
+ pendingContactListGroupChannels.append(channel);
+
+ // deref connection refcount here as connection will keep a ref to channel and we don't
+ // want a contact list group channel keeping a ref of connection, otherwise connection will
+ // leak, thus the channels.
+ channel->connection()->deref();
+
+ connect(channel->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListGroupChannelReady(Tp::PendingOperation*)));
+ }
+}
+
+void ContactManager::Roster::onContactListGroupChannelReady(PendingOperation *op)
+{
+ --featureContactListGroupsTodo; // incremented in onNewChannels
+
+ ConnectionPtr conn(contactManager->connection());
+
+ if (introspectGroupsPendingOp) {
+ checkContactListGroupsReady();
+ } else {
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ ChannelPtr channel = ChannelPtr::qObjectCast(pr->proxy());
+ QString id = addContactListGroupChannel(channel);
+ emit contactManager->groupAdded(id);
+ pendingContactListGroupChannels.removeOne(channel);
+ }
+}
+
+void ContactManager::Roster::gotChannels(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got channels";
+ onNewChannels(qdbus_cast<ChannelDetailsList>(reply.value()));
+ } else {
+ warning().nospace() << "Getting channels failed with " <<
+ reply.error().name() << ":" << reply.error().message();
+ }
+
+ --featureContactListGroupsTodo; // incremented in introspectRosterGroups
+
+ checkContactListGroupsReady();
+
+ watcher->deleteLater();
+}
+
+void ContactManager::Roster::onStoredChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ warning() << "Found local pending contacts on stored list";
+ }
+
+ if (!groupRemotePendingMembersAdded.isEmpty()) {
+ warning() << "Found remote pending contacts on stored list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "on stored list";
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from stored list";
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded,
+ groupLocalPendingMembersAdded, groupRemotePendingMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onSubscribeChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ warning() << "Found local pending contacts on subscribe list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "on subscribe list";
+ contact->setSubscriptionState(SubscriptionStateYes);
+ }
+
+ foreach (ContactPtr contact, groupRemotePendingMembersAdded) {
+ debug() << "Contact" << contact->id() << "added to subscribe list";
+ contact->setSubscriptionState(SubscriptionStateAsk);
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from subscribe list";
+ contact->setSubscriptionState(SubscriptionStateNo);
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded,
+ groupLocalPendingMembersAdded, groupRemotePendingMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onPublishChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupRemotePendingMembersAdded.isEmpty()) {
+ warning() << "Found remote pending contacts on publish list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "on publish list";
+ contact->setPublishState(SubscriptionStateYes);
+ }
+
+ foreach (ContactPtr contact, groupLocalPendingMembersAdded) {
+ debug() << "Contact" << contact->id() << "added to publish list";
+ contact->setPublishState(SubscriptionStateAsk, details.message());
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from publish list";
+ contact->setPublishState(SubscriptionStateNo);
+ }
+
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded);
+ // FIXME (API/ABI break) remove both of these signals
+ emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded,
+ details);
+ emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded,
+ details.message());
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded,
+ groupLocalPendingMembersAdded, groupRemotePendingMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onDenyChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ warning() << "Found local pending contacts on deny list";
+ }
+
+ if (!groupRemotePendingMembersAdded.isEmpty()) {
+ warning() << "Found remote pending contacts on deny list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "added to deny list";
+ contact->setBlocked(true);
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from deny list";
+ contact->setBlocked(false);
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded, Contacts(),
+ Contacts(), groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onContactListGroupMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ ChannelPtr contactListGroupChannel = ChannelPtr(
+ qobject_cast<Channel*>(sender()));
+ QString id = contactListGroupChannel->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
+
+ foreach (const ContactPtr &contact, groupMembersAdded) {
+ contact->setAddedToGroup(id);
+ }
+ foreach (const ContactPtr &contact, groupMembersRemoved) {
+ contact->setRemovedFromGroup(id);
+ }
+
+ emit contactManager->groupMembersChanged(id, groupMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onContactListGroupRemoved(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ Q_UNUSED(errorName);
+ Q_UNUSED(errorMessage);
+
+ // Is it correct to assume that if an user-defined contact list
+ // gets invalidated it means it was removed? Spec states that if a
+ // user-defined contact list gets closed it was removed, and Channel
+ // invalidates itself when it gets closed.
+ ChannelPtr contactListGroupChannel = ChannelPtr(qobject_cast<Channel*>(proxy));
+ QString id = contactListGroupChannel->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
+ contactListGroupChannels.remove(id);
+ removedContactListGroupChannels.append(contactListGroupChannel);
+ disconnect(contactListGroupChannel.data(), 0, 0, 0);
+ emit contactManager->groupRemoved(id);
+}
+
+void ContactManager::Roster::introspectContactBlocking()
+{
+ debug() << "Requesting ContactBlockingCapabilities property";
+
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactBlockingInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactBlockingInterface>();
+
+ PendingVariant *pv = iface->requestPropertyContactBlockingCapabilities();
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactBlockingCapabilities(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::introspectContactBlockingBlockedContacts()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactBlockingInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactBlockingInterface>();
+
+ Q_ASSERT(iface);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ iface->RequestBlockedContacts(), contactManager);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotContactBlockingBlockedContacts(QDBusPendingCallWatcher*)));
+
+ connect(iface,
+ SIGNAL(BlockedContactsChanged(Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)),
+ SLOT(onContactBlockingBlockedContactsChanged(Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)));
+}
+
+void ContactManager::Roster::introspectContactList()
+{
+ debug() << "Requesting ContactList properties";
+
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+
+ connect(iface,
+ SIGNAL(ContactListStateChanged(uint)),
+ SLOT(onContactListStateChanged(uint)));
+ connect(iface,
+ SIGNAL(ContactsChangedWithID(Tp::ContactSubscriptionMap,Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)),
+ SLOT(onContactListContactsChangedWithId(Tp::ContactSubscriptionMap,Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)));
+ connect(iface,
+ SIGNAL(ContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList)),
+ SLOT(onContactListContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList)));
+
+ PendingVariantMap *pvm = iface->requestAllProperties();
+ connect(pvm,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListProperties(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::introspectContactListContacts()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+
+ Features features(conn->contactFactory()->features());
+ Features supportedFeatures(contactManager->supportedFeatures());
+ QSet<QString> interfaces;
+ foreach (const Feature &feature, features) {
+ contactManager->ensureTracking(feature);
+
+ if (supportedFeatures.contains(feature)) {
+ // Only query interfaces which are reported as supported to not get an error
+ interfaces.insert(contactManager->featureToInterface(feature));
+ }
+ }
+ interfaces.insert(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ iface->GetContactListAttributes(interfaces.toList(), true), contactManager);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotContactListContacts(QDBusPendingCallWatcher*)));
+}
+
+void ContactManager::Roster::processContactListChanges()
+{
+ if (processingContactListChanges || contactListChangesQueue.isEmpty()) {
+ return;
+ }
+
+ processingContactListChanges = true;
+ (this->*(contactListChangesQueue.dequeue()))();
+}
+
+void ContactManager::Roster::processContactListBlockedContactsChanged()
+{
+ BlockedContactsChangedInfo info = contactListBlockedContactsChangedQueue.head();
+
+ UIntList contacts;
+ HandleIdentifierMap::const_iterator begin = info.added.constBegin();
+ HandleIdentifierMap::const_iterator end = info.added.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ contacts << bareHandle;
+ }
+
+ begin = info.removed.constBegin();
+ end = info.removed.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ contacts << bareHandle;
+ }
+
+ Features features;
+ if (contactManager->connection()->isReady(Connection::FeatureRosterGroups)) {
+ features << Contact::FeatureRosterGroups;
+ }
+ PendingContacts *pc = contactManager->contactsForHandles(contacts, features);
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListBlockedContactsConstructed(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::processContactListUpdates()
+{
+ UpdateInfo info = contactListUpdatesQueue.head();
+
+ // construct Contact objects for all contacts in added to the contact list
+ UIntList contacts;
+ ContactSubscriptionMap::const_iterator begin = info.changes.constBegin();
+ ContactSubscriptionMap::const_iterator end = info.changes.constEnd();
+ for (ContactSubscriptionMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ contacts << bareHandle;
+ }
+
+ Features features;
+ if (contactManager->connection()->isReady(Connection::FeatureRosterGroups)) {
+ features << Contact::FeatureRosterGroups;
+ }
+ PendingContacts *pc = contactManager->contactsForHandles(contacts, features);
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListNewContactsConstructed(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::processContactListGroupsUpdates()
+{
+ GroupsUpdateInfo info = contactListGroupsUpdatesQueue.dequeue();
+
+ foreach (const QString &group, info.groupsAdded) {
+ Contacts contacts;
+ foreach (uint bareHandle, info.contacts) {
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "contact with handle" << bareHandle << "was added to a group but "
+ "never added to the contact list, ignoring";
+ continue;
+ }
+ contacts << contact;
+ contact->setAddedToGroup(group);
+ }
+
+ emit contactManager->groupMembersChanged(group, contacts,
+ Contacts(), Channel::GroupMemberChangeDetails());
+ }
+
+ foreach (const QString &group, info.groupsRemoved) {
+ Contacts contacts;
+ foreach (uint bareHandle, info.contacts) {
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "contact with handle" << bareHandle << "was removed from a group but "
+ "never added to the contact list, ignoring";
+ continue;
+ }
+ contacts << contact;
+ contact->setRemovedFromGroup(group);
+ }
+
+ emit contactManager->groupMembersChanged(group, Contacts(),
+ contacts, Channel::GroupMemberChangeDetails());
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processContactListGroupsCreated()
+{
+ QStringList names = contactListGroupsCreatedQueue.dequeue();
+ foreach (const QString &name, names) {
+ cachedAllKnownGroups.insert(name);
+ emit contactManager->groupAdded(name);
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processContactListGroupRenamed()
+{
+ GroupRenamedInfo info = contactListGroupRenamedQueue.dequeue();
+ cachedAllKnownGroups.remove(info.oldName);
+ cachedAllKnownGroups.insert(info.newName);
+ emit contactManager->groupRenamed(info.oldName, info.newName);
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processContactListGroupsRemoved()
+{
+ QStringList names = contactListGroupsRemovedQueue.dequeue();
+ foreach (const QString &name, names) {
+ cachedAllKnownGroups.remove(name);
+ emit contactManager->groupRemoved(name);
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processFinishedModify()
+{
+ ModifyFinishOp *op = modifyFinishQueue.dequeue();
+ // Only continue processing changes (and thus, emitting change signals) when the op has signaled
+ // finish (it'll only do this after we've returned to the mainloop)
+ connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onModifyFinishSignaled()));
+ op->finish();
+}
+
+PendingOperation *ContactManager::Roster::queuedFinishVoid(const QDBusPendingCall &call)
+{
+ PendingOperation *actual = new PendingVoid(call, contactManager->connection());
+ connect(actual,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onModifyFinished(Tp::PendingOperation*)));
+ ModifyFinishOp *toReturn = new ModifyFinishOp(contactManager->connection());
+ returnedModifyOps.insert(actual, toReturn);
+ return toReturn;
+}
+
+void ContactManager::Roster::onModifyFinishSignaled()
+{
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::setContactListChannelsReady()
+{
+ if (!usingFallbackContactList) {
+ Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypeSubscribe));
+ Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypePublish));
+ Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypeStored));
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypeSubscribe)) {
+ subscribeChannel = contactListChannels[ChannelInfo::TypeSubscribe].channel;
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypePublish)) {
+ publishChannel = contactListChannels[ChannelInfo::TypePublish].channel;
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypeStored)) {
+ storedChannel = contactListChannels[ChannelInfo::TypeStored].channel;
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypeDeny)) {
+ denyChannel = contactListChannels[ChannelInfo::TypeDeny].channel;
+ }
+
+ uint type;
+ ChannelPtr channel;
+ const char *method;
+ for (QMap<uint, ChannelInfo>::const_iterator i = contactListChannels.constBegin();
+ i != contactListChannels.constEnd(); ++i) {
+ type = i.key();
+ channel = i.value().channel;
+ if (!channel) {
+ continue;
+ }
+
+ if (type == ChannelInfo::TypeStored) {
+ method = SLOT(onStoredChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else if (type == ChannelInfo::TypeSubscribe) {
+ method = SLOT(onSubscribeChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else if (type == ChannelInfo::TypePublish) {
+ method = SLOT(onPublishChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else if (type == ChannelInfo::TypeDeny) {
+ method = SLOT(onDenyChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else {
+ continue;
+ }
+
+ connect(channel.data(),
+ SIGNAL(groupMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ method);
+ }
+}
+
+void ContactManager::Roster::updateContactsBlockState()
+{
+ Q_ASSERT(!hasContactBlockingInterface);
+
+ if (!denyChannel) {
+ return;
+ }
+
+ Contacts denyContacts = denyChannel->groupContacts();
+ foreach (const ContactPtr &contact, denyContacts) {
+ contact->setBlocked(true);
+ }
+}
+
+void ContactManager::Roster::updateContactsPresenceState()
+{
+ if (!subscribeChannel && !publishChannel) {
+ return;
+ }
+
+ Contacts subscribeContacts;
+ Contacts subscribeContactsRP;
+
+ if (subscribeChannel) {
+ subscribeContacts = subscribeChannel->groupContacts();
+ subscribeContactsRP = subscribeChannel->groupRemotePendingContacts();
+ }
+
+ Contacts publishContacts;
+ Contacts publishContactsLP;
+ if (publishChannel) {
+ publishContacts = publishChannel->groupContacts();
+ publishContactsLP = publishChannel->groupLocalPendingContacts();
+ }
+
+ Contacts contacts = cachedAllKnownContacts;
+ foreach (ContactPtr contact, contacts) {
+ if (subscribeChannel) {
+ // not in "subscribe" -> No, in "subscribe" lp -> Ask, in "subscribe" current -> Yes
+ if (subscribeContacts.contains(contact)) {
+ contact->setSubscriptionState(SubscriptionStateYes);
+ } else if (subscribeContactsRP.contains(contact)) {
+ contact->setSubscriptionState(SubscriptionStateAsk);
+ } else {
+ contact->setSubscriptionState(SubscriptionStateNo);
+ }
+ }
+
+ if (publishChannel) {
+ // not in "publish" -> No, in "subscribe" rp -> Ask, in "publish" current -> Yes
+ if (publishContacts.contains(contact)) {
+ contact->setPublishState(SubscriptionStateYes);
+ } else if (publishContactsLP.contains(contact)) {
+ contact->setPublishState(SubscriptionStateAsk,
+ publishChannel->groupLocalPendingContactChangeInfo(contact).message());
+ } else {
+ contact->setPublishState(SubscriptionStateNo);
+ }
+ }
+ }
+}
+
+void ContactManager::Roster::computeKnownContactsChanges(const Tp::Contacts& added,
+ const Tp::Contacts& pendingAdded, const Tp::Contacts& remotePendingAdded,
+ const Tp::Contacts& removed, const Channel::GroupMemberChangeDetails &details)
+{
+ // First of all, compute the real additions/removals based upon our cache
+ Tp::Contacts realAdded;
+ realAdded.unite(added);
+ realAdded.unite(pendingAdded);
+ realAdded.unite(remotePendingAdded);
+ realAdded.subtract(cachedAllKnownContacts);
+ Tp::Contacts realRemoved = removed;
+ realRemoved.intersect(cachedAllKnownContacts);
+
+ // Check if realRemoved have been _really_ removed from all lists
+ foreach (const ChannelInfo &contactListChannel, contactListChannels) {
+ ChannelPtr channel = contactListChannel.channel;
+ if (!channel) {
+ continue;
+ }
+ realRemoved.subtract(channel->groupContacts());
+ realRemoved.subtract(channel->groupLocalPendingContacts());
+ realRemoved.subtract(channel->groupRemotePendingContacts());
+ }
+
+ // ...and from the Conn.I.ContactList / Conn.I.ContactBlocking contacts
+ realRemoved.subtract(contactListContacts);
+ realRemoved.subtract(blockedContacts);
+
+ // Are there any real changes?
+ if (!realAdded.isEmpty() || !realRemoved.isEmpty()) {
+ // Yes, update our "cache" and emit the signal
+ cachedAllKnownContacts.unite(realAdded);
+ cachedAllKnownContacts.subtract(realRemoved);
+ emit contactManager->allKnownContactsChanged(realAdded, realRemoved, details);
+ }
+}
+
+void ContactManager::Roster::checkContactListGroupsReady()
+{
+ if (featureContactListGroupsTodo != 0) {
+ return;
+ }
+
+ if (groupsSetSuccess) {
+ Q_ASSERT(contactManager->state() != ContactListStateSuccess);
+
+ if (introspectGroupsPendingOp) {
+ // Will emit stateChanged() signal when the op is finished in idle
+ // callback. This is to ensure FeatureRosterGroups is marked ready.
+ connect(introspectGroupsPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ } else {
+ setStateSuccess();
+ }
+
+ groupsSetSuccess = false;
+ }
+
+ setContactListGroupChannelsReady();
+ if (introspectGroupsPendingOp) {
+ introspectGroupsPendingOp->setFinished();
+ introspectGroupsPendingOp = 0;
+ }
+ pendingContactListGroupChannels.clear();
+}
+
+void ContactManager::Roster::setContactListGroupChannelsReady()
+{
+ Q_ASSERT(usingFallbackContactList == true);
+ Q_ASSERT(contactListGroupChannels.isEmpty());
+
+ foreach (const ChannelPtr &contactListGroupChannel, pendingContactListGroupChannels) {
+ addContactListGroupChannel(contactListGroupChannel);
+ }
+}
+
+QString ContactManager::Roster::addContactListGroupChannel(const ChannelPtr &contactListGroupChannel)
+{
+ QString id = contactListGroupChannel->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
+ contactListGroupChannels.insert(id, contactListGroupChannel);
+ connect(contactListGroupChannel.data(),
+ SIGNAL(groupMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(onContactListGroupMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)));
+ connect(contactListGroupChannel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onContactListGroupRemoved(Tp::DBusProxy*,QString,QString)));
+
+ foreach (const ContactPtr &contact, contactListGroupChannel->groupContacts()) {
+ contact->setAddedToGroup(id);
+ }
+
+ return id;
+}
+
+/**** ContactManager::Roster::ChannelInfo ****/
+QString ContactManager::Roster::ChannelInfo::identifierForType(Type type)
+{
+ static QString identifiers[LastType] = {
+ QLatin1String("subscribe"),
+ QLatin1String("publish"),
+ QLatin1String("stored"),
+ QLatin1String("deny"),
+ };
+ return identifiers[type];
+}
+
+uint ContactManager::Roster::ChannelInfo::typeForIdentifier(const QString &identifier)
+{
+ static QHash<QString, uint> types;
+ if (types.isEmpty()) {
+ types.insert(QLatin1String("subscribe"), TypeSubscribe);
+ types.insert(QLatin1String("publish"), TypePublish);
+ types.insert(QLatin1String("stored"), TypeStored);
+ types.insert(QLatin1String("deny"), TypeDeny);
+ }
+ if (types.contains(identifier)) {
+ return types[identifier];
+ }
+ return (uint) -1;
+}
+
+/**** ContactManager::Roster::ModifyFinishOp ****/
+ContactManager::Roster::ModifyFinishOp::ModifyFinishOp(const ConnectionPtr &conn)
+ : PendingOperation(conn)
+{
+}
+
+void ContactManager::Roster::ModifyFinishOp::setError(const QString &errorName, const QString &errorMessage)
+{
+ Q_ASSERT(this->errorName.isEmpty());
+ Q_ASSERT(this->errorMessage.isEmpty());
+
+ Q_ASSERT(!errorName.isEmpty());
+
+ this->errorName = errorName;
+ this->errorMessage = errorMessage;
+}
+
+void ContactManager::Roster::ModifyFinishOp::finish()
+{
+ if (errorName.isEmpty()) {
+ setFinished();
+ } else {
+ setFinishedWithError(errorName, errorMessage);
+ }
+}
+
+/**** ContactManager::Roster::RemoveGroupOp ****/
+ContactManager::Roster::RemoveGroupOp::RemoveGroupOp(const ChannelPtr &channel)
+ : PendingOperation(channel)
+{
+ Contacts contacts = channel->groupContacts();
+ if (!contacts.isEmpty()) {
+ connect(channel->groupRemoveContacts(contacts.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactsRemoved(Tp::PendingOperation*)));
+ } else {
+ connect(channel->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelClosed(Tp::PendingOperation*)));
+ }
+}
+
+void ContactManager::Roster::RemoveGroupOp::onContactsRemoved(PendingOperation *op)
+{
+ if (op->isError()) {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ // Let's ignore possible errors and try to remove the group
+ ChannelPtr channel = ChannelPtr(qobject_cast<Channel*>((Channel *) _object().data()));
+ connect(channel->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelClosed(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::RemoveGroupOp::onChannelClosed(PendingOperation *op)
+{
+ if (!op->isError()) {
+ setFinished();
+ } else {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ }
+}
+
+} // Tp