summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndre Moreira Magalhaes (andrunko) <andre.magalhaes@collabora.co.uk>2010-12-31 11:22:05 -0200
committerAndre Moreira Magalhaes (andrunko) <andre.magalhaes@collabora.co.uk>2010-12-31 11:22:10 -0200
commit994a3c140969fab694288e9252dc31bcf09f73f9 (patch)
treeac3ed01b704152d6dcb6e5ab0579678d2ef7efe0
parent5e2e943a37f4ae525ad7a69e73d47a1fb87f7148 (diff)
parenta398ff89229a9599e0d21f5976cb00f2a459eca2 (diff)
Merge branch 'contactlist'
Reviewed-by: Olli Salli (oggis) <olli.salli@collabora.co.uk>
-rw-r--r--CMakeLists.txt4
-rw-r--r--TelepathyQt4/channel.h2
-rw-r--r--TelepathyQt4/connection.cpp268
-rw-r--r--TelepathyQt4/connection.h11
-rw-r--r--TelepathyQt4/contact-manager.cpp1112
-rw-r--r--TelepathyQt4/contact-manager.h42
-rw-r--r--TelepathyQt4/contact.cpp161
-rw-r--r--TelepathyQt4/contact.h30
-rw-r--r--TelepathyQt4/referenced-handles.h1
-rw-r--r--tests/dbus/CMakeLists.txt6
-rw-r--r--tests/dbus/conn-roster-groups-legacy.cpp679
-rw-r--r--tests/dbus/conn-roster-groups.cpp60
-rw-r--r--tests/dbus/conn-roster-legacy.cpp376
-rw-r--r--tests/dbus/conn-roster.cpp2
-rw-r--r--tests/dbus/contacts.cpp39
-rw-r--r--tests/dbus/handles.cpp6
-rw-r--r--tests/lib/glib/CMakeLists.txt1
-rw-r--r--tests/lib/glib/contactlist2/CMakeLists.txt14
-rw-r--r--tests/lib/glib/contactlist2/conn.c601
-rw-r--r--tests/lib/glib/contactlist2/conn.h68
-rw-r--r--tests/lib/glib/contactlist2/connection-manager.c73
-rw-r--r--tests/lib/glib/contactlist2/connection-manager.h62
-rw-r--r--tests/lib/glib/contactlist2/contact-list.c1731
-rw-r--r--tests/lib/glib/contactlist2/contact-list.h78
-rw-r--r--tests/lib/glib/contactlist2/example_contact_list.manager23
-rw-r--r--tests/lib/glib/contactlist2/protocol.c186
-rw-r--r--tests/lib/glib/contactlist2/protocol.h68
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