diff options
author | Andre Moreira Magalhaes (andrunko) <andre.magalhaes@collabora.co.uk> | 2010-12-31 11:22:05 -0200 |
---|---|---|
committer | Andre Moreira Magalhaes (andrunko) <andre.magalhaes@collabora.co.uk> | 2010-12-31 11:22:10 -0200 |
commit | 994a3c140969fab694288e9252dc31bcf09f73f9 (patch) | |
tree | ac3ed01b704152d6dcb6e5ab0579678d2ef7efe0 | |
parent | 5e2e943a37f4ae525ad7a69e73d47a1fb87f7148 (diff) | |
parent | a398ff89229a9599e0d21f5976cb00f2a459eca2 (diff) |
Merge branch 'contactlist'
Reviewed-by: Olli Salli (oggis) <olli.salli@collabora.co.uk>
27 files changed, 5327 insertions, 377 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ea7d9200..f9840a4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,11 +200,11 @@ else (TELEPATHYFARSIGHT_FOUND AND GSTREAMER_FOUND AND GLIB2_FOUND AND GOBJECT_FO endif (TELEPATHYFARSIGHT_FOUND AND GSTREAMER_FOUND AND GLIB2_FOUND AND GOBJECT_FOUND AND DBUS_FOUND AND LIBXML2_FOUND) # Find telepathy-glib -set(TELEPATHY_GLIB_MIN_VERSION 0.11.16) +set(TELEPATHY_GLIB_MIN_VERSION 0.13.0) find_package(TelepathyGlib) macro_log_feature(TELEPATHYGLIB_FOUND "Telepathy-glib" "Glib bindings for Telepathy" - "http://telepathy.freedesktop.org/" FALSE "0.11.16" + "http://telepathy.freedesktop.org/" FALSE "0.13.0" "Needed, together with Qt Glib integration, to build most of the unit tests") find_program(GLIB_GENMARSHAL glib-genmarshal) diff --git a/TelepathyQt4/channel.h b/TelepathyQt4/channel.h index dce22c4e..eb10e581 100644 --- a/TelepathyQt4/channel.h +++ b/TelepathyQt4/channel.h @@ -131,6 +131,8 @@ public: private: friend class Channel; + friend class Contact; + friend class ContactManager; GroupMemberChangeDetails(const ContactPtr &actor, const QVariantMap &details); diff --git a/TelepathyQt4/connection.cpp b/TelepathyQt4/connection.cpp index 24a15da3..b7fde4ba 100644 --- a/TelepathyQt4/connection.cpp +++ b/TelepathyQt4/connection.cpp @@ -1,8 +1,8 @@ /* * This file is part of TelepathyQt4 * - * Copyright (C) 2008, 2009 Collabora Ltd. <http://www.collabora.co.uk/> - * Copyright (C) 2008, 2009 Nokia Corporation + * Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2008-2010 Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -41,6 +41,7 @@ #include <TelepathyQt4/PendingFailure> #include <TelepathyQt4/PendingHandles> #include <TelepathyQt4/PendingReady> +#include <TelepathyQt4/PendingVariantMap> #include <TelepathyQt4/PendingVoid> #include <TelepathyQt4/ReferencedHandles> @@ -76,6 +77,8 @@ struct TELEPATHY_QT4_NO_EXPORT Connection::Private static void introspectSelfContact(Private *self); static void introspectSimplePresence(Private *self); static void introspectRoster(Private *self); + void introspectContactList(); + void introspectContactListContacts(); static void introspectRosterGroups(Private *self); static void introspectBalance(Private *self); @@ -139,6 +142,10 @@ struct TELEPATHY_QT4_NO_EXPORT Connection::Private SimpleStatusSpecMap simplePresenceStatuses; // FeatureRoster + // new roster API + uint contactListState; + + // old roster API QMap<uint, ContactManager::ContactListChannel> contactListChannels; uint contactListChannelsReady; @@ -217,6 +224,7 @@ Connection::Private::Private(Connection *parent, statusReason(ConnectionStatusReasonNoneSpecified), selfHandle(0), contactManager(ContactManagerPtr(new ContactManager(parent))), + contactListState((uint) -1), contactListChannelsReady(0), featureRosterGroupsTodo(0), handleContext(0) @@ -492,47 +500,130 @@ void Connection::Private::introspectSimplePresence(Connection::Private *self) void Connection::Private::introspectRoster(Connection::Private *self) { - debug() << "Requesting handles for contact lists"; + if (self->parent->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) { + self->contactManager->setUseFallbackContactList(false); + + debug() << "Introspecting deny channel"; - for (uint i = 0; i < ContactManager::ContactListChannel::LastType; ++i) { - self->contactListChannels.insert(i, - ContactManager::ContactListChannel( - (ContactManager::ContactListChannel::Type) i)); + self->contactListChannels.insert(ContactManager::ContactListChannel::TypeDeny, + ContactManager::ContactListChannel(ContactManager::ContactListChannel::TypeDeny)); PendingHandles *pending = self->parent->lowlevel()->requestHandles( HandleTypeList, QStringList() << ContactManager::ContactListChannel::identifierForType( - (ContactManager::ContactListChannel::Type) i)); + ContactManager::ContactListChannel::TypeDeny)); self->parent->connect(pending, SIGNAL(finished(Tp::PendingOperation*)), SLOT(gotContactListsHandles(Tp::PendingOperation*))); + } else { + debug() << "Requesting handles for contact lists channels"; + + self->contactManager->setUseFallbackContactList(true); + + for (uint i = 0; i < ContactManager::ContactListChannel::LastType; ++i) { + self->contactListChannels.insert(i, + ContactManager::ContactListChannel( + (ContactManager::ContactListChannel::Type) i)); + + PendingHandles *pending = self->parent->lowlevel()->requestHandles( + HandleTypeList, + QStringList() << ContactManager::ContactListChannel::identifierForType( + (ContactManager::ContactListChannel::Type) i)); + self->parent->connect(pending, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListsHandles(Tp::PendingOperation*))); + } } } -void Connection::Private::introspectRosterGroups(Connection::Private *self) +void Connection::Private::introspectContactList() { - debug() << "Introspecting roster groups"; + debug() << "Requesting contact list properties"; - ++self->featureRosterGroupsTodo; // decremented in gotChannels + Client::ConnectionInterfaceContactListInterface *iface = + parent->interface<Client::ConnectionInterfaceContactListInterface>(); - // we already checked if requests interface exists, so bypass requests - // interface checking - Client::ConnectionInterfaceRequestsInterface *iface = - self->parent->interface<Client::ConnectionInterfaceRequestsInterface>(); + parent->connect(iface, + SIGNAL(ContactListStateChanged(uint)), + SLOT(onContactListStateChanged(uint))); + parent->connect(iface, + SIGNAL(ContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList)), + SLOT(onContactListContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList))); - debug() << "Connecting to Requests.NewChannels"; - self->parent->connect(iface, - SIGNAL(NewChannels(Tp::ChannelDetailsList)), - SLOT(onNewChannels(Tp::ChannelDetailsList))); + PendingVariantMap *pvm = iface->requestAllProperties(); + parent->connect(pvm, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListProperties(Tp::PendingOperation*))); +} - debug() << "Retrieving channels"; +void Connection::Private::introspectContactListContacts() +{ + Client::ConnectionInterfaceContactListInterface *iface = + parent->interface<Client::ConnectionInterfaceContactListInterface>(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( - self->properties->Get( - QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), - QLatin1String("Channels")), self->parent); - self->parent->connect(watcher, + iface->GetContactListAttributes( + QStringList() << QLatin1String(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST), + true), parent); + parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - SLOT(gotChannels(QDBusPendingCallWatcher*))); + SLOT(gotContactListContacts(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectRosterGroups(Connection::Private *self) +{ + if (self->parent->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) { + if (!self->parent->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + self->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, false, + TP_QT4_ERROR_NOT_IMPLEMENTED, QLatin1String("Roster groups not supported")); + return; + } + + debug() << "Introspecting contact list groups"; + + Client::ConnectionInterfaceContactGroupsInterface *iface = + self->parent->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + + self->contactManager->connect(iface, + SIGNAL(GroupsChanged(Tp::UIntList,QStringList,QStringList)), + SLOT(onContactListGroupsChanged(Tp::UIntList,QStringList,QStringList))); + self->contactManager->connect(iface, + SIGNAL(GroupsCreated(QStringList)), + SLOT(onContactListGroupsCreated(QStringList))); + self->contactManager->connect(iface, + SIGNAL(GroupRenamed(QString,QString)), + SLOT(onContactListGroupRenamed(QString,QString))); + self->contactManager->connect(iface, + SIGNAL(GroupsRemoved(QStringList)), + SLOT(onContactListGroupsRemoved(QStringList))); + + PendingVariantMap *pvm = iface->requestAllProperties(); + self->parent->connect(pvm, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListGroupsProperties(Tp::PendingOperation*))); + } else { + debug() << "Introspecting contact list group channels"; + + ++self->featureRosterGroupsTodo; // decremented in gotChannels + + // we already checked if requests interface exists, so bypass requests + // interface checking + Client::ConnectionInterfaceRequestsInterface *iface = + self->parent->interface<Client::ConnectionInterfaceRequestsInterface>(); + + debug() << "Connecting to Requests.NewChannels"; + self->parent->connect(iface, + SIGNAL(NewChannels(Tp::ChannelDetailsList)), + SLOT(onNewChannels(Tp::ChannelDetailsList))); + + debug() << "Retrieving channels"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->Get( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), + QLatin1String("Channels")), self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotChannels(QDBusPendingCallWatcher*))); + } } void Connection::Private::introspectBalance(Connection::Private *self) @@ -622,10 +713,8 @@ void Connection::Private::checkFeatureRosterGroupsReady() } debug() << "FeatureRosterGroups ready"; - contactManager->setContactListGroupChannels( - contactListGroupChannels); - readinessHelper->setIntrospectCompleted( - FeatureRosterGroups, true); + contactManager->setContactListGroupChannelsFallback(contactListGroupChannels); + readinessHelper->setIntrospectCompleted(FeatureRosterGroups, true); contactListGroupChannels.clear(); } @@ -1643,6 +1732,110 @@ void Connection::gotSelfContact(PendingOperation *op) } } +void Connection::gotContactListProperties(PendingOperation *op) +{ + if (op->isError()) { + // We may have been in state Failure and then Success, and we are already ready + if (!isReady(FeatureRoster)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, false, + op->errorName(), op->errorMessage()); + } + return; + } + + debug() << "Got contact list properties"; + PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op); + + QVariantMap props = pvm->result(); + + // only update the status if we did not get it from ContactListStateChanged + if (mPriv->contactListState == (uint) -1) { + uint state = qdbus_cast<uint>(props[QLatin1String("ContactListState")]); + onContactListStateChanged(state); + } + + mPriv->contactManager->setContactListProperties(props); +} + +void Connection::gotContactListContacts(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<ContactAttributesMap> reply = *watcher; + + if (watcher->isError()) { + // We may have been in state Failure and then Success, and we are already ready + if (!isReady(FeatureRoster)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, false, + reply.error()); + } + return; + } + + ContactAttributesMap attrs = reply.value(); + mPriv->contactManager->setContactListContacts(attrs); + + // We may have been in state Failure and then Success, and we are already ready + if (!isReady(FeatureRoster)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, true); + } +} + +void Connection::gotContactListGroupsProperties(PendingOperation *op) +{ + if (op->isError()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, false, + op->errorName(), op->errorMessage()); + return; + } + + debug() << "Got contact list groups properties"; + PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op); + + QVariantMap props = pvm->result(); + mPriv->contactManager->setContactListGroupsProperties(props); + + PendingContacts *pc = mPriv->contactManager->upgradeContacts( + mPriv->contactManager->allKnownContacts().toList(), + Contact::FeatureRosterGroups); + connect(pc, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListContactsUpgraded(Tp::PendingOperation*))); +} + +void Connection::onContactListContactsUpgraded(PendingOperation *op) +{ + if (op->isError()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, false, + op->errorName(), op->errorMessage()); + return; + } + + debug() << "Contact list groups ready"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, true); +} + +void Connection::onContactListStateChanged(uint state) +{ + if (mPriv->contactListState == state) { + // ignore redundant state changes + return; + } + + mPriv->contactListState = state; + + if (state == ContactListStateSuccess) { + mPriv->introspectContactListContacts(); + } else if (state == ContactListStateFailure) { + // Consider it ready here as the state may go from Failure to Success afterwards, in which + // case the contacts will appear in ContactManager. + mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, true); + } +} + +void Connection::onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes, + const Tp::UIntList &removals) +{ + mPriv->contactManager->updateContactListContacts(changes, removals); +} void Connection::gotContactListsHandles(PendingOperation *op) { @@ -1675,7 +1868,11 @@ void Connection::gotContactListsHandles(PendingOperation *op) ReferencedHandles handle = pending->handles(); uint type = ContactManager::ContactListChannel::typeForIdentifier( pending->namesRequested().first()); - Q_ASSERT(type != (uint) -1 && type < ContactManager::ContactListChannel::LastType); + if (hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) { + Q_ASSERT(type == ContactManager::ContactListChannel::TypeDeny); + } else { + Q_ASSERT(type != (uint) -1 && type < ContactManager::ContactListChannel::LastType); + } mPriv->contactListChannels[type].handle = handle; request[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")] = handle[0]; connect(lowlevel()->ensureChannel(request), @@ -1696,7 +1893,8 @@ void Connection::gotContactListChannel(PendingOperation *op) Q_ASSERT(channel); Q_ASSERT(handle); for (int i = 0; i < ContactManager::ContactListChannel::LastType; ++i) { - if (mPriv->contactListChannels[i].handle.size() > 0 && + if (mPriv->contactListChannels.contains(i) && + mPriv->contactListChannels[i].handle.size() > 0 && mPriv->contactListChannels[i].handle[0] == handle) { Q_ASSERT(!mPriv->contactListChannels[i].channel); mPriv->contactListChannels[i].channel = channel; @@ -1709,8 +1907,10 @@ void Connection::gotContactListChannel(PendingOperation *op) void Connection::contactListChannelReady() { - if (++mPriv->contactListChannelsReady == - ContactManager::ContactListChannel::LastType) { + if (hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) { + mPriv->contactManager->setContactListChannels(mPriv->contactListChannels); + mPriv->introspectContactList(); + } else if (++mPriv->contactListChannelsReady == ContactManager::ContactListChannel::LastType) { debug() << "FeatureRoster ready"; mPriv->contactManager->setContactListChannels(mPriv->contactListChannels); mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, true); @@ -1753,7 +1953,7 @@ void Connection::onContactListGroupChannelReady(Tp::PendingOperation *op) } else { PendingReady *pr = qobject_cast<PendingReady*>(op); ChannelPtr channel = ChannelPtr::qObjectCast(pr->proxy()); - mPriv->contactManager->addContactListGroupChannel(channel); + mPriv->contactManager->addContactListGroupChannelFallback(channel); mPriv->contactListGroupChannels.removeOne(channel); } } diff --git a/TelepathyQt4/connection.h b/TelepathyQt4/connection.h index 531d3c2f..075feb52 100644 --- a/TelepathyQt4/connection.h +++ b/TelepathyQt4/connection.h @@ -1,8 +1,8 @@ /* * This file is part of TelepathyQt4 * - * Copyright (C) 2008, 2009 Collabora Ltd. <http://www.collabora.co.uk/> - * Copyright (C) 2008, 2009 Nokia Corporation + * Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 2008-2010 Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -196,6 +196,13 @@ private Q_SLOTS: void gotContactAttributeInterfaces(QDBusPendingCallWatcher *watcher); void gotSimpleStatuses(QDBusPendingCallWatcher *watcher); void gotSelfContact(Tp::PendingOperation *op); + void gotContactListProperties(Tp::PendingOperation *op); + void gotContactListContacts(QDBusPendingCallWatcher *watcher); + void gotContactListGroupsProperties(Tp::PendingOperation *op); + void onContactListContactsUpgraded(Tp::PendingOperation *op); + void onContactListStateChanged(uint state); + void onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes, + const Tp::UIntList &removals); void gotContactListsHandles(Tp::PendingOperation *op); void gotContactListChannel(Tp::PendingOperation *op); void contactListChannelReady(); diff --git a/TelepathyQt4/contact-manager.cpp b/TelepathyQt4/contact-manager.cpp index 21e47cdf..d49d9415 100644 --- a/TelepathyQt4/contact-manager.cpp +++ b/TelepathyQt4/contact-manager.cpp @@ -59,19 +59,47 @@ struct TELEPATHY_QT4_NO_EXPORT ContactManager::Private void ensureTracking(const Feature &feature); // roster specific methods - Contacts allKnownContacts() const; - void computeKnownContactsChanges(const Contacts &added, + void processContactListChanges(); + void processContactListUpdates(); + void processContactListGroupsUpdates(); + void processContactListGroupsCreated(); + void processContactListGroupRenamed(); + void processContactListGroupsRemoved(); + + Contacts allKnownContactsFallback() const; + void computeKnownContactsChangesFallback(const Contacts &added, const Contacts &pendingAdded, const Contacts &remotePendingAdded, const Contacts &removed, const Channel::GroupMemberChangeDetails &details); - void updateContactsPresenceState(); + void updateContactsBlockState(); + void updateContactsPresenceStateFallback(); + PendingOperation *requestPresenceSubscriptionFallback( + const QList<ContactPtr> &contacts, const QString &message); + PendingOperation *removePresenceSubscriptionFallback( + const QList<ContactPtr> &contacts, const QString &message); + PendingOperation *authorizePresencePublicationFallback( + const QList<ContactPtr> &contacts, const QString &message); + PendingOperation *removePresencePublicationFallback( + const QList<ContactPtr> &contacts, const QString &message); + PendingOperation *removeContactsFallback( + const QList<ContactPtr> &contacts, const QString &message); // roster group specific methods - QString addContactListGroupChannel(const ChannelPtr &contactListGroupChannel); + QString addContactListGroupChannelFallback(const ChannelPtr &contactListGroupChannel); + PendingOperation *addGroupFallback(const QString &group); + PendingOperation *removeGroupFallback(const QString &group); + PendingOperation *addContactsToGroupFallback(const QString &group, + const QList<ContactPtr> &contacts); + PendingOperation *removeContactsFromGroupFallback(const QString &group, + const QList<ContactPtr> &contacts); // avatar specific methods bool buildAvatarFileName(QString token, bool createDir, QString &avatarFileName, QString &mimeTypeFileName); + struct ContactListUpdateInfo; + struct ContactListGroupsUpdateInfo; + struct ContactListGroupRenamedInfo; + ContactManager *parent; QWeakPointer<Connection> connection; @@ -81,7 +109,23 @@ struct TELEPATHY_QT4_NO_EXPORT ContactManager::Private Features supportedFeatures; // roster + bool fallbackContactList; Contacts cachedAllKnownContacts; + + // new roster API + bool canChangeContactList; + bool contactListRequestUsesMessage; + QSet<QString> allKnownGroups; + bool contactListGroupPropertiesReceived; + QQueue<void (Private::*)()> contactListChangesQueue; + QQueue<ContactListUpdateInfo> contactListUpdatesQueue; + QQueue<ContactListGroupsUpdateInfo> contactListGroupsUpdatesQueue; + QQueue<QStringList> contactListGroupsCreatedQueue; + QQueue<ContactListGroupRenamedInfo> contactListGroupRenamedQueue; + QQueue<QStringList> contactListGroupsRemovedQueue; + bool processingContactListChanges; + + // old roster API QMap<uint, ContactListChannel> contactListChannels; ChannelPtr subscribeChannel; ChannelPtr publishChannel; @@ -94,9 +138,53 @@ struct TELEPATHY_QT4_NO_EXPORT ContactManager::Private bool requestAvatarsIdle; }; +struct ContactManager::Private::ContactListUpdateInfo +{ + ContactListUpdateInfo(const ContactSubscriptionMap &changes, const UIntList &removals) + : changes(changes), + removals(removals) + { + } + + ContactSubscriptionMap changes; + UIntList removals; +}; + +struct ContactManager::Private::ContactListGroupsUpdateInfo +{ + ContactListGroupsUpdateInfo(const UIntList &contacts, + const QStringList &groupsAdded, const QStringList &groupsRemoved) + : contacts(contacts), + groupsAdded(groupsAdded), + groupsRemoved(groupsRemoved) + { + } + + UIntList contacts; + QStringList groupsAdded; + QStringList groupsRemoved; +}; + +struct ContactManager::Private::ContactListGroupRenamedInfo +{ + ContactListGroupRenamedInfo(const QString &oldName, const QString &newName) + : oldName(oldName), + newName(newName) + { + } + + QString oldName; + QString newName; +}; + ContactManager::Private::Private(ContactManager *parent, Connection *connection) : parent(parent), connection(connection), + fallbackContactList(false), + canChangeContactList(false), + contactListRequestUsesMessage(false), + contactListGroupPropertiesReceived(false), + processingContactListChanges(false), requestAvatarsIdle(false) { } @@ -165,6 +253,9 @@ void ContactManager::Private::ensureTracking(const Feature &feature) simplePresenceInterface, SIGNAL(PresencesChanged(Tp::SimpleContactPresences)), SLOT(onPresencesChanged(Tp::SimpleContactPresences))); + } else if (feature == Contact::FeatureRosterGroups) { + // nothing to do here, but we don't want to warn + ; } else { warning() << " Unknown feature" << feature << "when trying to figure out how to connect change notification!"; @@ -173,7 +264,118 @@ void ContactManager::Private::ensureTracking(const Feature &feature) tracking[feature] = true; } -Contacts ContactManager::Private::allKnownContacts() const + +void ContactManager::Private::processContactListChanges() +{ + if (processingContactListChanges || contactListChangesQueue.isEmpty()) { + return; + } + + processingContactListChanges = true; + (this->*(contactListChangesQueue.dequeue()))(); +} + +void ContactManager::Private::processContactListUpdates() +{ + ContactListUpdateInfo 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 (parent->connection()->isReady(Connection::FeatureRosterGroups)) { + features << Contact::FeatureRosterGroups; + } + PendingContacts *pc = parent->contactsForHandles(contacts, features); + parent->connect(pc, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListNewContactsConstructed(Tp::PendingOperation*))); +} + +void ContactManager::Private::processContactListGroupsUpdates() +{ + ContactListGroupsUpdateInfo info = contactListGroupsUpdatesQueue.dequeue(); + + foreach (const QString &group, info.groupsAdded) { + Contacts contacts; + foreach (uint bareHandle, info.contacts) { + ContactPtr contact = parent->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 parent->groupMembersChanged(group, contacts, + Contacts(), Channel::GroupMemberChangeDetails()); + } + + foreach (const QString &group, info.groupsRemoved) { + Contacts contacts; + foreach (uint bareHandle, info.contacts) { + ContactPtr contact = parent->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 parent->groupMembersChanged(group, Contacts(), + contacts, Channel::GroupMemberChangeDetails()); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Private::processContactListGroupsCreated() +{ + QStringList names = contactListGroupsCreatedQueue.dequeue(); + foreach (const QString &name, names) { + allKnownGroups.insert(name); + emit parent->groupAdded(name); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Private::processContactListGroupRenamed() +{ + Private::ContactListGroupRenamedInfo info = contactListGroupRenamedQueue.dequeue(); + allKnownGroups.remove(info.oldName); + allKnownGroups.insert(info.newName); + emit parent->groupRenamed(info.oldName, info.newName); + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Private::processContactListGroupsRemoved() +{ + QStringList names = contactListGroupsRemovedQueue.dequeue(); + foreach (const QString &name, names) { + allKnownGroups.remove(name); + emit parent->groupRemoved(name); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +Contacts ContactManager::Private::allKnownContactsFallback() const { Contacts contacts; foreach (const ContactListChannel &contactListChannel, contactListChannels) { @@ -188,7 +390,7 @@ Contacts ContactManager::Private::allKnownContacts() const return contacts; } -void ContactManager::Private::computeKnownContactsChanges(const Tp::Contacts& added, +void ContactManager::Private::computeKnownContactsChangesFallback(const Tp::Contacts& added, const Tp::Contacts& pendingAdded, const Tp::Contacts& remotePendingAdded, const Tp::Contacts& removed, const Channel::GroupMemberChangeDetails &details) { @@ -221,8 +423,28 @@ void ContactManager::Private::computeKnownContactsChanges(const Tp::Contacts& ad } } -void ContactManager::Private::updateContactsPresenceState() +void ContactManager::Private::updateContactsBlockState() +{ + if (!denyChannel) { + return; + } + + Contacts denyContacts; + if (denyChannel) { + denyContacts = denyChannel->groupContacts(); + } + + foreach (ContactPtr contact, denyContacts) { + contact->setBlocked(true); + } +} + +void ContactManager::Private::updateContactsPresenceStateFallback() { + if (!subscribeChannel && !publishChannel) { + return; + } + Contacts subscribeContacts; Contacts subscribeContactsRP; @@ -238,48 +460,117 @@ void ContactManager::Private::updateContactsPresenceState() publishContactsLP = publishChannel->groupLocalPendingContacts(); } - Contacts denyContacts; - if (denyChannel) { - denyContacts = denyChannel->groupContacts(); - } - - if (!subscribeChannel && !publishChannel && !denyChannel) { - return; - } - - Contacts contacts = allKnownContacts(); + Contacts contacts = allKnownContactsFallback(); foreach (ContactPtr contact, contacts) { if (subscribeChannel) { // not in "subscribe" -> No, in "subscribe" lp -> Ask, in "subscribe" current -> Yes if (subscribeContacts.contains(contact)) { - contact->setSubscriptionState(Contact::PresenceStateYes); + contact->setSubscriptionState(SubscriptionStateYes); } else if (subscribeContactsRP.contains(contact)) { - contact->setSubscriptionState(Contact::PresenceStateAsk); + contact->setSubscriptionState(SubscriptionStateAsk); } else { - contact->setSubscriptionState(Contact::PresenceStateNo); + contact->setSubscriptionState(SubscriptionStateNo); } } if (publishChannel) { // not in "publish" -> No, in "subscribe" rp -> Ask, in "publish" current -> Yes if (publishContacts.contains(contact)) { - contact->setPublishState(Contact::PresenceStateYes); + contact->setPublishState(SubscriptionStateYes); } else if (publishContactsLP.contains(contact)) { - contact->setPublishState(Contact::PresenceStateAsk); + contact->setPublishState(SubscriptionStateAsk, + publishChannel->groupLocalPendingContactChangeInfo(contact).message()); } else { - contact->setPublishState(Contact::PresenceStateNo); + contact->setPublishState(SubscriptionStateNo); } } + } +} - if (denyChannel) { - if (denyContacts.contains(contact)) { - contact->setBlocked(true); - } - } +PendingOperation *ContactManager::Private::requestPresenceSubscriptionFallback( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!subscribeChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot subscribe to contacts' presence on this protocol"), + parent->connection()); + } + + return subscribeChannel->groupAddContacts(contacts, message); +} + +PendingOperation *ContactManager::Private::removePresenceSubscriptionFallback( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!subscribeChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot subscribe to contacts' presence on this protocol"), + parent->connection()); + } + + return subscribeChannel->groupRemoveContacts(contacts, message); +} + +PendingOperation *ContactManager::Private::authorizePresencePublicationFallback( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!publishChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot control publication of presence on this protocol"), + parent->connection()); } + + return publishChannel->groupAddContacts(contacts, message); } -QString ContactManager::Private::addContactListGroupChannel( +PendingOperation *ContactManager::Private::removePresencePublicationFallback( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!publishChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot control publication of presence on this protocol"), + parent->connection()); + } + + return publishChannel->groupRemoveContacts(contacts, message); +} + +PendingOperation *ContactManager::Private::removeContactsFallback( + const QList<ContactPtr> &contacts, const QString &message) +{ + /* If the CM implements stored channel correctly, it should have the + * wanted behaviour. Otherwise we have to fallback 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 (parent->canRemovePresenceSubscription()) { + debug() << "Removing contacts from subscribe list"; + operations << parent->removePresenceSubscription(contacts, message); + } + + if (parent->canRemovePresencePublication()) { + debug() << "Removing contacts from publish list"; + operations << parent->removePresencePublication(contacts, message); + } + + if (operations.isEmpty()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot remove contacts on this protocol"), + parent->connection()); + } + + return new PendingComposite(operations, parent->connection()); +} + +QString ContactManager::Private::addContactListGroupChannelFallback( const ChannelPtr &contactListGroupChannel) { QString id = contactListGroupChannel->immutableProperties().value( @@ -292,7 +583,7 @@ QString ContactManager::Private::addContactListGroupChannel( Tp::Contacts, Tp::Contacts, Tp::Channel::GroupMemberChangeDetails)), - SLOT(onContactListGroupMembersChanged( + SLOT(onContactListGroupMembersChangedFallback( Tp::Contacts, Tp::Contacts, Tp::Contacts, @@ -300,7 +591,7 @@ QString ContactManager::Private::addContactListGroupChannel( Tp::Channel::GroupMemberChangeDetails))); parent->connect(contactListGroupChannel.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), - SLOT(onContactListGroupRemoved(Tp::DBusProxy*,QString,QString))); + SLOT(onContactListGroupRemovedFallback(Tp::DBusProxy*,QString,QString))); foreach (const ContactPtr &contact, contactListGroupChannel->groupContacts()) { contact->setAddedToGroup(id); @@ -308,6 +599,58 @@ QString ContactManager::Private::addContactListGroupChannel( return id; } +PendingOperation *ContactManager::Private::addGroupFallback(const QString &group) +{ + 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 parent->connection()->lowlevel()->ensureChannel(request); +} + +PendingOperation *ContactManager::Private::removeGroupFallback(const QString &group) +{ + if (!contactListGroupChannels.contains(group)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid group"), + parent->connection()); + } + + ChannelPtr channel = contactListGroupChannels[group]; + PendingContactManagerRemoveContactListGroup *op = + new PendingContactManagerRemoveContactListGroup(channel); + return op; +} + +PendingOperation *ContactManager::Private::addContactsToGroupFallback(const QString &group, + const QList<ContactPtr> &contacts) +{ + if (!contactListGroupChannels.contains(group)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid group"), + parent->connection()); + } + + ChannelPtr channel = contactListGroupChannels[group]; + return channel->groupAddContacts(contacts); +} + +PendingOperation *ContactManager::Private::removeContactsFromGroupFallback(const QString &group, + const QList<ContactPtr> &contacts) +{ + if (!contactListGroupChannels.contains(group)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid group"), + parent->connection()); + } + + ChannelPtr channel = contactListGroupChannels[group]; + return channel->groupRemoveContacts(contacts); +} + bool ContactManager::Private::buildAvatarFileName(QString token, bool createDir, QString &avatarFileName, QString &mimeTypeFileName) { @@ -330,34 +673,6 @@ bool ContactManager::Private::buildAvatarFileName(QString token, bool createDir, return true; } -namespace -{ - -QString featureToInterface(const Feature &feature) -{ - if (feature == Contact::FeatureAlias) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_ALIASING); - } else if (feature == Contact::FeatureAvatarToken) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS); - } else if (feature == Contact::FeatureAvatarData) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS); - } else if (feature == Contact::FeatureSimplePresence) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE); - } else if (feature == Contact::FeatureCapabilities) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES); - } if (feature == Contact::FeatureLocation) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_LOCATION); - } if (feature == Contact::FeatureInfo) { - return QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACT_INFO); - } else { - warning() << "ContactManager doesn't know which interface corresponds to feature" - << feature; - return QString(); - } -} - -} - ContactManager::ContactManager(Connection *connection) : Object(), mPriv(new Private(this, connection)) @@ -419,7 +734,15 @@ Features ContactManager::supportedFeatures() const */ Contacts ContactManager::allKnownContacts() const { - return mPriv->allKnownContacts(); + if (!connection()->isReady(Connection::FeatureRoster)) { + return Contacts(); + } + + if (mPriv->fallbackContactList) { + return mPriv->allKnownContactsFallback(); + } + + return mPriv->cachedAllKnownContacts; } /** @@ -431,7 +754,15 @@ Contacts ContactManager::allKnownContacts() const */ QStringList ContactManager::allKnownGroups() const { - return mPriv->contactListGroupChannels.keys(); + if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return QStringList(); + } + + if (mPriv->fallbackContactList) { + return mPriv->contactListGroupChannels.keys(); + } + + return mPriv->allKnownGroups.toList(); } /** @@ -469,14 +800,20 @@ PendingOperation *ContactManager::addGroup(const QString &group) connection()); } - 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 connection()->lowlevel()->ensureChannel(request); + if (mPriv->fallbackContactList) { + return mPriv->addGroupFallback(group); + } + + if (!connection()->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), + connection()); + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->AddToGroup(group, UIntList()), connection()); } /** @@ -508,16 +845,20 @@ PendingOperation *ContactManager::removeGroup(const QString &group) connection()); } - if (!mPriv->contactListGroupChannels.contains(group)) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), - QLatin1String("Invalid group"), + if (mPriv->fallbackContactList) { + return mPriv->removeGroupFallback(group); + } + + if (!connection()->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), connection()); } - ChannelPtr channel = mPriv->contactListGroupChannels[group]; - PendingContactManagerRemoveContactListGroup *op = - new PendingContactManagerRemoveContactListGroup(channel); - return op; + Client::ConnectionInterfaceContactGroupsInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->RemoveGroup(group), connection()); } /** @@ -533,12 +874,25 @@ PendingOperation *ContactManager::removeGroup(const QString &group) */ Contacts ContactManager::groupContacts(const QString &group) const { - if (!mPriv->contactListGroupChannels.contains(group)) { + if (!connection()->isReady(Connection::FeatureRosterGroups)) { return Contacts(); } - ChannelPtr channel = mPriv->contactListGroupChannels[group]; - return channel->groupContacts(); + if (mPriv->fallbackContactList) { + if (!mPriv->contactListGroupChannels.contains(group)) { + return Contacts(); + } + + ChannelPtr channel = mPriv->contactListGroupChannels[group]; + return channel->groupContacts(); + } + + Contacts ret; + foreach (const ContactPtr &contact, allKnownContacts()) { + if (contact->groups().contains(group)) + ret << contact; + } + return ret; } /** @@ -565,14 +919,25 @@ PendingOperation *ContactManager::addContactsToGroup(const QString &group, connection()); } - if (!mPriv->contactListGroupChannels.contains(group)) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), - QLatin1String("Invalid group"), + if (mPriv->fallbackContactList) { + return mPriv->addContactsToGroupFallback(group, contacts); + } + + if (!connection()->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), connection()); } - ChannelPtr channel = mPriv->contactListGroupChannels[group]; - return channel->groupAddContacts(contacts); + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->AddToGroup(group, handles), connection()); } /** @@ -599,14 +964,25 @@ PendingOperation *ContactManager::removeContactsFromGroup(const QString &group, connection()); } - if (!mPriv->contactListGroupChannels.contains(group)) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), - QLatin1String("Invalid group"), + if (mPriv->fallbackContactList) { + return mPriv->removeContactsFromGroupFallback(group, contacts); + } + + if (!connection()->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), connection()); } - ChannelPtr channel = mPriv->contactListGroupChannels[group]; - return channel->groupRemoveContacts(contacts); + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->RemoveFromGroup(group, handles), connection()); } /** @@ -624,8 +1000,16 @@ PendingOperation *ContactManager::removeContactsFromGroup(const QString &group, */ bool ContactManager::canRequestPresenceSubscription() const { - return mPriv->subscribeChannel && - mPriv->subscribeChannel->groupCanAddContacts(); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + mPriv->subscribeChannel->groupCanAddContacts(); + } + + return mPriv->canChangeContactList; } /** @@ -641,9 +1025,17 @@ bool ContactManager::canRequestPresenceSubscription() const */ bool ContactManager::subscriptionRequestHasMessage() const { - return mPriv->subscribeChannel && - (mPriv->subscribeChannel->groupFlags() & - ChannelGroupFlagMessageAdd); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + (mPriv->subscribeChannel->groupFlags() & + ChannelGroupFlagMessageAdd); + } + + return mPriv->contactListRequestUsesMessage; } /** @@ -683,13 +1075,19 @@ PendingOperation *ContactManager::requestPresenceSubscription( connection()); } - if (!mPriv->subscribeChannel) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), - QLatin1String("Cannot subscribe to contacts' presence on this protocol"), - connection()); + if (mPriv->fallbackContactList) { + return mPriv->requestPresenceSubscriptionFallback(contacts, message); } - return mPriv->subscribeChannel->groupAddContacts(contacts, message); + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactListInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->RequestSubscription(handles, message), connection()); } /** @@ -702,8 +1100,16 @@ PendingOperation *ContactManager::requestPresenceSubscription( */ bool ContactManager::canRemovePresenceSubscription() const { - return mPriv->subscribeChannel && - mPriv->subscribeChannel->groupCanRemoveContacts(); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + mPriv->subscribeChannel->groupCanRemoveContacts(); + } + + return mPriv->canChangeContactList; } /** @@ -720,9 +1126,17 @@ bool ContactManager::canRemovePresenceSubscription() const */ bool ContactManager::subscriptionRemovalHasMessage() const { - return mPriv->subscribeChannel && - (mPriv->subscribeChannel->groupFlags() & - ChannelGroupFlagMessageRemove); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + (mPriv->subscribeChannel->groupFlags() & + ChannelGroupFlagMessageRemove); + } + + return false; } /** @@ -735,8 +1149,16 @@ bool ContactManager::subscriptionRemovalHasMessage() const */ bool ContactManager::canRescindPresenceSubscriptionRequest() const { - return mPriv->subscribeChannel && - mPriv->subscribeChannel->groupCanRescindContacts(); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + mPriv->subscribeChannel->groupCanRescindContacts(); + } + + return mPriv->canChangeContactList; } /** @@ -753,9 +1175,17 @@ bool ContactManager::canRescindPresenceSubscriptionRequest() const */ bool ContactManager::subscriptionRescindingHasMessage() const { - return mPriv->subscribeChannel && - (mPriv->subscribeChannel->groupFlags() & - ChannelGroupFlagMessageRescind); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + (mPriv->subscribeChannel->groupFlags() & + ChannelGroupFlagMessageRescind); + } + + return false; } /** @@ -783,13 +1213,19 @@ PendingOperation *ContactManager::removePresenceSubscription( connection()); } - if (!mPriv->subscribeChannel) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), - QLatin1String("Cannot subscribe to contacts' presence on this protocol"), - connection()); + if (mPriv->fallbackContactList) { + return mPriv->removePresenceSubscriptionFallback(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; } - return mPriv->subscribeChannel->groupRemoveContacts(contacts, message); + Client::ConnectionInterfaceContactListInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->Unsubscribe(handles), connection()); } /** @@ -802,10 +1238,18 @@ PendingOperation *ContactManager::removePresenceSubscription( */ bool ContactManager::canAuthorizePresencePublication() const { - // 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) mPriv->publishChannel; + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + // 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) mPriv->publishChannel; + } + + return mPriv->canChangeContactList; } /** @@ -822,9 +1266,17 @@ bool ContactManager::canAuthorizePresencePublication() const */ bool ContactManager::publicationAuthorizationHasMessage() const { - return mPriv->subscribeChannel && - (mPriv->subscribeChannel->groupFlags() & - ChannelGroupFlagMessageAccept); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + (mPriv->subscribeChannel->groupFlags() & + ChannelGroupFlagMessageAccept); + } + + return false; } /** @@ -853,13 +1305,19 @@ PendingOperation *ContactManager::authorizePresencePublication( connection()); } - if (!mPriv->publishChannel) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), - QLatin1String("Cannot control publication of presence on this protocol"), - connection()); + if (mPriv->fallbackContactList) { + return mPriv->authorizePresencePublicationFallback(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; } - return mPriv->publishChannel->groupAddContacts(contacts, message); + Client::ConnectionInterfaceContactListInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->AuthorizePublication(handles), connection()); } /** @@ -876,9 +1334,17 @@ PendingOperation *ContactManager::authorizePresencePublication( */ bool ContactManager::publicationRejectionHasMessage() const { - return mPriv->subscribeChannel && - (mPriv->subscribeChannel->groupFlags() & - ChannelGroupFlagMessageReject); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + (mPriv->subscribeChannel->groupFlags() & + ChannelGroupFlagMessageReject); + } + + return false; } /** @@ -893,8 +1359,16 @@ bool ContactManager::publicationRejectionHasMessage() const */ bool ContactManager::canRemovePresencePublication() const { - return mPriv->publishChannel && - mPriv->publishChannel->groupCanRemoveContacts(); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->publishChannel && + mPriv->publishChannel->groupCanRemoveContacts(); + } + + return mPriv->canChangeContactList; } /** @@ -911,9 +1385,17 @@ bool ContactManager::canRemovePresencePublication() const */ bool ContactManager::publicationRemovalHasMessage() const { - return mPriv->subscribeChannel && - (mPriv->subscribeChannel->groupFlags() & - ChannelGroupFlagMessageRemove); + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + if (mPriv->fallbackContactList) { + return mPriv->subscribeChannel && + (mPriv->subscribeChannel->groupFlags() & + ChannelGroupFlagMessageRemove); + } + + return false; } /** @@ -948,13 +1430,19 @@ PendingOperation *ContactManager::removePresencePublication( connection()); } - if (!mPriv->publishChannel) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), - QLatin1String("Cannot control publication of presence on this protocol"), - connection()); + if (mPriv->fallbackContactList) { + return mPriv->removePresencePublicationFallback(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; } - return mPriv->publishChannel->groupRemoveContacts(contacts, message); + Client::ConnectionInterfaceContactListInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->Unpublish(handles), connection()); } /** @@ -981,36 +1469,19 @@ PendingOperation *ContactManager::removeContacts( connection()); } - /* If the CM implements stored channel correctly, it should have the - * wanted behaviour. Otherwise we have to fallback to remove from publish - * and subscribe channels. - */ - - if (mPriv->storedChannel && - mPriv->storedChannel->groupCanRemoveContacts()) { - debug() << "Removing contacts from stored list"; - return mPriv->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 (mPriv->fallbackContactList) { + return mPriv->removeContactsFallback(contacts, message); } - if (operations.isEmpty()) { - return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), - QLatin1String("Cannot remove contacts on this protocol"), - connection()); + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; } - return new PendingComposite(operations, connection()); + Client::ConnectionInterfaceContactListInterface *iface = + connection()->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return new PendingVoid(iface->RemoveContacts(handles), connection()); } /** @@ -1020,6 +1491,10 @@ PendingOperation *ContactManager::removeContacts( */ bool ContactManager::canBlockContacts() const { + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + return (bool) mPriv->denyChannel; } @@ -1334,7 +1809,132 @@ void ContactManager::onContactInfoChanged(uint handle, } } -void ContactManager::onStoredChannelMembersChanged( +void ContactManager::onContactListNewContactsConstructed(Tp::PendingOperation *op) +{ + if (op->isError()) { + mPriv->contactListUpdatesQueue.dequeue(); + mPriv->processingContactListChanges = false; + mPriv->processContactListChanges(); + return; + } + + Private::ContactListUpdateInfo info = mPriv->contactListUpdatesQueue.dequeue(); + + Tp::Contacts added; + Tp::Contacts removed; + + 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 = lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "Unable to construct contact for handle" << bareHandle; + continue; + } + + if (!mPriv->cachedAllKnownContacts.contains(contact)) { + mPriv->cachedAllKnownContacts.insert(contact); + added << contact; + } + + contact->setSubscriptionState((SubscriptionState) subscriptions.subscribe); + if (!subscriptions.publishRequest.isEmpty() && + subscriptions.publish == SubscriptionStateAsk) { + Channel::GroupMemberChangeDetails publishRequestDetails; + QVariantMap detailsMap; + detailsMap.insert(QLatin1String("message"), subscriptions.publishRequest); + publishRequestDetails = Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap); + // FIXME (API/ABI break) remove signal with details + emit presencePublicationRequested(Contacts() << contact, publishRequestDetails); + + emit presencePublicationRequested(Contacts() << contact, subscriptions.publishRequest); + } + contact->setPublishState((SubscriptionState) subscriptions.publish, + subscriptions.publishRequest); + } + + foreach (uint bareHandle, info.removals) { + ContactPtr contact = lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "Unable to find removed contact with handle" << bareHandle; + continue; + } + + Q_ASSERT(mPriv->cachedAllKnownContacts.contains(contact)); + + contact->setSubscriptionState(SubscriptionStateNo); + contact->setPublishState(SubscriptionStateNo); + mPriv->cachedAllKnownContacts.remove(contact); + removed << contact; + } + + if (!added.isEmpty() || !removed.isEmpty()) { + emit allKnownContactsChanged(added, removed, Channel::GroupMemberChangeDetails()); + } + + mPriv->processingContactListChanges = false; + mPriv->processContactListChanges(); +} + +void ContactManager::onContactListGroupsChanged(const Tp::UIntList &contacts, + const QStringList &added, const QStringList &removed) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + if (!mPriv->contactListGroupPropertiesReceived) { + return; + } + + mPriv->contactListGroupsUpdatesQueue.enqueue(Private::ContactListGroupsUpdateInfo(contacts, + added, removed)); + mPriv->contactListChangesQueue.enqueue(&Private::processContactListGroupsUpdates); + mPriv->processContactListChanges(); +} + +void ContactManager::onContactListGroupsCreated(const QStringList &names) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + if (!mPriv->contactListGroupPropertiesReceived) { + return; + } + + mPriv->contactListGroupsCreatedQueue.enqueue(names); + mPriv->contactListChangesQueue.enqueue(&Private::processContactListGroupsCreated); + mPriv->processContactListChanges(); +} + +void ContactManager::onContactListGroupRenamed(const QString &oldName, const QString &newName) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + if (!mPriv->contactListGroupPropertiesReceived) { + return; + } + + mPriv->contactListGroupRenamedQueue.enqueue( + Private::ContactListGroupRenamedInfo(oldName, newName)); + mPriv->contactListChangesQueue.enqueue(&Private::processContactListGroupRenamed); + mPriv->processContactListChanges(); +} + +void ContactManager::onContactListGroupsRemoved(const QStringList &names) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + if (!mPriv->contactListGroupPropertiesReceived) { + return; + } + + mPriv->contactListGroupsRemovedQueue.enqueue(names); + mPriv->contactListChangesQueue.enqueue(&Private::processContactListGroupsRemoved); + mPriv->processContactListChanges(); +} + +void ContactManager::onStoredChannelMembersChangedFallback( const Contacts &groupMembersAdded, const Contacts &groupLocalPendingMembersAdded, const Contacts &groupRemotePendingMembersAdded, @@ -1358,13 +1958,12 @@ void ContactManager::onStoredChannelMembersChanged( } // Perform the needed computation for allKnownContactsChanged - mPriv->computeKnownContactsChanges(groupMembersAdded, - groupLocalPendingMembersAdded, - groupRemotePendingMembersAdded, - groupMembersRemoved, details); + mPriv->computeKnownContactsChangesFallback(groupMembersAdded, + groupLocalPendingMembersAdded, groupRemotePendingMembersAdded, + groupMembersRemoved, details); } -void ContactManager::onSubscribeChannelMembersChanged( +void ContactManager::onSubscribeChannelMembersChangedFallback( const Contacts &groupMembersAdded, const Contacts &groupLocalPendingMembersAdded, const Contacts &groupRemotePendingMembersAdded, @@ -1377,27 +1976,26 @@ void ContactManager::onSubscribeChannelMembersChanged( foreach (ContactPtr contact, groupMembersAdded) { debug() << "Contact" << contact->id() << "on subscribe list"; - contact->setSubscriptionState(Contact::PresenceStateYes, details); + contact->setSubscriptionState(SubscriptionStateYes); } foreach (ContactPtr contact, groupRemotePendingMembersAdded) { debug() << "Contact" << contact->id() << "added to subscribe list"; - contact->setSubscriptionState(Contact::PresenceStateAsk, details); + contact->setSubscriptionState(SubscriptionStateAsk); } foreach (ContactPtr contact, groupMembersRemoved) { debug() << "Contact" << contact->id() << "removed from subscribe list"; - contact->setSubscriptionState(Contact::PresenceStateNo, details); + contact->setSubscriptionState(SubscriptionStateNo); } // Perform the needed computation for allKnownContactsChanged - mPriv->computeKnownContactsChanges(groupMembersAdded, - groupLocalPendingMembersAdded, - groupRemotePendingMembersAdded, - groupMembersRemoved, details); + mPriv->computeKnownContactsChangesFallback(groupMembersAdded, + groupLocalPendingMembersAdded, groupRemotePendingMembersAdded, + groupMembersRemoved, details); } -void ContactManager::onPublishChannelMembersChanged( +void ContactManager::onPublishChannelMembersChangedFallback( const Contacts &groupMembersAdded, const Contacts &groupLocalPendingMembersAdded, const Contacts &groupRemotePendingMembersAdded, @@ -1410,29 +2008,32 @@ void ContactManager::onPublishChannelMembersChanged( foreach (ContactPtr contact, groupMembersAdded) { debug() << "Contact" << contact->id() << "on publish list"; - contact->setPublishState(Contact::PresenceStateYes, details); + contact->setPublishState(SubscriptionStateYes); } foreach (ContactPtr contact, groupLocalPendingMembersAdded) { debug() << "Contact" << contact->id() << "added to publish list"; - contact->setPublishState(Contact::PresenceStateAsk, details); + contact->setPublishState(SubscriptionStateAsk, details.message()); } foreach (ContactPtr contact, groupMembersRemoved) { debug() << "Contact" << contact->id() << "removed from publish list"; - contact->setPublishState(Contact::PresenceStateNo, details); + contact->setPublishState(SubscriptionStateNo); } if (!groupLocalPendingMembersAdded.isEmpty()) { + // FIXME (API/ABI break) remove signal with details emit presencePublicationRequested(groupLocalPendingMembersAdded, details); + + emit presencePublicationRequested(groupLocalPendingMembersAdded, + details.message()); } // Perform the needed computation for allKnownContactsChanged - mPriv->computeKnownContactsChanges(groupMembersAdded, - groupLocalPendingMembersAdded, - groupRemotePendingMembersAdded, - groupMembersRemoved, details); + mPriv->computeKnownContactsChangesFallback(groupMembersAdded, + groupLocalPendingMembersAdded, groupRemotePendingMembersAdded, + groupMembersRemoved, details); } void ContactManager::onDenyChannelMembersChanged( @@ -1452,16 +2053,16 @@ void ContactManager::onDenyChannelMembersChanged( foreach (ContactPtr contact, groupMembersAdded) { debug() << "Contact" << contact->id() << "added to deny list"; - contact->setBlocked(true, details); + contact->setBlocked(true); } foreach (ContactPtr contact, groupMembersRemoved) { debug() << "Contact" << contact->id() << "removed from deny list"; - contact->setBlocked(false, details); + contact->setBlocked(false); } } -void ContactManager::onContactListGroupMembersChanged( +void ContactManager::onContactListGroupMembersChangedFallback( const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded, const Tp::Contacts &groupRemotePendingMembersAdded, @@ -1483,7 +2084,7 @@ void ContactManager::onContactListGroupMembersChanged( emit groupMembersChanged(id, groupMembersAdded, groupMembersRemoved, details); } -void ContactManager::onContactListGroupRemoved(Tp::DBusProxy *proxy, +void ContactManager::onContactListGroupRemovedFallback(Tp::DBusProxy *proxy, const QString &errorName, const QString &errorMessage) { Q_UNUSED(errorName); @@ -1516,10 +2117,64 @@ ContactPtr ContactManager::ensureContact(const ReferencedHandles &handle, return contact; } +void ContactManager::setUseFallbackContactList(bool value) +{ + mPriv->fallbackContactList = value; +} + +void ContactManager::setContactListProperties(const QVariantMap &props) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + mPriv->canChangeContactList = qdbus_cast<uint>(props[QLatin1String("CanChangeContactList")]); + mPriv->contactListRequestUsesMessage = qdbus_cast<uint>(props[QLatin1String("RequestUsesMessage")]); +} + +void ContactManager::setContactListContacts(const ContactAttributesMap &attrsMap) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + 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 = ensureContact(ReferencedHandles(connection(), + HandleTypeContact, UIntList() << bareHandle), + Features(), attrs); + mPriv->cachedAllKnownContacts.insert(contact); + } +} + +void ContactManager::updateContactListContacts(const ContactSubscriptionMap &changes, + const UIntList &removals) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + + mPriv->contactListUpdatesQueue.enqueue(Private::ContactListUpdateInfo(changes, removals)); + mPriv->contactListChangesQueue.enqueue(&Private::processContactListUpdates); + mPriv->processContactListChanges(); +} + +void ContactManager::setContactListGroupsProperties(const QVariantMap &props) +{ + Q_ASSERT(mPriv->fallbackContactList == false); + Q_ASSERT(mPriv->contactListGroupPropertiesReceived == false); + + mPriv->allKnownGroups = qdbus_cast<QStringList>(props[QLatin1String("Groups")]).toSet(); + mPriv->contactListGroupPropertiesReceived = true; +} + void ContactManager::setContactListChannels( const QMap<uint, ContactListChannel> &contactListChannels) { - Q_ASSERT(mPriv->contactListChannels.isEmpty()); + if (!mPriv->fallbackContactList) { + Q_ASSERT(!contactListChannels.contains(ContactListChannel::TypeSubscribe)); + Q_ASSERT(!contactListChannels.contains(ContactListChannel::TypePublish)); + Q_ASSERT(!contactListChannels.contains(ContactListChannel::TypeStored)); + } + mPriv->contactListChannels = contactListChannels; if (mPriv->contactListChannels.contains(ContactListChannel::TypeSubscribe)) { @@ -1538,9 +2193,13 @@ void ContactManager::setContactListChannels( mPriv->denyChannel = mPriv->contactListChannels[ContactListChannel::TypeDeny].channel; } - mPriv->updateContactsPresenceState(); - // Refresh the cache for the current known contacts - mPriv->cachedAllKnownContacts = allKnownContacts(); + mPriv->updateContactsBlockState(); + + if (mPriv->fallbackContactList) { + mPriv->updateContactsPresenceStateFallback(); + // Refresh the cache for the current known contacts + mPriv->cachedAllKnownContacts = allKnownContacts(); + } uint type; ChannelPtr channel; @@ -1554,21 +2213,21 @@ void ContactManager::setContactListChannels( } if (type == ContactListChannel::TypeStored) { - method = SLOT(onStoredChannelMembersChanged( + method = SLOT(onStoredChannelMembersChangedFallback( Tp::Contacts, Tp::Contacts, Tp::Contacts, Tp::Contacts, Tp::Channel::GroupMemberChangeDetails)); }else if (type == ContactListChannel::TypeSubscribe) { - method = SLOT(onSubscribeChannelMembersChanged( + method = SLOT(onSubscribeChannelMembersChangedFallback( Tp::Contacts, Tp::Contacts, Tp::Contacts, Tp::Contacts, Tp::Channel::GroupMemberChangeDetails)); } else if (type == ContactListChannel::TypePublish) { - method = SLOT(onPublishChannelMembersChanged( + method = SLOT(onPublishChannelMembersChangedFallback( Tp::Contacts, Tp::Contacts, Tp::Contacts, @@ -1596,23 +2255,52 @@ void ContactManager::setContactListChannels( } } -void ContactManager::setContactListGroupChannels( +void ContactManager::setContactListGroupChannelsFallback( const QList<ChannelPtr> &contactListGroupChannels) { + Q_ASSERT(mPriv->fallbackContactList == true); + Q_ASSERT(mPriv->contactListGroupChannels.isEmpty()); foreach (const ChannelPtr &contactListGroupChannel, contactListGroupChannels) { - mPriv->addContactListGroupChannel(contactListGroupChannel); + mPriv->addContactListGroupChannelFallback(contactListGroupChannel); } } -void ContactManager::addContactListGroupChannel( +void ContactManager::addContactListGroupChannelFallback( const ChannelPtr &contactListGroupChannel) { - QString id = mPriv->addContactListGroupChannel(contactListGroupChannel); + Q_ASSERT(mPriv->fallbackContactList == true); + + QString id = mPriv->addContactListGroupChannelFallback(contactListGroupChannel); emit groupAdded(id); } +QString ContactManager::featureToInterface(const Feature &feature) +{ + if (feature == Contact::FeatureAlias) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_ALIASING; + } else if (feature == Contact::FeatureAvatarToken) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_AVATARS; + } else if (feature == Contact::FeatureAvatarData) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_AVATARS; + } else if (feature == Contact::FeatureSimplePresence) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE; + } else if (feature == Contact::FeatureCapabilities) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES; + } else if (feature == Contact::FeatureLocation) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_LOCATION; + } else if (feature == Contact::FeatureInfo) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_INFO; + } else if (feature == Contact::FeatureRosterGroups) { + return TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS; + } else { + warning() << "ContactManager doesn't know which interface corresponds to feature" + << feature; + return QString(); + } +} + QString ContactManager::ContactListChannel::identifierForType(Type type) { static QString identifiers[LastType] = { @@ -1640,13 +2328,22 @@ uint ContactManager::ContactListChannel::typeForIdentifier(const QString &identi } /** - * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts); - * const Tp::Channel::GroupMemberChangeDetails &details); + * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts, + * const QString &message); * * This signal is emitted whenever some contacts request for presence publication. * * \param contacts A set of contacts which requested presence publication. - * \param details The request details. + * \param message An optional message that was sent by the contacts asking to receive the local + * user's presence. + */ + +/** + * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts, + * const Tp::Channel::GroupMemberChangeDetails &details); + * + * \deprecated Use presencePublicationRequested(const Tp::Contacts &contact, const QString &message) + * instead. */ /** @@ -1721,4 +2418,11 @@ void PendingContactManagerRemoveContactListGroup::onChannelClosed(PendingOperati } } +void ContactManager::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(presencePublicationRequested(Tp::Contacts,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal presencePublicationRequested(Tp::Contacts,Tp::Channel::GroupMemberChangeDetails)"; + } +} + } // Tp diff --git a/TelepathyQt4/contact-manager.h b/TelepathyQt4/contact-manager.h index 2055f868..a676c386 100644 --- a/TelepathyQt4/contact-manager.h +++ b/TelepathyQt4/contact-manager.h @@ -117,10 +117,13 @@ public: void requestContactAvatar(Contact *contact); Q_SIGNALS: + void presencePublicationRequested(const Tp::Contacts &contacts, const QString &message); + // deprecated void presencePublicationRequested(const Tp::Contacts &contacts, const Tp::Channel::GroupMemberChangeDetails &details); void groupAdded(const QString &group); + void groupRenamed(const QString &oldGroup, const QString &newGroup); void groupRemoved(const QString &group); void groupMembersChanged(const QString &group, @@ -132,6 +135,10 @@ Q_SIGNALS: const Tp::Contacts &contactsRemoved, const Tp::Channel::GroupMemberChangeDetails &details); +protected: + // FIXME: (API/ABI break) Remove connectNotify + void connectNotify(const char *); + private Q_SLOTS: void onAliasesChanged(const Tp::AliasPairList &); void doRequestAvatars(); @@ -142,19 +149,26 @@ private Q_SLOTS: void onLocationUpdated(uint, const QVariantMap &); void onContactInfoChanged(uint, const Tp::ContactInfoFieldList &); - void onStoredChannelMembersChanged( + void onContactListNewContactsConstructed(Tp::PendingOperation *op); + void onContactListGroupsChanged(const Tp::UIntList &contacts, + const QStringList &added, const QStringList &removed); + void onContactListGroupsCreated(const QStringList &names); + void onContactListGroupRenamed(const QString &oldName, const QString &newName); + void onContactListGroupsRemoved(const QStringList &names); + + void onStoredChannelMembersChangedFallback( const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded, const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved, const Tp::Channel::GroupMemberChangeDetails &details); - void onSubscribeChannelMembersChanged( + void onSubscribeChannelMembersChangedFallback( const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded, const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved, const Tp::Channel::GroupMemberChangeDetails &details); - void onPublishChannelMembersChanged( + void onPublishChannelMembersChangedFallback( const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded, const Tp::Contacts &groupRemotePendingMembersAdded, @@ -167,14 +181,14 @@ private Q_SLOTS: const Tp::Contacts &groupMembersRemoved, const Tp::Channel::GroupMemberChangeDetails &details); - void onContactListGroupMembersChanged( + void onContactListGroupMembersChangedFallback( const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded, const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved, const Tp::Channel::GroupMemberChangeDetails &details); - void onContactListGroupRemoved(Tp::DBusProxy *, - const QString &, const QString &); + void onContactListGroupRemovedFallback(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage); private: friend class Connection; @@ -218,11 +232,23 @@ private: const Features &features, const QVariantMap &attributes); + void setUseFallbackContactList(bool value); + + void setContactListProperties(const QVariantMap &props); + void setContactListContacts(const ContactAttributesMap &attrs); + void updateContactListContacts(const ContactSubscriptionMap &changes, + const UIntList &removals); + void setContactListGroupsProperties(const QVariantMap &props); + void setContactListChannels( const QMap<uint, ContactListChannel> &contactListChannels); - void setContactListGroupChannels( + + void setContactListGroupChannelsFallback( const QList<ChannelPtr> &contactListGroupChannels); - void addContactListGroupChannel(const ChannelPtr &contactListGroupChannel); + void addContactListGroupChannelFallback( + const ChannelPtr &contactListGroupChannel); + + static QString featureToInterface(const Feature &feature); struct Private; friend struct Private; diff --git a/TelepathyQt4/contact.cpp b/TelepathyQt4/contact.cpp index 8da6f5a8..b4136fc0 100644 --- a/TelepathyQt4/contact.cpp +++ b/TelepathyQt4/contact.cpp @@ -51,8 +51,8 @@ struct TELEPATHY_QT4_NO_EXPORT Contact::Private ContactCapabilities(true) : ContactCapabilities( manager->connection()->capabilities().allClassSpecs(), false)), isAvatarTokenKnown(false), - subscriptionState(PresenceStateNo), - publishState(PresenceStateNo), + subscriptionState(SubscriptionStateUnknown), + publishState(SubscriptionStateUnknown), blocked(false) { } @@ -78,8 +78,9 @@ struct TELEPATHY_QT4_NO_EXPORT Contact::Private QString avatarToken; AvatarData avatarData; - PresenceState subscriptionState; - PresenceState publishState; + SubscriptionState subscriptionState; + SubscriptionState publishState; + QString publishStateMessage; bool blocked; QSet<QString> groups; @@ -165,6 +166,7 @@ const Feature Contact::FeatureCapabilities = Feature(QLatin1String(Contact::stat const Feature Contact::FeatureInfo = Feature(QLatin1String(Contact::staticMetaObject.className()), 4, false); const Feature Contact::FeatureLocation = Feature(QLatin1String(Contact::staticMetaObject.className()), 5, false); const Feature Contact::FeatureSimplePresence = Feature(QLatin1String(Contact::staticMetaObject.className()), 6, false); +const Feature Contact::FeatureRosterGroups = Feature(QLatin1String(Contact::staticMetaObject.className()), 7, false); Contact::Contact(ContactManager *manager, const ReferencedHandles &handle, const Features &requestedFeatures, const QVariantMap &attributes) @@ -391,14 +393,39 @@ PendingContactInfo *Contact::requestInfo() return new PendingContactInfo(self); } +bool Contact::isSubscriptionStateKnown() const +{ + return mPriv->subscriptionState != SubscriptionStateUnknown; +} + +bool Contact::isSubscriptionRejected() const +{ + return mPriv->subscriptionState == SubscriptionStateRemovedRemotely; +} + Contact::PresenceState Contact::subscriptionState() const { - return mPriv->subscriptionState; + return subscriptionStateToPresenceState(mPriv->subscriptionState); +} + +bool Contact::isPublishStateKnown() const +{ + return mPriv->publishState != SubscriptionStateUnknown; +} + +bool Contact::isPublishCancelled() const +{ + return mPriv->publishState == SubscriptionStateRemovedRemotely; } Contact::PresenceState Contact::publishState() const { - return mPriv->publishState; + return subscriptionStateToPresenceState(mPriv->publishState); +} + +QString Contact::publishStateMessage() const +{ + return mPriv->publishStateMessage; } PendingOperation *Contact::requestPresenceSubscription(const QString &message) @@ -489,6 +516,22 @@ void Contact::augment(const Features &requestedFeatures, const QVariantMap &attr mPriv->id = qdbus_cast<QString>(attributes[ QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id")]); + if (attributes.contains(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + + QLatin1String("/subscribe"))) { + uint subscriptionState = qdbus_cast<uint>(attributes.value( + TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/subscribe"))); + setSubscriptionState((SubscriptionState) subscriptionState); + } + + if (attributes.contains(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + + QLatin1String("/publish"))) { + uint publishState = qdbus_cast<uint>(attributes.value( + TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/publish"))); + QString publishRequest = qdbus_cast<QString>(attributes.value( + TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/publish-request"))); + setPublishState((SubscriptionState) publishState, publishRequest); + } + foreach (const Feature &feature, requestedFeatures) { QString maybeAlias; SimplePresence maybePresence; @@ -580,6 +623,10 @@ void Contact::augment(const Features &requestedFeatures, const QVariantMap &attr mPriv->presence.setStatus(ConnectionPresenceTypeUnknown, QLatin1String("unknown"), QLatin1String("")); } + } else if (feature == FeatureRosterGroups) { + QStringList groups = qdbus_cast<QStringList>(attributes.value( + TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS + QLatin1String("/groups"))); + mPriv->groups = groups.toSet(); } else { warning() << "Unknown feature" << feature << "encountered when augmenting Contact"; } @@ -687,33 +734,63 @@ void Contact::receiveInfo(const ContactInfoFieldList &info) } } -void Contact::setSubscriptionState(Contact::PresenceState state, - const Channel::GroupMemberChangeDetails &details) +Contact::PresenceState Contact::subscriptionStateToPresenceState(uint subscriptionState) +{ + switch (subscriptionState) { + case SubscriptionStateAsk: + return PresenceStateAsk; + case SubscriptionStateYes: + return PresenceStateYes; + default: + return PresenceStateNo; + } +} + +void Contact::setSubscriptionState(SubscriptionState state) { if (mPriv->subscriptionState == state) { return; } + mPriv->subscriptionState = state; - emit subscriptionStateChanged(state, details); + + // FIXME (API/ABI break) remove signal with details + emit subscriptionStateChanged(subscriptionStateToPresenceState(state), + Channel::GroupMemberChangeDetails()); + + emit subscriptionStateChanged(subscriptionStateToPresenceState(state)); } -void Contact::setPublishState(Contact::PresenceState state, - const Channel::GroupMemberChangeDetails &details) +void Contact::setPublishState(SubscriptionState state, const QString &message) { if (mPriv->publishState == state) { return; } + mPriv->publishState = state; - emit publishStateChanged(state, details); + mPriv->publishStateMessage = message; + + // FIXME (API/ABI break) remove signal with details + QVariantMap detailsMap; + detailsMap.insert(QLatin1String("message"), message); + emit publishStateChanged(subscriptionStateToPresenceState(state), + Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap)); + + emit publishStateChanged(subscriptionStateToPresenceState(state), message); } -void Contact::setBlocked(bool value, const Channel::GroupMemberChangeDetails &details) +void Contact::setBlocked(bool value) { if (mPriv->blocked == value) { return; } + mPriv->blocked = value; - emit blockStatusChanged(value, details); + + // FIXME (API/ABI break) remove signal with details + emit blockStatusChanged(value, Channel::GroupMemberChangeDetails()); + + emit blockStatusChanged(value); } void Contact::setAddedToGroup(const QString &group) @@ -749,4 +826,60 @@ void Contact::setRemovedFromGroup(const QString &group) * \sa infoFields() */ +/** + * \fn void Contact::subscriptionStateChanged(Tp::Contact::PresenceState state) + * + * This signal is emitted whenever the value of subscriptionState() changes. + * + * \param state The new subscription state. + */ + +/** + * \fn void Contact::subscriptionStateChanged(Tp::Contact::PresenceState state, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Use subscriptionStateChanged(Tp::Contact::PresenceState state) instead. + */ + +/** + * \fn void Contact::publishStateChanged(Tp::Contact::PresenceState state) + * + * This signal is emitted whenever the value of publishState() changes. + * + * \param state The new publish state. + */ + +/** + * \fn void Contact::publishStateChanged(Tp::Contact::PresenceState state, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Use publishStateChanged(Tp::Contact::PresenceState state) instead. + */ + +/** + * \fn void Contact::blockStatusChanged(bool blocked) + * + * This signal is emitted whenever the value of isBlocked() changes. + * + * \param status The new block status. + */ + +/** + * \fn void Contact::blockStatusChanged(bool blocked, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Use blockStatusChanged(bool blocked) instead. + */ + +void Contact::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)"; + } else if (qstrcmp(signalName, SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)"; + } else if (qstrcmp(signalName, SIGNAL(blockStatusChanged(bool,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal blockStatusChanged(bool,Tp::Channel::GroupMemberChangeDetails)"; + } +} + } // Tp diff --git a/TelepathyQt4/contact.h b/TelepathyQt4/contact.h index fa9097cf..b2544bca 100644 --- a/TelepathyQt4/contact.h +++ b/TelepathyQt4/contact.h @@ -131,8 +131,13 @@ public: /* * Filters on exact values of these, but also the "in your contact list at all or not" usecase */ + bool isSubscriptionStateKnown() const; + bool isSubscriptionRejected() const; PresenceState subscriptionState() const; + bool isPublishStateKnown() const; + bool isPublishCancelled() const; PresenceState publishState() const; + QString publishStateMessage() const; PendingOperation *requestPresenceSubscription(const QString &message = QString()); PendingOperation *removePresenceSubscription(const QString &message = QString()); @@ -168,10 +173,18 @@ Q_SIGNALS: void infoFieldsChanged(const Tp::Contact::InfoFields &infoFields); + void subscriptionStateChanged(Tp::Contact::PresenceState state); + // deprecated void subscriptionStateChanged(Tp::Contact::PresenceState state, const Tp::Channel::GroupMemberChangeDetails &details); + + void publishStateChanged(Tp::Contact::PresenceState state, const QString &message); + // deprecated void publishStateChanged(Tp::Contact::PresenceState state, const Tp::Channel::GroupMemberChangeDetails &details); + + void blockStatusChanged(bool blocked); + // deprecated void blockStatusChanged(bool blocked, const Tp::Channel::GroupMemberChangeDetails &details); void addedToGroup(const QString &group); @@ -183,7 +196,13 @@ Q_SIGNALS: // with that contact getting the same features requested as the current one. Or would we rather // want to signal that change right away with a handle? +protected: + // FIXME: (API/ABI break) Remove connectNotify + void connectNotify(const char *); + private: + static const Feature FeatureRosterGroups; + Contact(ContactManager *manager, const ReferencedHandles &handle, const Features &requestedFeatures, const QVariantMap &attributes); @@ -198,17 +217,16 @@ private: void receiveLocation(const QVariantMap &location); void receiveInfo(const ContactInfoFieldList &info); - void setSubscriptionState(PresenceState state, const Channel::GroupMemberChangeDetails &details = - Channel::GroupMemberChangeDetails()); - void setPublishState(PresenceState state, const Channel::GroupMemberChangeDetails &details = - Channel::GroupMemberChangeDetails()); - void setBlocked(bool value, const Channel::GroupMemberChangeDetails &details = - Channel::GroupMemberChangeDetails()); + static PresenceState subscriptionStateToPresenceState(uint subscriptionState); + void setSubscriptionState(SubscriptionState state); + void setPublishState(SubscriptionState state, const QString &message = QString()); + void setBlocked(bool value); void setAddedToGroup(const QString &group); void setRemovedFromGroup(const QString &group); struct Private; + friend class Connection; friend class ContactManager; friend struct Private; Private *mPriv; diff --git a/TelepathyQt4/referenced-handles.h b/TelepathyQt4/referenced-handles.h index 4704b10f..7da5624e 100644 --- a/TelepathyQt4/referenced-handles.h +++ b/TelepathyQt4/referenced-handles.h @@ -241,6 +241,7 @@ public: private: // For access to the "prime" constructor + friend class ContactManager; friend class PendingContactAttributes; friend class PendingContacts; friend class PendingHandles; diff --git a/tests/dbus/CMakeLists.txt b/tests/dbus/CMakeLists.txt index 6ec1a998..b0e0d191 100644 --- a/tests/dbus/CMakeLists.txt +++ b/tests/dbus/CMakeLists.txt @@ -29,8 +29,10 @@ if(ENABLE_TP_GLIB_TESTS) tpqt4_add_dbus_unit_test(ConnectionBasics conn-basics tp-glib-tests) tpqt4_add_dbus_unit_test(ConnectionCapabilities conn-capabilities tp-glib-tests) tpqt4_add_dbus_unit_test(ConnectionRequests conn-requests tp-glib-tests) - tpqt4_add_dbus_unit_test(ConnectionRoster conn-roster tp-glib-tests) - tpqt4_add_dbus_unit_test(ConnectionRosterGroups conn-roster-groups tp-glib-tests) + tpqt4_add_dbus_unit_test(ConnectionRosterLegacy conn-roster-legacy tp-glib-tests) + tpqt4_add_dbus_unit_test(ConnectionRoster conn-roster example-cm-contactlist2 ${TELEPATHY_GLIB_LIBRARIES}) + tpqt4_add_dbus_unit_test(ConnectionRosterGroupsLegacy conn-roster-groups-legacy tp-glib-tests) + tpqt4_add_dbus_unit_test(ConnectionRosterGroups conn-roster-groups example-cm-contactlist2 ${TELEPATHY_GLIB_LIBRARIES}) tpqt4_add_dbus_unit_test(Contacts contacts tp-glib-tests) tpqt4_add_dbus_unit_test(ContactsAvatar contacts-avatar tp-glib-tests) tpqt4_add_dbus_unit_test(ContactsCapabilities contacts-capabilities tp-glib-tests) diff --git a/tests/dbus/conn-roster-groups-legacy.cpp b/tests/dbus/conn-roster-groups-legacy.cpp new file mode 100644 index 00000000..8270ed52 --- /dev/null +++ b/tests/dbus/conn-roster-groups-legacy.cpp @@ -0,0 +1,679 @@ +#include <QtCore/QDebug> +#include <QtCore/QTimer> + +#include <QtDBus/QtDBus> + +#include <QtTest/QtTest> + +#define TP_QT4_ENABLE_LOWLEVEL_API + +#include <TelepathyQt4/ChannelFactory> +#include <TelepathyQt4/Connection> +#include <TelepathyQt4/ConnectionLowlevel> +#include <TelepathyQt4/Contact> +#include <TelepathyQt4/ContactFactory> +#include <TelepathyQt4/ContactManager> +#include <TelepathyQt4/PendingContacts> +#include <TelepathyQt4/PendingReady> +#include <TelepathyQt4/Debug> + +#include <telepathy-glib/debug.h> + +#include <tests/lib/glib/contactlist/conn.h> +#include <tests/lib/test.h> + +using namespace Tp; + +class TestConnRosterGroupsLegacy : public Test +{ + Q_OBJECT + +public: + TestConnRosterGroupsLegacy(QObject *parent = 0) + : Test(parent), mConnService(0), + mContactsAddedToGroup(0), mContactsRemovedFromGroup(0) + { } + +protected Q_SLOTS: + void onGroupAdded(const QString &group); + void onGroupRemoved(const QString &group); + void onContactAddedToGroup(const QString &group); + void onContactRemovedFromGroup(const QString &group); + void expectConnInvalidated(); + void expectContact(Tp::PendingOperation*); + +private Q_SLOTS: + void initTestCase(); + void init(); + + void testRosterGroups(); + void testNotADeathTrap(); + + void cleanup(); + void cleanupTestCase(); + +private: + QString mConnName, mConnPath; + ExampleContactListConnection *mConnService; + ConnectionPtr mConn; + ContactPtr mContact; + + QString mGroupAdded; + QString mGroupRemoved; + int mContactsAddedToGroup; + int mContactsRemovedFromGroup; + bool mConnInvalidated; +}; + +void TestConnRosterGroupsLegacy::onGroupAdded(const QString &group) +{ + mGroupAdded = group; + mLoop->exit(0); +} + +void TestConnRosterGroupsLegacy::onGroupRemoved(const QString &group) +{ + mGroupRemoved = group; + mLoop->exit(0); +} + + +void TestConnRosterGroupsLegacy::onContactAddedToGroup(const QString &group) +{ + mContactsAddedToGroup++; + mLoop->exit(0); +} + +void TestConnRosterGroupsLegacy::onContactRemovedFromGroup(const QString &group) +{ + mContactsRemovedFromGroup++; + mLoop->exit(0); +} + +void TestConnRosterGroupsLegacy::expectConnInvalidated() +{ + mConnInvalidated = true; + mLoop->exit(0); +} + +void TestConnRosterGroupsLegacy::expectContact(Tp::PendingOperation *op) +{ + PendingContacts *contacts = qobject_cast<PendingContacts *>(op); + QVERIFY(contacts != 0); + + QVERIFY(contacts->isValid()); + QCOMPARE(contacts->contacts().length(), 1); + + mContact = contacts->contacts()[0]; + + mLoop->exit(0); +} + +void TestConnRosterGroupsLegacy::initTestCase() +{ + initTestCaseImpl(); + + g_type_init(); + g_set_prgname("conn-roster-groups"); + tp_debug_set_flags("all"); + dbus_g_bus_get(DBUS_BUS_STARTER, 0); +} + +void TestConnRosterGroupsLegacy::init() +{ + gchar *name; + gchar *connPath; + GError *error = 0; + + mConnService = EXAMPLE_CONTACT_LIST_CONNECTION(g_object_new( + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, + "account", "me@example.com", + "simulation-delay", 0, + "protocol", "example-contact-list", + NULL)); + QVERIFY(mConnService != 0); + QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService), + "foo", &name, &connPath, &error)); + QVERIFY(error == 0); + + QVERIFY(name != 0); + QVERIFY(connPath != 0); + + mConnName = QLatin1String(name); + mConnPath = QLatin1String(connPath); + + g_free(name); + g_free(connPath); + initImpl(); + + mConnInvalidated = false; +} + +void TestConnRosterGroupsLegacy::testRosterGroups() +{ + mConn = Connection::create(mConnName, mConnPath, + ChannelFactory::create(QDBusConnection::sessionBus()), + ContactFactory::create()); + + QVERIFY(connect(mConn->lowlevel()->requestConnect(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(), true); + QCOMPARE(mConn->status(), ConnectionStatusConnected); + + Features features = Features() << Connection::FeatureRoster << Connection::FeatureRosterGroups; + QVERIFY(connect(mConn->becomeReady(features), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(features), true); + + ContactManagerPtr contactManager = mConn->contactManager(); + + QStringList expectedGroups; + expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones") + << QLatin1String("Montreal"); + expectedGroups.sort(); + QStringList groups = contactManager->allKnownGroups(); + groups.sort(); + QCOMPARE(groups, expectedGroups); + + // Cambridge + { + QStringList expectedContacts; + expectedContacts << QLatin1String("geraldine@example.com") + << QLatin1String("helen@example.com") + << QLatin1String("guillaume@example.com") + << QLatin1String("sjoerd@example.com"); + expectedContacts.sort(); + QStringList contacts; + Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) { + contacts << contact->id(); + } + contacts.sort(); + QCOMPARE(contacts, expectedContacts); + } + + // Francophones + { + QStringList expectedContacts; + expectedContacts << QLatin1String("olivier@example.com") + << QLatin1String("geraldine@example.com") + << QLatin1String("guillaume@example.com"); + expectedContacts.sort(); + QStringList contacts; + Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Francophones"))) { + contacts << contact->id(); + } + contacts.sort(); + QCOMPARE(contacts, expectedContacts); + } + + // Montreal + { + QStringList expectedContacts; + expectedContacts << QLatin1String("olivier@example.com"); + expectedContacts.sort(); + QStringList contacts; + Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Montreal"))) { + contacts << contact->id(); + } + contacts.sort(); + QCOMPARE(contacts, expectedContacts); + } + + QString group(QLatin1String("foo")); + QVERIFY(contactManager->groupContacts(group).isEmpty()); + + // add group foo + QVERIFY(connect(contactManager.data(), + SIGNAL(groupAdded(const QString&)), + SLOT(onGroupAdded(const QString&)))); + QVERIFY(connect(contactManager->addGroup(group), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + while (mGroupAdded.isEmpty()) { + QCOMPARE(mLoop->exec(), 0); + } + QCOMPARE(mGroupAdded, group); + + expectedGroups << group; + expectedGroups.sort(); + groups = contactManager->allKnownGroups(); + groups.sort(); + QCOMPARE(groups, expectedGroups); + + // add Montreal contacts to group foo + Contacts contacts = contactManager->groupContacts(QLatin1String("Montreal")); + Q_FOREACH (const ContactPtr &contact, contacts) { + QVERIFY(connect(contact.data(), + SIGNAL(addedToGroup(const QString&)), + SLOT(onContactAddedToGroup(const QString&)))); + } + QVERIFY(connect(contactManager->addContactsToGroup(group, contacts.toList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + while (mContactsAddedToGroup != contacts.size()) { + QCOMPARE(mLoop->exec(), 0); + } + Q_FOREACH (const ContactPtr &contact, contacts) { + QVERIFY(contact->groups().contains(group)); + } + + // remove all contacts from group foo + contacts = contactManager->groupContacts(group); + Q_FOREACH (const ContactPtr &contact, contacts) { + QVERIFY(connect(contact.data(), + SIGNAL(removedFromGroup(const QString&)), + SLOT(onContactRemovedFromGroup(const QString&)))); + } + QVERIFY(connect(contactManager->removeContactsFromGroup(group, contacts.toList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + while (mContactsRemovedFromGroup != contacts.size()) { + QCOMPARE(mLoop->exec(), 0); + } + Q_FOREACH (const ContactPtr &contact, contacts) { + QVERIFY(!contact->groups().contains(group)); + } + + // add group foo + QVERIFY(connect(contactManager.data(), + SIGNAL(groupRemoved(const QString&)), + SLOT(onGroupRemoved(const QString&)))); + QVERIFY(connect(contactManager->removeGroup(group), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + while (mGroupRemoved.isEmpty()) { + QCOMPARE(mLoop->exec(), 0); + } + QCOMPARE(mGroupRemoved, group); + + expectedGroups.removeOne(group); + expectedGroups.sort(); + groups = contactManager->allKnownGroups(); + groups.sort(); + QCOMPARE(groups, expectedGroups); +} + +/** + * Verify that ContactManager isn't a death-trap. + * + * Background: Connection::contactManager() used to unpredictably waver between NULL and the real + * manager when the connection was in the process of being disconnected / otherwise invalidated, + * which led to a great many segfaults, which was especially unfortunate considering the + * ContactManager methods didn't do much any checks at all. + */ +void TestConnRosterGroupsLegacy::testNotADeathTrap() +{ + mConn = Connection::create(mConnName, mConnPath, + ChannelFactory::create(QDBusConnection::sessionBus()), + ContactFactory::create()); + QCOMPARE(mConn->isReady(), false); + + // Check that the contact manager doesn't crash, but returns an error (because the conn isn't + // ready) + QVERIFY(!mConn->contactManager().isNull()); + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->lowlevel()->requestConnect(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(), true); + QCOMPARE(mConn->status(), ConnectionStatusConnected); + + // As the conn is now ready, the contact building functions shouldn't return an error now + QVERIFY(!mConn->contactManager().isNull()); + + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(), + Features()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + // In fact, let's build a contact for future use + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers( + QStringList() << QLatin1String("friendorfoe@example.com")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectContact(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QVERIFY(mContact->id() == QLatin1String("friendorfoe@example.com")); + + // Roster operations SHOULD still fail though, as FeatureRoster isn't ready + QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription( + QList<ContactPtr>() << mContact, + QLatin1String("I just want to see you fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removePresenceSubscription( + QList<ContactPtr>() << mContact, + QLatin1String("I just want to see you fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->authorizePresencePublication( + QList<ContactPtr>() << mContact, + QLatin1String("I just want to see you fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removePresencePublication( + QList<ContactPtr>() << mContact, + QLatin1String("I just want to see you fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->blockContacts(QList<ContactPtr>() << mContact, true), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + // Now, make Roster ready + Features features = Features() << Connection::FeatureRoster; + QVERIFY(connect(mConn->becomeReady(features), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(features), true); + + // The roster functions should work now + QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(mContact->subscriptionState() != Contact::PresenceStateNo); + + QVERIFY(connect(mConn->contactManager()->removePresenceSubscription( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QCOMPARE(mContact->subscriptionState(), Contact::PresenceStateNo); + + QVERIFY(connect(mConn->contactManager()->authorizePresencePublication( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removePresencePublication( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + // The test CM doesn't support block, so it will never be successful + + // ... but still not the RosterGroup ones + QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Those who failed")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("Those who failed")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("Those who failed"), + QList<ContactPtr>() << mContact), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("Those who failed"), + QList<ContactPtr>() << mContact), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + // Make RosterGroups ready too + features = Features() << Connection::FeatureRosterGroups; + QVERIFY(connect(mConn->becomeReady(features), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(features), true); + + // Now that Core, Roster and RosterGroups are all ready, everything should work + QVERIFY(!mConn->contactManager().isNull()); + + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(), + Features()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(mContact->subscriptionState() != Contact::PresenceStateNo); + + QVERIFY(connect(mConn->contactManager()->removePresenceSubscription( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QCOMPARE(mContact->subscriptionState(), Contact::PresenceStateNo); + + QVERIFY(connect(mConn->contactManager()->authorizePresencePublication( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removePresencePublication( + QList<ContactPtr>() << mContact, + QLatin1String("Please don't fail")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + // The test CM doesn't support block, so it will never be successful + + QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("My successful entourage")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + qDebug() << "waiting for group to be added"; + + // FIXME: Remove this once fd.o #29728 is fixed + while (!mConn->contactManager()->allKnownGroups().contains(QLatin1String("My successful entourage"))) { + mLoop->processEvents(); + } + + qDebug() << "group has been added"; + + QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("My successful entourage"), + QList<ContactPtr>() << mContact), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("My successful entourage"), + QList<ContactPtr>() << mContact), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("My successful entourage")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + // Now, invalidate the connection by disconnecting it + QVERIFY(connect(mConn.data(), + SIGNAL(invalidated(Tp::DBusProxy *, + const QString &, const QString &)), + SLOT(expectConnInvalidated()))); + mConn->lowlevel()->requestDisconnect(); + + // Check that contactManager doesn't go NULL in the process of the connection going invalidated + do { + QVERIFY(!mConn->contactManager().isNull()); + mLoop->processEvents(); + } while (!mConnInvalidated); + + QVERIFY(!mConn->isValid()); + QCOMPARE(mConn->status(), ConnectionStatusDisconnected); + + // Now that the conn is invalidated NOTHING should work anymore + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(), + Features()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(QList<ContactPtr>(), + QLatin1String("You fail at life")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(QList<ContactPtr>(), + QLatin1String("You fail at life")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(QList<ContactPtr>(), + QLatin1String("You fail at life")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removePresencePublication(QList<ContactPtr>(), + QLatin1String("You fail at life")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->blockContacts(QList<ContactPtr>() << mContact, true), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Future failures")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("Future failures")), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("Future failures"), + QList<ContactPtr>() << mContact), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("Future failures"), + QList<ContactPtr>() << mContact), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectFailure(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); +} + +void TestConnRosterGroupsLegacy::cleanup() +{ + mContact.reset(); + + if (mConn) { + if (mConn->status() != ConnectionStatusDisconnected) { + // Disconnect and wait for the readiness change + QVERIFY(connect(mConn->lowlevel()->requestDisconnect(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + } + + if (mConn->isValid()) { + QVERIFY(connect(mConn.data(), + SIGNAL(invalidated(Tp::DBusProxy *, + const QString &, const QString &)), + SLOT(expectConnInvalidated()))); + + while (!mConnInvalidated) { + mLoop->processEvents(); + } + } + + mConn.reset(); + } + + if (mConnService != 0) { + g_object_unref(mConnService); + mConnService = 0; + } + + cleanupImpl(); +} + +void TestConnRosterGroupsLegacy::cleanupTestCase() +{ + cleanupTestCaseImpl(); +} + +QTEST_MAIN(TestConnRosterGroupsLegacy) +#include "_gen/conn-roster-groups-legacy.cpp.moc.hpp" diff --git a/tests/dbus/conn-roster-groups.cpp b/tests/dbus/conn-roster-groups.cpp index 6504a7e7..99631361 100644 --- a/tests/dbus/conn-roster-groups.cpp +++ b/tests/dbus/conn-roster-groups.cpp @@ -19,7 +19,7 @@ #include <telepathy-glib/debug.h> -#include <tests/lib/glib/contactlist/conn.h> +#include <tests/lib/glib/contactlist2/conn.h> #include <tests/lib/test.h> using namespace Tp; @@ -179,50 +179,6 @@ void TestConnRosterGroups::testRosterGroups() groups.sort(); QCOMPARE(groups, expectedGroups); - // Cambridge - { - QStringList expectedContacts; - expectedContacts << QLatin1String("geraldine@example.com") - << QLatin1String("helen@example.com") - << QLatin1String("guillaume@example.com") - << QLatin1String("sjoerd@example.com"); - expectedContacts.sort(); - QStringList contacts; - Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) { - contacts << contact->id(); - } - contacts.sort(); - QCOMPARE(contacts, expectedContacts); - } - - // Francophones - { - QStringList expectedContacts; - expectedContacts << QLatin1String("olivier@example.com") - << QLatin1String("geraldine@example.com") - << QLatin1String("guillaume@example.com"); - expectedContacts.sort(); - QStringList contacts; - Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Francophones"))) { - contacts << contact->id(); - } - contacts.sort(); - QCOMPARE(contacts, expectedContacts); - } - - // Montreal - { - QStringList expectedContacts; - expectedContacts << QLatin1String("olivier@example.com"); - expectedContacts.sort(); - QStringList contacts; - Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Montreal"))) { - contacts << contact->id(); - } - contacts.sort(); - QCOMPARE(contacts, expectedContacts); - } - QString group(QLatin1String("foo")); QVERIFY(contactManager->groupContacts(group).isEmpty()); @@ -387,11 +343,6 @@ void TestConnRosterGroups::testNotADeathTrap() SLOT(expectFailure(Tp::PendingOperation*)))); QCOMPARE(mLoop->exec(), 0); - QVERIFY(connect(mConn->contactManager()->blockContacts(QList<ContactPtr>() << mContact, true), - SIGNAL(finished(Tp::PendingOperation*)), - SLOT(expectFailure(Tp::PendingOperation*)))); - QCOMPARE(mLoop->exec(), 0); - // Now, make Roster ready Features features = Features() << Connection::FeatureRoster; QVERIFY(connect(mConn->becomeReady(features), @@ -433,8 +384,6 @@ void TestConnRosterGroups::testNotADeathTrap() SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); QCOMPARE(mLoop->exec(), 0); - // The test CM doesn't support block, so it will never be successful - // ... but still not the RosterGroup ones QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Those who failed")), SIGNAL(finished(Tp::PendingOperation*)), @@ -517,8 +466,6 @@ void TestConnRosterGroups::testNotADeathTrap() SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); QCOMPARE(mLoop->exec(), 0); - // The test CM doesn't support block, so it will never be successful - QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("My successful entourage")), SIGNAL(finished(Tp::PendingOperation*)), SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); @@ -607,11 +554,6 @@ void TestConnRosterGroups::testNotADeathTrap() SLOT(expectFailure(Tp::PendingOperation*)))); QCOMPARE(mLoop->exec(), 0); - QVERIFY(connect(mConn->contactManager()->blockContacts(QList<ContactPtr>() << mContact, true), - SIGNAL(finished(Tp::PendingOperation*)), - SLOT(expectFailure(Tp::PendingOperation*)))); - QCOMPARE(mLoop->exec(), 0); - QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Future failures")), SIGNAL(finished(Tp::PendingOperation*)), SLOT(expectFailure(Tp::PendingOperation*)))); diff --git a/tests/dbus/conn-roster-legacy.cpp b/tests/dbus/conn-roster-legacy.cpp new file mode 100644 index 00000000..3422d140 --- /dev/null +++ b/tests/dbus/conn-roster-legacy.cpp @@ -0,0 +1,376 @@ +#include <QtCore/QDebug> +#include <QtCore/QTimer> + +#include <QtDBus/QtDBus> + +#include <QtTest/QtTest> + +#define TP_QT4_ENABLE_LOWLEVEL_API + +#include <TelepathyQt4/ChannelFactory> +#include <TelepathyQt4/Connection> +#include <TelepathyQt4/ConnectionLowlevel> +#include <TelepathyQt4/Contact> +#include <TelepathyQt4/ContactFactory> +#include <TelepathyQt4/ContactManager> +#include <TelepathyQt4/PendingChannel> +#include <TelepathyQt4/PendingContacts> +#include <TelepathyQt4/PendingReady> +#include <TelepathyQt4/Debug> + +#include <telepathy-glib/debug.h> + +#include <tests/lib/glib/contactlist/conn.h> +#include <tests/lib/test.h> + +using namespace Tp; + +class TestConnRosterLegacy : public Test +{ + Q_OBJECT + +public: + TestConnRosterLegacy(QObject *parent = 0) + : Test(parent), mConnService(0) + { } + +protected Q_SLOTS: + void expectConnInvalidated(); + void expectPendingContactsFinished(Tp::PendingOperation *); + void expectPresenceStateChanged(Tp::Contact::PresenceState); + void expectAllKnownContactsChanged(const Tp::Contacts &added, const Tp::Contacts &removed, + const Tp::Channel::GroupMemberChangeDetails &details); + +private Q_SLOTS: + void initTestCase(); + void init(); + + void testRoster(); + + void cleanup(); + void cleanupTestCase(); + +private: + QString mConnName, mConnPath; + ExampleContactListConnection *mConnService; + ConnectionPtr mConn; + QList<ContactPtr> mContacts; + int mHowManyKnownContacts; + bool mGotPresenceStateChanged; +}; + +void TestConnRosterLegacy::expectConnInvalidated() +{ + mLoop->exit(0); +} + +void TestConnRosterLegacy::expectPendingContactsFinished(PendingOperation *op) +{ + if (!op->isFinished()) { + qWarning() << "unfinished"; + mLoop->exit(1); + return; + } + + if (op->isError()) { + qWarning().nospace() << op->errorName() + << ": " << op->errorMessage(); + mLoop->exit(2); + return; + } + + if (!op->isValid()) { + qWarning() << "inconsistent results"; + mLoop->exit(3); + return; + } + + qDebug() << "finished"; + PendingContacts *pending = qobject_cast<PendingContacts *>(op); + mContacts = pending->contacts(); + + mLoop->exit(0); +} + +void TestConnRosterLegacy::expectAllKnownContactsChanged(const Tp::Contacts& added, const Tp::Contacts& removed, + const Tp::Channel::GroupMemberChangeDetails &details) +{ + qDebug() << added.size() << " contacts added, " << removed.size() << " contacts removed"; + mHowManyKnownContacts += added.size(); + mHowManyKnownContacts -= removed.size(); + + if (details.hasMessage()) { + QCOMPARE(details.message(), QLatin1String("add me now")); + } + + if (mConn->contactManager()->allKnownContacts().size() != mHowManyKnownContacts) { + qWarning() << "Contacts number mismatch! Watched value: " << mHowManyKnownContacts + << "allKnownContacts(): " << mConn->contactManager()->allKnownContacts().size(); + mLoop->exit(1); + } else { + mLoop->exit(0); + } +} + +void TestConnRosterLegacy::expectPresenceStateChanged(Contact::PresenceState state) +{ + mGotPresenceStateChanged = true; +} + +void TestConnRosterLegacy::initTestCase() +{ + initTestCaseImpl(); + + g_type_init(); + g_set_prgname("conn-roster"); + tp_debug_set_flags("all"); + dbus_g_bus_get(DBUS_BUS_STARTER, 0); + + gchar *name; + gchar *connPath; + GError *error = 0; + + mConnService = EXAMPLE_CONTACT_LIST_CONNECTION(g_object_new( + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, + "account", "me@example.com", + "protocol", "contactlist", + "simulation-delay", 1, + NULL)); + QVERIFY(mConnService != 0); + QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService), + "contacts", &name, &connPath, &error)); + QVERIFY(error == 0); + + QVERIFY(name != 0); + QVERIFY(connPath != 0); + + mConnName = QLatin1String(name); + mConnPath = QLatin1String(connPath); + + g_free(name); + g_free(connPath); +} + +void TestConnRosterLegacy::init() +{ + initImpl(); + + mConn = Connection::create(mConnName, mConnPath, + ChannelFactory::create(QDBusConnection::sessionBus()), + ContactFactory::create()); + + QVERIFY(connect(mConn->lowlevel()->requestConnect(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(), true); + QCOMPARE(mConn->status(), ConnectionStatusConnected); +} + +void TestConnRosterLegacy::testRoster() +{ + Features features = Features() << Connection::FeatureRoster; + QVERIFY(connect(mConn->becomeReady(features), + SIGNAL(finished(Tp::PendingOperation*)), + this, + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + QCOMPARE(mConn->isReady(features), true); + + QStringList toCheck = QStringList() << + QLatin1String("sjoerd@example.com") << + QLatin1String("travis@example.com") << + QLatin1String("wim@example.com") << + QLatin1String("olivier@example.com") << + QLatin1String("helen@example.com") << + QLatin1String("geraldine@example.com") << + QLatin1String("guillaume@example.com") << + QLatin1String("christian@example.com"); + QStringList ids; + QList<ContactPtr> pendingSubscription; + QList<ContactPtr> pendingPublish; + Q_FOREACH (const ContactPtr &contact, + mConn->contactManager()->allKnownContacts()) { + qDebug() << " contact:" << contact->id() << + "- subscription:" << contact->subscriptionState() << + "- publish:" << contact->publishState(); + ids << contact->id(); + if (contact->subscriptionState() == Contact::PresenceStateAsk) { + pendingSubscription.append(contact); + } else if (contact->publishState() == Contact::PresenceStateAsk) { + pendingPublish.append(contact); + } + } + ids.sort(); + toCheck.sort(); + QCOMPARE(ids, toCheck); + QCOMPARE(pendingSubscription.size(), 2); + QCOMPARE(pendingPublish.size(), 2); + + // Wait for the contacts to be built + ids = QStringList() << QString(QLatin1String("john@example.com")) + << QString(QLatin1String("mary@example.com")); + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(ids), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectPendingContactsFinished(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + int i = 0; + + Q_FOREACH (const ContactPtr &contact, mContacts) { + mGotPresenceStateChanged = false; + + QVERIFY(connect(contact.data(), + SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)), + SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState)))); + QVERIFY(connect(contact.data(), + SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)), + SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState)))); + if ((i % 2) == 0) { + contact->requestPresenceSubscription(QLatin1String("please add me")); + } else { + contact->requestPresenceSubscription(QLatin1String("add me now")); + } + + while (!mGotPresenceStateChanged) { + mLoop->processEvents(); + } + + if ((i % 2) == 0) { + // I asked to see his presence - he might have already accepted it, though + QVERIFY(contact->subscriptionState() == Contact::PresenceStateAsk + || contact->subscriptionState() == Contact::PresenceStateYes); + + // if he accepted it already, one iteration won't be enough as the + // first iteration will just flush the subscription -> Yes event + while (contact->publishState() != Contact::PresenceStateAsk) { + mLoop->processEvents(); + } + + contact->authorizePresencePublication(); + while (contact->publishState() != Contact::PresenceStateYes) { + mLoop->processEvents(); + } + // I authorized him to see my presence + QCOMPARE(static_cast<uint>(contact->publishState()), + static_cast<uint>(Contact::PresenceStateYes)); + // He replied the presence request + QCOMPARE(static_cast<uint>(contact->subscriptionState()), + static_cast<uint>(Contact::PresenceStateYes)); + + contact->removePresenceSubscription(); + + while (contact->subscriptionState() != Contact::PresenceStateNo) { + mLoop->processEvents(); + } + } else { + // I asked to see her presence - she might have already rejected it, though + QVERIFY(contact->subscriptionState() == Contact::PresenceStateAsk + || contact->subscriptionState() == Contact::PresenceStateNo); + + // If she didn't already reject it, wait until she does + while (contact->subscriptionState() != Contact::PresenceStateNo) { + mLoop->processEvents(); + } + } + + ++i; + + // Disconnect the signals so the contacts doing something won't early-exit future mainloop + // iterations (the simulation CM does things like - after a delay since we removed them, try + // to re-add us - and such, which mess up the test if the simulated network event happens + // before we've finished with the next contact) + QVERIFY(contact->disconnect(this)); + + // TODO: The roster API, frankly speaking, seems rather error/race prone, as evidenced by + // this test. Should we perhaps change its semantics? Then again, this test also simulates + // the remote user accepting/rejecting the request with a quite unpredictable timer delay, + // while real-world applications don't do any such assumptions about the timing of the + // remote user actions, so most of the problems won't be applicable there. + } + + i = 0; + Contact::PresenceState expectedPresenceState; + Q_FOREACH (const ContactPtr &contact, pendingPublish) { + mGotPresenceStateChanged = false; + + QVERIFY(connect(contact.data(), + SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)), + SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState)))); + + if ((i++ % 2) == 0) { + expectedPresenceState = Contact::PresenceStateYes; + contact->authorizePresencePublication(); + } else { + expectedPresenceState = Contact::PresenceStateNo; + contact->removePresencePublication(); + } + + while (!mGotPresenceStateChanged) { + mLoop->processEvents(); + } + + QCOMPARE(static_cast<uint>(contact->publishState()), + static_cast<uint>(expectedPresenceState)); + } + + // Test allKnownContactsChanged. + // In this test, everytime a subscription is requested or rejected, allKnownContacts changes + // Cache the current value + mHowManyKnownContacts = mConn->contactManager()->allKnownContacts().size(); + // Watch for contacts changed + QVERIFY(connect(mConn->contactManager().data(), + SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)), + SLOT(expectAllKnownContactsChanged(Tp::Contacts,Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)))); + + // Wait for the contacts to be built + ids = QStringList() << QString(QLatin1String("kctest1@example.com")) + << QString(QLatin1String("kctest2@example.com")); + QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(ids), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectPendingContactsFinished(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + Q_FOREACH (const ContactPtr &contact, mContacts) { + contact->requestPresenceSubscription(QLatin1String("add me now")); + + // allKnownContacts is supposed to change here. + QCOMPARE(mLoop->exec(), 0); + } +} + +void TestConnRosterLegacy::cleanup() +{ + if (mConn) { + // Disconnect and wait for the readiness change + QVERIFY(connect(mConn->lowlevel()->requestDisconnect(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); + QCOMPARE(mLoop->exec(), 0); + + if (mConn->isValid()) { + QVERIFY(connect(mConn.data(), + SIGNAL(invalidated(Tp::DBusProxy *, + const QString &, const QString &)), + SLOT(expectConnInvalidated()))); + QCOMPARE(mLoop->exec(), 0); + } + } + + cleanupImpl(); +} + +void TestConnRosterLegacy::cleanupTestCase() +{ + if (mConnService != 0) { + g_object_unref(mConnService); + mConnService = 0; + } + + cleanupTestCaseImpl(); +} + +QTEST_MAIN(TestConnRosterLegacy) +#include "_gen/conn-roster-legacy.cpp.moc.hpp" diff --git a/tests/dbus/conn-roster.cpp b/tests/dbus/conn-roster.cpp index 61b64c33..a66b9c2e 100644 --- a/tests/dbus/conn-roster.cpp +++ b/tests/dbus/conn-roster.cpp @@ -20,7 +20,7 @@ #include <telepathy-glib/debug.h> -#include <tests/lib/glib/contactlist/conn.h> +#include <tests/lib/glib/contactlist2/conn.h> #include <tests/lib/test.h> using namespace Tp; diff --git a/tests/dbus/contacts.cpp b/tests/dbus/contacts.cpp index a5bd0991..fb6d54f3 100644 --- a/tests/dbus/contacts.cpp +++ b/tests/dbus/contacts.cpp @@ -333,16 +333,6 @@ void TestContacts::testForHandles() mContacts.clear(); mLoop->processEvents(); processDBusQueue(mConn.data()); - - // Unref the handles we created service-side - tp_handle_unref(serviceRepo, handles[0]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[0], NULL)); - tp_handle_unref(serviceRepo, handles[1]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[1], NULL)); - tp_handle_unref(serviceRepo, handles[2]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[2], NULL)); - tp_handle_unref(serviceRepo, handles[3]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[3], NULL)); } void TestContacts::testForIdentifiers() @@ -421,11 +411,6 @@ void TestContacts::testForIdentifiers() mContacts.clear(); mLoop->processEvents(); processDBusQueue(mConn.data()); - - // Check that their handles are in fact released - Q_FOREACH (uint handle, saveHandles) { - QVERIFY(!tp_handle_is_valid(serviceRepo, handle, NULL)); - } } void TestContacts::testFeatures() @@ -578,14 +563,6 @@ void TestContacts::testFeatures() mContacts.clear(); mLoop->processEvents(); processDBusQueue(mConn.data()); - - // Unref the handles we created service-side - tp_handle_unref(serviceRepo, handles[0]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[0], NULL)); - tp_handle_unref(serviceRepo, handles[1]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[1], NULL)); - tp_handle_unref(serviceRepo, handles[2]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[2], NULL)); } void TestContacts::testFeaturesNotRequested() @@ -629,14 +606,6 @@ void TestContacts::testFeaturesNotRequested() mContacts.clear(); mLoop->processEvents(); processDBusQueue(mConn.data()); - - // Unref the handles we created service-side - tp_handle_unref(serviceRepo, handles[0]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[0], NULL)); - tp_handle_unref(serviceRepo, handles[1]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[1], NULL)); - tp_handle_unref(serviceRepo, handles[2]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[2], NULL)); } void TestContacts::testUpgrade() @@ -746,14 +715,6 @@ void TestContacts::testUpgrade() mContacts.clear(); mLoop->processEvents(); processDBusQueue(mConn.data()); - - // Unref the handles we created service-side - tp_handle_unref(serviceRepo, handles[0]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[0], NULL)); - tp_handle_unref(serviceRepo, handles[1]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[1], NULL)); - tp_handle_unref(serviceRepo, handles[2]); - QVERIFY(!tp_handle_is_valid(serviceRepo, handles[2], NULL)); } void TestContacts::testSelfContactFallback() diff --git a/tests/dbus/handles.cpp b/tests/dbus/handles.cpp index b133405d..a489912e 100644 --- a/tests/dbus/handles.cpp +++ b/tests/dbus/handles.cpp @@ -207,12 +207,6 @@ void TestHandles::testRequestAndRelease() handles = ReferencedHandles(); mLoop->processEvents(); processDBusQueue(mConn.data()); - - // Check that the handles have been released - for (int i = 0; i < 3; i++) { - uint handle = saveHandles[0]; - QVERIFY(!tp_handle_is_valid(serviceRepo, handle, NULL)); - } } void TestHandles::cleanup() diff --git a/tests/lib/glib/CMakeLists.txt b/tests/lib/glib/CMakeLists.txt index e068c69d..c8542181 100644 --- a/tests/lib/glib/CMakeLists.txt +++ b/tests/lib/glib/CMakeLists.txt @@ -8,6 +8,7 @@ include_directories( if(ENABLE_TP_GLIB_TESTS) add_subdirectory(callable) add_subdirectory(contactlist) + add_subdirectory(contactlist2) add_subdirectory(csh) add_subdirectory(echo) add_subdirectory(echo2) diff --git a/tests/lib/glib/contactlist2/CMakeLists.txt b/tests/lib/glib/contactlist2/CMakeLists.txt new file mode 100644 index 00000000..c920d8ed --- /dev/null +++ b/tests/lib/glib/contactlist2/CMakeLists.txt @@ -0,0 +1,14 @@ +if(ENABLE_TP_GLIB_TESTS) + set(example_cm_contactlist2_SRCS + conn.c + conn.h + connection-manager.c + connection-manager.h + contact-list.c + contact-list.h + protocol.c + protocol.h) + + add_library(example-cm-contactlist2 STATIC ${example_cm_contactlist2_SRCS}) + target_link_libraries(example-cm-contactlist2 ${TPGLIB_LIBRARIES}) +endif(ENABLE_TP_GLIB_TESTS) diff --git a/tests/lib/glib/contactlist2/conn.c b/tests/lib/glib/contactlist2/conn.c new file mode 100644 index 00000000..05a30261 --- /dev/null +++ b/tests/lib/glib/contactlist2/conn.c @@ -0,0 +1,601 @@ +/* + * conn.c - an example connection + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#include "conn.h" + +#include <dbus/dbus-glib.h> + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/handle-repo-dynamic.h> +#include <telepathy-glib/handle-repo-static.h> + +#include "contact-list.h" +#include "protocol.h" + +static void init_aliasing (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (ExampleContactListConnection, + example_contact_list_connection, + TP_TYPE_BASE_CONNECTION, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING, + init_aliasing); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS, + tp_contacts_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST, + tp_base_contact_list_mixin_list_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS, + tp_base_contact_list_mixin_groups_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, + tp_presence_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + tp_presence_mixin_simple_presence_iface_init)) + +enum +{ + PROP_ACCOUNT = 1, + PROP_SIMULATION_DELAY, + N_PROPS +}; + +struct _ExampleContactListConnectionPrivate +{ + gchar *account; + guint simulation_delay; + ExampleContactList *contact_list; + gboolean away; +}; + +static void +example_contact_list_connection_init (ExampleContactListConnection *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, + ExampleContactListConnectionPrivate); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *spec) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (object); + + switch (property_id) + { + case PROP_ACCOUNT: + g_value_set_string (value, self->priv->account); + break; + + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *spec) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (object); + + switch (property_id) + { + case PROP_ACCOUNT: + g_free (self->priv->account); + self->priv->account = g_value_dup_string (value); + break; + + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + } +} + +static void +finalize (GObject *object) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (object); + + tp_contacts_mixin_finalize (object); + g_free (self->priv->account); + + G_OBJECT_CLASS (example_contact_list_connection_parent_class)->finalize ( + object); +} + +static gchar * +get_unique_connection_name (TpBaseConnection *conn) +{ + ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn); + + return g_strdup_printf ("%s@%p", self->priv->account, self); +} + +gchar * +example_contact_list_normalize_contact (TpHandleRepoIface *repo, + const gchar *id, + gpointer context, + GError **error) +{ + gchar *normal = NULL; + + if (example_contact_list_protocol_check_contact_id (id, &normal, error)) + return normal; + else + return NULL; +} + +static void +create_handle_repos (TpBaseConnection *conn, + TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) +{ + repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new + (TP_HANDLE_TYPE_CONTACT, example_contact_list_normalize_contact, NULL); +} + +static void +alias_updated_cb (ExampleContactList *contact_list, + TpHandle contact, + ExampleContactListConnection *self) +{ + GPtrArray *aliases; + GValueArray *pair; + + pair = g_value_array_new (2); + g_value_array_append (pair, NULL); + g_value_array_append (pair, NULL); + g_value_init (pair->values + 0, G_TYPE_UINT); + g_value_init (pair->values + 1, G_TYPE_STRING); + g_value_set_uint (pair->values + 0, contact); + g_value_set_string (pair->values + 1, + example_contact_list_get_alias (contact_list, contact)); + + aliases = g_ptr_array_sized_new (1); + g_ptr_array_add (aliases, pair); + + tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases); + + g_ptr_array_free (aliases, TRUE); + g_value_array_free (pair); +} + +static void +presence_updated_cb (ExampleContactList *contact_list, + TpHandle contact, + ExampleContactListConnection *self) +{ + TpBaseConnection *base = (TpBaseConnection *) self; + TpPresenceStatus *status; + + /* we ignore the presence indicated by the contact list for our own handle */ + if (contact == base->self_handle) + return; + + status = tp_presence_status_new ( + example_contact_list_get_presence (contact_list, contact), + NULL); + tp_presence_mixin_emit_one_presence_update ((GObject *) self, + contact, status); + tp_presence_status_free (status); +} + +static GPtrArray * +create_channel_managers (TpBaseConnection *conn) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (conn); + GPtrArray *ret = g_ptr_array_sized_new (1); + + self->priv->contact_list = EXAMPLE_CONTACT_LIST (g_object_new ( + EXAMPLE_TYPE_CONTACT_LIST, + "connection", conn, + "simulation-delay", self->priv->simulation_delay, + NULL)); + + g_signal_connect (self->priv->contact_list, "alias-updated", + G_CALLBACK (alias_updated_cb), self); + g_signal_connect (self->priv->contact_list, "presence-updated", + G_CALLBACK (presence_updated_cb), self); + + g_ptr_array_add (ret, self->priv->contact_list); + + return ret; +} + +static gboolean +start_connecting (TpBaseConnection *conn, + GError **error) +{ + ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, + TP_HANDLE_TYPE_CONTACT); + + /* In a real connection manager we'd ask the underlying implementation to + * start connecting, then go to state CONNECTED when finished, but here + * we can do it immediately. */ + + conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account, + NULL, error); + + if (conn->self_handle == 0) + return FALSE; + + tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED, + TP_CONNECTION_STATUS_REASON_REQUESTED); + + return TRUE; +} + +static void +shut_down (TpBaseConnection *conn) +{ + /* In a real connection manager we'd ask the underlying implementation to + * start shutting down, then call this function when finished, but here + * we can do it immediately. */ + tp_base_connection_finish_shutdown (conn); +} + +static void +aliasing_fill_contact_attributes (GObject *object, + const GArray *contacts, + GHashTable *attributes) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (object); + guint i; + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, guint, i); + + tp_contacts_mixin_set_contact_attribute (attributes, contact, + TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS, + tp_g_value_slice_new_string ( + example_contact_list_get_alias (self->priv->contact_list, + contact))); + } +} + +static void +constructed (GObject *object) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (object); + void (*chain_up) (GObject *) = + G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed; + + if (chain_up != NULL) + chain_up (object); + + tp_contacts_mixin_init (object, + G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin)); + + tp_base_connection_register_with_contacts_mixin (base); + tp_base_contact_list_mixin_register_with_contacts_mixin (base); + + tp_contacts_mixin_add_contact_attributes_iface (object, + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + aliasing_fill_contact_attributes); + + tp_presence_mixin_init (object, + G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin)); + tp_presence_mixin_simple_presence_register_with_contacts_mixin (object); +} + +static gboolean +status_available (GObject *object, + guint index_) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (object); + + if (base->status != TP_CONNECTION_STATUS_CONNECTED) + return FALSE; + + return TRUE; +} + +static GHashTable * +get_contact_statuses (GObject *object, + const GArray *contacts, + GError **error) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (object); + TpBaseConnection *base = TP_BASE_CONNECTION (object); + guint i; + GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) tp_presence_status_free); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, guint, i); + ExampleContactListPresence presence; + GHashTable *parameters; + + /* we get our own status from the connection, and everyone else's status + * from the contact lists */ + if (contact == base->self_handle) + { + presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY + : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE); + } + else + { + presence = example_contact_list_get_presence ( + self->priv->contact_list, contact); + } + + parameters = g_hash_table_new_full (g_str_hash, + g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); + g_hash_table_insert (result, GUINT_TO_POINTER (contact), + tp_presence_status_new (presence, parameters)); + g_hash_table_destroy (parameters); + } + + return result; +} + +static gboolean +set_own_status (GObject *object, + const TpPresenceStatus *status, + GError **error) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (object); + TpBaseConnection *base = TP_BASE_CONNECTION (object); + GHashTable *presences; + + if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY) + { + if (self->priv->away) + return TRUE; + + self->priv->away = TRUE; + } + else + { + if (!self->priv->away) + return TRUE; + + self->priv->away = FALSE; + } + + presences = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, NULL); + g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle), + (gpointer) status); + tp_presence_mixin_emit_presence_update (object, presences); + g_hash_table_destroy (presences); + return TRUE; +} + +static const gchar *interfaces_always_present[] = { + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + TP_IFACE_CONNECTION_INTERFACE_CONTACTS, + TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST, + TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS, + TP_IFACE_CONNECTION_INTERFACE_PRESENCE, + TP_IFACE_CONNECTION_INTERFACE_REQUESTS, + TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + NULL }; + +const gchar * const * +example_contact_list_connection_get_possible_interfaces (void) +{ + /* in this example CM we don't have any extra interfaces that are sometimes, + * but not always, present */ + return interfaces_always_present; +} + +static void +example_contact_list_connection_class_init ( + ExampleContactListConnectionClass *klass) +{ + TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->finalize = finalize; + g_type_class_add_private (klass, + sizeof (ExampleContactListConnectionPrivate)); + + base_class->create_handle_repos = create_handle_repos; + base_class->get_unique_connection_name = get_unique_connection_name; + base_class->create_channel_managers = create_channel_managers; + base_class->start_connecting = start_connecting; + base_class->shut_down = shut_down; + base_class->interfaces_always_present = interfaces_always_present; + + param_spec = g_param_spec_string ("account", "Account name", + "The username of this user", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec); + + param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay", + "Delay between simulated network events", + 0, G_MAXUINT32, 1000, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SIMULATION_DELAY, + param_spec); + + tp_contacts_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin)); + + tp_presence_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin), + status_available, get_contact_statuses, set_own_status, + example_contact_list_presence_statuses ()); + tp_presence_mixin_simple_presence_init_dbus_properties (object_class); + + tp_base_contact_list_mixin_class_init (base_class); +} + +static void +get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing, + DBusGMethodInvocation *context) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context, + TP_CONNECTION_ALIAS_FLAG_USER_SET); +} + +static void +get_aliases (TpSvcConnectionInterfaceAliasing *aliasing, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (aliasing); + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + GHashTable *result; + GError *error = NULL; + guint i; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, TpHandle, i); + const gchar *alias = example_contact_list_get_alias ( + self->priv->contact_list, contact); + + g_hash_table_insert (result, GUINT_TO_POINTER (contact), + (gchar *) alias); + } + + tp_svc_connection_interface_aliasing_return_from_get_aliases (context, + result); + g_hash_table_destroy (result); +} + +static void +request_aliases (TpSvcConnectionInterfaceAliasing *aliasing, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (aliasing); + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + GPtrArray *result; + gchar **strings; + GError *error = NULL; + guint i; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + result = g_ptr_array_sized_new (contacts->len + 1); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, TpHandle, i); + const gchar *alias = example_contact_list_get_alias ( + self->priv->contact_list, contact); + + g_ptr_array_add (result, (gchar *) alias); + } + + g_ptr_array_add (result, NULL); + strings = (gchar **) g_ptr_array_free (result, FALSE); + tp_svc_connection_interface_aliasing_return_from_request_aliases (context, + (const gchar **) strings); + g_free (strings); +} + +static void +set_aliases (TpSvcConnectionInterfaceAliasing *aliasing, + GHashTable *aliases, + DBusGMethodInvocation *context) +{ + ExampleContactListConnection *self = + EXAMPLE_CONTACT_LIST_CONNECTION (aliasing); + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, aliases); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GError *error = NULL; + + if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key), + &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + } + + g_hash_table_iter_init (&iter, aliases); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + example_contact_list_set_alias (self->priv->contact_list, + GPOINTER_TO_UINT (key), value); + } + + tp_svc_connection_interface_aliasing_return_from_set_aliases (context); +} + +static void +init_aliasing (gpointer iface, + gpointer iface_data G_GNUC_UNUSED) +{ + TpSvcConnectionInterfaceAliasingClass *klass = iface; + +#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\ + klass, x) + IMPLEMENT(get_alias_flags); + IMPLEMENT(request_aliases); + IMPLEMENT(get_aliases); + IMPLEMENT(set_aliases); +#undef IMPLEMENT +} diff --git a/tests/lib/glib/contactlist2/conn.h b/tests/lib/glib/contactlist2/conn.h new file mode 100644 index 00000000..73311d3c --- /dev/null +++ b/tests/lib/glib/contactlist2/conn.h @@ -0,0 +1,68 @@ +/* + * conn.h - header for an example connection + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#ifndef __EXAMPLE_CONTACT_LIST_CONN_H__ +#define __EXAMPLE_CONTACT_LIST_CONN_H__ + +#include <glib-object.h> +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/contacts-mixin.h> +#include <telepathy-glib/presence-mixin.h> + +G_BEGIN_DECLS + +typedef struct _ExampleContactListConnection ExampleContactListConnection; +typedef struct _ExampleContactListConnectionClass + ExampleContactListConnectionClass; +typedef struct _ExampleContactListConnectionPrivate + ExampleContactListConnectionPrivate; + +struct _ExampleContactListConnectionClass { + TpBaseConnectionClass parent_class; + TpPresenceMixinClass presence_mixin; + TpContactsMixinClass contacts_mixin; +}; + +struct _ExampleContactListConnection { + TpBaseConnection parent; + TpPresenceMixin presence_mixin; + TpContactsMixin contacts_mixin; + + ExampleContactListConnectionPrivate *priv; +}; + +GType example_contact_list_connection_get_type (void); + +#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION \ + (example_contact_list_connection_get_type ()) +#define EXAMPLE_CONTACT_LIST_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \ + ExampleContactListConnection)) +#define EXAMPLE_CONTACT_LIST_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \ + ExampleContactListConnectionClass)) +#define EXAMPLE_IS_CONTACT_LIST_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION)) +#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION)) +#define EXAMPLE_CONTACT_LIST_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \ + ExampleContactListConnectionClass)) + +gchar *example_contact_list_normalize_contact (TpHandleRepoIface *repo, + const gchar *id, gpointer context, GError **error); + +const gchar * const * example_contact_list_connection_get_possible_interfaces ( + void); + +G_END_DECLS + +#endif diff --git a/tests/lib/glib/contactlist2/connection-manager.c b/tests/lib/glib/contactlist2/connection-manager.c new file mode 100644 index 00000000..386f61f0 --- /dev/null +++ b/tests/lib/glib/contactlist2/connection-manager.c @@ -0,0 +1,73 @@ +/* + * manager.c - an example connection manager + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#include "connection-manager.h" + +#include <dbus/dbus-protocol.h> +#include <dbus/dbus-glib.h> + +#include <telepathy-glib/telepathy-glib.h> + +#include "conn.h" +#include "protocol.h" + +G_DEFINE_TYPE (ExampleContactListConnectionManager, + example_contact_list_connection_manager, + TP_TYPE_BASE_CONNECTION_MANAGER) + +struct _ExampleContactListConnectionManagerPrivate +{ + int dummy; +}; + +static void +example_contact_list_connection_manager_init ( + ExampleContactListConnectionManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, + ExampleContactListConnectionManagerPrivate); +} + +static void +example_contact_list_connection_manager_constructed (GObject *object) +{ + ExampleContactListConnectionManager *self = + EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER (object); + TpBaseConnectionManager *base = (TpBaseConnectionManager *) self; + void (*constructed) (GObject *) = + ((GObjectClass *) example_contact_list_connection_manager_parent_class)->constructed; + TpBaseProtocol *protocol; + + if (constructed != NULL) + constructed (object); + + protocol = g_object_new (EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, + "name", "example", + NULL); + tp_base_connection_manager_add_protocol (base, protocol); + g_object_unref (protocol); +} + +static void +example_contact_list_connection_manager_class_init ( + ExampleContactListConnectionManagerClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + TpBaseConnectionManagerClass *base_class = + (TpBaseConnectionManagerClass *) klass; + + g_type_class_add_private (klass, + sizeof (ExampleContactListConnectionManagerPrivate)); + + object_class->constructed = example_contact_list_connection_manager_constructed; + base_class->cm_dbus_name = "example_contact_list"; +} diff --git a/tests/lib/glib/contactlist2/connection-manager.h b/tests/lib/glib/contactlist2/connection-manager.h new file mode 100644 index 00000000..b99d1596 --- /dev/null +++ b/tests/lib/glib/contactlist2/connection-manager.h @@ -0,0 +1,62 @@ +/* + * manager.h - header for an example connection manager + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#ifndef __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__ +#define __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__ + +#include <glib-object.h> +#include <telepathy-glib/base-connection-manager.h> + +G_BEGIN_DECLS + +typedef struct _ExampleContactListConnectionManager + ExampleContactListConnectionManager; +typedef struct _ExampleContactListConnectionManagerClass + ExampleContactListConnectionManagerClass; +typedef struct _ExampleContactListConnectionManagerPrivate + ExampleContactListConnectionManagerPrivate; + +struct _ExampleContactListConnectionManagerClass { + TpBaseConnectionManagerClass parent_class; +}; + +struct _ExampleContactListConnectionManager { + TpBaseConnectionManager parent; + + ExampleContactListConnectionManagerPrivate *priv; +}; + +GType example_contact_list_connection_manager_get_type (void); + +#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER \ + (example_contact_list_connection_manager_get_type ()) +#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \ + ExampleContactListConnectionManager)) +#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \ + ExampleContactListConnectionManagerClass)) +#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER)) +#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), \ + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER)) +#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \ + ExampleContactListConnectionManagerClass)) + +G_END_DECLS + +#endif diff --git a/tests/lib/glib/contactlist2/contact-list.c b/tests/lib/glib/contactlist2/contact-list.c new file mode 100644 index 00000000..fec9afe7 --- /dev/null +++ b/tests/lib/glib/contactlist2/contact-list.c @@ -0,0 +1,1731 @@ +/* + * Example implementation of TpBaseContactList. + * + * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#include "contact-list.h" + +#include <string.h> + +#include <dbus/dbus-glib.h> + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/telepathy-glib.h> + +/* this array must be kept in sync with the enum + * ExampleContactListPresence in contact-list.h */ +static const TpPresenceStatusSpec _statuses[] = { + { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL }, + { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL }, + { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL }, + { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL }, + { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL }, + { NULL } +}; + +const TpPresenceStatusSpec * +example_contact_list_presence_statuses (void) +{ + return _statuses; +} + +typedef struct { + gchar *alias; + + guint subscribe:1; + guint publish:1; + guint pre_approved:1; + guint subscribe_requested:1; + guint subscribe_rejected:1; + + /* string borrowed from priv->all_tags => the same pointer */ + GHashTable *tags; +} ExampleContactDetails; + +static ExampleContactDetails * +example_contact_details_new (void) +{ + return g_slice_new0 (ExampleContactDetails); +} + +static void +example_contact_details_destroy (gpointer p) +{ + ExampleContactDetails *d = p; + + tp_clear_pointer (&d->tags, g_hash_table_unref); + + g_free (d->alias); + g_slice_free (ExampleContactDetails, d); +} + +static void mutable_contact_list_iface_init (TpMutableContactListInterface *); +static void blockable_contact_list_iface_init ( + TpBlockableContactListInterface *); +static void contact_group_list_iface_init (TpContactGroupListInterface *); +static void mutable_contact_group_list_iface_init ( + TpMutableContactGroupListInterface *); + +G_DEFINE_TYPE_WITH_CODE (ExampleContactList, + example_contact_list, + TP_TYPE_BASE_CONTACT_LIST, + G_IMPLEMENT_INTERFACE (TP_TYPE_BLOCKABLE_CONTACT_LIST, + blockable_contact_list_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_CONTACT_GROUP_LIST, + contact_group_list_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_GROUP_LIST, + mutable_contact_group_list_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_LIST, + mutable_contact_list_iface_init)) + +enum +{ + ALIAS_UPDATED, + PRESENCE_UPDATED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0 }; + +enum +{ + PROP_SIMULATION_DELAY = 1, + N_PROPS +}; + +struct _ExampleContactListPrivate +{ + TpBaseConnection *conn; + guint simulation_delay; + TpHandleRepoIface *contact_repo; + + /* g_strdup (group name) => the same pointer */ + GHashTable *all_tags; + + /* All contacts on our (fake) protocol-level contact list, + * plus all contacts in publish_requests or cancelled_publish_requests */ + TpHandleSet *contacts; + + /* All contacts on our (fake) protocol-level contact list + * GUINT_TO_POINTER (handle borrowed from contacts) + * => ExampleContactDetails */ + GHashTable *contact_details; + + /* Contacts with an outstanding request for presence publication + * (may or may not be in contact_details) + * handle borrowed from contacts => g_strdup (message) */ + GHashTable *publish_requests; + + /* Contacts who have requested presence but then cancelled their request + * (may or may not be in contact_details) */ + TpHandleSet *cancelled_publish_requests; + + TpHandleSet *blocked_contacts; + + gulong status_changed_id; +}; + +static void +example_contact_list_init (ExampleContactList *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CONTACT_LIST, ExampleContactListPrivate); + + self->priv->contact_details = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, example_contact_details_destroy); + self->priv->publish_requests = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, g_free); + self->priv->all_tags = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + /* initialized properly in constructed() */ + self->priv->contact_repo = NULL; + self->priv->contacts = NULL; + self->priv->blocked_contacts = NULL; + self->priv->cancelled_publish_requests = NULL; +} + +static void +example_contact_list_close_all (ExampleContactList *self) +{ + tp_clear_pointer (&self->priv->contacts, tp_handle_set_destroy); + tp_clear_pointer (&self->priv->blocked_contacts, tp_handle_set_destroy); + tp_clear_pointer (&self->priv->cancelled_publish_requests, + tp_handle_set_destroy); + tp_clear_pointer (&self->priv->publish_requests, g_hash_table_unref); + tp_clear_pointer (&self->priv->contact_details, g_hash_table_unref); + /* this must come after freeing contact_details, because the strings are + * borrowed */ + tp_clear_pointer (&self->priv->all_tags, g_hash_table_unref); + + if (self->priv->status_changed_id != 0) + { + g_signal_handler_disconnect (self->priv->conn, + self->priv->status_changed_id); + self->priv->status_changed_id = 0; + } +} + +static void +dispose (GObject *object) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (object); + + example_contact_list_close_all (self); + + ((GObjectClass *) example_contact_list_parent_class)->dispose ( + object); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (object); + + switch (property_id) + { + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (object); + + switch (property_id) + { + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static ExampleContactDetails * +lookup_contact (ExampleContactList *self, + TpHandle contact) +{ + return g_hash_table_lookup (self->priv->contact_details, + GUINT_TO_POINTER (contact)); +} + +static ExampleContactDetails * +ensure_contact (ExampleContactList *self, + TpHandle contact, + gboolean *created) +{ + ExampleContactDetails *ret = lookup_contact (self, contact); + + if (ret == NULL) + { + tp_handle_set_add (self->priv->contacts, contact); + + ret = example_contact_details_new (); + ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo, + contact)); + + g_hash_table_insert (self->priv->contact_details, + GUINT_TO_POINTER (contact), ret); + + /* if we already had a publish request from them, then adding them to + * the protocol-level contact list doesn't alter the Telepathy contact + * list */ + if (created != NULL) + { + *created = (g_hash_table_lookup (self->priv->publish_requests, + GUINT_TO_POINTER (contact)) == NULL); + } + } + else if (created != NULL) + { + *created = FALSE; + } + + return ret; +} + +static gchar * +ensure_tag (ExampleContactList *self, + const gchar *s, + gboolean emit_signal) +{ + gchar *r = g_hash_table_lookup (self->priv->all_tags, s); + + if (r == NULL) + { + g_message ("creating group %s", s); + r = g_strdup (s); + g_hash_table_insert (self->priv->all_tags, r, r); + + if (emit_signal) + tp_base_contact_list_groups_created ((TpBaseContactList *) self, + &s, 1); + } + + return r; +} + +static void +example_contact_list_set_contact_groups_async (TpBaseContactList *contact_list, + TpHandle contact, + const gchar * const *names, + gsize n, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + gboolean created; + gsize i; + ExampleContactDetails *d; + GPtrArray *old_names, *new_names; + GHashTableIter iter; + gpointer k; + + for (i = 0; i < n; i++) + ensure_tag (self, names[i], FALSE); + + tp_base_contact_list_groups_created (contact_list, names, n); + + d = ensure_contact (self, contact, &created); + + if (created) + tp_base_contact_list_one_contact_changed (contact_list, contact); + + if (d->tags == NULL) + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + + old_names = g_ptr_array_sized_new (g_hash_table_size (d->tags)); + new_names = g_ptr_array_sized_new (n); + + for (i = 0; i < n; i++) + { + if (g_hash_table_lookup (d->tags, names[i]) == NULL) + { + gchar *tag = g_hash_table_lookup (self->priv->all_tags, names[i]); + + g_assert (tag != NULL); /* already ensured to exist, above */ + g_hash_table_insert (d->tags, tag, tag); + g_ptr_array_add (new_names, tag); + } + } + + g_hash_table_iter_init (&iter, d->tags); + + while (g_hash_table_iter_next (&iter, &k, NULL)) + { + for (i = 0; i < n; i++) + { + if (!tp_strdiff (names[i], k)) + goto next_hash_element; + } + + /* not found in @names, so remove it */ + g_ptr_array_add (old_names, k); + g_hash_table_iter_remove (&iter); + +next_hash_element: + continue; + } + + tp_base_contact_list_one_contact_groups_changed (contact_list, contact, + (const gchar * const *) new_names->pdata, new_names->len, + (const gchar * const *) old_names->pdata, old_names->len); + g_ptr_array_unref (old_names); + g_ptr_array_unref (new_names); + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_set_contact_groups_async); +} + +static gboolean +receive_contact_lists (gpointer p) +{ + TpBaseContactList *contact_list = p; + ExampleContactList *self = p; + TpHandle handle; + gchar *cambridge, *montreal, *francophones; + ExampleContactDetails *d; + GHashTableIter iter; + gpointer handle_p; + + if (self->priv->all_tags == NULL) + { + /* connection already disconnected, so don't process the + * "data from the server" */ + return FALSE; + } + + /* In a real CM we'd have received a contact list from the server at this + * point. But this isn't a real CM, so we have to make one up... */ + + g_message ("Receiving roster from server"); + + cambridge = ensure_tag (self, "Cambridge", FALSE); + montreal = ensure_tag (self, "Montreal", FALSE); + francophones = ensure_tag (self, "Francophones", FALSE); + + /* Add various people who are already subscribing and publishing */ + + handle = tp_handle_ensure (self->priv->contact_repo, "sjoerd@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Sjoerd"); + d->subscribe = TRUE; + d->publish = TRUE; + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (d->tags, cambridge, cambridge); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "guillaume@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Guillaume"); + d->subscribe = TRUE; + d->publish = TRUE; + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (d->tags, cambridge, cambridge); + g_hash_table_insert (d->tags, francophones, francophones); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "olivier@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Olivier"); + d->subscribe = TRUE; + d->publish = TRUE; + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (d->tags, montreal, montreal); + g_hash_table_insert (d->tags, francophones, francophones); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "travis@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Travis"); + d->subscribe = TRUE; + d->publish = TRUE; + tp_handle_unref (self->priv->contact_repo, handle); + + /* Add a couple of people whose presence we've requested. They are + * remote-pending in subscribe */ + + handle = tp_handle_ensure (self->priv->contact_repo, "geraldine@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Géraldine"); + d->subscribe_requested = TRUE; + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (d->tags, cambridge, cambridge); + g_hash_table_insert (d->tags, francophones, francophones); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "helen@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Helen"); + d->subscribe_requested = TRUE; + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (d->tags, cambridge, cambridge); + tp_handle_unref (self->priv->contact_repo, handle); + + /* Receive a couple of authorization requests too. These people are + * local-pending in publish; they're not actually on our protocol-level + * contact list */ + + handle = tp_handle_ensure (self->priv->contact_repo, "wim@example.com", + NULL, NULL); + tp_handle_set_add (self->priv->contacts, handle); + g_hash_table_insert (self->priv->publish_requests, + GUINT_TO_POINTER (handle), g_strdup ("I'm more metal than you!")); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "christian@example.com", + NULL, NULL); + tp_handle_set_add (self->priv->contacts, handle); + g_hash_table_insert (self->priv->publish_requests, + GUINT_TO_POINTER (handle), + g_strdup ("I have some fermented herring for you")); + tp_handle_unref (self->priv->contact_repo, handle); + + /* Add a couple of blocked contacts. */ + handle = tp_handle_ensure (self->priv->contact_repo, "bill@example.com", + NULL, NULL); + tp_handle_set_add (self->priv->blocked_contacts, handle); + tp_handle_unref (self->priv->contact_repo, handle); + handle = tp_handle_ensure (self->priv->contact_repo, "steve@example.com", + NULL, NULL); + tp_handle_set_add (self->priv->blocked_contacts, handle); + tp_handle_unref (self->priv->contact_repo, handle); + + g_hash_table_iter_init (&iter, self->priv->contact_details); + + /* emit initial aliases, presences */ + while (g_hash_table_iter_next (&iter, &handle_p, NULL)) + { + handle = GPOINTER_TO_UINT (handle_p); + + g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle); + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle); + } + + /* ... and off we go */ + tp_base_contact_list_set_list_received (contact_list); + + return FALSE; +} + +static void +status_changed_cb (TpBaseConnection *conn, + guint status, + guint reason, + ExampleContactList *self) +{ + switch (status) + { + case TP_CONNECTION_STATUS_CONNECTED: + { + /* Do network I/O to get the contact list. This connection manager + * doesn't really have a server, so simulate a small network delay + * then invent a contact list */ + tp_base_contact_list_set_list_pending ((TpBaseContactList *) self); + + g_timeout_add_full (G_PRIORITY_DEFAULT, + 2 * self->priv->simulation_delay, receive_contact_lists, + g_object_ref (self), g_object_unref); + } + break; + + case TP_CONNECTION_STATUS_DISCONNECTED: + { + example_contact_list_close_all (self); + + tp_clear_object (&self->priv->conn); + } + break; + } +} + +static void +constructed (GObject *object) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) example_contact_list_parent_class)->constructed; + + if (chain_up != NULL) + { + chain_up (object); + } + + g_object_get (self, + "connection", &self->priv->conn, + NULL); + g_assert (self->priv->conn != NULL); + + self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn, + TP_HANDLE_TYPE_CONTACT); + self->priv->contacts = tp_handle_set_new (self->priv->contact_repo); + self->priv->blocked_contacts = tp_handle_set_new (self->priv->contact_repo); + self->priv->cancelled_publish_requests = tp_handle_set_new ( + self->priv->contact_repo); + + self->priv->status_changed_id = g_signal_connect (self->priv->conn, + "status-changed", (GCallback) status_changed_cb, self); +} + +static void +send_updated_roster (ExampleContactList *self, + TpHandle contact) +{ + ExampleContactDetails *d = g_hash_table_lookup (self->priv->contact_details, + GUINT_TO_POINTER (contact)); + const gchar *request = g_hash_table_lookup (self->priv->publish_requests, + GUINT_TO_POINTER (contact)); + const gchar *identifier = tp_handle_inspect (self->priv->contact_repo, + contact); + + /* In a real connection manager, we'd transmit these new details to the + * server, rather than just printing messages. */ + + if (d == NULL) + { + g_message ("Deleting contact %s from server", identifier); + } + else + { + g_message ("Transmitting new state of contact %s to server", identifier); + g_message ("\talias = %s", d->alias); + g_message ("\tcan see our presence = %s", + d->publish ? "yes" : + (request != NULL ? "no, but has requested it" : "no")); + g_message ("\tsends us presence = %s", + d->subscribe ? "yes" : + (d->subscribe_requested ? "no, but we have requested it" : + (d->subscribe_rejected ? "no, request refused" : "no"))); + + if (d->tags == NULL || g_hash_table_size (d->tags) == 0) + { + g_message ("\tnot in any groups"); + } + else + { + GHashTableIter iter; + gpointer k; + + g_hash_table_iter_init (&iter, d->tags); + + while (g_hash_table_iter_next (&iter, &k, NULL)) + { + g_message ("\tin group: %s", (gchar *) k); + } + } + } +} + +static void +example_contact_list_set_group_members_async (TpBaseContactList *contact_list, + const gchar *group, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *new_contacts = tp_handle_set_new (self->priv->contact_repo); + TpHandleSet *added = tp_handle_set_new (self->priv->contact_repo); + TpHandleSet *removed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + gchar *tag = ensure_tag (self, group, TRUE); + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + gboolean created = FALSE, updated = FALSE; + ExampleContactDetails *d = ensure_contact (self, member, &created); + + if (created) + tp_handle_set_add (new_contacts, member); + + if (d->tags == NULL) + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + + if (g_hash_table_lookup (d->tags, tag) == NULL) + { + g_hash_table_insert (d->tags, tag, tag); + updated = TRUE; + } + + if (created || updated) + { + send_updated_roster (self, member); + tp_handle_set_add (added, member); + } + } + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d; + + if (tp_handle_set_is_member (contacts, member)) + continue; + + d = lookup_contact (self, member); + + if (d != NULL && d->tags != NULL && g_hash_table_remove (d->tags, group)) + tp_handle_set_add (removed, member); + } + + if (!tp_handle_set_is_empty (new_contacts)) + tp_base_contact_list_contacts_changed (contact_list, new_contacts, NULL); + + if (!tp_handle_set_is_empty (added)) + tp_base_contact_list_groups_changed (contact_list, added, &group, 1, + NULL, 0); + + if (!tp_handle_set_is_empty (removed)) + tp_base_contact_list_groups_changed (contact_list, removed, NULL, 0, + &group, 1); + + tp_handle_set_destroy (added); + tp_handle_set_destroy (removed); + tp_handle_set_destroy (new_contacts); + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_set_group_members_async); +} + +static void +example_contact_list_add_to_group_async (TpBaseContactList *contact_list, + const gchar *group, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *new_contacts = tp_handle_set_new (self->priv->contact_repo); + TpHandleSet *new_to_group = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + gchar *tag = ensure_tag (self, group, TRUE); + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + gboolean created = FALSE, updated = FALSE; + ExampleContactDetails *d = ensure_contact (self, member, &created); + + if (created) + tp_handle_set_add (new_contacts, member); + + if (d->tags == NULL) + d->tags = g_hash_table_new (g_str_hash, g_str_equal); + + if (g_hash_table_lookup (d->tags, tag) == NULL) + { + g_hash_table_insert (d->tags, tag, tag); + updated = TRUE; + } + + if (created || updated) + { + send_updated_roster (self, member); + tp_handle_set_add (new_to_group, member); + } + } + + if (!tp_handle_set_is_empty (new_contacts)) + tp_base_contact_list_contacts_changed (contact_list, new_contacts, NULL); + + if (!tp_handle_set_is_empty (new_to_group)) + tp_base_contact_list_groups_changed (contact_list, new_to_group, &group, 1, + NULL, 0); + + tp_handle_set_destroy (new_to_group); + tp_handle_set_destroy (new_contacts); + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_add_to_group_async); +} + +static void +example_contact_list_remove_from_group_async (TpBaseContactList *contact_list, + const gchar *group, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d = lookup_contact (self, member); + + /* If not on the roster or not in any groups, we have nothing to do */ + if (d != NULL && d->tags != NULL && g_hash_table_remove (d->tags, group)) + { + send_updated_roster (self, member); + tp_handle_set_add (changed, member); + } + } + + if (!tp_handle_set_is_empty (changed)) + tp_base_contact_list_groups_changed (contact_list, changed, NULL, 0, + &group, 1); + + tp_handle_set_destroy (changed); + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_remove_from_group_async); +} + +typedef struct { + ExampleContactList *self; + TpHandle contact; +} SelfAndContact; + +static SelfAndContact * +self_and_contact_new (ExampleContactList *self, + TpHandle contact) +{ + SelfAndContact *ret = g_slice_new0 (SelfAndContact); + + ret->self = g_object_ref (self); + ret->contact = tp_handle_ref (self->priv->contact_repo, contact); + return ret; +} + +static void +self_and_contact_destroy (gpointer p) +{ + SelfAndContact *s = p; + + tp_handle_unref (s->self->priv->contact_repo, s->contact); + g_object_unref (s->self); + g_slice_free (SelfAndContact, s); +} + +static void +receive_auth_request (ExampleContactList *self, + TpHandle contact) +{ + ExampleContactDetails *d; + + /* if shutting down, do nothing */ + if (self->priv->conn == NULL) + return; + + /* A remote contact has asked to see our presence. + * + * In a real connection manager this would be the result of incoming + * data from the server. */ + + g_message ("From server: %s has sent us a publish request", + tp_handle_inspect (self->priv->contact_repo, contact)); + + d = lookup_contact (self, contact); + + if (d != NULL && d->publish) + return; + + if (d != NULL && d->pre_approved) + { + /* the user already said yes, no need to signal anything */ + g_message ("... this publish request was already approved"); + d->pre_approved = FALSE; + d->publish = TRUE; + g_hash_table_remove (self->priv->publish_requests, + GUINT_TO_POINTER (contact)); + tp_handle_set_remove (self->priv->cancelled_publish_requests, contact); + send_updated_roster (self, contact); + } + else + { + tp_handle_set_add (self->priv->contacts, contact); + g_hash_table_insert (self->priv->publish_requests, + GUINT_TO_POINTER (contact), + g_strdup ("May I see your presence, please?")); + } + + tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self, + contact); + + /* If the contact has a name ending with "@cancel.something", they + * immediately take it back; this is mainly for the regression test. */ + if (strstr (tp_handle_inspect (self->priv->contact_repo, contact), + "@cancel.") != NULL) + { + g_message ("From server: %s has cancelled their publish request", + tp_handle_inspect (self->priv->contact_repo, contact)); + + d->publish = FALSE; + d->pre_approved = FALSE; + g_hash_table_remove (self->priv->publish_requests, + GUINT_TO_POINTER (contact)); + tp_handle_set_add (self->priv->cancelled_publish_requests, contact); + + tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self, + contact); + } +} + +static gboolean +receive_authorized (gpointer p) +{ + SelfAndContact *s = p; + ExampleContactDetails *d; + + /* if shutting down, do nothing */ + if (s->self->priv->conn == NULL) + return FALSE; + + /* A remote contact has accepted our request to see their presence. + * + * In a real connection manager this would be the result of incoming + * data from the server. */ + + g_message ("From server: %s has accepted our subscribe request", + tp_handle_inspect (s->self->priv->contact_repo, s->contact)); + + d = ensure_contact (s->self, s->contact, NULL); + + /* if we were already subscribed to them, then nothing really happened */ + if (d->subscribe) + return FALSE; + + d->subscribe_requested = FALSE; + d->subscribe_rejected = FALSE; + d->subscribe = TRUE; + + tp_base_contact_list_one_contact_changed ((TpBaseContactList *) s->self, + s->contact); + + /* their presence changes to something other than UNKNOWN */ + g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact); + + /* if we're not publishing to them, also pretend they have asked us to + * do so */ + if (!d->publish) + { + receive_auth_request (s->self, s->contact); + } + + return FALSE; +} + +static gboolean +receive_unauthorized (gpointer p) +{ + SelfAndContact *s = p; + ExampleContactDetails *d; + + /* if shutting down, do nothing */ + if (s->self->priv->conn == NULL) + return FALSE; + + /* A remote contact has rejected our request to see their presence. + * + * In a real connection manager this would be the result of incoming + * data from the server. */ + + g_message ("From server: %s has rejected our subscribe request", + tp_handle_inspect (s->self->priv->contact_repo, s->contact)); + + d = ensure_contact (s->self, s->contact, NULL); + + if (!d->subscribe && !d->subscribe_requested) + return FALSE; + + d->subscribe_requested = FALSE; + d->subscribe_rejected = TRUE; + d->subscribe = FALSE; + + tp_base_contact_list_one_contact_changed ((TpBaseContactList *) s->self, + s->contact); + + /* their presence changes to UNKNOWN */ + g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact); + + return FALSE; +} + +static gboolean +auth_request_cb (gpointer p) +{ + SelfAndContact *s = p; + + receive_auth_request (s->self, s->contact); + + return FALSE; +} + +ExampleContactListPresence +example_contact_list_get_presence (ExampleContactList *self, + TpHandle contact) +{ + ExampleContactDetails *d = lookup_contact (self, contact); + const gchar *id; + + if (d == NULL || !d->subscribe) + { + /* we don't know the presence of people not on the subscribe list, + * by definition */ + return EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN; + } + + id = tp_handle_inspect (self->priv->contact_repo, contact); + + /* In this example CM, we fake contacts' presence based on their name: + * contacts in the first half of the alphabet are available, the rest + * (including non-alphabetic and non-ASCII initial letters) are away. */ + if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm')) + { + return EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE; + } + + return EXAMPLE_CONTACT_LIST_PRESENCE_AWAY; +} + +const gchar * +example_contact_list_get_alias (ExampleContactList *self, + TpHandle contact) +{ + ExampleContactDetails *d = lookup_contact (self, contact); + + if (d == NULL) + { + /* we don't have a user-defined alias for people not on the roster */ + return tp_handle_inspect (self->priv->contact_repo, contact); + } + + return d->alias; +} + +void +example_contact_list_set_alias (ExampleContactList *self, + TpHandle contact, + const gchar *alias) +{ + gboolean created; + ExampleContactDetails *d; + gchar *old; + + /* if shutting down, do nothing */ + if (self->priv->conn == NULL) + return; + + d = ensure_contact (self, contact, &created); + + if (created) + { + tp_base_contact_list_one_contact_changed ( + (TpBaseContactList *) self, contact); + } + + /* FIXME: if stored list hasn't been retrieved yet, queue the change for + * later */ + + old = d->alias; + d->alias = g_strdup (alias); + + if (created || tp_strdiff (old, alias)) + send_updated_roster (self, contact); + + g_free (old); +} + +static TpHandleSet * +example_contact_list_dup_contacts (TpBaseContactList *contact_list) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + + return tp_handle_set_copy (self->priv->contacts); +} + +static TpHandleSet * +example_contact_list_dup_group_members (TpBaseContactList *contact_list, + const gchar *group) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpIntsetFastIter iter; + TpHandle member; + TpHandleSet *members = tp_handle_set_new (self->priv->contact_repo); + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d = lookup_contact (self, member); + + if (d != NULL && d->tags != NULL && + g_hash_table_lookup_extended (d->tags, group, NULL, NULL)) + tp_handle_set_add (members, member); + } + + return members; +} + +static const ExampleContactDetails no_details = { + NULL, + FALSE, + FALSE, + FALSE, + FALSE, + FALSE, + NULL +}; + +static inline TpSubscriptionState +compose_presence (gboolean full, + gboolean ask, + gboolean removed_remotely) +{ + if (full) + return TP_SUBSCRIPTION_STATE_YES; + else if (ask) + return TP_SUBSCRIPTION_STATE_ASK; + else if (removed_remotely) + return TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY; + else + return TP_SUBSCRIPTION_STATE_NO; +} + +static void +example_contact_list_dup_states (TpBaseContactList *contact_list, + TpHandle contact, + TpSubscriptionState *subscribe, + TpSubscriptionState *publish, + gchar **publish_request) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + const ExampleContactDetails *details = lookup_contact (self, contact); + const gchar *request = g_hash_table_lookup (self->priv->publish_requests, + GUINT_TO_POINTER (contact)); + + if (details == NULL) + details = &no_details; + + if (subscribe != NULL) + *subscribe = compose_presence (details->subscribe, + details->subscribe_requested, details->subscribe_rejected); + + if (publish != NULL) + *publish = compose_presence (details->publish, (request != NULL), + tp_handle_set_is_member (self->priv->cancelled_publish_requests, + contact)); + + if (publish_request != NULL) + *publish_request = g_strdup (request); +} + +static void +example_contact_list_request_subscription_async ( + TpBaseContactList *contact_list, + TpHandleSet *contacts, + const gchar *message, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + gboolean created; + ExampleContactDetails *d = ensure_contact (self, member, &created); + gchar *message_lc; + + /* if they already authorized us, it's a no-op */ + if (d->subscribe) + continue; + + /* In a real connection manager we'd start a network request here */ + g_message ("Transmitting authorization request to %s: %s", + tp_handle_inspect (self->priv->contact_repo, member), + message); + + tp_handle_set_add (changed, member); + d->subscribe_rejected = FALSE; + d->subscribe_requested = TRUE; + send_updated_roster (self, member); + + /* Pretend that after a delay, the contact notices the request + * and allows or rejects it. In this example connection manager, + * empty requests are allowed, as are requests that contain "please" + * case-insensitively. All other requests are denied. */ + message_lc = g_ascii_strdown (message, -1); + + if (message[0] == '\0' || strstr (message_lc, "please") != NULL) + { + g_timeout_add_full (G_PRIORITY_LOW, + self->priv->simulation_delay, receive_authorized, + self_and_contact_new (self, member), + self_and_contact_destroy); + } + else + { + g_timeout_add_full (G_PRIORITY_LOW, + self->priv->simulation_delay, + receive_unauthorized, + self_and_contact_new (self, member), + self_and_contact_destroy); + } + + g_free (message_lc); + } + + tp_base_contact_list_contacts_changed (contact_list, changed, NULL); + + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_request_subscription_async); +} + +static void +example_contact_list_authorize_publication_async ( + TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d = ensure_contact (self, member, NULL); + const gchar *request = g_hash_table_lookup (self->priv->publish_requests, + GUINT_TO_POINTER (member)); + + if (tp_handle_set_remove (self->priv->cancelled_publish_requests, + member)) + tp_handle_set_add (changed, member); + + /* We would like member to see our presence. In this simulated protocol, + * this is meaningless, unless they have asked for it; but we can still + * remember the pre-authorization in case they ask later. */ + if (request == NULL) + { + d->pre_approved = TRUE; + } + else if (!d->publish) + { + d->publish = TRUE; + g_hash_table_remove (self->priv->publish_requests, + GUINT_TO_POINTER (member)); + send_updated_roster (self, member); + tp_handle_set_add (changed, member); + } + } + + tp_base_contact_list_contacts_changed (contact_list, changed, NULL); + + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_authorize_publication_async); +} + +static void +example_contact_list_store_contacts_async ( + TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + /* We would like member to be on the roster, but nothing more. */ + + if (lookup_contact (self, member) == NULL) + { + gboolean created; + + ensure_contact (self, member, &created); + send_updated_roster (self, member); + + /* If we'd had a publish request from this member, then adding them + * to the protocol-level contact list doesn't actually cause a + * state change visible on Telepathy. */ + if (created) + tp_handle_set_add (changed, member); + } + } + + tp_base_contact_list_contacts_changed (contact_list, changed, NULL); + + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_store_contacts_async); +} + +static void +example_contact_list_remove_contacts_async (TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *removed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + /* we would like to remove member from the roster altogether */ + if (lookup_contact (self, member) != NULL + || g_hash_table_lookup (self->priv->publish_requests, + GUINT_TO_POINTER (member)) != NULL + || tp_handle_set_is_member (self->priv->cancelled_publish_requests, + member)) + { + tp_handle_set_add (removed, member); + + g_hash_table_remove (self->priv->contact_details, + GUINT_TO_POINTER (member)); + g_hash_table_remove (self->priv->publish_requests, + GUINT_TO_POINTER (member)); + tp_handle_set_remove (self->priv->contacts, member); + tp_handle_set_remove (self->priv->cancelled_publish_requests, + member); + + send_updated_roster (self, member); + + /* since they're no longer on the subscribe list, we can't + * see their presence, so emit a signal changing it to + * UNKNOWN */ + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member); + } + } + + tp_base_contact_list_contacts_changed (contact_list, NULL, removed); + + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_remove_contacts_async); +} + +static void +example_contact_list_unsubscribe_async (TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d = lookup_contact (self, member); + + /* we would like to avoid receiving member's presence any more, + * or we would like to cancel an outstanding request for their + * presence */ + + if (d != NULL) + { + if (d->subscribe_requested) + { + g_message ("Cancelling our authorization request to %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->subscribe_requested = FALSE; + + tp_handle_set_add (changed, member); + send_updated_roster (self, member); + } + else if (d->subscribe_rejected) + { + g_message ("Forgetting rejected authorization request to %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->subscribe_rejected = FALSE; + + tp_handle_set_add (changed, member); + send_updated_roster (self, member); + } + else if (d->subscribe) + { + g_message ("We no longer want presence from %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->subscribe = FALSE; + + /* since they're no longer on the subscribe list, we can't + * see their presence, so emit a signal changing it to + * UNKNOWN */ + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member); + + tp_handle_set_add (changed, member); + send_updated_roster (self, member); + } + } + } + + tp_base_contact_list_contacts_changed (contact_list, changed, NULL); + + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_unsubscribe_async); +} + +static void +example_contact_list_unpublish_async (TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpHandleSet *removed = tp_handle_set_new (self->priv->contact_repo); + TpIntsetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d = lookup_contact (self, member); + const gchar *request = g_hash_table_lookup (self->priv->publish_requests, + GUINT_TO_POINTER (member)); + + /* we would like member not to see our presence any more, or we + * would like to reject a request from them to see our presence */ + + if (request != NULL) + { + g_message ("Rejecting authorization request from %s", + tp_handle_inspect (self->priv->contact_repo, member)); + g_hash_table_remove (self->priv->publish_requests, + GUINT_TO_POINTER (member)); + + if (d == NULL) + { + /* the contact wasn't actually on our protocol-level contact + * list, only on the Telepathy-level contact list, so rejecting + * authorization makes them disappear */ + tp_handle_set_add (removed, member); + } + else + { + tp_handle_set_add (changed, member); + } + } + + if (tp_handle_set_remove (self->priv->cancelled_publish_requests, + member)) + { + g_message ("Acknowledging remotely-cancelled publish request"); + tp_handle_set_add (changed, member); + } + + if (d != NULL) + { + d->pre_approved = FALSE; + + if (d->publish) + { + g_message ("Removing authorization from %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->publish = FALSE; + tp_handle_set_add (changed, member); + send_updated_roster (self, member); + + /* Pretend that after a delay, the contact notices the change + * and asks for our presence again */ + g_timeout_add_full (G_PRIORITY_LOW, + self->priv->simulation_delay, auth_request_cb, + self_and_contact_new (self, member), + self_and_contact_destroy); + } + } + } + + tp_base_contact_list_contacts_changed (contact_list, changed, removed); + + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_unpublish_async); +} + +static TpHandleSet * +example_contact_list_dup_blocked_contacts (TpBaseContactList *contact_list) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + + return tp_handle_set_copy (self->priv->blocked_contacts); +} + +static void +example_contact_list_block_contacts_async (TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpIntsetFastIter iter; + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + if (!tp_handle_set_is_member (self->priv->blocked_contacts, member)) + { + g_message ("Adding contact %s to blocked list", + tp_handle_inspect (self->priv->contact_repo, member)); + tp_handle_set_add (self->priv->blocked_contacts, member); + tp_handle_set_add (changed, member); + } + } + + tp_base_contact_list_contact_blocking_changed (contact_list, changed); + tp_handle_set_destroy (changed); + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_block_contacts_async); +} + +static void +example_contact_list_unblock_contacts_async (TpBaseContactList *contact_list, + TpHandleSet *contacts, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpIntsetFastIter iter; + TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo); + TpHandle member; + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + if (tp_handle_set_remove (self->priv->blocked_contacts, member)) + { + g_message ("Removing contact %s from blocked list", + tp_handle_inspect (self->priv->contact_repo, member)); + tp_handle_set_add (changed, member); + } + } + + tp_base_contact_list_contact_blocking_changed (contact_list, changed); + tp_handle_set_destroy (changed); + tp_simple_async_report_success_in_idle ((GObject *) self, callback, + user_data, example_contact_list_unblock_contacts_async); +} + +static guint +example_contact_list_get_group_storage ( + TpBaseContactList *contact_list G_GNUC_UNUSED) +{ + return TP_CONTACT_METADATA_STORAGE_TYPE_ANYONE; +} + +static GStrv +example_contact_list_dup_groups (TpBaseContactList *contact_list) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + GPtrArray *tags = g_ptr_array_sized_new ( + g_hash_table_size (self->priv->all_tags) + 1); + GHashTableIter iter; + gpointer tag; + + g_hash_table_iter_init (&iter, self->priv->all_tags); + + while (g_hash_table_iter_next (&iter, &tag, NULL)) + g_ptr_array_add (tags, g_strdup (tag)); + + g_ptr_array_add (tags, NULL); + return (GStrv) g_ptr_array_free (tags, FALSE); +} + +static GStrv +example_contact_list_dup_contact_groups (TpBaseContactList *contact_list, + TpHandle contact) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + GPtrArray *tags = g_ptr_array_sized_new ( + g_hash_table_size (self->priv->all_tags) + 1); + ExampleContactDetails *d = lookup_contact (self, contact); + + if (d != NULL && d->tags != NULL) + { + GHashTableIter iter; + gpointer tag; + + g_hash_table_iter_init (&iter, d->tags); + + while (g_hash_table_iter_next (&iter, &tag, NULL)) + g_ptr_array_add (tags, g_strdup (tag)); + } + + g_ptr_array_add (tags, NULL); + return (GStrv) g_ptr_array_free (tags, FALSE); +} + +static void +example_contact_list_remove_group_async (TpBaseContactList *contact_list, + const gchar *group, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + TpIntsetFastIter iter; + TpHandle member; + + /* signal the deletion */ + g_message ("deleting group %s", group); + tp_base_contact_list_groups_removed (contact_list, &group, 1); + + /* apply the change to our model of the contacts too; we don't need to signal + * the change, because TpBaseContactList already did */ + + tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->contacts)); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + ExampleContactDetails *d = lookup_contact (self, member); + + if (d != NULL && d->tags != NULL) + g_hash_table_remove (d->tags, group); + } + + tp_simple_async_report_success_in_idle ((GObject *) contact_list, callback, + user_data, example_contact_list_remove_group_async); +} + +static gchar * +example_contact_list_normalize_group (TpBaseContactList *contact_list, + const gchar *id) +{ + if (id[0] == '\0') + return NULL; + + return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE); +} + +static void +example_contact_list_rename_group_async (TpBaseContactList *contact_list, + const gchar *old_name, + const gchar *new_name, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list); + gchar *tag = ensure_tag (self, new_name, FALSE); + GHashTableIter iter; + gpointer v; + + /* signal the rename */ + g_print ("renaming group %s to %s", old_name, new_name); + tp_base_contact_list_group_renamed (contact_list, old_name, new_name); + + /* update our model (this doesn't need to signal anything because + * TpBaseContactList already did) */ + + g_hash_table_iter_init (&iter, self->priv->contact_details); + + while (g_hash_table_iter_next (&iter, NULL, &v)) + { + ExampleContactDetails *d = v; + + if (d->tags != NULL && g_hash_table_remove (d->tags, old_name)) + g_hash_table_insert (d->tags, tag, tag); + } + + tp_simple_async_report_success_in_idle ((GObject *) contact_list, callback, + user_data, example_contact_list_rename_group_async); +} + +static void +example_contact_list_class_init (ExampleContactListClass *klass) +{ + TpBaseContactListClass *contact_list_class = + (TpBaseContactListClass *) klass; + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + g_object_class_install_property (object_class, PROP_SIMULATION_DELAY, + g_param_spec_uint ("simulation-delay", "Simulation delay", + "Delay between simulated network events", + 0, G_MAXUINT32, 1000, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + contact_list_class->dup_contacts = example_contact_list_dup_contacts; + contact_list_class->dup_states = example_contact_list_dup_states; + /* for this example CM we pretend there is a server-stored contact list, + * like in XMPP, even though there obviously isn't really */ + contact_list_class->get_contact_list_persists = + tp_base_contact_list_true_func; + + g_type_class_add_private (klass, sizeof (ExampleContactListPrivate)); + + signals[ALIAS_UPDATED] = g_signal_new ("alias-updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); + + signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); +} + +static void +mutable_contact_list_iface_init (TpMutableContactListInterface *iface) +{ + iface->can_change_contact_list = tp_base_contact_list_true_func; + iface->get_request_uses_message = tp_base_contact_list_true_func; + iface->request_subscription_async = + example_contact_list_request_subscription_async; + iface->authorize_publication_async = + example_contact_list_authorize_publication_async; + iface->store_contacts_async = example_contact_list_store_contacts_async; + iface->remove_contacts_async = example_contact_list_remove_contacts_async; + iface->unsubscribe_async = example_contact_list_unsubscribe_async; + iface->unpublish_async = example_contact_list_unpublish_async; +} + +static void +blockable_contact_list_iface_init (TpBlockableContactListInterface *iface) +{ + iface->can_block = tp_base_contact_list_true_func; + iface->dup_blocked_contacts = example_contact_list_dup_blocked_contacts; + iface->block_contacts_async = example_contact_list_block_contacts_async; + iface->unblock_contacts_async = example_contact_list_unblock_contacts_async; +} + +static void +contact_group_list_iface_init (TpContactGroupListInterface *iface) +{ + iface->dup_groups = example_contact_list_dup_groups; + iface->dup_group_members = example_contact_list_dup_group_members; + iface->dup_contact_groups = example_contact_list_dup_contact_groups; + iface->normalize_group = example_contact_list_normalize_group; +} + +static void +mutable_contact_group_list_iface_init ( + TpMutableContactGroupListInterface *iface) +{ + iface->set_group_members_async = + example_contact_list_set_group_members_async; + iface->add_to_group_async = example_contact_list_add_to_group_async; + iface->remove_from_group_async = + example_contact_list_remove_from_group_async; + iface->remove_group_async = example_contact_list_remove_group_async; + iface->rename_group_async = example_contact_list_rename_group_async; + iface->set_contact_groups_async = + example_contact_list_set_contact_groups_async; + iface->get_group_storage = example_contact_list_get_group_storage; +} diff --git a/tests/lib/glib/contactlist2/contact-list.h b/tests/lib/glib/contactlist2/contact-list.h new file mode 100644 index 00000000..06f83bd8 --- /dev/null +++ b/tests/lib/glib/contactlist2/contact-list.h @@ -0,0 +1,78 @@ +/* + * Example channel manager for contact lists + * + * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#ifndef __EXAMPLE_CONTACT_LIST_H__ +#define __EXAMPLE_CONTACT_LIST_H__ + +#include <glib-object.h> + +#include <telepathy-glib/base-contact-list.h> +#include <telepathy-glib/channel-manager.h> +#include <telepathy-glib/handle.h> +#include <telepathy-glib/presence-mixin.h> + +G_BEGIN_DECLS + +typedef struct _ExampleContactList ExampleContactList; +typedef struct _ExampleContactListClass ExampleContactListClass; +typedef struct _ExampleContactListPrivate ExampleContactListPrivate; + +struct _ExampleContactListClass { + TpBaseContactListClass parent_class; +}; + +struct _ExampleContactList { + TpBaseContactList parent; + + ExampleContactListPrivate *priv; +}; + +GType example_contact_list_get_type (void); + +#define EXAMPLE_TYPE_CONTACT_LIST \ + (example_contact_list_get_type ()) +#define EXAMPLE_CONTACT_LIST(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST, \ + ExampleContactList)) +#define EXAMPLE_CONTACT_LIST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST, \ + ExampleContactListClass)) +#define EXAMPLE_IS_CONTACT_LIST(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST)) +#define EXAMPLE_IS_CONTACT_LIST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST)) +#define EXAMPLE_CONTACT_LIST_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST, \ + ExampleContactListClass)) + +/* this enum must be kept in sync with the array _statuses in + * contact-list.c */ +typedef enum { + EXAMPLE_CONTACT_LIST_PRESENCE_OFFLINE = 0, + EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN, + EXAMPLE_CONTACT_LIST_PRESENCE_ERROR, + EXAMPLE_CONTACT_LIST_PRESENCE_AWAY, + EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE +} ExampleContactListPresence; + +const TpPresenceStatusSpec *example_contact_list_presence_statuses ( + void); + +ExampleContactListPresence example_contact_list_get_presence ( + ExampleContactList *self, TpHandle contact); +const gchar *example_contact_list_get_alias ( + ExampleContactList *self, TpHandle contact); +void example_contact_list_set_alias ( + ExampleContactList *self, TpHandle contact, const gchar *alias); + +G_END_DECLS + +#endif diff --git a/tests/lib/glib/contactlist2/example_contact_list.manager b/tests/lib/glib/contactlist2/example_contact_list.manager new file mode 100644 index 00000000..379d822b --- /dev/null +++ b/tests/lib/glib/contactlist2/example_contact_list.manager @@ -0,0 +1,23 @@ +[ConnectionManager] +Interfaces= + +[Protocol example] +Interfaces= +ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.Requests;org.freedesktop.Telepathy.Connection.Interface.Contacts;org.freedesktop.Telepathy.Connection.Interface.Presence;org.freedesktop.Telepathy.Connection.Interface.SimplePresence; +param-account=s required register +param-simulation-delay=u +default-simulation-delay=1000 +RequestableChannelClasses=contactlist;contactgroup; +VCardField=x-telepathy-example +EnglishName=Example with a contact list +Icon=face-smile + +[contactlist] +org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.ContactList +org.freedesktop.Telepathy.Channel.TargetHandleType u=3 +allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID; + +[contactgroup] +org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.ContactList +org.freedesktop.Telepathy.Channel.TargetHandleType u=4 +allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID; diff --git a/tests/lib/glib/contactlist2/protocol.c b/tests/lib/glib/contactlist2/protocol.c new file mode 100644 index 00000000..66fc200f --- /dev/null +++ b/tests/lib/glib/contactlist2/protocol.c @@ -0,0 +1,186 @@ +/* + * protocol.c - an example Protocol + * + * Copyright © 2007-2010 Collabora Ltd. + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#include "protocol.h" + +#include <telepathy-glib/telepathy-glib.h> + +#include "conn.h" +#include "contact-list.h" + +G_DEFINE_TYPE (ExampleContactListProtocol, + example_contact_list_protocol, + TP_TYPE_BASE_PROTOCOL) + +static void +example_contact_list_protocol_init ( + ExampleContactListProtocol *self) +{ +} + +gboolean +example_contact_list_protocol_check_contact_id (const gchar *id, + gchar **normal, + GError **error) +{ + g_return_val_if_fail (id != NULL, FALSE); + + if (id[0] == '\0') + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE, + "ID must not be empty"); + return FALSE; + } + + if (normal != NULL) + *normal = g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE); + + return TRUE; +} + +static gboolean +account_param_filter (const TpCMParamSpec *paramspec, + GValue *value, + GError **error) +{ + const gchar *id = g_value_get_string (value); + + return example_contact_list_protocol_check_contact_id (id, NULL, error); +} + +static const TpCMParamSpec example_contact_list_example_params[] = { + { "account", "s", G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER, + NULL, /* no default */ + 0, /* unused, formerly struct offset */ + account_param_filter, + NULL, /* filter data, unused here */ + NULL }, /* setter data, now unused */ + { "simulation-delay", "u", G_TYPE_UINT, + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, + GUINT_TO_POINTER (1000), /* default */ + 0, /* unused, formerly struct offset */ + NULL, /* no filter */ + NULL, /* filter data, unused here */ + NULL }, /* setter data, now unused */ + { NULL } +}; + +static const TpCMParamSpec * +get_parameters (TpBaseProtocol *self) +{ + return example_contact_list_example_params; +} + +static TpBaseConnection * +new_connection (TpBaseProtocol *protocol, + GHashTable *asv, + GError **error) +{ + ExampleContactListConnection *conn; + const gchar *account; + guint sim_delay; + + account = tp_asv_get_string (asv, "account"); + /* telepathy-glib checked this for us */ + g_assert (account != NULL); + + sim_delay = tp_asv_get_uint32 (asv, "simulation-delay", NULL); + + conn = EXAMPLE_CONTACT_LIST_CONNECTION ( + g_object_new (EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, + "account", account, + "protocol", tp_base_protocol_get_name (protocol), + "simulation-delay", sim_delay, + NULL)); + + return (TpBaseConnection *) conn; +} + +static gchar * +normalize_contact (TpBaseProtocol *self G_GNUC_UNUSED, + const gchar *contact, + GError **error) +{ + gchar *normal; + + if (example_contact_list_protocol_check_contact_id (contact, &normal, error)) + return normal; + else + return NULL; +} + +static gchar * +identify_account (TpBaseProtocol *self G_GNUC_UNUSED, + GHashTable *asv, + GError **error) +{ + const gchar *account = tp_asv_get_string (asv, "account"); + + if (account != NULL) + return normalize_contact (self, account, error); + + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'account' parameter not given"); + return NULL; +} + +static GStrv +get_interfaces (TpBaseProtocol *self) +{ + return NULL; +} + +static void +get_connection_details (TpBaseProtocol *self G_GNUC_UNUSED, + GStrv *connection_interfaces, + GType **channel_managers, + gchar **icon_name, + gchar **english_name, + gchar **vcard_field) +{ + if (connection_interfaces != NULL) + { + *connection_interfaces = g_strdupv ( + (GStrv) example_contact_list_connection_get_possible_interfaces ()); + } + + if (channel_managers != NULL) + { + GType types[] = { EXAMPLE_TYPE_CONTACT_LIST, G_TYPE_INVALID }; + + *channel_managers = g_memdup (types, sizeof (types)); + } + + if (icon_name != NULL) + *icon_name = g_strdup ("face-smile"); + + if (english_name != NULL) + *english_name = g_strdup ("Example with a contact list"); + + if (vcard_field != NULL) + *vcard_field = g_strdup ("x-telepathy-example"); +} + +static void +example_contact_list_protocol_class_init ( + ExampleContactListProtocolClass *klass) +{ + TpBaseProtocolClass *base_class = + (TpBaseProtocolClass *) klass; + + base_class->get_parameters = get_parameters; + base_class->new_connection = new_connection; + + base_class->normalize_contact = normalize_contact; + base_class->identify_account = identify_account; + base_class->get_interfaces = get_interfaces; + base_class->get_connection_details = get_connection_details; +} diff --git a/tests/lib/glib/contactlist2/protocol.h b/tests/lib/glib/contactlist2/protocol.h new file mode 100644 index 00000000..cfa07f79 --- /dev/null +++ b/tests/lib/glib/contactlist2/protocol.h @@ -0,0 +1,68 @@ +/* + * protocol.h - header for an example Protocol + * Copyright © 2007-2010 Collabora Ltd. + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#ifndef EXAMPLE_CONTACT_LIST_PROTOCOL_H +#define EXAMPLE_CONTACT_LIST_PROTOCOL_H + +#include <glib-object.h> +#include <telepathy-glib/base-protocol.h> + +G_BEGIN_DECLS + +typedef struct _ExampleContactListProtocol + ExampleContactListProtocol; +typedef struct _ExampleContactListProtocolPrivate + ExampleContactListProtocolPrivate; +typedef struct _ExampleContactListProtocolClass + ExampleContactListProtocolClass; +typedef struct _ExampleContactListProtocolClassPrivate + ExampleContactListProtocolClassPrivate; + +struct _ExampleContactListProtocolClass { + TpBaseProtocolClass parent_class; + + ExampleContactListProtocolClassPrivate *priv; +}; + +struct _ExampleContactListProtocol { + TpBaseProtocol parent; + + ExampleContactListProtocolPrivate *priv; +}; + +GType example_contact_list_protocol_get_type (void); + +#define EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL \ + (example_contact_list_protocol_get_type ()) +#define EXAMPLE_CONTACT_LIST_PROTOCOL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, \ + ExampleContactListProtocol)) +#define EXAMPLE_CONTACT_LIST_PROTOCOL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, \ + ExampleContactListProtocolClass)) +#define EXAMPLE_IS_CONTACT_LIST_PROTOCOL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL)) +#define EXAMPLE_IS_CONTACT_LIST_PROTOCOL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL)) +#define EXAMPLE_CONTACT_LIST_PROTOCOL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, \ + ExampleContactListProtocolClass)) + +gboolean example_contact_list_protocol_check_contact_id (const gchar *id, + gchar **normal, + GError **error); + +G_END_DECLS + +#endif |