summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2010-12-02 12:35:23 (GMT)
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2010-12-02 12:35:25 (GMT)
commitc5d4b95649e5c4263f1d199760de492084f77fec (patch)
treee49deb59357052b6b8c4604ee8efe3160b7cb0bf
parenta4a52af0ff20290a39992ae77522495eb6de677a (diff)
parentfb7105898cf19a9236e4b61bc3811e28117ed7ae (diff)
downloadtelepathy-haze-c5d4b95649e5c4263f1d199760de492084f77fec.tar.gz
telepathy-haze-c5d4b95649e5c4263f1d199760de492084f77fec.tar.xz
Merge branch 'base-contact-list'
Reviewed-by: Will Thompson <will.thompson@collabora.co.uk> Bug: https://bugs.freedesktop.org/show_bug.cgi?id=19902
-rw-r--r--configure.ac4
-rw-r--r--src/Makefile.am2
-rw-r--r--src/connection.c34
-rw-r--r--src/connection.h12
-rw-r--r--src/contact-list-channel.c600
-rw-r--r--src/contact-list-channel.h71
-rw-r--r--src/contact-list.c1560
-rw-r--r--src/contact-list.h10
-rw-r--r--tests/twisted/hazetest.py10
-rw-r--r--tests/twisted/roster/groups.py17
-rw-r--r--tests/twisted/roster/initial-roster.py23
-rw-r--r--tests/twisted/roster/publish.py47
-rw-r--r--tests/twisted/roster/removed-from-rp-subscribe.py30
-rw-r--r--tests/twisted/roster/subscribe.py15
14 files changed, 927 insertions, 1508 deletions
diff --git a/configure.ac b/configure.ac
index 55ad0f8..2400805 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,8 +72,8 @@ AC_ARG_ENABLE(leaky-request-stubs,
AC_SUBST(ENABLE_LEAKY_REQUEST_STUBS)
PKG_CHECK_MODULES(PURPLE,[purple >= 2.7])
-PKG_CHECK_MODULES(TP_GLIB,[telepathy-glib >= 0.11.16])
-PKG_CHECK_MODULES(GLIB,[glib-2.0 >= 2.22, gobject-2.0])
+PKG_CHECK_MODULES(TP_GLIB,[telepathy-glib >= 0.13.8])
+PKG_CHECK_MODULES(GLIB,[glib-2.0 >= 2.22, gobject-2.0, gio-2.0])
PKG_CHECK_MODULES(DBUS_GLIB,[dbus-glib-1 >= 0.73])
GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
diff --git a/src/Makefile.am b/src/Makefile.am
index d5b3fa3..bc9fd28 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -41,8 +41,6 @@ telepathy_haze_SOURCES = main.c \
connection.h \
contact-list.c \
contact-list.h \
- contact-list-channel.c \
- contact-list-channel.h \
im-channel.h \
im-channel.c \
im-channel-factory.c \
diff --git a/src/connection.c b/src/connection.c
index c5d58aa..9e4ec21 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -27,7 +27,6 @@
#include <telepathy-glib/dbus-properties-mixin.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/handle-repo-dynamic.h>
-#include <telepathy-glib/handle-repo-static.h>
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/svc-generic.h>
@@ -42,7 +41,6 @@
#include "connection-aliasing.h"
#include "connection-avatars.h"
#include "connection-mail.h"
-#include "contact-list-channel.h"
#include "extensions/extensions.h"
#include "connection-capabilities.h"
@@ -81,6 +79,10 @@ G_DEFINE_TYPE_WITH_CODE(HazeConnection,
haze_connection_capabilities_iface_init);
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 (HAZE_TYPE_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION,
haze_connection_mail_iface_init);
);
@@ -94,6 +96,8 @@ static const gchar * implemented_interfaces[] = {
/* Always present */
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
@@ -154,6 +158,9 @@ connected_cb (PurpleConnection *pc)
tp_base_connection_add_interfaces (base_conn, avatar_ifaces);
}
+ tp_base_contact_list_set_list_received (
+ (TpBaseContactList *) conn->contact_list);
+
tp_base_connection_change_status (base_conn,
TP_CONNECTION_STATUS_CONNECTED,
TP_CONNECTION_STATUS_REASON_REQUESTED);
@@ -444,19 +451,6 @@ _haze_connection_shut_down (TpBaseConnection *base)
}
}
-/* Must be in the same order as HazeListHandle in connection.h */
-static const char *list_handle_strings[] =
-{
- "subscribe", /* HAZE_LIST_HANDLE_SUBSCRIBE */
- "publish", /* HAZE_LIST_HANDLE_PUBLISH */
-#if 0
- "hide", /* HAZE_LIST_HANDLE_HIDE */
- "allow", /* HAZE_LIST_HANDLE_ALLOW */
- "deny" /* HAZE_LIST_HANDLE_DENY */
-#endif
- NULL
-};
-
static gchar*
_contact_normalize (TpHandleRepoIface *repo,
const gchar *id,
@@ -476,10 +470,6 @@ _haze_connection_create_handle_repos (TpBaseConnection *base,
tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, _contact_normalize,
base);
/* repos[TP_HANDLE_TYPE_ROOM] = XXX MUC */
- repos[TP_HANDLE_TYPE_GROUP] =
- tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_GROUP, NULL, NULL);
- repos[TP_HANDLE_TYPE_LIST] =
- tp_static_handle_repo_new (TP_HANDLE_TYPE_LIST, list_handle_strings);
}
static GPtrArray *
@@ -588,6 +578,7 @@ haze_connection_constructor (GType type,
HazeConnection *self = HAZE_CONNECTION (
G_OBJECT_CLASS (haze_connection_parent_class)->constructor (
type, n_construct_properties, construct_params));
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self);
GObject *object = (GObject *) self;
HazeConnectionPrivate *priv = self->priv;
@@ -601,8 +592,8 @@ haze_connection_constructor (GType type,
tp_contacts_mixin_init (object,
G_STRUCT_OFFSET (HazeConnection, contacts));
- tp_base_connection_register_with_contacts_mixin (
- TP_BASE_CONNECTION (self));
+ tp_base_connection_register_with_contacts_mixin (base_conn);
+ tp_base_contact_list_mixin_register_with_contacts_mixin (base_conn);
haze_connection_aliasing_init (object);
haze_connection_avatars_init (object);
@@ -732,6 +723,7 @@ haze_connection_class_init (HazeConnectionClass *klass)
tp_contacts_mixin_class_init (object_class,
G_STRUCT_OFFSET (HazeConnectionClass, contacts_class));
+ tp_base_contact_list_mixin_class_init (base_class);
haze_connection_presence_class_init (object_class);
haze_connection_aliasing_class_init (object_class);
diff --git a/src/connection.h b/src/connection.h
index d0bdd2c..a7b5e5a 100644
--- a/src/connection.h
+++ b/src/connection.h
@@ -35,18 +35,6 @@
G_BEGIN_DECLS
-/* Must be in the same order as list_handle_strings in connection.c */
-typedef enum
-{
- HAZE_LIST_HANDLE_SUBSCRIBE = 1,
- HAZE_LIST_HANDLE_PUBLISH,
-#if 0
- HAZE_LIST_HANDLE_HIDE,
- HAZE_LIST_HANDLE_ALLOW,
- HAZE_LIST_HANDLE_DENY
-#endif
-} HazeListHandle;
-
typedef struct _HazeConnection HazeConnection;
typedef struct _HazeConnectionPrivate HazeConnectionPrivate;
typedef struct _HazeConnectionClass HazeConnectionClass;
diff --git a/src/contact-list-channel.c b/src/contact-list-channel.c
deleted file mode 100644
index 5a8b320..0000000
--- a/src/contact-list-channel.c
+++ /dev/null
@@ -1,600 +0,0 @@
-/*
- * contact-list-channel.c - HazeContactListChannel source
- * Copyright (C) 2007 Will Thompson
- * Copyright (C) 2007-2008 Collabora Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/exportable-channel.h>
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/channel-iface.h>
-#include <telepathy-glib/svc-generic.h>
-
-#include "contact-list-channel.h"
-#include "connection.h"
-#include "debug.h"
-
-struct _HazeContactListChannelPrivate {
- HazeConnection *conn;
-
- char *object_path;
- TpHandle handle;
- guint handle_type;
-
- gboolean closed;
- gboolean dispose_has_run;
-};
-
-static void channel_iface_init (gpointer g_iface, gpointer iface_data);
-
-G_DEFINE_TYPE_WITH_CODE (HazeContactListChannel,
- haze_contact_list_channel,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
- tp_group_mixin_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
- tp_dbus_properties_mixin_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL))
-
-const char *haze_contact_list_channel_interfaces[] = {
- TP_IFACE_CHANNEL_INTERFACE_GROUP,
- NULL
-};
-
-
-/* properties: */
-enum {
- PROP_CONNECTION = 1,
- PROP_OBJECT_PATH,
- PROP_CHANNEL_TYPE,
- PROP_HANDLE_TYPE,
- PROP_HANDLE,
- PROP_TARGET_ID,
- PROP_INTERFACES,
- PROP_REQUESTED,
- PROP_INITIATOR_HANDLE,
- PROP_INITIATOR_ID,
- PROP_CHANNEL_PROPERTIES,
- PROP_CHANNEL_DESTROYED,
-
- LAST_PROPERTY
-};
-
-
-static gboolean
-_list_add_member_cb (HazeContactListChannel *chan,
- TpHandle handle,
- const gchar *message,
- GError **error)
-{
- HazeContactListChannelPrivate *priv =
- chan->priv;
- HazeConnection *conn = priv->conn;
-
- g_assert (priv->handle_type == TP_HANDLE_TYPE_LIST);
-
- switch (priv->handle)
- {
- case HAZE_LIST_HANDLE_SUBSCRIBE:
- haze_contact_list_request_subscription (conn->contact_list,
- handle, message);
- return TRUE; /* FIXME: How am I meant to know if it failed? */
-
- case HAZE_LIST_HANDLE_PUBLISH:
- haze_contact_list_accept_publish_request (conn->contact_list,
- handle);
- return TRUE;
-
- default:
- /* TODO: more list types */
- g_assert_not_reached ();
- return FALSE;
- }
-}
-
-static gboolean
-_haze_contact_list_channel_add_member_cb (GObject *obj,
- TpHandle handle,
- const gchar *message,
- GError **error)
-{
- HazeContactListChannel *chan = HAZE_CONTACT_LIST_CHANNEL (obj);
- HazeContactListChannelPrivate *priv =
- chan->priv;
- const gchar *group_name;
-
- switch (priv->handle_type)
- {
- case TP_HANDLE_TYPE_LIST:
- return _list_add_member_cb (chan, handle, message, error);
- case TP_HANDLE_TYPE_GROUP:
- group_name = haze_connection_handle_inspect (priv->conn,
- TP_HANDLE_TYPE_GROUP, priv->handle);
- haze_contact_list_add_to_group (priv->conn->contact_list,
- group_name, handle);
- return TRUE;
- default:
- g_assert_not_reached ();
- return FALSE;
- }
-}
-
-static gboolean
-_list_remove_member_cb (HazeContactListChannel *chan,
- TpHandle handle,
- const gchar *message G_GNUC_UNUSED,
- GError **error)
-{
- HazeContactListChannelPrivate *priv =
- chan->priv;
- HazeConnection *conn = priv->conn;
-
- switch (priv->handle)
- {
- case HAZE_LIST_HANDLE_SUBSCRIBE:
- haze_contact_list_remove_contact (conn->contact_list, handle);
- return TRUE;
-
- case HAZE_LIST_HANDLE_PUBLISH:
- haze_contact_list_reject_publish_request (conn->contact_list,
- handle);
- return TRUE;
-
- default:
- /* TODO: More list types */
- g_assert_not_reached ();
- return FALSE;
- }
-}
-
-static gboolean
-_haze_contact_list_channel_remove_member_cb (GObject *obj,
- TpHandle handle,
- const gchar *message,
- GError **error)
-{
- HazeContactListChannel *chan = HAZE_CONTACT_LIST_CHANNEL (obj);
- HazeContactListChannelPrivate *priv =
- chan->priv;
- const gchar *group_name;
-
- switch (priv->handle_type)
- {
- case TP_HANDLE_TYPE_LIST:
- return _list_remove_member_cb (chan, handle, message, error);
- case TP_HANDLE_TYPE_GROUP:
- group_name = haze_connection_handle_inspect (priv->conn,
- TP_HANDLE_TYPE_GROUP, priv->handle);
- return haze_contact_list_remove_from_group (
- priv->conn->contact_list, group_name, handle, error);
- default:
- g_assert_not_reached ();
- return FALSE;
- }
-}
-
-
-static void
-haze_contact_list_channel_init (HazeContactListChannel *self)
-{
- HazeContactListChannelPrivate *priv =
- (G_TYPE_INSTANCE_GET_PRIVATE((self), HAZE_TYPE_CONTACT_LIST_CHANNEL,
- HazeContactListChannelPrivate));
-
- self->priv = priv;
- priv->dispose_has_run = FALSE;
- priv->closed = FALSE;
-}
-
-static GObject *
-haze_contact_list_channel_constructor (GType type, guint n_props,
- GObjectConstructParam *props)
-{
- GObject *obj;
- HazeContactListChannel *chan;
- HazeContactListChannelPrivate *priv;
- TpBaseConnection *conn;
- TpHandle self_handle;
- TpHandleRepoIface *handle_repo, *contact_repo;
- TpDBusDaemon *bus;
- guint handle_type;
-
- obj = G_OBJECT_CLASS (haze_contact_list_channel_parent_class)->
- constructor (type, n_props, props);
- chan = HAZE_CONTACT_LIST_CHANNEL (obj);
- priv = chan->priv;
- conn = TP_BASE_CONNECTION (priv->conn);
- self_handle = conn->self_handle;
- handle_type = priv->handle_type;
- handle_repo = tp_base_connection_get_handles (conn, handle_type);
- contact_repo = tp_base_connection_get_handles (conn,
- TP_HANDLE_TYPE_CONTACT);
-
- bus = tp_base_connection_get_dbus_daemon (conn);
- tp_dbus_daemon_register_object (bus, priv->object_path, obj);
-
- g_assert (handle_type == TP_HANDLE_TYPE_LIST ||
- handle_type == TP_HANDLE_TYPE_GROUP);
-
- tp_handle_ref (handle_repo, priv->handle);
-
- tp_group_mixin_init (obj, G_STRUCT_OFFSET (HazeContactListChannel, group),
- contact_repo, self_handle);
-
- tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
-
- switch (handle_type) {
- case TP_HANDLE_TYPE_GROUP:
- tp_group_mixin_change_flags (obj,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD |
- TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
- 0);
- break;
- case TP_HANDLE_TYPE_LIST:
- switch (priv->handle) {
- case HAZE_LIST_HANDLE_SUBSCRIBE:
- tp_group_mixin_change_flags (obj,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD |
- TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
- TP_CHANNEL_GROUP_FLAG_CAN_RESCIND,
- 0);
- break;
- case HAZE_LIST_HANDLE_PUBLISH:
- /* This is kind of a fake publish channel: it's only used
- * to accept or reject incoming subscription requests. So,
- * you can't add or remove anyone from it yourself; people
- * get onto local_pending when they make a subscribe
- * request, and then you can move them to the list proper
- * or remove them.
- *
- * So, we'll leave the channel's flags as 0.
- */
- break;
- /* TODO: More magic lists go here */
- default:
- g_assert_not_reached ();
- }
- break;
- default:
- g_assert_not_reached ();
- }
-
- return obj;
-}
-
-static void
-haze_contact_list_channel_dispose (GObject *object)
-{
- HazeContactListChannel *self = HAZE_CONTACT_LIST_CHANNEL (object);
- HazeContactListChannelPrivate *priv = self->priv;
-
- if (priv->dispose_has_run)
- return;
-
- priv->dispose_has_run = TRUE;
-
- if (!priv->closed)
- {
- priv->closed = TRUE;
- tp_svc_channel_emit_closed ((TpSvcChannel *)self);
- }
-
- g_free (priv->object_path);
-
- if (G_OBJECT_CLASS (haze_contact_list_channel_parent_class)->dispose)
- G_OBJECT_CLASS (haze_contact_list_channel_parent_class)->dispose (object);
-}
-
-static void
-haze_contact_list_channel_finalize (GObject *object)
-{
- tp_group_mixin_finalize (object);
-
- G_OBJECT_CLASS (haze_contact_list_channel_parent_class)->finalize (object);
-}
-
-static void
-haze_contact_list_channel_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- HazeContactListChannel *self = HAZE_CONTACT_LIST_CHANNEL (object);
- HazeContactListChannelPrivate *priv = self->priv;
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
-
- switch (property_id) {
- case PROP_OBJECT_PATH:
- g_value_set_string (value, priv->object_path);
- break;
- case PROP_CHANNEL_TYPE:
- g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
- break;
- case PROP_HANDLE_TYPE:
- g_value_set_uint (value, priv->handle_type);
- break;
- case PROP_HANDLE:
- g_value_set_uint (value, priv->handle);
- break;
- case PROP_TARGET_ID:
- {
- TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
- priv->handle_type);
-
- g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
- break;
- }
- case PROP_INITIATOR_HANDLE:
- g_value_set_uint (value, 0);
- break;
- case PROP_INITIATOR_ID:
- g_value_set_static_string (value, "");
- break;
- case PROP_REQUESTED:
- g_value_set_boolean (value, FALSE);
- break;
- case PROP_CONNECTION:
- g_value_set_object (value, priv->conn);
- break;
- case PROP_INTERFACES:
- g_value_set_boxed (value, haze_contact_list_channel_interfaces);
- break;
- case PROP_CHANNEL_DESTROYED:
- g_value_set_boolean (value, priv->closed);
- break;
- case PROP_CHANNEL_PROPERTIES:
- g_value_take_boxed (value,
- tp_dbus_properties_mixin_make_properties_hash (object,
- TP_IFACE_CHANNEL, "TargetHandle",
- TP_IFACE_CHANNEL, "TargetHandleType",
- TP_IFACE_CHANNEL, "ChannelType",
- TP_IFACE_CHANNEL, "TargetID",
- TP_IFACE_CHANNEL, "InitiatorHandle",
- TP_IFACE_CHANNEL, "InitiatorID",
- TP_IFACE_CHANNEL, "Requested",
- TP_IFACE_CHANNEL, "Interfaces",
- NULL));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void
-haze_contact_list_channel_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- HazeContactListChannel *self = HAZE_CONTACT_LIST_CHANNEL (object);
- HazeContactListChannelPrivate *priv = self->priv;
-
- switch (property_id) {
- case PROP_OBJECT_PATH:
- g_free (priv->object_path);
- priv->object_path = g_value_dup_string (value);
- break;
- case PROP_CHANNEL_TYPE:
- /* this property is writable in the interface (in
- * telepathy-glib > 0.7.0), but not actually
- * meaningfully changeable on this channel, so we do nothing */
- break;
- case PROP_HANDLE_TYPE:
- priv->handle_type = g_value_get_uint (value);
- break;
- case PROP_HANDLE:
- priv->handle = g_value_get_uint (value);
- break;
- case PROP_CONNECTION:
- priv->conn = g_value_get_object (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void
-haze_contact_list_channel_class_init (HazeContactListChannelClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GParamSpec *param_spec;
-
- static gboolean properties_mixin_initialized = FALSE;
- static TpDBusPropertiesMixinPropImpl channel_props[] = {
- { "TargetHandleType", "handle-type", NULL },
- { "TargetHandle", "handle", NULL },
- { "TargetID", "target-id", NULL },
- { "ChannelType", "channel-type", NULL },
- { "Interfaces", "interfaces", NULL },
- { "Requested", "requested", NULL },
- { "InitiatorHandle", "initiator-handle", NULL },
- { "InitiatorID", "initiator-id", NULL },
- { NULL }
- };
- static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
- { TP_IFACE_CHANNEL,
- tp_dbus_properties_mixin_getter_gobject_properties,
- NULL,
- channel_props,
- },
- { NULL }
- };
-
- g_type_class_add_private (object_class,
- sizeof(HazeContactListChannelPrivate));
-
- object_class->constructor = haze_contact_list_channel_constructor;
-
- object_class->dispose = haze_contact_list_channel_dispose;
- object_class->finalize = haze_contact_list_channel_finalize;
-
- object_class->get_property = haze_contact_list_channel_get_property;
- object_class->set_property = haze_contact_list_channel_set_property;
-
-
- param_spec = g_param_spec_object ("connection", "HazeConnection object",
- "Haze connection object that owns this contact list channel object.",
- HAZE_TYPE_CONNECTION,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
- param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
- "Additional Channel.Interface.* interfaces",
- G_TYPE_STRV,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
-
- param_spec = g_param_spec_string ("target-id", "Contact list name",
- "The stringy name of the contact list (\"subscribe\" etc.)",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
-
- param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
- "The contact who initiated the channel",
- 0, G_MAXUINT32, 0,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
- param_spec);
-
- param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
- "The string obtained by inspecting the initiator-handle",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIATOR_ID,
- param_spec);
-
- param_spec = g_param_spec_boolean ("requested", "Requested?",
- "True if this channel was requested by the local user",
- FALSE,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
-
- g_object_class_override_property (object_class, PROP_OBJECT_PATH,
- "object-path");
- g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
- "channel-type");
- g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
- "handle-type");
- g_object_class_override_property (object_class, PROP_HANDLE, "handle");
- g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
- "channel-destroyed");
- g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
- "channel-properties");
-
-
- tp_group_mixin_class_init (object_class,
- G_STRUCT_OFFSET (HazeContactListChannelClass, group_class),
- _haze_contact_list_channel_add_member_cb,
- _haze_contact_list_channel_remove_member_cb);
-
- if (!properties_mixin_initialized)
- {
- properties_mixin_initialized = TRUE;
- klass->properties_class.interfaces = prop_interfaces;
- tp_dbus_properties_mixin_class_init (object_class,
- G_STRUCT_OFFSET (HazeContactListChannelClass, properties_class));
-
- tp_group_mixin_init_dbus_properties (object_class);
- }
-}
-
-static void
-haze_contact_list_channel_close (TpSvcChannel *iface,
- DBusGMethodInvocation *context)
-{
- HazeContactListChannel *self = HAZE_CONTACT_LIST_CHANNEL (iface);
- HazeContactListChannelPrivate *priv;
-
- g_assert (HAZE_IS_CONTACT_LIST_CHANNEL (self));
-
- priv = self->priv;
-
- if (priv->handle_type == TP_HANDLE_TYPE_LIST)
- {
- GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
- "you may not close contact list channels" };
-
- dbus_g_method_return_error (context, &e);
- }
- else /* TP_HANDLE_TYPE_GROUP */
- {
- if (tp_handle_set_size (self->group.members) == 0)
- {
- priv->closed = TRUE;
- tp_svc_channel_emit_closed ((TpSvcChannel *)self);
- tp_svc_channel_return_from_close (context);
- }
- else
- {
- GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "This group is not empty, so you cannot close it." };
- dbus_g_method_return_error (context, &e);
- }
- }
-}
-
-static void
-haze_contact_list_channel_get_channel_type (TpSvcChannel *iface,
- DBusGMethodInvocation *context)
-{
- tp_svc_channel_return_from_get_channel_type (context,
- TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
-}
-
-static void
-haze_contact_list_channel_get_handle (TpSvcChannel *iface,
- DBusGMethodInvocation *context)
-{
- HazeContactListChannel *self = HAZE_CONTACT_LIST_CHANNEL (iface);
- HazeContactListChannelPrivate *priv;
-
- g_assert (HAZE_IS_CONTACT_LIST_CHANNEL (self));
-
- priv = self->priv;
-
- tp_svc_channel_return_from_get_handle (context, priv->handle_type,
- priv->handle);
-}
-
-static void
-haze_contact_list_channel_get_interfaces (TpSvcChannel *self,
- DBusGMethodInvocation *context)
-{
- tp_svc_channel_return_from_get_interfaces (context,
- haze_contact_list_channel_interfaces);
-}
-
-static void
-channel_iface_init (gpointer g_iface, gpointer iface_data)
-{
- TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
-
-#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
- klass, haze_contact_list_channel_##x)
- IMPLEMENT(close);
- IMPLEMENT(get_channel_type);
- IMPLEMENT(get_handle);
- IMPLEMENT(get_interfaces);
-#undef IMPLEMENT
-}
diff --git a/src/contact-list-channel.h b/src/contact-list-channel.h
deleted file mode 100644
index d2660e2..0000000
--- a/src/contact-list-channel.h
+++ /dev/null
@@ -1,71 +0,0 @@
-#ifndef __HAZE_CONTACT_LIST_CHANNEL_H__
-#define __HAZE_CONTACT_LIST_CHANNEL_H__
-/*
- * contact-list-channel.h - HazeContactListChannel header
- * Copyright (C) 2007 Will Thompson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
-
-#include <glib-object.h>
-
-#include <telepathy-glib/group-mixin.h>
-#include <telepathy-glib/dbus-properties-mixin.h>
-
-#include <libpurple/account.h>
-
-G_BEGIN_DECLS
-
-typedef struct _HazeContactListChannel HazeContactListChannel;
-typedef struct _HazeContactListChannelPrivate HazeContactListChannelPrivate;
-typedef struct _HazeContactListChannelClass HazeContactListChannelClass;
-
-struct _HazeContactListChannelClass {
- GObjectClass parent_class;
- TpGroupMixinClass group_class;
- TpDBusPropertiesMixinClass properties_class;
-};
-
-struct _HazeContactListChannel {
- GObject parent;
-
- TpGroupMixin group;
-
- HazeContactListChannelPrivate *priv;
-};
-
-GType haze_contact_list_channel_get_type (void);
-
-/* TYPE MACROS */
-#define HAZE_TYPE_CONTACT_LIST_CHANNEL \
- (haze_contact_list_channel_get_type ())
-#define HAZE_CONTACT_LIST_CHANNEL(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST((obj), HAZE_TYPE_CONTACT_LIST_CHANNEL, \
- HazeContactListChannel))
-#define HAZE_CONTACT_LIST_CHANNEL_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), HAZE_TYPE_CONTACT_LIST_CHANNEL, \
- HazeContactListChannelClass))
-#define HAZE_IS_CONTACT_LIST_CHANNEL(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE((obj), HAZE_TYPE_CONTACT_LIST_CHANNEL))
-#define HAZE_IS_CONTACT_LIST_CHANNEL_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), HAZE_TYPE_CONTACT_LIST_CHANNEL))
-#define HAZE_CONTACT_LIST_CHANNEL_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), HAZE_CONTACT_LIST_CHANNEL, \
- HazeContactListChannelClass))
-
-G_END_DECLS
-
-#endif /* #ifndef __HAZE_CONTACT_LIST_CHANNEL_H__*/
diff --git a/src/contact-list.c b/src/contact-list.c
index 5d03e1b..8cee5a7 100644
--- a/src/contact-list.c
+++ b/src/contact-list.c
@@ -21,7 +21,6 @@
#include <string.h>
-#include <telepathy-glib/channel-manager.h>
#include <telepathy-glib/dbus.h>
#include <telepathy-glib/gtypes.h>
#include <telepathy-glib/interfaces.h>
@@ -29,7 +28,6 @@
#include "connection.h"
#include "contact-list.h"
-#include "contact-list-channel.h"
#include "debug.h"
typedef struct _PublishRequestData PublishRequestData;
@@ -41,8 +39,8 @@ typedef struct _PublishRequestData PublishRequestData;
*/
struct _PublishRequestData {
HazeContactList *self;
- HazeContactListChannel *publish;
TpHandle handle;
+ gchar *message;
PurpleAccountRequestAuthorizationCb allow;
PurpleAccountRequestAuthorizationCb deny;
@@ -59,39 +57,39 @@ publish_request_data_new (void)
static void
publish_request_data_free (PublishRequestData *prd)
{
- g_object_unref (prd->publish);
+ g_free (prd->message);
g_slice_free (PublishRequestData, prd);
}
struct _HazeContactListPrivate {
HazeConnection *conn;
- GHashTable *list_channels;
- GHashTable *group_channels;
- gulong status_changed_id;
-
/* Maps TpHandle to PublishRequestData, corresponding to the handles on
* publish's local_pending.
*/
GHashTable *pending_publish_requests;
+ /* Contacts whose publish requests we've accepted or declined. */
+ TpHandleSet *publishing_to;
+ TpHandleSet *not_publishing_to;
+
gboolean dispose_has_run;
};
-static void channel_manager_iface_init (gpointer, gpointer);
+static void haze_contact_list_mutable_init (TpMutableContactListInterface *);
+static void haze_contact_list_groups_init (TpContactGroupListInterface *);
+static void haze_contact_list_mutable_groups_init (
+ TpMutableContactGroupListInterface *);
G_DEFINE_TYPE_WITH_CODE(HazeContactList,
haze_contact_list,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
- channel_manager_iface_init))
-
-/* properties: */
-enum {
- PROP_CONNECTION = 1,
-
- LAST_PROPERTY
-};
+ TP_TYPE_BASE_CONTACT_LIST,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_LIST,
+ haze_contact_list_mutable_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CONTACT_GROUP_LIST,
+ haze_contact_list_groups_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_GROUP_LIST,
+ haze_contact_list_mutable_groups_init))
static void
haze_contact_list_init (HazeContactList *self)
@@ -102,63 +100,32 @@ haze_contact_list_init (HazeContactList *self)
self->priv = priv;
- priv->list_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
- NULL, g_object_unref);
- priv->group_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
- NULL, g_object_unref);
-
priv->dispose_has_run = FALSE;
}
-static void haze_contact_list_close_all (HazeContactList *self);
-
-static void _add_initial_buddies (HazeContactList *self,
- HazeContactListChannel *subscribe);
-
-static void
-status_changed_cb (HazeConnection *conn,
- guint status,
- guint reason,
- HazeContactList *self)
-{
- switch (status)
- {
- case TP_CONNECTION_STATUS_DISCONNECTED:
- haze_contact_list_close_all (self);
- break;
- case TP_CONNECTION_STATUS_CONNECTED:
- {
- HazeContactListChannel *subscribe, *publish;
-
- /* Ensure contact lists exist before going any further. */
- subscribe = haze_contact_list_get_channel (self,
- TP_HANDLE_TYPE_LIST, HAZE_LIST_HANDLE_SUBSCRIBE,
- NULL, NULL /*created*/);
- g_assert (subscribe != NULL);
-
- publish = haze_contact_list_get_channel (self, TP_HANDLE_TYPE_LIST,
- HAZE_LIST_HANDLE_PUBLISH, NULL, NULL /*created*/);
- g_assert (publish != NULL);
-
- _add_initial_buddies (self, subscribe);
- }
- break;
- }
-}
-
static GObject *
haze_contact_list_constructor (GType type, guint n_props,
GObjectConstructParam *props)
{
GObject *obj;
HazeContactList *self;
+ TpHandleRepoIface *contact_repo;
obj = G_OBJECT_CLASS (haze_contact_list_parent_class)->
constructor (type, n_props, props);
self = HAZE_CONTACT_LIST (obj);
- self->priv->status_changed_id = g_signal_connect (self->priv->conn,
- "status-changed", (GCallback) status_changed_cb, self);
+
+ self->priv->conn = HAZE_CONNECTION (tp_base_contact_list_get_connection (
+ (TpBaseContactList *) self, NULL));
+ g_assert (self->priv->conn != NULL);
+ /* not reffed, for the moment */
+
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ self->priv->publishing_to = tp_handle_set_new (contact_repo);
+ self->priv->not_publishing_to = tp_handle_set_new (contact_repo);
self->priv->pending_publish_requests = g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) publish_request_data_free);
@@ -177,9 +144,8 @@ haze_contact_list_dispose (GObject *object)
priv->dispose_has_run = TRUE;
- haze_contact_list_close_all (self);
- g_assert (priv->list_channels == NULL);
- g_assert (priv->group_channels == NULL);
+ tp_clear_pointer (&priv->publishing_to, tp_handle_set_destroy);
+ tp_clear_pointer (&priv->not_publishing_to, tp_handle_set_destroy);
if (priv->pending_publish_requests)
{
@@ -198,344 +164,130 @@ haze_contact_list_finalize (GObject *object)
G_OBJECT_CLASS (haze_contact_list_parent_class)->finalize (object);
}
-static void
-haze_contact_list_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- HazeContactList *self = HAZE_CONTACT_LIST (object);
- HazeContactListPrivate *priv = self->priv;
-
- switch (property_id) {
- case PROP_CONNECTION:
- g_value_set_object (value, priv->conn);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void
-haze_contact_list_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- HazeContactList *self = HAZE_CONTACT_LIST (object);
- HazeContactListPrivate *priv = self->priv;
-
- switch (property_id) {
- case PROP_CONNECTION:
- priv->conn = g_value_get_object (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
static void buddy_added_cb (PurpleBuddy *buddy, gpointer unused);
static void buddy_removed_cb (PurpleBuddy *buddy, gpointer unused);
-static void
-haze_contact_list_class_init (HazeContactListClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GParamSpec *param_spec;
-
- object_class->constructor = haze_contact_list_constructor;
-
- object_class->dispose = haze_contact_list_dispose;
- object_class->finalize = haze_contact_list_finalize;
-
- object_class->get_property = haze_contact_list_get_property;
- object_class->set_property = haze_contact_list_set_property;
-
- param_spec = g_param_spec_object ("connection", "HazeConnection object",
- "Haze connection object that owns this "
- "contact list object.",
- HAZE_TYPE_CONNECTION,
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_READWRITE |
- G_PARAM_STATIC_NICK |
- G_PARAM_STATIC_BLURB);
- g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
- g_type_class_add_private (object_class,
- sizeof(HazeContactListPrivate));
-
- purple_signal_connect (purple_blist_get_handle(), "buddy-added",
- klass, PURPLE_CALLBACK(buddy_added_cb), NULL);
- purple_signal_connect (purple_blist_get_handle(), "buddy-removed",
- klass, PURPLE_CALLBACK(buddy_removed_cb), NULL);
-}
-
-static void
-contact_list_channel_closed_cb (HazeContactListChannel *chan,
- gpointer user_data)
+static TpHandleSet *
+haze_contact_list_dup_contacts (TpBaseContactList *cl)
{
- HazeContactList *contact_list = HAZE_CONTACT_LIST (user_data);
- HazeContactListPrivate *priv = contact_list->priv;
- TpHandle handle;
- guint handle_type;
- GHashTable *channels;
-
- tp_channel_manager_emit_channel_closed_for_object (contact_list,
- TP_EXPORTABLE_CHANNEL (chan));
-
- g_object_get (chan, "handle", &handle, "handle-type", &handle_type, NULL);
- g_assert (handle_type == TP_HANDLE_TYPE_LIST ||
- handle_type == TP_HANDLE_TYPE_GROUP);
-
- channels = (handle_type == TP_HANDLE_TYPE_GROUP
- ? priv->group_channels : priv->list_channels);
-
- if (channels)
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ /* The list initially contains anyone we're definitely publishing to.
+ * Because libpurple, that's only people whose request we accepted during
+ * this session :-( */
+ TpHandleSet *handles = tp_handle_set_copy (self->priv->publishing_to);
+ GSList *buddies = purple_find_buddies (self->priv->conn->account, NULL);
+ GSList *sl_iter;
+ GHashTableIter hash_iter;
+ gpointer k;
+
+ /* Also include anyone on our buddy list */
+ for (sl_iter = buddies; sl_iter != NULL; sl_iter = sl_iter->next)
{
- DEBUG ("removing channel %d:%d", handle_type, handle);
+ TpHandle handle = tp_handle_ensure (contact_repo,
+ purple_buddy_get_name (sl_iter->data), NULL, NULL);
- g_hash_table_remove (channels, GINT_TO_POINTER (handle));
+ if (G_LIKELY (handle != 0))
+ {
+ tp_handle_set_add (handles, handle);
+ tp_handle_unref (contact_repo, handle);
+ }
}
-}
-
-/**
- * Instantiates the described channel. Requires that the channel does
- * not already exist.
- */
-static HazeContactListChannel *
-_haze_contact_list_create_channel (HazeContactList *contact_list,
- guint handle_type,
- TpHandle handle,
- gpointer request_token)
-{
- HazeContactListPrivate *priv = contact_list->priv;
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
- GHashTable *channels = (handle_type == TP_HANDLE_TYPE_LIST
- ? priv->list_channels
- : priv->group_channels);
-
- HazeContactListChannel *chan;
- const char *name;
- char *mangled_name;
- char *object_path;
- GSList *requests = NULL;
-
- g_assert (handle_type == TP_HANDLE_TYPE_LIST ||
- handle_type == TP_HANDLE_TYPE_GROUP);
- g_assert (channels != NULL);
- g_assert (g_hash_table_lookup (channels, GINT_TO_POINTER (handle)) == NULL);
-
- name = haze_connection_handle_inspect (priv->conn, handle_type, handle);
- DEBUG ("Instantiating channel %u:%u \"%s\"", handle_type, handle, name);
- mangled_name = tp_escape_as_identifier (name);
- object_path = g_strdup_printf ("%s/ContactListChannel/%s/%s",
- base_conn->object_path,
- handle_type == TP_HANDLE_TYPE_LIST ? "List"
- : "Group",
- mangled_name);
- g_free (mangled_name);
- mangled_name = NULL;
-
- if (handle_type == TP_HANDLE_TYPE_GROUP)
- purple_group_new (name);
-
- chan = g_object_new (HAZE_TYPE_CONTACT_LIST_CHANNEL,
- "connection", priv->conn,
- "object-path", object_path,
- "handle", handle,
- "handle-type", handle_type,
- NULL);
-
- DEBUG ("created %s", object_path);
-
- g_signal_connect (chan, "closed",
- G_CALLBACK (contact_list_channel_closed_cb), contact_list);
-
- g_hash_table_insert (channels, GINT_TO_POINTER (handle), chan);
-
- if (request_token != NULL)
- requests = g_slist_prepend (requests, request_token);
-
- tp_channel_manager_emit_new_channel (contact_list,
- TP_EXPORTABLE_CHANNEL (chan), requests);
- g_slist_free (requests);
-
- g_free (object_path);
-
- return chan;
-}
-struct foreach_data {
- TpExportableChannelFunc func;
- gpointer data;
-};
-
-static void
-foreach_helper (gpointer k,
- gpointer v,
- gpointer data)
-{
- struct foreach_data *foreach = data;
+ g_slist_free (buddies);
- foreach->func (TP_EXPORTABLE_CHANNEL (v), foreach->data);
-}
+ /* Also include anyone with an outstanding request */
+ g_hash_table_iter_init (&hash_iter, self->priv->pending_publish_requests);
-static void
-haze_contact_list_foreach_channel (TpChannelManager *manager,
- TpExportableChannelFunc func,
- gpointer data)
-{
- HazeContactList *self = HAZE_CONTACT_LIST (manager);
- struct foreach_data foreach = { func, data };
+ while (g_hash_table_iter_next (&hash_iter, &k, NULL))
+ {
+ tp_handle_set_add (handles, GPOINTER_TO_UINT (k));
+ }
- g_hash_table_foreach (self->priv->group_channels, foreach_helper,
- &foreach);
- g_hash_table_foreach (self->priv->list_channels, foreach_helper,
- &foreach);
+ return handles;
}
-static const gchar * const list_fixed_properties[] = {
- TP_IFACE_CHANNEL ".ChannelType",
- TP_IFACE_CHANNEL ".TargetHandleType",
- NULL
-};
-static const gchar * const list_allowed_properties[] = {
- TP_IFACE_CHANNEL ".TargetHandle",
- TP_IFACE_CHANNEL ".TargetID",
- NULL
-};
-static const gchar * const *group_fixed_properties = list_fixed_properties;
-static const gchar * const *group_allowed_properties = list_allowed_properties;
-
static void
-haze_contact_list_foreach_channel_class (TpChannelManager *manager,
- TpChannelManagerChannelClassFunc func,
- gpointer user_data)
+haze_contact_list_dup_states (TpBaseContactList *cl,
+ TpHandle contact,
+ TpSubscriptionState *subscribe_out,
+ TpSubscriptionState *publish_out,
+ gchar **publish_request_out)
{
- GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
- NULL, (GDestroyNotify) tp_g_value_slice_free);
- GValue *value, *handle_type_value;
-
- value = tp_g_value_slice_new (G_TYPE_STRING);
- g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
- g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", value);
-
- handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
- g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
- handle_type_value);
-
- g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_LIST);
- func (manager, table, list_allowed_properties, user_data);
-
- g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_GROUP);
- func (manager, table, group_allowed_properties, user_data);
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ const gchar *bname = haze_connection_handle_inspect (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT, contact);
+ PurpleBuddy *buddy = purple_find_buddy (self->priv->conn->account, bname);
+ TpSubscriptionState pub, sub;
+ PublishRequestData *pub_req = g_hash_table_lookup (
+ self->priv->pending_publish_requests, GUINT_TO_POINTER (contact));
- g_hash_table_destroy (table);
-}
+ if (publish_request_out != NULL)
+ *publish_request_out = NULL;
-HazeContactListChannel *
-haze_contact_list_get_channel (HazeContactList *contact_list,
- guint handle_type,
- TpHandle handle,
- gpointer request_token,
- gboolean *created)
-{
- HazeContactListPrivate *priv = contact_list->priv;
- TpBaseConnection *conn = TP_BASE_CONNECTION (priv->conn);
- TpHandleRepoIface *handle_repo =
- tp_base_connection_get_handles (conn, handle_type);
- GHashTable *channels = (handle_type == TP_HANDLE_TYPE_LIST
- ? priv->list_channels
- : priv->group_channels);
- HazeContactListChannel *chan;
-
- g_assert (handle_type == TP_HANDLE_TYPE_LIST ||
- handle_type == TP_HANDLE_TYPE_GROUP);
- g_assert (tp_handle_is_valid (handle_repo, handle, NULL));
-
- DEBUG ("looking up channel %u:%u '%s'", handle_type, handle,
- tp_handle_inspect (handle_repo, handle));
- chan = g_hash_table_lookup (channels, GINT_TO_POINTER (handle));
-
- if (chan) {
- if (created)
- *created = FALSE;
+ if (buddy != NULL)
+ {
+ /* Well, it's on the contact list. Are we subscribed to its presence?
+ * Who knows? Let's assume we are. */
+ sub = TP_SUBSCRIPTION_STATE_YES;
}
- else
+ else
{
- chan = _haze_contact_list_create_channel (contact_list, handle_type,
- handle, request_token);
- if (created)
- *created = TRUE;
+ /* We're definitely not subscribed. */
+ sub = TP_SUBSCRIPTION_STATE_NO;
}
- return chan;
-}
-
-static HazeContactListChannel *
-_haze_contact_list_get_group (HazeContactList *contact_list,
- const char *group_name,
- gboolean *created)
-{
- HazeContactListPrivate *priv = contact_list->priv;
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
- TpHandleRepoIface *group_repo = tp_base_connection_get_handles (base_conn,
- TP_HANDLE_TYPE_GROUP);
- TpHandle group_handle = tp_handle_ensure (group_repo, group_name, NULL,
- NULL);
- HazeContactListChannel *group;
-
- group = haze_contact_list_get_channel (contact_list, TP_HANDLE_TYPE_GROUP,
- group_handle, NULL, created);
-
- tp_handle_unref (group_repo, group_handle);
-
- return group;
-}
-
-static void
-haze_contact_list_close_all (HazeContactList *self)
-{
- HazeContactListPrivate *priv = self->priv;
- GHashTable *tmp;
+ if (pub_req != NULL)
+ {
+ pub = TP_SUBSCRIPTION_STATE_ASK;
- if (priv->list_channels)
+ if (publish_request_out != NULL)
+ *publish_request_out = g_strdup (pub_req->message);
+ }
+ else if (tp_handle_set_is_member (self->priv->publishing_to, contact))
{
- tmp = priv->list_channels;
- priv->list_channels = NULL;
- g_hash_table_destroy (tmp);
+ pub = TP_SUBSCRIPTION_STATE_YES;
}
- if (priv->group_channels)
+ else if (tp_handle_set_is_member (self->priv->not_publishing_to, contact))
{
- tmp = priv->group_channels;
- priv->group_channels = NULL;
- g_hash_table_destroy (tmp);
+ pub = TP_SUBSCRIPTION_STATE_NO;
}
-
- if (priv->status_changed_id != 0)
+ else
{
- g_signal_handler_disconnect (priv->conn, priv->status_changed_id);
- priv->status_changed_id = 0;
+ pub = TP_SUBSCRIPTION_STATE_UNKNOWN;
}
+
+ if (subscribe_out != NULL)
+ *subscribe_out = sub;
+
+ if (publish_out != NULL)
+ *publish_out = pub;
}
-static TpHandleSet *
-_handle_a_buddy (HazeConnection *conn,
- PurpleBuddy *buddy)
+static void
+haze_contact_list_class_init (HazeContactListClass *klass)
{
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
- TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
- TP_HANDLE_TYPE_CONTACT);
- TpHandleSet *set = tp_handle_set_new (contact_repo);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpBaseContactListClass *parent_class = TP_BASE_CONTACT_LIST_CLASS (klass);
- const gchar *name = purple_buddy_get_name (buddy);
- TpHandle handle = tp_handle_ensure (contact_repo, name,
- NULL, NULL);
- tp_handle_set_add (set, handle);
- tp_handle_unref (contact_repo, handle); /* reffed by set */
+ object_class->constructor = haze_contact_list_constructor;
+
+ object_class->dispose = haze_contact_list_dispose;
+ object_class->finalize = haze_contact_list_finalize;
+
+ parent_class->dup_contacts = haze_contact_list_dup_contacts;
+ parent_class->dup_states = haze_contact_list_dup_states;
+ /* we assume the contact list does persist, which is the default */
+
+ g_type_class_add_private (object_class,
+ sizeof(HazeContactListPrivate));
- return set;
+ purple_signal_connect (purple_blist_get_handle(), "buddy-added",
+ klass, PURPLE_CALLBACK(buddy_added_cb), NULL);
+ purple_signal_connect (purple_blist_get_handle(), "buddy-removed",
+ klass, PURPLE_CALLBACK(buddy_removed_cb), NULL);
}
static void
@@ -543,44 +295,31 @@ buddy_added_cb (PurpleBuddy *buddy, gpointer unused)
{
HazeConnection *conn = ACCOUNT_GET_HAZE_CONNECTION (buddy->account);
HazeContactList *contact_list = conn->contact_list;
- HazeContactListPrivate *priv = contact_list->priv;
- HazeContactListChannel *subscribe, *group;
- TpHandleSet *add_handles;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+ const gchar *name = purple_buddy_get_name (buddy);
+ TpHandle handle = tp_handle_ensure (contact_repo, name, NULL, NULL);
const char *group_name;
- DEBUG ("%s", purple_buddy_get_name (buddy));
-
- if (TP_BASE_CONNECTION (conn)->status == TP_CONNECTION_STATUS_DISCONNECTED)
- {
- DEBUG ("disconnected, ignoring");
- return;
- }
-
- add_handles = _handle_a_buddy (priv->conn, buddy);
-
- subscribe = haze_contact_list_get_channel (contact_list,
- TP_HANDLE_TYPE_LIST, HAZE_LIST_HANDLE_SUBSCRIBE, NULL, NULL);
-
- tp_group_mixin_change_members (G_OBJECT (subscribe), "",
- tp_handle_set_peek (add_handles), NULL, NULL, NULL, 0, 0);
+ tp_base_contact_list_one_contact_changed (
+ (TpBaseContactList *) contact_list, handle);
group_name = purple_group_get_name (purple_buddy_get_group (buddy));
- group = _haze_contact_list_get_group (contact_list, group_name, NULL);
+ tp_base_contact_list_one_contact_groups_changed (
+ (TpBaseContactList *) contact_list, handle, &group_name, 1, NULL, 0);
- tp_group_mixin_change_members (G_OBJECT (group), "",
- tp_handle_set_peek (add_handles), NULL, NULL, NULL, 0, 0);
-
- tp_handle_set_destroy (add_handles);
+ tp_handle_unref (contact_repo, handle);
}
static void
buddy_removed_cb (PurpleBuddy *buddy, gpointer unused)
{
HazeConnection *conn = ACCOUNT_GET_HAZE_CONNECTION (buddy->account);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
HazeContactList *contact_list;
- HazeContactListPrivate *priv;
- HazeContactListChannel *subscribe, *group;
- TpHandleSet *rem_handles;
+ TpHandleRepoIface *contact_repo;
+ TpHandle handle;
const char *group_name, *buddy_name;
GSList *buddies, *l;
gboolean last_instance = TRUE;
@@ -588,18 +327,22 @@ buddy_removed_cb (PurpleBuddy *buddy, gpointer unused)
/* Every buddy gets removed after disconnection, because the PurpleAccount
* gets deleted. So let's ignore removals when we're offline.
*/
- if (TP_BASE_CONNECTION (conn)->status == TP_CONNECTION_STATUS_DISCONNECTED)
+ if (base_conn->status == TP_CONNECTION_STATUS_DISCONNECTED)
return;
contact_list = conn->contact_list;
- priv = contact_list->priv;
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
buddy_name = purple_buddy_get_name (buddy);
- DEBUG ("%s", buddy_name);
+ handle = tp_handle_ensure (contact_repo, buddy_name, NULL, NULL);
+ group_name = purple_group_get_name (purple_buddy_get_group (buddy));
+
+ tp_base_contact_list_one_contact_groups_changed (
+ (TpBaseContactList *) contact_list, handle, NULL, 0, &group_name, 1);
- rem_handles = _handle_a_buddy (priv->conn, buddy);
+ buddies = purple_find_buddies (conn->account, buddy_name);
- buddies = purple_find_buddies (priv->conn->account, buddy_name);
for (l = buddies; l != NULL; l = l->next)
{
if (l->data != buddy)
@@ -608,24 +351,16 @@ buddy_removed_cb (PurpleBuddy *buddy, gpointer unused)
break;
}
}
+
g_slist_free (buddies);
if (last_instance)
{
- subscribe = haze_contact_list_get_channel (contact_list,
- TP_HANDLE_TYPE_LIST, HAZE_LIST_HANDLE_SUBSCRIBE, NULL, NULL);
-
- tp_group_mixin_change_members (G_OBJECT (subscribe), "",
- NULL, tp_handle_set_peek (rem_handles), NULL, NULL, 0, 0);
+ tp_base_contact_list_one_contact_removed (
+ (TpBaseContactList *) contact_list, handle);
}
- group_name = purple_group_get_name (purple_buddy_get_group (buddy));
- group = _haze_contact_list_get_group (contact_list, group_name, NULL);
-
- tp_group_mixin_change_members (G_OBJECT (group), "",
- NULL, tp_handle_set_peek (rem_handles), NULL, NULL, 0, 0);
-
- tp_handle_set_destroy (rem_handles);
+ tp_handle_unref (contact_repo, handle);
}
@@ -640,221 +375,6 @@ typedef struct _HandleContext {
GHashTable *group_handles;
} HandleContext;
-
-/* Called for each buddy on the purple buddy list at login. Adds them to the
- * handle set that will become the 'subscribe' list, and to the handle set
- * which will become their group's channel.
- */
-static void
-_initial_buddies_foreach (PurpleBuddy *buddy,
- HandleContext *context)
-{
- const gchar *name = purple_buddy_get_name (buddy);
- PurpleGroup *group = purple_buddy_get_group (buddy);
- gchar *group_name = group->name;
- TpIntSet *group_set;
- TpHandle handle;
-
- handle = tp_handle_ensure (context->contact_repo, name, NULL, NULL);
- tp_handle_set_add (context->add_handles, handle);
-
- group_set = g_hash_table_lookup (context->group_handles, group_name);
-
- if (!group_set) {
- group_set = tp_intset_new ();
- g_hash_table_insert (context->group_handles, group_name, group_set);
- }
-
- tp_intset_add (group_set, handle);
-
- tp_handle_unref (context->contact_repo, handle);
-
-}
-
-
-/* Creates a group channel on contact_list called group_name, containing the
- * supplied handles. Called while traversing the buddy list at login.
- */
-static gboolean
-_create_initial_group(gchar *group_name,
- TpIntSet *handles,
- HazeContactList *contact_list)
-{
- HazeContactListPrivate *priv = contact_list->priv;
- TpBaseConnection *conn = TP_BASE_CONNECTION (priv->conn);
- TpHandleRepoIface *handle_repo =
- tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_GROUP);
- HazeContactListChannel *group;
- TpHandle group_handle = tp_handle_ensure (handle_repo, group_name, NULL,
- NULL);
- group = haze_contact_list_get_channel (contact_list,
- TP_HANDLE_TYPE_GROUP, group_handle, NULL, NULL /*created*/);
-
- tp_group_mixin_change_members (G_OBJECT (group), "", handles, NULL,
- NULL, NULL, 0, 0);
-
- tp_handle_unref (handle_repo, group_handle); /* reffed by group */
-
- /* Remove this group from the hash of groups to be constructed. */
- return TRUE;
-}
-
-
-/* Iterates across all buddies on a purple account's buddy list at login,
- * adding them to subscribe.
- */
-static void
-_add_initial_buddies (HazeContactList *self,
- HazeContactListChannel *subscribe)
-{
- HazeContactListPrivate *priv = self->priv;
-
- PurpleAccount *account = priv->conn->account;
- GSList *buddies = purple_find_buddies(account, NULL);
-
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
- TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
- TP_HANDLE_TYPE_CONTACT);
-
- TpHandleSet *add_handles = tp_handle_set_new (contact_repo);
- GHashTable *group_handles = g_hash_table_new_full (NULL, NULL, NULL,
- (GDestroyNotify) tp_intset_destroy);
- HandleContext context = { contact_repo, add_handles, group_handles };
-
- g_slist_foreach (buddies, (GFunc) _initial_buddies_foreach, &context);
- g_slist_free (buddies);
-
- tp_group_mixin_change_members (G_OBJECT (subscribe), "",
- tp_handle_set_peek (add_handles), NULL, NULL, NULL, 0, 0);
-
- g_hash_table_foreach_remove (group_handles, (GHRFunc) _create_initial_group,
- self);
- g_hash_table_destroy (group_handles);
-
- tp_handle_set_destroy (add_handles);
-}
-
-static gboolean
-haze_contact_list_request (HazeContactList *self,
- gpointer request_token,
- GHashTable *request_properties,
- gboolean require_new)
-{
- TpHandleRepoIface *handle_repo;
- TpHandleType handle_type;
- TpHandle handle;
- const gchar *channel_name;
- gboolean created;
- HazeContactListChannel *chan;
- GError *error = NULL;
- const gchar * const *fixed_properties;
- const gchar * const *allowed_properties;
-
- if (tp_strdiff (tp_asv_get_string (request_properties,
- TP_IFACE_CHANNEL ".ChannelType"),
- TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
- {
- return FALSE;
- }
-
- handle_type = tp_asv_get_uint32 (request_properties,
- TP_IFACE_CHANNEL ".TargetHandleType", NULL);
-
- switch (handle_type)
- {
- case TP_HANDLE_TYPE_LIST:
- fixed_properties = list_fixed_properties;
- allowed_properties = list_allowed_properties;
- break;
- case TP_HANDLE_TYPE_GROUP:
- fixed_properties = group_fixed_properties;
- allowed_properties = group_allowed_properties;
- break;
- default:
- return FALSE;
- }
-
- handle_repo = tp_base_connection_get_handles (
- TP_BASE_CONNECTION (self->priv->conn), handle_type);
-
- handle = tp_asv_get_uint32 (request_properties,
- TP_IFACE_CHANNEL ".TargetHandle", NULL);
- g_assert (tp_handle_is_valid (handle_repo, handle, NULL));
-
- if (tp_channel_manager_asv_has_unknown_properties (request_properties,
- fixed_properties, allowed_properties, &error))
- {
- goto error;
- }
-
- channel_name = tp_handle_inspect (handle_repo, handle);
- DEBUG ("grabbing channel '%s'...", channel_name);
-
- chan = haze_contact_list_get_channel (self, handle_type, handle,
- request_token, &created);
- g_assert (chan != NULL);
-
- if (!created)
- {
- if (require_new)
- {
- tp_channel_manager_emit_request_failed (self, request_token,
- TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Channel already exists");
- }
- else
- {
- tp_channel_manager_emit_request_already_satisfied (self,
- request_token, TP_EXPORTABLE_CHANNEL (chan));
- }
- }
-
- return TRUE;
-
-error:
- tp_channel_manager_emit_request_failed (self, request_token,
- error->domain, error->code, error->message);
- g_error_free (error);
- return TRUE;
-}
-
-
-static gboolean
-haze_contact_list_create_channel (TpChannelManager *manager,
- gpointer request_token,
- GHashTable *request_properties)
-{
- HazeContactList *self = HAZE_CONTACT_LIST (manager);
-
- return haze_contact_list_request (self, request_token,
- request_properties, TRUE);
-}
-
-static gboolean
-haze_contact_list_ensure_channel (TpChannelManager *manager,
- gpointer request_token,
- GHashTable *request_properties)
-{
- HazeContactList *self = HAZE_CONTACT_LIST (manager);
-
- return haze_contact_list_request (self, request_token,
- request_properties, FALSE);
-}
-
-static void
-channel_manager_iface_init (gpointer g_iface,
- gpointer iface_data G_GNUC_UNUSED)
-{
- TpChannelManagerIface *iface = g_iface;
-
- iface->foreach_channel = haze_contact_list_foreach_channel;
- iface->foreach_channel_class = haze_contact_list_foreach_channel_class;
- iface->create_channel = haze_contact_list_create_channel;
- iface->ensure_channel = haze_contact_list_ensure_channel;
- /* Request is equivalent to Ensure for this channel class */
- iface->request_channel = haze_contact_list_ensure_channel;
-}
-
-
/* Removes the PublishRequestData for the given handle, from the
* pending_publish_requests table, dropping its reference to that handle.
*/
@@ -880,52 +400,65 @@ void
haze_contact_list_accept_publish_request (HazeContactList *self,
TpHandle handle)
{
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->conn);
gpointer key = GUINT_TO_POINTER (handle);
PublishRequestData *request_data = g_hash_table_lookup (
self->priv->pending_publish_requests, key);
- TpIntSet *add;
const gchar *bname = haze_connection_handle_inspect (self->priv->conn,
TP_HANDLE_TYPE_CONTACT, handle);
- g_return_if_fail (request_data != NULL);
+ if (request_data == NULL)
+ return;
DEBUG ("allowing publish request for %s", bname);
request_data->allow(request_data->data);
- add = tp_intset_new_containing (handle);
- tp_group_mixin_change_members (G_OBJECT (request_data->publish), "",
- add, NULL, NULL, NULL, base_conn->self_handle,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
- tp_intset_destroy (add);
-
+ tp_handle_set_add (self->priv->publishing_to, handle);
remove_pending_publish_request (self, handle);
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ handle);
+}
+
+static void
+haze_contact_list_authorize_publication_async (TpBaseContactList *cl,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpIntsetFastIter iter;
+ TpHandle handle;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ haze_contact_list_accept_publish_request (self, handle);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, haze_contact_list_authorize_publication_async);
}
void
haze_contact_list_reject_publish_request (HazeContactList *self,
TpHandle handle)
{
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->conn);
gpointer key = GUINT_TO_POINTER (handle);
PublishRequestData *request_data = g_hash_table_lookup (
self->priv->pending_publish_requests, key);
- TpIntSet *to_remove;
const gchar *bname = haze_connection_handle_inspect (self->priv->conn,
TP_HANDLE_TYPE_CONTACT, handle);
- g_return_if_fail (request_data != NULL);
+ if (request_data == NULL)
+ return;
DEBUG ("denying publish request for %s", bname);
request_data->deny(request_data->data);
- to_remove = tp_intset_new_containing (handle);
- tp_group_mixin_change_members (G_OBJECT (request_data->publish), "",
- NULL, to_remove, NULL, NULL, base_conn->self_handle,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
- tp_intset_destroy (to_remove);
-
+ tp_handle_set_add (self->priv->not_publishing_to, handle);
remove_pending_publish_request (self, handle);
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ handle);
}
@@ -944,13 +477,8 @@ haze_request_authorize (PurpleAccount *account,
TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
TP_HANDLE_TYPE_CONTACT);
- HazeContactListChannel *publish =
- haze_contact_list_get_channel (conn->contact_list,
- TP_HANDLE_TYPE_LIST, HAZE_LIST_HANDLE_PUBLISH, NULL, NULL);
-
- TpIntSet *add_local_pending = tp_intset_new ();
+ HazeContactList *self = conn->contact_list;
TpHandle remote_handle;
- gboolean changed;
PublishRequestData *request_data = publish_request_data_new ();
/* This handle is owned by request_data, and is unreffed in
@@ -958,20 +486,22 @@ haze_request_authorize (PurpleAccount *account,
*/
remote_handle = tp_handle_ensure (contact_repo, remote_user, NULL, NULL);
request_data->self = g_object_ref (conn->contact_list);
- request_data->publish = g_object_ref (publish);
request_data->handle = remote_handle;
request_data->allow = authorize_cb;
request_data->deny = deny_cb;
request_data->data = user_data;
+ request_data->message = g_strdup (message);
g_hash_table_insert (conn->contact_list->priv->pending_publish_requests,
GUINT_TO_POINTER (remote_handle), request_data);
- tp_intset_add (add_local_pending, remote_handle);
- changed = tp_group_mixin_change_members (G_OBJECT (publish), message, NULL,
- NULL, add_local_pending, NULL, remote_handle,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
- tp_intset_destroy (add_local_pending);
+ /* If we got a publish request from them, then presumably we weren't
+ * already publishing to them? */
+ tp_handle_set_remove (self->priv->publishing_to, remote_handle);
+ tp_handle_set_add (self->priv->not_publishing_to, remote_handle);
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ remote_handle);
return request_data;
}
@@ -981,32 +511,22 @@ void
haze_close_account_request (gpointer request_data_)
{
PublishRequestData *request_data = request_data_;
-
/* When 'request_data' is removed from the pending request table, its
- * reference to 'publish' is dropped. So, we take our own reference here,
- * in case the reference in 'request_data' was the last one. (If we don't,
- * the channel (including 'pending_publish_requests') is destroyed half-way
- * through g_hash_table_remove() in remove_pending_publish_request(); cue
- * stack corruption.)
- */
+ * reference to 'self' is dropped. So, we take our own reference here,
+ * in case the reference in 'request_data' was the last one. */
HazeContactList *self = g_object_ref (request_data->self);
- HazeContactListChannel *publish = g_object_ref (request_data->publish);
-
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->conn);
+ TpHandle handle = request_data->handle;
- TpIntSet *to_remove = tp_intset_new ();
+ /* Note that adding the handle to @not_publishing_to has the side-effect
+ * of ensuring that it remains valid long enough for us to signal the
+ * change. */
+ DEBUG ("cancelling publish request for handle %u", handle);
+ tp_handle_set_add (self->priv->not_publishing_to, handle);
+ remove_pending_publish_request (self, handle);
- DEBUG ("cancelling publish request for handle %u", request_data->handle);
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ handle);
- tp_intset_add (to_remove, request_data->handle);
- tp_group_mixin_change_members (G_OBJECT (publish), NULL, NULL, to_remove,
- NULL, NULL, base_conn->self_handle,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
- tp_intset_destroy (to_remove);
-
- remove_pending_publish_request (self, request_data->handle);
-
- g_object_unref (publish);
g_object_unref (self);
}
@@ -1035,6 +555,36 @@ haze_contact_list_request_subscription (HazeContactList *self,
purple_account_add_buddy (account, buddy);
}
+static void
+haze_contact_list_request_subscription_async (TpBaseContactList *cl,
+ TpHandleSet *contacts,
+ const gchar *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpIntsetFastIter iter;
+ TpHandle handle;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ haze_contact_list_request_subscription (self, handle, message);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, haze_contact_list_request_subscription_async);
+}
+
+static void
+haze_contact_list_store_contacts_async (TpBaseContactList *cl,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ haze_contact_list_request_subscription_async (cl, contacts, "", callback,
+ user_data);
+}
+
void
haze_contact_list_remove_contact (HazeContactList *self,
TpHandle handle)
@@ -1045,16 +595,7 @@ haze_contact_list_remove_contact (HazeContactList *self,
GSList *buddies, *l;
buddies = purple_find_buddies (account, bname);
-
- if (buddies == NULL)
- {
- g_warning("'%s' is in the group mixin for '%s' but not on the "
- "libpurple blist", bname, account->username);
- /* This occurring is a bug in haze or libpurple, but I figure
- * it's better not to explode
- */
- return;
- }
+ /* buddies may be NULL, but that's a perfectly reasonable GSList */
/* Removing a buddy from subscribe entails removing it from all
* groups since you can't have a buddy without groups in libpurple.
@@ -1068,9 +609,31 @@ haze_contact_list_remove_contact (HazeContactList *self,
purple_blist_remove_buddy (buddy);
}
+ /* Also decline any publication requests we might have had */
+ haze_contact_list_reject_publish_request (self, handle);
+
g_slist_free (buddies);
}
+static void
+haze_contact_list_remove_contacts_async (TpBaseContactList *cl,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpIntsetFastIter iter;
+ TpHandle handle;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ haze_contact_list_remove_contact (self, handle);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, haze_contact_list_remove_contacts_async);
+}
+
void
haze_contact_list_add_to_group (HazeContactList *self,
const gchar *group_name,
@@ -1080,17 +643,19 @@ haze_contact_list_add_to_group (HazeContactList *self,
const gchar *bname =
haze_connection_handle_inspect (conn, TP_HANDLE_TYPE_CONTACT, handle);
PurpleBuddy *buddy;
- /* This is correct, despite the naming: it returns a borrowed reference
- * to an existing group (if possible) or to a new group (otherwise). */
+ /* This actually has "ensure" semantics, and doesn't return a ref */
PurpleGroup *group = purple_group_new (group_name);
+ /* We have to reassure the TpBaseContactList that the group exists,
+ * because libpurple doesn't have a group-added signal */
+ tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+ &group_name, 1);
+
g_return_if_fail (group != NULL);
- /* If the buddy is already in this group then this callback should
- * never have been called.
- */
- g_assert (purple_find_buddy_in_group (conn->account, bname, group)
- == NULL);
+ /* if the contact is in the group, we have nothing to do */
+ if (purple_find_buddy_in_group (conn->account, bname, group) != NULL)
+ return;
buddy = purple_buddy_new (conn->account, bname, NULL);
@@ -1099,73 +664,562 @@ haze_contact_list_add_to_group (HazeContactList *self,
purple_account_add_buddy (conn->account, buddy);
}
+/* Prepare to @contacts from @group_name. If some of the @contacts are not in
+ * any other group, add them to the fallback group, or if @group_name *is* the
+ * fallback group, fail without doing anything else. */
+static gboolean
+haze_contact_list_prep_remove_from_group (HazeContactList *self,
+ const gchar *group_name,
+ TpHandleSet *contacts,
+ GError **error)
+{
+ HazeConnection *conn = self->priv->conn;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ PurpleAccount *account = conn->account;
+ PurpleGroup *group = purple_find_group (group_name);
+ TpIntsetFastIter iter;
+ TpHandle handle;
+ TpHandleSet *orphans;
+
+ /* no such group? that was easy, we "already removed them" */
+ if (group == NULL)
+ return TRUE;
+
+ orphans = tp_handle_set_new (contact_repo);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ {
+ gboolean is_in = FALSE;
+ gboolean orphaned = TRUE;
+ GSList *buddies;
+ GSList *l;
+ const gchar *bname =
+ haze_connection_handle_inspect (conn, TP_HANDLE_TYPE_CONTACT,
+ handle);
+
+ g_assert (bname != NULL);
+
+ buddies = purple_find_buddies (account, bname);
+
+ for (l = buddies; l != NULL; l = l->next)
+ {
+ PurpleGroup *their_group = purple_buddy_get_group (l->data);
+
+ if (their_group == group)
+ is_in = TRUE;
+ else
+ orphaned = FALSE;
+ }
+
+ if (is_in && orphaned)
+ tp_handle_set_add (orphans, handle);
+
+ g_slist_free (buddies);
+ }
+
+ /* If they're in the group and it's their last group, we need to move
+ * them to the fallback group. If the group they're being removed from *is*
+ * the fallback group, we just fail (before we've actually done anything). */
+ if (!tp_handle_set_is_empty (orphans))
+ {
+ const gchar *def_name = haze_get_fallback_group ();
+ PurpleGroup *default_group = purple_group_new (def_name);
+
+ /* We might have just created that group; libpurple doesn't have
+ * a group-added signal, so tell TpBaseContactList about it */
+ tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+ &def_name, 1);
+
+ if (default_group == group)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Contacts can't be removed from '%s' unless they are in "
+ "another group", group->name);
+ return FALSE;
+ }
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (orphans));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ {
+ const gchar *bname =
+ haze_connection_handle_inspect (conn, TP_HANDLE_TYPE_CONTACT,
+ handle);
+
+ PurpleBuddy *copy = purple_buddy_new (conn->account, bname, NULL);
+ purple_blist_add_buddy (copy, NULL, default_group, NULL);
+ purple_account_add_buddy (account, copy);
+ }
+ }
+
+ return TRUE;
+}
+
+/* haze_contact_list_prep_remove_from_group() must succeed first. */
+static void
+haze_contact_list_remove_many_from_group (HazeContactList *self,
+ const gchar *group_name,
+ TpHandleSet *contacts)
+{
+ PurpleAccount *account = self->priv->conn->account;
+ PurpleGroup *group = purple_find_group (group_name);
+ TpIntsetFastIter iter;
+ TpHandle handle;
+
+ if (group == NULL)
+ return;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ {
+ GSList *buddies;
+ GSList *l;
+ const gchar *bname = haze_connection_handle_inspect (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT, handle);
+
+ buddies = purple_find_buddies (account, bname);
+
+ /* See if the buddy was in the group more than once, since this is
+ * possible in libpurple... */
+ for (l = buddies; l != NULL; l = l->next)
+ {
+ PurpleGroup *their_group = purple_buddy_get_group (l->data);
+
+ if (their_group == group)
+ {
+ purple_account_remove_buddy (account, l->data, group);
+ purple_blist_remove_buddy (l->data);
+ }
+ }
+ }
+}
+
gboolean
haze_contact_list_remove_from_group (HazeContactList *self,
const gchar *group_name,
TpHandle handle,
GError **error)
{
- HazeConnection *conn = self->priv->conn;
- PurpleAccount *account = conn->account;
- const gchar *bname =
- haze_connection_handle_inspect (conn, TP_HANDLE_TYPE_CONTACT, handle);
- GSList *buddies;
- GSList *l;
- gboolean orphaned = TRUE;
- gboolean ret = TRUE;
- PurpleGroup *group = purple_find_group (group_name);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ gboolean ok;
+ TpHandleSet *contacts = tp_handle_set_new_containing (contact_repo, handle);
- g_return_val_if_fail (group != NULL, FALSE);
+ ok = haze_contact_list_prep_remove_from_group (self, group_name, contacts,
+ error);
- buddies = purple_find_buddies (account, bname);
+ if (ok)
+ haze_contact_list_remove_many_from_group (self, group_name, contacts);
- for (l = buddies; l != NULL; l = l->next)
- {
- PurpleGroup *their_group = purple_buddy_get_group (l->data);
+ tp_handle_set_destroy (contacts);
+ return ok;
+}
- if (their_group != group)
- {
- orphaned = FALSE;
- break;
- }
- }
-
- if (orphaned)
- {
- /* the contact needs to be copied to the default group first */
- PurpleGroup *default_group = purple_group_new (
- haze_get_fallback_group ());
- PurpleBuddy *copy;
-
- if (default_group == group)
- {
- /* we could make them bounce back, but that'd be insane */
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Contacts can't be removed from '%s' unless they are in "
- "another group", group->name);
- ret = FALSE;
- goto finally;
- }
-
- copy = purple_buddy_new (conn->account, bname, NULL);
- purple_blist_add_buddy (copy, NULL, default_group, NULL);
- purple_account_add_buddy (account, copy);
- }
-
- /* See if the buddy was in the group more than once, since this is
- * possible in libpurple... */
- for (l = buddies; l != NULL; l = l->next)
- {
- PurpleGroup *their_group = purple_buddy_get_group (l->data);
+static void
+haze_contact_list_mutable_init (TpMutableContactListInterface *vtable)
+{
+ /* we use the default _finish functions, which assume a GSimpleAsyncResult */
+ vtable->request_subscription_async =
+ haze_contact_list_request_subscription_async;
+ vtable->authorize_publication_async =
+ haze_contact_list_authorize_publication_async;
+ vtable->remove_contacts_async = haze_contact_list_remove_contacts_async;
+ /* this is about the best we can do for unsubscribe/unpublish */
+ vtable->unsubscribe_async = haze_contact_list_remove_contacts_async;
+ vtable->unpublish_async = haze_contact_list_remove_contacts_async;
+ vtable->store_contacts_async = haze_contact_list_store_contacts_async;
+ /* assume defaults: can change the contact list, and requests use the
+ * message */
+}
+
+static GStrv
+haze_contact_list_dup_groups (TpBaseContactList *cl G_GNUC_UNUSED)
+{
+ PurpleBlistNode *node;
+ /* borrowed group name => NULL */
+ GHashTable *groups = g_hash_table_new (g_str_hash, g_str_equal);
+ GHashTableIter iter;
+ gpointer k;
+ GPtrArray *arr;
+
+ for (node = purple_blist_get_root ();
+ node != NULL;
+ node = purple_blist_node_next (node, TRUE))
+ {
+ if (purple_blist_node_get_type (node) == PURPLE_BLIST_GROUP_NODE)
+ g_hash_table_insert (groups, PURPLE_GROUP (node)->name, NULL);
+ }
- if (their_group == group)
- {
- purple_account_remove_buddy (account, l->data, group);
- purple_blist_remove_buddy (l->data);
- }
- }
+ arr = g_ptr_array_sized_new (g_hash_table_size (groups) + 1);
-finally:
- g_slist_free (buddies);
- return ret;
+ g_hash_table_iter_init (&iter, groups);
+
+ while (g_hash_table_iter_next (&iter, &k, NULL))
+ g_ptr_array_add (arr, g_strdup (k));
+
+ g_hash_table_unref (groups);
+ g_ptr_array_add (arr, NULL);
+ return (GStrv) g_ptr_array_free (arr, FALSE);
+}
+
+static GStrv
+haze_contact_list_dup_contact_groups (TpBaseContactList *cl,
+ TpHandle contact)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ const gchar *bname = haze_connection_handle_inspect (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT, contact);
+ GSList *buddies, *sl_iter;
+ GPtrArray *arr;
+
+ g_return_val_if_fail (bname != NULL, NULL);
+
+ buddies = purple_find_buddies (self->priv->conn->account, bname);
+
+ arr = g_ptr_array_sized_new (g_slist_length (buddies));
+
+ for (sl_iter = buddies; sl_iter != NULL; sl_iter = sl_iter->next)
+ {
+ PurpleGroup *group = purple_buddy_get_group (sl_iter->data);
+
+ g_ptr_array_add (arr, g_strdup (purple_group_get_name (group)));
+ }
+
+ g_slist_free (buddies);
+ g_ptr_array_add (arr, NULL);
+ return (GStrv) g_ptr_array_free (arr, FALSE);
+}
+
+static TpHandleSet *
+haze_contact_list_dup_group_members (TpBaseContactList *cl,
+ const gchar *group_name)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ PurpleGroup *group = purple_find_group (group_name);
+ PurpleBlistNode *contact, *buddy;
+ TpHandleSet *members = tp_handle_set_new (contact_repo);
+
+ if (group == NULL)
+ return members;
+
+ for (contact = purple_blist_node_get_first_child ((PurpleBlistNode *) group);
+ contact != NULL;
+ contact = purple_blist_node_get_sibling_next (contact))
+ {
+ if (G_UNLIKELY (purple_blist_node_get_type (contact) !=
+ PURPLE_BLIST_CONTACT_NODE))
+ {
+ g_warning ("a child of a Group had unexpected type %d",
+ purple_blist_node_get_type (contact));
+ continue;
+ }
+
+ for (buddy = purple_blist_node_get_first_child (contact);
+ buddy != NULL;
+ buddy = purple_blist_node_get_sibling_next (buddy))
+ {
+ const gchar *bname;
+ TpHandle handle;
+
+ if (G_UNLIKELY (purple_blist_node_get_type (buddy) !=
+ PURPLE_BLIST_BUDDY_NODE))
+ {
+ g_warning ("a child of a Contact had unexpected type %d",
+ purple_blist_node_get_type (buddy));
+ continue;
+ }
+
+ bname = purple_buddy_get_name (PURPLE_BUDDY (buddy));
+ handle = tp_handle_ensure (contact_repo, bname, NULL, NULL);
+
+ if (G_LIKELY (handle != 0))
+ tp_handle_set_add (members, handle);
+ }
+ }
+
+ return members;
+}
+
+static gchar *
+haze_contact_list_normalize_group (TpBaseContactList *cl G_GNUC_UNUSED,
+ const gchar *s)
+{
+ /* By inspection of blist.c: group names are normalized by stripping
+ * unprintable characters, then considering groups that collate to the
+ * same thing in the current locale to be the same group. Empty group names
+ * aren't allowed. */
+ gchar *ret = purple_utf8_strip_unprintables (s);
+ PurpleGroup *group;
+ const gchar *group_name;
+
+ if (tp_str_empty (ret))
+ {
+ g_free (ret);
+ return NULL;
+ }
+
+ group = purple_find_group (ret);
+
+ if (group != NULL)
+ {
+ group_name = purple_group_get_name (group);
+
+ if (tp_strdiff (group_name, ret))
+ {
+ g_free (ret);
+ return g_strdup (group_name);
+ }
+ }
+
+ return ret;
+}
+
+static void
+haze_contact_list_groups_init (TpContactGroupListInterface *vtable)
+{
+ vtable->dup_groups = haze_contact_list_dup_groups;
+ vtable->dup_group_members = haze_contact_list_dup_group_members;
+ vtable->dup_contact_groups = haze_contact_list_dup_contact_groups;
+ vtable->normalize_group = haze_contact_list_normalize_group;
+ /* assume default: groups aren't disjoint */
+}
+
+static void
+haze_contact_list_set_contact_groups_async (TpBaseContactList *cl,
+ TpHandle contact,
+ const gchar * const *names,
+ gsize n_names,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ PurpleAccount *account = self->priv->conn->account;
+ const gchar *fallback_group;
+ const gchar *bname = haze_connection_handle_inspect (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT, contact);
+ gsize i;
+ GSList *buddies, *l;
+
+ g_assert (bname != NULL);
+
+ if (n_names == 0)
+ {
+ /* the contact must be in some group */
+ fallback_group = haze_get_fallback_group ();
+ names = &fallback_group;
+ n_names = 1;
+ }
+
+ /* put them in any groups they ought to be in */
+ for (i = 0; i < n_names; i++)
+ haze_contact_list_add_to_group (self, names[i], contact);
+
+ /* remove them from any groups they ought to not be in */
+ buddies = purple_find_buddies (account, bname);
+
+ for (l = buddies; l != NULL; l = l->next)
+ {
+ PurpleGroup *group = purple_buddy_get_group (l->data);
+ const gchar *group_name = purple_group_get_name (group);
+ gboolean desired = FALSE;
+
+ for (i = 0; i < n_names; i++)
+ {
+ if (tp_strdiff (group_name, names[i]))
+ {
+ desired = TRUE;
+ break;
+ }
+ }
+
+ if (!desired)
+ {
+ purple_account_remove_buddy (account, l->data, group);
+ purple_blist_remove_buddy (l->data);
+ }
+ }
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, haze_contact_list_set_contact_groups_async);
+}
+
+static void
+haze_contact_list_add_to_group_async (TpBaseContactList *cl,
+ const gchar *group_name,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpIntsetFastIter iter;
+ TpHandle handle;
+ /* This actually has "ensure" semantics, and doesn't return a ref */
+ PurpleGroup *group = purple_group_new (group_name);
+
+ /* We have to reassure the TpBaseContactList that the group exists,
+ * because libpurple doesn't have a group-added signal */
+ g_assert (group != NULL);
+ tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+ &group_name, 1);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ haze_contact_list_add_to_group (self, group_name, handle);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, haze_contact_list_add_to_group_async);
+}
+
+static void
+haze_contact_list_remove_from_group_async (TpBaseContactList *cl,
+ const gchar *group_name,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ GError *error = NULL;
+
+ if (haze_contact_list_prep_remove_from_group (self, group_name, contacts,
+ &error))
+ {
+ haze_contact_list_remove_many_from_group (self, group_name, contacts);
+ tp_simple_async_report_success_in_idle ((GObject *) cl, callback,
+ user_data, haze_contact_list_remove_from_group_async);
+ }
+ else
+ {
+ g_simple_async_report_gerror_in_idle ((GObject *) cl, callback,
+ user_data, error);
+ g_clear_error (&error);
+ }
+}
+
+static void
+haze_contact_list_remove_group_async (TpBaseContactList *cl,
+ const gchar *group_name,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TpHandleSet *members = haze_contact_list_dup_group_members (cl, group_name);
+ GError *error = NULL;
+
+ if (haze_contact_list_prep_remove_from_group (HAZE_CONTACT_LIST (cl),
+ group_name, members, &error))
+ {
+ PurpleGroup *group = purple_find_group (group_name);
+
+ if (group != NULL)
+ purple_blist_remove_group (group);
+
+ tp_base_contact_list_groups_removed (cl, &group_name, 1);
+
+ tp_simple_async_report_success_in_idle ((GObject *) cl, callback,
+ user_data, haze_contact_list_remove_group_async);
+ }
+ else
+ {
+ g_simple_async_report_gerror_in_idle ((GObject *) cl, callback,
+ user_data, error);
+ g_clear_error (&error);
+ }
+
+ tp_handle_set_destroy (members);
+}
+
+static void
+haze_contact_list_set_group_members_async (TpBaseContactList *cl,
+ const gchar *group_name,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ HazeContactList *self = HAZE_CONTACT_LIST (cl);
+ TpHandleSet *outcasts = haze_contact_list_dup_group_members (cl, group_name);
+ GError *error = NULL;
+ /* This actually has "ensure" semantics, and doesn't return a ref.
+ * We do this even if there are no contacts, to create the group as a
+ * side-effect. */
+ PurpleGroup *group = purple_group_new (group_name);
+
+ /* We have to reassure the TpBaseContactList that the group exists,
+ * because libpurple doesn't have a group-added signal */
+ g_assert (group != NULL);
+ tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+ &group_name, 1);
+
+ tp_intset_destroy (tp_handle_set_difference_update (outcasts,
+ tp_handle_set_peek (contacts)));
+
+ if (haze_contact_list_prep_remove_from_group (HAZE_CONTACT_LIST (cl),
+ group_name, outcasts, &error))
+ {
+ haze_contact_list_add_to_group_async (cl, group_name, contacts, callback,
+ user_data);
+ haze_contact_list_remove_many_from_group (self, group_name, outcasts);
+ }
+ else
+ {
+ g_simple_async_report_gerror_in_idle ((GObject *) cl, callback,
+ user_data, error);
+ g_clear_error (&error);
+ }
+}
+
+static void
+haze_contact_list_rename_group_async (TpBaseContactList *cl,
+ const gchar *old_name,
+ const gchar *new_name,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PurpleGroup *group = purple_find_group (old_name);
+ PurpleGroup *other = purple_find_group (new_name);
+
+ if (group == NULL)
+ {
+ g_simple_async_report_error_in_idle ((GObject *) cl, callback,
+ user_data, TP_ERROR, TP_ERROR_DOES_NOT_EXIST,
+ "The group '%s' does not exist", old_name);
+ return;
+ }
+
+ if (other != NULL)
+ {
+ g_simple_async_report_error_in_idle ((GObject *) cl, callback,
+ user_data, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
+ "The group '%s' already exists", new_name);
+ return;
+ }
+
+ purple_blist_rename_group (group, new_name);
+ tp_base_contact_list_group_renamed (cl, old_name, new_name);
+
+ tp_simple_async_report_success_in_idle ((GObject *) cl, callback,
+ user_data, haze_contact_list_rename_group_async);
+}
+
+static void
+haze_contact_list_mutable_groups_init (
+ TpMutableContactGroupListInterface *vtable)
+{
+ /* we use the default _finish functions, which assume a GSimpleAsyncResult */
+ vtable->set_contact_groups_async =
+ haze_contact_list_set_contact_groups_async;
+ vtable->set_group_members_async = haze_contact_list_set_group_members_async;
+ vtable->add_to_group_async = haze_contact_list_add_to_group_async;
+ vtable->remove_from_group_async = haze_contact_list_remove_from_group_async;
+ vtable->remove_group_async = haze_contact_list_remove_group_async;
+ vtable->rename_group_async = haze_contact_list_rename_group_async;
+ /* assume default: groups are stored persistently */
}
diff --git a/src/contact-list.h b/src/contact-list.h
index 58b97bf..b6fa085 100644
--- a/src/contact-list.h
+++ b/src/contact-list.h
@@ -22,7 +22,7 @@
#include <glib-object.h>
-#include "contact-list-channel.h"
+#include <telepathy-glib/base-contact-list.h>
G_BEGIN_DECLS
@@ -31,11 +31,11 @@ typedef struct _HazeContactListClass HazeContactListClass;
typedef struct _HazeContactListPrivate HazeContactListPrivate;
struct _HazeContactListClass {
- GObjectClass parent_class;
+ TpBaseContactListClass parent_class;
};
struct _HazeContactList {
- GObject parent;
+ TpBaseContactList parent;
HazeContactListPrivate *priv;
};
@@ -60,10 +60,6 @@ GType haze_contact_list_get_type (void);
G_END_DECLS
-HazeContactListChannel *haze_contact_list_get_channel (HazeContactList *self,
- guint handle_type, TpHandle handle, gpointer request_token,
- gboolean *created);
-
gpointer haze_request_authorize (PurpleAccount *account,
const char *remote_user, const char *id, const char *alias,
const char *message, gboolean on_list,
diff --git a/tests/twisted/hazetest.py b/tests/twisted/hazetest.py
index c179309..2c43b53 100644
--- a/tests/twisted/hazetest.py
+++ b/tests/twisted/hazetest.py
@@ -792,3 +792,13 @@ def make_presence(_from, to='test@localhost', type=None, show=None,
x.addElement('photo').addContent(photo)
return presence
+
+def close_all_groups(q, bus, conn, stream):
+ channels = conn.Properties.Get(cs.CONN_IFACE_REQUESTS, 'Channels')
+ for path, props in channels:
+ if props.get(cs.CHANNEL_TYPE) != cs.CHANNEL_TYPE_CONTACT_LIST:
+ continue
+ if props.get(cs.TARGET_HANDLE_TYPE) != cs.HT_GROUP:
+ continue
+ wrap_channel(bus.get_object(conn.bus_name, path),
+ cs.CHANNEL_TYPE_CONTACT_LIST).Close()
diff --git a/tests/twisted/roster/groups.py b/tests/twisted/roster/groups.py
index 0e5e137..9b3bd02 100644
--- a/tests/twisted/roster/groups.py
+++ b/tests/twisted/roster/groups.py
@@ -9,7 +9,7 @@ from twisted.words.xish import domish, xpath
from servicetest import (EventPattern, wrap_channel, assertLength,
assertEquals, call_async, sync_dbus, assertContains)
-from hazetest import acknowledge_iq, exec_test, sync_stream
+from hazetest import acknowledge_iq, exec_test, sync_stream, close_all_groups
import constants as cs
import ns
@@ -17,6 +17,11 @@ def test(q, bus, conn, stream):
conn.Connect()
q.expect('dbus-signal', signal='StatusChanged',
args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
+ self_handle = conn.GetSelfHandle()
+
+ # Close all Group channels to get a clean slate, so we can rely on
+ # the NewChannels signal for the default group later
+ close_all_groups(q, bus, conn, stream)
call_async(q, conn.Requests, 'EnsureChannel',{
cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
@@ -131,7 +136,7 @@ def test(q, bus, conn, stream):
query_ns=ns.ROSTER),
EventPattern('dbus-signal', signal='MembersChanged',
path=scots.object_path,
- args=['', [duncan], [], [], [], 0, cs.GC_REASON_NONE]),
+ args=['', [duncan], [], [], [], self_handle, cs.GC_REASON_NONE]),
EventPattern('dbus-return', method='AddMembers'),
)
assertEquals('duncan@scotland.lit', iq.stanza.query.item['jid'])
@@ -148,7 +153,7 @@ def test(q, bus, conn, stream):
query_ns=ns.ROSTER),
EventPattern('dbus-signal', signal='MembersChanged',
path=default_group.object_path,
- args=['', [], [duncan], [], [], 0, cs.GC_REASON_NONE]),
+ args=['', [], [duncan], [], [], self_handle, cs.GC_REASON_NONE]),
EventPattern('dbus-return', method='RemoveMembers'),
)
assertEquals('duncan@scotland.lit', iq.stanza.query.item['jid'])
@@ -168,10 +173,10 @@ def test(q, bus, conn, stream):
query_ns=ns.ROSTER),
EventPattern('dbus-signal', signal='MembersChanged',
path=still_alive.object_path,
- args=['', [], [romeo], [], [], 0, cs.GC_REASON_NONE]),
+ args=['', [], [romeo], [], [], self_handle, cs.GC_REASON_NONE]),
EventPattern('dbus-signal', signal='MembersChanged',
path=default_group.object_path,
- args=['', [romeo], [], [], [], 0, cs.GC_REASON_NONE]),
+ args=['', [romeo], [], [], [], self_handle, cs.GC_REASON_NONE]),
EventPattern('dbus-return', method='RemoveMembers'),
)
@@ -196,7 +201,7 @@ def test(q, bus, conn, stream):
query_ns=ns.ROSTER),
EventPattern('dbus-signal', signal='MembersChanged',
path=still_alive.object_path,
- args=['', [], [juliet], [], [], 0, cs.GC_REASON_NONE]),
+ args=['', [], [juliet], [], [], self_handle, cs.GC_REASON_NONE]),
EventPattern('dbus-return', method='RemoveMembers'),
)
assertEquals('juliet@capulet.lit', iq.stanza.query.item['jid'])
diff --git a/tests/twisted/roster/initial-roster.py b/tests/twisted/roster/initial-roster.py
index c73fb1a..61b4731 100644
--- a/tests/twisted/roster/initial-roster.py
+++ b/tests/twisted/roster/initial-roster.py
@@ -50,18 +50,17 @@ def test(q, bus, conn, stream):
q.expect('dbus-signal', signal='StatusChanged',
args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
- # there is no 'stored' yet; when it exists, it should have Amy, Bob and
- # Chris
- #call_async(q, conn.Requests, 'EnsureChannel',{
- # cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
- # cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
- # cs.TARGET_ID: 'stored',
- # })
- #e = q.expect('dbus-return', method='EnsureChannel')
- #stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]),
- # cs.CHANNEL_TYPE_CONTACT_LIST)
- #jids = set(conn.InspectHandles(cs.HT_CONTACT, stored.Group.GetMembers()))
- #assertEquals(set(['amy@foo.com', 'bob@foo.com', 'chris@foo.com']), jids)
+ # Amy, Bob and Chris are all stored on our server-side roster
+ call_async(q, conn.Requests, 'EnsureChannel',{
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
+ cs.TARGET_ID: 'stored',
+ })
+ e = q.expect('dbus-return', method='EnsureChannel')
+ stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]),
+ cs.CHANNEL_TYPE_CONTACT_LIST)
+ jids = set(conn.InspectHandles(cs.HT_CONTACT, stored.Group.GetMembers()))
+ assertEquals(set(['amy@foo.com', 'bob@foo.com', 'chris@foo.com']), jids)
call_async(q, conn.Requests, 'EnsureChannel',{
cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
diff --git a/tests/twisted/roster/publish.py b/tests/twisted/roster/publish.py
index 60e7a49..02eec95 100644
--- a/tests/twisted/roster/publish.py
+++ b/tests/twisted/roster/publish.py
@@ -9,7 +9,7 @@ from twisted.words.xish import domish
from servicetest import (EventPattern, wrap_channel, assertLength,
assertEquals, call_async, sync_dbus)
-from hazetest import acknowledge_iq, exec_test, sync_stream
+from hazetest import acknowledge_iq, exec_test, sync_stream, close_all_groups
import constants as cs
import ns
@@ -18,6 +18,10 @@ def test(q, bus, conn, stream):
q.expect('dbus-signal', signal='StatusChanged',
args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
+ # Close all Group channels to get a clean slate, so we can rely on
+ # the NewChannels signal for the default group later
+ close_all_groups(q, bus, conn, stream)
+
call_async(q, conn.Requests, 'EnsureChannel',{
cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
@@ -32,6 +36,17 @@ def test(q, bus, conn, stream):
call_async(q, conn.Requests, 'EnsureChannel',{
cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
+ cs.TARGET_ID: 'stored',
+ })
+ e = q.expect('dbus-return', method='EnsureChannel')
+ stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]),
+ cs.CHANNEL_TYPE_CONTACT_LIST)
+ jids = set(conn.InspectHandles(cs.HT_CONTACT, stored.Group.GetMembers()))
+ assertEquals(set(), jids)
+
+ call_async(q, conn.Requests, 'EnsureChannel',{
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
cs.TARGET_ID: 'subscribe',
})
e = q.expect('dbus-return', method='EnsureChannel')
@@ -50,9 +65,20 @@ def test(q, bus, conn, stream):
stream.send(presence)
# it seems either libpurple or haze doesn't pass the message through
- q.expect('dbus-signal', path=publish.object_path,
- args=['', [], [], [alice], [], alice,
- cs.GC_REASON_NONE])
+ q.expect_many(
+ EventPattern('dbus-signal', path=publish.object_path,
+ args=['', [], [], [alice], [], alice,
+ cs.GC_REASON_NONE]),
+ # In the Conn.I.ContactList world, 'stored' has been
+ # re-purposed to mean "we have some reason to care", so she
+ # appears here even though she's not on the server-side roster
+ # just yet
+ EventPattern('dbus-signal', signal='MembersChanged',
+ path=stored.object_path,
+ args=['', [alice], [], [], [], 0, cs.GC_REASON_NONE]),
+ )
+
+ self_handle = conn.GetSelfHandle()
# accept
call_async(q, publish.Group, 'AddMembers', [alice], '')
@@ -62,7 +88,7 @@ def test(q, bus, conn, stream):
to='alice@wonderland.lit'),
EventPattern('dbus-signal', signal='MembersChanged',
path=publish.object_path,
- args=['', [alice], [], [], [], conn.GetSelfHandle(),
+ args=['', [alice], [], [], [], self_handle,
cs.GC_REASON_NONE]),
EventPattern('dbus-return', method='AddMembers'),
)
@@ -80,11 +106,13 @@ def test(q, bus, conn, stream):
_, _, new_group = q.expect_many(
EventPattern('stream-iq', iq_type='result',
predicate=lambda e: e.stanza['id'] == 'roster-push'),
- # this isn't really true, but it's the closest we can guess from
- # libpurple
+ # She's not really on our subscribe list, but this is the closest
+ # we can guess from libpurple
+ # FIXME: TpBaseContactList assumes she's the actor - she must have
+ # accepted our request, right? Not actually true in libpurple.
EventPattern('dbus-signal', signal='MembersChanged',
path=subscribe.object_path,
- args=['', [alice], [], [], [], 0, cs.GC_REASON_NONE]),
+ args=['', [alice], [], [], [], alice, cs.GC_REASON_NONE]),
# the buddy needs a group, because libpurple
EventPattern('dbus-signal', signal='NewChannels',
predicate=lambda e:
@@ -122,8 +150,7 @@ def test(q, bus, conn, stream):
to='queen.of.hearts@wonderland.lit'),
EventPattern('dbus-signal', signal='MembersChanged',
path=publish.object_path,
- args=['', [], [queen], [], [], conn.GetSelfHandle(),
- cs.GC_REASON_NONE]),
+ args=['', [], [queen], [], [], 0, cs.GC_REASON_NONE]),
EventPattern('dbus-return', method='RemoveMembers'),
)
diff --git a/tests/twisted/roster/removed-from-rp-subscribe.py b/tests/twisted/roster/removed-from-rp-subscribe.py
index a399063..522255e 100644
--- a/tests/twisted/roster/removed-from-rp-subscribe.py
+++ b/tests/twisted/roster/removed-from-rp-subscribe.py
@@ -29,6 +29,15 @@ def test(q, bus, conn, stream, remove, local):
call_async(q, conn.Requests, 'EnsureChannel',{
cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
+ cs.TARGET_ID: 'stored',
+ })
+ e = q.expect('dbus-return', method='EnsureChannel')
+ stored = wrap_channel(bus.get_object(conn.bus_name, e.value[1]),
+ cs.CHANNEL_TYPE_CONTACT_LIST)
+
+ call_async(q, conn.Requests, 'EnsureChannel',{
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
+ cs.TARGET_HANDLE_TYPE: cs.HT_LIST,
cs.TARGET_ID: 'publish',
})
e = q.expect('dbus-return', method='EnsureChannel')
@@ -58,9 +67,13 @@ def test(q, bus, conn, stream, remove, local):
stream.send(iq)
# In response, Haze adds Marco to the roster, which we guess (wrongly,
- # in this case) means subscribe
- q.expect('dbus-signal', signal='MembersChanged',
- args=['', [h], [], [], [], 0, 0], path=subscribe.object_path)
+ # in this case) also means subscribe
+ q.expect_many(
+ EventPattern('dbus-signal', signal='MembersChanged',
+ args=['', [h], [], [], [], h, 0], path=subscribe.object_path),
+ EventPattern('dbus-signal', signal='MembersChanged',
+ args=['', [h], [], [], [], 0, 0], path=stored.object_path),
+ )
# Gajim sends a <presence type='subscribe'/> to Marco. 'As a result, the
# user's server MUST initiate a second roster push to all of the user's
@@ -81,9 +94,8 @@ def test(q, bus, conn, stream, remove, local):
if remove:
# ...removes him from the roster...
if local:
- # ...by telling Haze to remove him from subscribe (which is
- # really more like stored)
- subscribe.Group.RemoveMembers([h], '')
+ # ...by telling Haze to remove him from stored
+ stored.Group.RemoveMembers([h], '')
event = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER)
item = event.query.firstChildElement()
@@ -107,12 +119,14 @@ def test(q, bus, conn, stream, remove, local):
stream.send(iq)
# In response, Haze should announce that Marco has been removed from
- # subscribe:remote-pending and stored:members: but it has no stored
- # channel.
+ # subscribe:remote-pending and stored:members
q.expect_many(
EventPattern('dbus-signal', signal='MembersChanged',
args=['', [], [h], [], [], 0, 0],
path=subscribe.object_path),
+ EventPattern('dbus-signal', signal='MembersChanged',
+ args=['', [], [h], [], [], 0, 0],
+ path=stored.object_path),
)
else:
# ...rescinds the subscription request...
diff --git a/tests/twisted/roster/subscribe.py b/tests/twisted/roster/subscribe.py
index 8c718c4..ba20cdd 100644
--- a/tests/twisted/roster/subscribe.py
+++ b/tests/twisted/roster/subscribe.py
@@ -8,7 +8,7 @@ from twisted.words.xish import domish
from servicetest import (EventPattern, wrap_channel, assertLength,
assertEquals, call_async, sync_dbus)
-from hazetest import acknowledge_iq, exec_test, sync_stream
+from hazetest import acknowledge_iq, exec_test, sync_stream, close_all_groups
import constants as cs
import ns
@@ -16,6 +16,11 @@ def test(q, bus, conn, stream):
conn.Connect()
q.expect('dbus-signal', signal='StatusChanged',
args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
+ self_handle = conn.GetSelfHandle()
+
+ # Close all Group channels to get a clean slate, so we can rely on
+ # the NewChannels signal for the default group later
+ close_all_groups(q, bus, conn, stream)
call_async(q, conn.Requests, 'EnsureChannel',{
cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CONTACT_LIST,
@@ -44,9 +49,11 @@ def test(q, bus, conn, stream):
EventPattern('stream-presence', presence_type='subscribe',
to='suggs@night.boat.cairo'),
EventPattern('dbus-return', method='AddMembers', value=()),
+ # FIXME: TpBaseContactList wrongly assumes that he's the actor,
+ # because he must have accepted our request... right? Wrong.
EventPattern('dbus-signal', signal='MembersChanged',
path=subscribe.object_path,
- args=['', [handle], [], [], [], 0, 0]),
+ args=['', [handle], [], [], [], handle, 0]),
EventPattern('dbus-signal', signal='NewChannels',
predicate=lambda e:
e.args[0][0][1].get(cs.TARGET_HANDLE_TYPE) == cs.HT_GROUP),
@@ -85,10 +92,10 @@ def test(q, bus, conn, stream):
EventPattern('dbus-return', method='AddMembers', value=()),
EventPattern('dbus-signal', signal='MembersChanged',
path=subscribe.object_path,
- args=['', [handle], [], [], [], 0, 0]),
+ args=['', [handle], [], [], [], handle, 0]),
EventPattern('dbus-signal', signal='MembersChanged',
path=def_group.object_path,
- args=['', [handle], [], [], [], 0, 0]),
+ args=['', [handle], [], [], [], self_handle, 0]),
)
acknowledge_iq(stream, set_iq.stanza)