X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy%2Fempathy-tp-contact-list.c;h=95ec3bea2109631ee34d9ef2d22b599996792b68;hp=5b0062f9e3fc5e8169f4469fde5d9ee2562d5e93;hb=940d0e9778828657a6ffbcadd35a8a84d706ac70;hpb=8e87910f8c6842af6f2d060173ebf4bbc08f2ebd diff --git a/libempathy/empathy-tp-contact-list.c b/libempathy/empathy-tp-contact-list.c index 5b0062f9..95ec3bea 100644 --- a/libempathy/empathy-tp-contact-list.c +++ b/libempathy/empathy-tp-contact-list.c @@ -1,61 +1,61 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2007 Xavier Claessens - * Copyright (C) 2007 Collabora Ltd. + * Copyright (C) 2007-2009 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 library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. * - * This program is distributed in the hope that it will be useful, + * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * - * 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. - * * Authors: Xavier Claessens */ #include #include -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "empathy-tp-contact-list.h" +#include "empathy-tp-contact-factory.h" #include "empathy-contact-list.h" -#include "empathy-tp-group.h" -#include "empathy-debug.h" #include "empathy-utils.h" -#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ - EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv)) +#define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT +#include "empathy-debug.h" -#define DEBUG_DOMAIN "TpContactList" +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactList) +typedef struct { + EmpathyTpContactFactory *factory; + TpConnection *connection; -struct _EmpathyTpContactListPriv { - TpConn *tp_conn; - McAccount *account; - MissionControl *mc; - const gchar *protocol_group; + TpChannel *publish; + TpChannel *subscribe; + TpChannel *stored; + GHashTable *members; /* handle -> EmpathyContact */ + GHashTable *pendings; /* handle -> EmpathyContact */ + GHashTable *groups; /* group name -> TpChannel */ + GHashTable *add_to_group; /* group name -> GArray of handles */ - EmpathyTpGroup *publish; - EmpathyTpGroup *subscribe; - GList *members; - GList *pendings; + EmpathyContactListFlags flags; - GList *groups; - GHashTable *contacts_groups; -}; + TpProxySignalConnection *new_channels_sig; +} EmpathyTpContactListPriv; typedef enum { TP_CONTACT_LIST_TYPE_PUBLISH, @@ -63,65 +63,91 @@ typedef enum { TP_CONTACT_LIST_TYPE_UNKNOWN } TpContactListType; -static void empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass); -static void empathy_tp_contact_list_init (EmpathyTpContactList *list); static void tp_contact_list_iface_init (EmpathyContactListIface *iface); enum { - DESTROY, - LAST_SIGNAL + PROP_0, + PROP_CONNECTION, }; -static guint signals[LAST_SIGNAL]; - G_DEFINE_TYPE_WITH_CODE (EmpathyTpContactList, empathy_tp_contact_list, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST, tp_contact_list_iface_init)); static void -tp_contact_list_group_destroy_cb (EmpathyTpGroup *group, - EmpathyTpContactList *list) +tp_contact_list_forget_group (EmpathyTpContactList *list, + TpChannel *channel) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (list); + const TpIntSet *members; + TpIntSetIter iter; + const gchar *group_name; + + group_name = tp_channel_get_identifier (channel); + + /* Signal that all members are not in that group anymore */ + members = tp_channel_group_get_members (channel); + tp_intset_iter_init (&iter, members); + while (tp_intset_iter_next (&iter)) { + EmpathyContact *contact; + + contact = g_hash_table_lookup (priv->members, + GUINT_TO_POINTER (iter.element)); + if (contact == NULL) { + continue; + } + + DEBUG ("Contact %s (%d) removed from group %s", + empathy_contact_get_id (contact), iter.element, + group_name); + g_signal_emit_by_name (list, "groups-changed", contact, + group_name, + FALSE); + } +} + +static void +tp_contact_list_group_invalidated_cb (TpChannel *channel, + guint domain, + gint code, + gchar *message, + EmpathyTpContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); + const gchar *group_name; + + group_name = tp_channel_get_identifier (channel); + DEBUG ("Group %s invalidated. Message: %s", group_name, message); - empathy_debug (DEBUG_DOMAIN, "Group destroyed: %s", - empathy_tp_group_get_name (group)); + tp_contact_list_forget_group (list, channel); - priv->groups = g_list_remove (priv->groups, group); - g_object_unref (group); + g_hash_table_remove (priv->groups, group_name); } static void -tp_contact_list_group_member_added_cb (EmpathyTpGroup *group, - EmpathyContact *contact, - EmpathyContact *actor, - guint reason, - const gchar *message, - EmpathyTpContactList *list) +contacts_added_to_group (EmpathyTpContactList *list, + TpChannel *channel, + GArray *added) { - EmpathyTpContactListPriv *priv = GET_PRIV (list); - const gchar *group_name; - GList **groups; + EmpathyTpContactListPriv *priv = GET_PRIV (list); + const gchar *group_name; + guint i; - if (!g_list_find (priv->members, contact)) { - return; - } + group_name = tp_channel_get_identifier (channel); - groups = g_hash_table_lookup (priv->contacts_groups, contact); - if (!groups) { - groups = g_slice_new0 (GList*); - g_hash_table_insert (priv->contacts_groups, - g_object_ref (contact), - groups); - } + for (i = 0; i < added->len; i++) { + EmpathyContact *contact; + TpHandle handle; - group_name = empathy_tp_group_get_name (group); - if (!g_list_find_custom (*groups, group_name, (GCompareFunc) strcmp)) { - empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) added to group %s", - empathy_contact_get_id (contact), - empathy_contact_get_handle (contact), - group_name); - *groups = g_list_prepend (*groups, g_strdup (group_name)); + handle = g_array_index (added, TpHandle, i); + contact = g_hash_table_lookup (priv->members, + GUINT_TO_POINTER (handle)); + if (contact == NULL) { + continue; + } + + DEBUG ("Contact %s (%d) added to group %s", + empathy_contact_get_id (contact), handle, group_name); g_signal_emit_by_name (list, "groups-changed", contact, group_name, TRUE); @@ -129,446 +155,502 @@ tp_contact_list_group_member_added_cb (EmpathyTpGroup *group, } static void -tp_contact_list_group_member_removed_cb (EmpathyTpGroup *group, - EmpathyContact *contact, - EmpathyContact *actor, - guint reason, - const gchar *message, - EmpathyTpContactList *list) +tp_contact_list_group_members_changed_cb (TpChannel *channel, + gchar *message, + GArray *added, + GArray *removed, + GArray *local_pending, + GArray *remote_pending, + guint actor, + guint reason, + EmpathyTpContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - const gchar *group_name; - GList **groups, *l; + const gchar *group_name; + guint i; - if (!g_list_find (priv->members, contact)) { - return; - } + contacts_added_to_group (list, channel, added); - groups = g_hash_table_lookup (priv->contacts_groups, contact); - if (!groups) { - return; - } + group_name = tp_channel_get_identifier (channel); + + for (i = 0; i < removed->len; i++) { + EmpathyContact *contact; + TpHandle handle; + + handle = g_array_index (removed, TpHandle, i); + contact = g_hash_table_lookup (priv->members, + GUINT_TO_POINTER (handle)); + if (contact == NULL) { + continue; + } + + DEBUG ("Contact %s (%d) removed from group %s", + empathy_contact_get_id (contact), handle, group_name); - group_name = empathy_tp_group_get_name (group); - if ((l = g_list_find_custom (*groups, group_name, (GCompareFunc) strcmp))) { - empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) removed from group %s", - empathy_contact_get_id (contact), - empathy_contact_get_handle (contact), - group_name); - *groups = g_list_delete_link (*groups, l); g_signal_emit_by_name (list, "groups-changed", contact, group_name, FALSE); } } -static EmpathyTpGroup * -tp_contact_list_find_group (EmpathyTpContactList *list, - const gchar *group) +static void +tp_contact_list_group_ready_cb (TpChannel *channel, + const GError *error, + gpointer list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - GList *l; + TpChannel *old_group; + const gchar *group_name; + const TpIntSet *members; + GArray *arr; + + if (error) { + DEBUG ("Error: %s", error->message); + g_object_unref (channel); + return; + } - for (l = priv->groups; l; l = l->next) { - if (strcmp (group, empathy_tp_group_get_name (l->data)) == 0) { - return l->data; - } + group_name = tp_channel_get_identifier (channel); + + /* If there's already a group with this name in the table, we can't + * just let it be replaced. Replacing it causes it to be unreffed, + * which causes it to be invalidated (see + * ), which causes + * it to be removed from the hash table again, which causes it to be + * unreffed again. + */ + old_group = g_hash_table_lookup (priv->groups, group_name); + + if (old_group != NULL) { + DEBUG ("Discarding old group %s (%p)", group_name, old_group); + g_hash_table_steal (priv->groups, group_name); + tp_contact_list_forget_group (list, old_group); + g_object_unref (old_group); } - return NULL; -} -static TpContactListType -tp_contact_list_get_type (EmpathyTpContactList *list, - EmpathyTpGroup *group) -{ - EmpathyTpContactListPriv *priv; - TpContactListType list_type; - const gchar *name; + g_hash_table_insert (priv->groups, (gpointer) group_name, channel); + DEBUG ("Group %s added", group_name); - priv = GET_PRIV (list); + g_signal_connect (channel, "group-members-changed", + G_CALLBACK (tp_contact_list_group_members_changed_cb), + list); - name = empathy_tp_group_get_name (group); - if (strcmp (name, "subscribe") == 0) { - list_type = TP_CONTACT_LIST_TYPE_SUBSCRIBE; - } else if (strcmp (name, "publish") == 0) { - list_type = TP_CONTACT_LIST_TYPE_PUBLISH; - } else { - list_type = TP_CONTACT_LIST_TYPE_UNKNOWN; + g_signal_connect (channel, "invalidated", + G_CALLBACK (tp_contact_list_group_invalidated_cb), + list); + + if (priv->add_to_group) { + GArray *handles; + + handles = g_hash_table_lookup (priv->add_to_group, group_name); + if (handles) { + DEBUG ("Adding initial members to group %s", group_name); + tp_cli_channel_interface_group_call_add_members (channel, + -1, handles, NULL, NULL, NULL, NULL, NULL); + g_hash_table_remove (priv->add_to_group, group_name); + } } - return list_type; + /* Get initial members of the group */ + members = tp_channel_group_get_members (channel); + g_assert (members != NULL); + arr = tp_intset_to_array (members); + contacts_added_to_group (list, channel, arr); + g_array_free (arr, TRUE); } static void -tp_contact_list_add_member (EmpathyTpContactList *list, - EmpathyContact *contact, - EmpathyContact *actor, - guint reason, - const gchar *message) +tp_contact_list_group_add_channel (EmpathyTpContactList *list, + const gchar *object_path, + const gchar *channel_type, + TpHandleType handle_type, + guint handle) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - GList *l; - - /* Add to the list and emit signal */ - priv->members = g_list_prepend (priv->members, g_object_ref (contact)); - g_signal_emit_by_name (list, "members-changed", - contact, actor, reason, message, - TRUE); + TpChannel *channel; - /* This contact is now member, implicitly accept pending. */ - if (g_list_find (priv->pendings, contact)) { - empathy_tp_group_add_member (priv->publish, contact, ""); + /* Only accept server-side contact groups */ + if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) || + handle_type != TP_HANDLE_TYPE_GROUP) { + return; } - /* Update groups of the contact */ - for (l = priv->groups; l; l = l->next) { - if (empathy_tp_group_is_member (l->data, contact)) { - tp_contact_list_group_member_added_cb (l->data, contact, - NULL, 0, NULL, - list); - } - } + channel = tp_channel_new (priv->connection, + object_path, channel_type, + handle_type, handle, NULL); + + /* Give the ref to the callback */ + tp_channel_call_when_ready (channel, + tp_contact_list_group_ready_cb, + list); } static void -tp_contact_list_added_cb (EmpathyTpGroup *group, - EmpathyContact *contact, - EmpathyContact *actor, - guint reason, - const gchar *message, - EmpathyTpContactList *list) +tp_contact_list_group_request_channel_cb (TpConnection *connection, + const gchar *object_path, + const GError *error, + gpointer user_data, + GObject *list) { - EmpathyTpContactListPriv *priv = GET_PRIV (list); - TpContactListType list_type; + /* The new channel will be handled in NewChannel cb. Here we only + * handle the error if RequestChannel failed */ + if (error) { + DEBUG ("Error: %s", error->message); + return; + } +} - list_type = tp_contact_list_get_type (list, group); - empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) added to list type %d", - empathy_contact_get_id (contact), - empathy_contact_get_handle (contact), - list_type); +static void +tp_contact_list_group_request_handles_cb (TpConnection *connection, + const GArray *handles, + const GError *error, + gpointer user_data, + GObject *list) +{ + TpHandle channel_handle; - /* We now get the presence of that contact, add it to members */ - if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && - !g_list_find (priv->members, contact)) { - tp_contact_list_add_member (list, contact, actor, reason, message); + if (error) { + DEBUG ("Error: %s", error->message); + return; } - /* We now send our presence to that contact, remove it from pendings */ - if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && - g_list_find (priv->pendings, contact)) { - g_signal_emit_by_name (list, "pendings-changed", - contact, actor, reason, message, - FALSE); - priv->pendings = g_list_remove (priv->pendings, contact); - g_object_unref (contact); - } + channel_handle = g_array_index (handles, TpHandle, 0); + tp_cli_connection_call_request_channel (connection, -1, + TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, + TP_HANDLE_TYPE_GROUP, + channel_handle, + TRUE, + tp_contact_list_group_request_channel_cb, + NULL, NULL, + list); } +/* This function takes ownership of handles array */ static void -tp_contact_list_removed_cb (EmpathyTpGroup *group, - EmpathyContact *contact, - EmpathyContact *actor, - guint reason, - const gchar *message, - EmpathyTpContactList *list) +tp_contact_list_group_add (EmpathyTpContactList *list, + const gchar *group_name, + GArray *handles) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - TpContactListType list_type; - - list_type = tp_contact_list_get_type (list, group); - empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) removed from list type %d", - empathy_contact_get_id (contact), - empathy_contact_get_handle (contact), - list_type); - - /* This contact refuses to send us his presence, remove from members. */ - if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && - g_list_find (priv->members, contact)) { - g_signal_emit_by_name (list, "members-changed", - contact, actor, reason, message, - FALSE); - priv->members = g_list_remove (priv->members, contact); - g_object_unref (contact); + TpChannel *channel; + const gchar *names[] = {group_name, NULL}; + + /* Search the channel for that group name */ + channel = g_hash_table_lookup (priv->groups, group_name); + if (channel) { + tp_cli_channel_interface_group_call_add_members (channel, -1, + handles, NULL, NULL, NULL, NULL, NULL); + g_array_free (handles, TRUE); + return; } - /* We refuse to send our presence to that contact, remove from pendings */ - if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && - g_list_find (priv->pendings, contact)) { - g_signal_emit_by_name (list, "pendings-changed", - contact, actor, reason, message, - FALSE); - priv->pendings = g_list_remove (priv->pendings, contact); - g_object_unref (contact); - } + /* That group does not exist yet, we have to: + * 1) Request an handle for the group name + * 2) Request a channel + * 3) When NewChannel is emitted, add handles in members + */ + g_hash_table_insert (priv->add_to_group, + g_strdup (group_name), + handles); + tp_cli_connection_call_request_handles (priv->connection, -1, + TP_HANDLE_TYPE_GROUP, names, + tp_contact_list_group_request_handles_cb, + NULL, NULL, + G_OBJECT (list)); } static void -tp_contact_list_pending_cb (EmpathyTpGroup *group, - EmpathyContact *contact, - EmpathyContact *actor, - guint reason, - const gchar *message, - EmpathyTpContactList *list) +tp_contact_list_got_added_members_cb (EmpathyTpContactFactory *factory, + guint n_contacts, + EmpathyContact * const * contacts, + guint n_failed, + const TpHandle *failed, + const GError *error, + gpointer user_data, + GObject *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - TpContactListType list_type; + guint i; + + if (error) { + DEBUG ("Error: %s", error->message); + return; + } - list_type = tp_contact_list_get_type (list, group); - empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) pending in list type %d", - empathy_contact_get_id (contact), - empathy_contact_get_handle (contact), - list_type); + for (i = 0; i < n_contacts; i++) { + EmpathyContact *contact = contacts[i]; + TpHandle handle; - /* We want this contact in our contact list but we don't get its - * presence yet. Add to members anyway. */ - if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && - !g_list_find (priv->members, contact)) { - tp_contact_list_add_member (list, contact, actor, reason, message); - } - - /* This contact wants our presence, auto accept if he is member, - * otherwise he is pending. */ - if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && - !g_list_find (priv->pendings, contact)) { - if (g_list_find (priv->members, contact)) { - empathy_tp_group_add_member (priv->publish, contact, ""); - } else { - priv->pendings = g_list_prepend (priv->pendings, - g_object_ref (contact)); - g_signal_emit_by_name (list, "pendings-changed", - contact, actor, reason, message, - TRUE); + handle = empathy_contact_get_handle (contact); + if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle))) + continue; + + /* Add to the list and emit signal */ + g_hash_table_insert (priv->members, GUINT_TO_POINTER (handle), + g_object_ref (contact)); + g_signal_emit_by_name (list, "members-changed", contact, + 0, 0, NULL, TRUE); + + /* This contact is now member, implicitly accept pending. */ + if (g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) { + GArray handles = {(gchar *) &handle, 1}; + + tp_cli_channel_interface_group_call_add_members (priv->publish, + -1, &handles, NULL, NULL, NULL, NULL, NULL); } } } static void -tp_contact_list_newchannel_cb (DBusGProxy *proxy, - const gchar *object_path, - const gchar *channel_type, - TelepathyHandleType handle_type, - guint channel_handle, - gboolean suppress_handle, - EmpathyTpContactList *list) +tp_contact_list_got_local_pending_cb (EmpathyTpContactFactory *factory, + guint n_contacts, + EmpathyContact * const * contacts, + guint n_failed, + const TpHandle *failed, + const GError *error, + gpointer user_data, + GObject *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - EmpathyTpGroup *group; - TpChan *new_chan; - const gchar *bus_name; + guint i; - if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 || - suppress_handle) { + if (error) { + DEBUG ("Error: %s", error->message); return; } - bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn)); - new_chan = tp_chan_new (tp_get_bus (), - bus_name, - object_path, - channel_type, - handle_type, - channel_handle); - g_return_if_fail (TELEPATHY_IS_CHAN (new_chan)); - - group = empathy_tp_group_new (priv->account, new_chan); - g_object_unref (new_chan); - - if (handle_type == TP_HANDLE_TYPE_LIST) { - TpContactListType list_type; - GList *contacts, *l; - - list_type = tp_contact_list_get_type (list, group); - if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && !priv->publish) { - priv->publish = group; - - /* Publish is the list of contacts to who we send our - * presence. Makes no sense to be in remote-pending */ - g_signal_connect (group, "local-pending", - G_CALLBACK (tp_contact_list_pending_cb), - list); - - contacts = empathy_tp_group_get_local_pendings (group); - for (l = contacts; l; l = l->next) { - EmpathyPendingInfo *info = l->data; - - tp_contact_list_pending_cb (group, - info->member, - info->actor, - 0, - info->message, - list); - empathy_pending_info_free (info); - } - g_list_free (contacts); - } - else if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && !priv->subscribe) { - priv->subscribe = group; - - /* Subscribe is the list of contacts from who we - * receive presence. Makes no sense to be in - * local-pending */ - g_signal_connect (group, "remote-pending", - G_CALLBACK (tp_contact_list_pending_cb), - list); - - contacts = empathy_tp_group_get_remote_pendings (group); - for (l = contacts; l; l = l->next) { - tp_contact_list_pending_cb (group, - l->data, - NULL, 0, - NULL, list); - g_object_unref (l->data); - } - g_list_free (contacts); - } else { - empathy_debug (DEBUG_DOMAIN, - "Type of contact list channel unknown " - "or aleady have that list: %s", - empathy_tp_group_get_name (group)); - g_object_unref (group); - return; - } - empathy_debug (DEBUG_DOMAIN, - "New contact list channel of type: %d", - list_type); + for (i = 0; i < n_contacts; i++) { + EmpathyContact *contact = contacts[i]; + TpHandle handle; + const gchar *message; + TpChannelGroupChangeReason reason; - g_signal_connect (group, "member-added", - G_CALLBACK (tp_contact_list_added_cb), - list); - g_signal_connect (group, "member-removed", - G_CALLBACK (tp_contact_list_removed_cb), - list); + handle = empathy_contact_get_handle (contact); + if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle))) { + GArray handles = {(gchar *) &handle, 1}; - contacts = empathy_tp_group_get_members (group); - for (l = contacts; l; l = l->next) { - tp_contact_list_added_cb (group, - l->data, - NULL, 0, NULL, - list); - g_object_unref (l->data); + /* This contact is already member, auto accept. */ + tp_cli_channel_interface_group_call_add_members (priv->publish, + -1, &handles, NULL, NULL, NULL, NULL, NULL); } - g_list_free (contacts); - } - else if (handle_type == TP_HANDLE_TYPE_GROUP) { - const gchar *group_name; - GList *contacts, *l; - - /* Check if already exists */ - group_name = empathy_tp_group_get_name (group); - if (tp_contact_list_find_group (list, group_name)) { - g_object_unref (group); - return; + else if (tp_channel_group_get_local_pending_info (priv->publish, + handle, + NULL, + &reason, + &message)) { + /* Add contact to pendings */ + g_hash_table_insert (priv->pendings, GUINT_TO_POINTER (handle), + g_object_ref (contact)); + g_signal_emit_by_name (list, "pendings-changed", contact, + contact, reason, message, TRUE); } + } +} - empathy_debug (DEBUG_DOMAIN, "New server-side group channel: %s", - group_name); - - priv->groups = g_list_prepend (priv->groups, group); - - g_signal_connect (group, "member-added", - G_CALLBACK (tp_contact_list_group_member_added_cb), - list); - g_signal_connect (group, "member-removed", - G_CALLBACK (tp_contact_list_group_member_removed_cb), - list); - g_signal_connect (group, "destroy", - G_CALLBACK (tp_contact_list_group_destroy_cb), - list); +static void +tp_contact_list_remove_handle (EmpathyTpContactList *list, + GHashTable *table, + TpHandle handle) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (list); + EmpathyContact *contact; + const gchar *sig; + + if (table == priv->pendings) + sig = "pendings-changed"; + else if (table == priv->members) + sig = "members-changed"; + else + return; - contacts = empathy_tp_group_get_members (group); - for (l = contacts; l; l = l->next) { - tp_contact_list_group_member_added_cb (group, l->data, - NULL, 0, NULL, - list); - g_object_unref (l->data); - } - g_list_free (contacts); - } else { - empathy_debug (DEBUG_DOMAIN, - "Unknown handle type (%d) for contact list channel", - handle_type); - g_object_unref (group); + contact = g_hash_table_lookup (table, GUINT_TO_POINTER (handle)); + if (contact) { + g_object_ref (contact); + g_hash_table_remove (table, GUINT_TO_POINTER (handle)); + g_signal_emit_by_name (list, sig, contact, 0, 0, NULL, + FALSE); + g_object_unref (contact); } } static void -tp_contact_list_destroy_cb (TpConn *tp_conn, - EmpathyTpContactList *list) +tp_contact_list_publish_group_members_changed_cb (TpChannel *channel, + gchar *message, + GArray *added, + GArray *removed, + GArray *local_pending, + GArray *remote_pending, + TpHandle actor, + TpChannelGroupChangeReason reason, + EmpathyTpContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - GList *l; + guint i; - empathy_debug (DEBUG_DOMAIN, "Account disconnected or CM crashed"); + /* We now send our presence to those contacts, remove them from pendings */ + for (i = 0; i < added->len; i++) { + tp_contact_list_remove_handle (list, priv->pendings, + g_array_index (added, TpHandle, i)); + } - /* DBus proxie should NOT be used anymore */ - g_object_unref (priv->tp_conn); - priv->tp_conn = NULL; + /* We refuse to send our presence to those contacts, remove from pendings */ + for (i = 0; i < removed->len; i++) { + tp_contact_list_remove_handle (list, priv->pendings, + g_array_index (removed, TpHandle, i)); + } - /* Remove all contacts */ - for (l = priv->members; l; l = l->next) { - g_signal_emit_by_name (list, "members-changed", l->data, - NULL, 0, NULL, - FALSE); - g_object_unref (l->data); + /* Those contacts want our presence, auto accept those that are already + * member, otherwise add in pendings. */ + if (local_pending->len > 0) { + empathy_tp_contact_factory_get_from_handles (priv->factory, + local_pending->len, (TpHandle *) local_pending->data, + tp_contact_list_got_local_pending_cb, NULL, NULL, + G_OBJECT (list)); } - for (l = priv->pendings; l; l = l->next) { - g_signal_emit_by_name (list, "pendings-changed", l->data, - NULL, 0, NULL, - FALSE); - g_object_unref (l->data); +} + +static void +tp_contact_list_get_alias_flags_cb (TpConnection *connection, + guint flags, + const GError *error, + gpointer user_data, + GObject *list) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (list); + + if (error) { + DEBUG ("Error: %s", error->message); + return; } - g_list_free (priv->members); - g_list_free (priv->pendings); - priv->members = NULL; - priv->pendings = NULL; - /* Tell the world to not use us anymore */ - g_signal_emit (list, signals[DESTROY], 0); + if (flags & TP_CONNECTION_ALIAS_FLAG_USER_SET) { + priv->flags |= EMPATHY_CONTACT_LIST_CAN_ALIAS; + } } static void -tp_contact_list_disconnect (EmpathyTpContactList *list) +tp_contact_list_get_requestablechannelclasses_cb (TpProxy *connection, + const GValue *value, + const GError *error, + gpointer user_data, + GObject *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); + GPtrArray *classes; + guint i; - if (priv->tp_conn) { - g_signal_handlers_disconnect_by_func (priv->tp_conn, - tp_contact_list_destroy_cb, - list); - dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel", - G_CALLBACK (tp_contact_list_newchannel_cb), - list); + if (error) { + DEBUG ("Error: %s", error->message); + return; + } + + classes = g_value_get_boxed (value); + for (i = 0; i < classes->len; i++) { + GValueArray *class = g_ptr_array_index (classes, i); + GHashTable *props; + const char *channel_type; + guint handle_type; + + props = g_value_get_boxed (g_value_array_get_nth (class, 0)); + + channel_type = tp_asv_get_string (props, + TP_IFACE_CHANNEL ".ChannelType"); + handle_type = tp_asv_get_uint32 (props, + TP_IFACE_CHANNEL ".TargetHandleType", NULL); + + if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) && + handle_type == TP_HANDLE_TYPE_GROUP) { + DEBUG ("Got channel class for a contact group"); + priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP; + break; + } } } static void -tp_contact_list_status_changed_cb (MissionControl *mc, - TelepathyConnectionStatus status, - McPresence presence, - TelepathyConnectionStatusReason reason, - const gchar *unique_name, - EmpathyTpContactList *list) +tp_contact_list_subscribe_group_members_changed_cb (TpChannel *channel, + gchar *message, + GArray *added, + GArray *removed, + GArray *local_pending, + GArray *remote_pending, + guint actor, + guint reason, + EmpathyTpContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - McAccount *account; + guint i; + + /* We now get the presence of those contacts, add them to members */ + if (added->len > 0) { + empathy_tp_contact_factory_get_from_handles (priv->factory, + added->len, (TpHandle *) added->data, + tp_contact_list_got_added_members_cb, NULL, NULL, + G_OBJECT (list)); + } - account = mc_account_lookup (unique_name); - if (status != TP_CONN_STATUS_CONNECTED && - empathy_account_equal (account, priv->account)) { - /* We are disconnected */ - tp_contact_list_disconnect (list); - tp_contact_list_destroy_cb (priv->tp_conn, list); + /* Those contacts refuse to send us their presence, remove from members. */ + for (i = 0; i < removed->len; i++) { + tp_contact_list_remove_handle (list, priv->members, + g_array_index (removed, TpHandle, i)); } - g_object_unref (account); + /* We want those contacts in our contact list but we don't get their + * presence yet. Add to members anyway. */ + if (remote_pending->len > 0) { + empathy_tp_contact_factory_get_from_handles (priv->factory, + remote_pending->len, (TpHandle *) remote_pending->data, + tp_contact_list_got_added_members_cb, NULL, NULL, + G_OBJECT (list)); + } +} + +static void +tp_contact_list_new_channel_cb (TpConnection *proxy, + const gchar *object_path, + const gchar *channel_type, + guint handle_type, + guint handle, + gboolean suppress_handler, + gpointer user_data, + GObject *list) +{ + tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list), + object_path, channel_type, + handle_type, handle); } static void -tp_contact_list_group_list_free (GList **groups) +tp_contact_list_list_channels_cb (TpConnection *connection, + const GPtrArray *channels, + const GError *error, + gpointer user_data, + GObject *list) { - g_list_foreach (*groups, (GFunc) g_free, NULL); - g_list_free (*groups); - g_slice_free (GList*, groups); + guint i; + + if (error) { + DEBUG ("Error: %s", error->message); + return; + } + + for (i = 0; i < channels->len; i++) { + GValueArray *chan_struct; + const gchar *object_path; + const gchar *channel_type; + TpHandleType handle_type; + guint handle; + + chan_struct = g_ptr_array_index (channels, i); + object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0)); + channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1)); + handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2)); + handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3)); + + tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list), + object_path, channel_type, + handle_type, handle); + } } static void @@ -576,21 +658,13 @@ tp_contact_list_finalize (GObject *object) { EmpathyTpContactListPriv *priv; EmpathyTpContactList *list; + GHashTableIter iter; + gpointer channel; list = EMPATHY_TP_CONTACT_LIST (object); priv = GET_PRIV (list); - empathy_debug (DEBUG_DOMAIN, "finalize: %p", object); - - tp_contact_list_disconnect (list); - - if (priv->mc) { - dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc), - "AccountStatusChanged", - G_CALLBACK (tp_contact_list_status_changed_cb), - list); - g_object_unref (priv->mc); - } + DEBUG ("finalize: %p", object); if (priv->subscribe) { g_object_unref (priv->subscribe); @@ -598,158 +672,340 @@ tp_contact_list_finalize (GObject *object) if (priv->publish) { g_object_unref (priv->publish); } - if (priv->account) { - g_object_unref (priv->account); + if (priv->stored) { + g_object_unref (priv->stored); + } + + if (priv->connection) { + g_object_unref (priv->connection); + } + + if (priv->factory) { + g_object_unref (priv->factory); } - if (priv->tp_conn) { - g_object_unref (priv->tp_conn); + + g_hash_table_iter_init (&iter, priv->groups); + while (g_hash_table_iter_next (&iter, NULL, &channel)) { + g_signal_handlers_disconnect_by_func (channel, + tp_contact_list_group_invalidated_cb, list); } - g_hash_table_destroy (priv->contacts_groups); - g_list_foreach (priv->groups, (GFunc) g_object_unref, NULL); - g_list_free (priv->groups); - g_list_foreach (priv->members, (GFunc) g_object_unref, NULL); - g_list_free (priv->members); - g_list_foreach (priv->pendings, (GFunc) g_object_unref, NULL); - g_list_free (priv->pendings); + g_hash_table_destroy (priv->groups); + g_hash_table_destroy (priv->members); + g_hash_table_destroy (priv->pendings); + g_hash_table_destroy (priv->add_to_group); G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object); } +static gboolean +received_all_list_channels (EmpathyTpContactList *self) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (self); + + return (priv->stored != NULL && priv->publish != NULL && + priv->subscribe != NULL); +} + static void -empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass) +got_list_channel (EmpathyTpContactList *list, + TpChannel *channel) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); + EmpathyTpContactListPriv *priv = GET_PRIV (list); + const gchar *id; - object_class->finalize = tp_contact_list_finalize; + /* We requested that channel by providing TargetID property, so it's + * guaranteed that tp_channel_get_identifier will return it. */ + id = tp_channel_get_identifier (channel); - signals[DESTROY] = - g_signal_new ("destroy", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0); + /* TpChannel emits initial set of members just before being ready */ + if (!tp_strdiff (id, "stored")) { + if (priv->stored != NULL) + return; + priv->stored = g_object_ref (channel); + } else if (!tp_strdiff (id, "publish")) { + if (priv->publish != NULL) + return; + priv->publish = g_object_ref (channel); + g_signal_connect (priv->publish, "group-members-changed", + G_CALLBACK (tp_contact_list_publish_group_members_changed_cb), + list); + } else if (!tp_strdiff (id, "subscribe")) { + if (priv->subscribe != NULL) + return; + priv->subscribe = g_object_ref (channel); + g_signal_connect (priv->subscribe, "group-members-changed", + G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb), + list); + } - g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv)); + if (received_all_list_channels (list) && priv->new_channels_sig != NULL) { + /* We don't need to watch NewChannels anymore */ + tp_proxy_signal_connection_disconnect (priv->new_channels_sig); + priv->new_channels_sig = NULL; + } } static void -empathy_tp_contact_list_init (EmpathyTpContactList *list) +list_ensure_channel_cb (TpConnection *conn, + gboolean yours, + const gchar *path, + GHashTable *properties, + const GError *error, + gpointer user_data, + GObject *weak_object) { + EmpathyTpContactList *list = user_data; + TpChannel *channel; + + if (error != NULL) { + DEBUG ("failed: %s\n", error->message); + return; + } + + channel = tp_channel_new_from_properties (conn, path, properties, NULL); + got_list_channel (list, channel); + g_object_unref (channel); } static void -tp_contact_list_setup (EmpathyTpContactList *list) +new_channels_cb (TpConnection *conn, + const GPtrArray *channels, + gpointer user_data, + GObject *weak_object) { - EmpathyTpContactListPriv *priv = GET_PRIV (list); - GPtrArray *channels; - guint i; - GError *error = NULL; + EmpathyTpContactList *list = EMPATHY_TP_CONTACT_LIST (weak_object); + guint i; + + for (i = 0; i < channels->len ; i++) { + GValueArray *arr = g_ptr_array_index (channels, i); + const gchar *path; + GHashTable *properties; + const gchar *id; + TpChannel *channel; + + path = g_value_get_boxed (g_value_array_get_nth (arr, 0)); + properties = g_value_get_boxed (g_value_array_get_nth (arr, 1)); + + if (tp_strdiff (tp_asv_get_string (properties, + TP_IFACE_CHANNEL ".ChannelType"), + TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) + return; - g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); + if (tp_asv_get_uint32 (properties, + TP_IFACE_CHANNEL ".TargetHandleType", NULL) + != TP_HANDLE_TYPE_LIST) + return; - /* Get existing channels */ - if (!tp_conn_list_channels (DBUS_G_PROXY (priv->tp_conn), - &channels, - &error)) { - empathy_debug (DEBUG_DOMAIN, - "Failed to get list of open channels: %s", - error ? error->message : "No error given"); - g_clear_error (&error); - return; + id = tp_asv_get_string (properties, + TP_IFACE_CHANNEL ".TargetID"); + if (id == NULL) + return; + + channel = tp_channel_new_from_properties (conn, path, + properties, NULL); + got_list_channel (list, channel); + g_object_unref (channel); } +} - for (i = 0; i < channels->len; i++) { - GValueArray *chan_struct; - const gchar *object_path; - const gchar *chan_iface; - TelepathyHandleType handle_type; - guint handle; +static void +conn_ready_cb (TpConnection *connection, + const GError *error, + gpointer data) +{ + EmpathyTpContactList *list = data; + EmpathyTpContactListPriv *priv = GET_PRIV (list); + GHashTable *request; - chan_struct = g_ptr_array_index (channels, i); - object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0)); - chan_iface = g_value_get_string (g_value_array_get_nth (chan_struct, 1)); - handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2)); - handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3)); + if (error != NULL) { + DEBUG ("failed: %s", error->message); + goto out; + } + + request = tp_asv_new ( + TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, + TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, TP_HANDLE_TYPE_LIST, + NULL); + + /* Watch the NewChannels signal so if ensuring list channels fails (for + * example because the server is slow and the D-Bus call timeouts before CM + * fetches the roster), we have a chance to get them later. */ + priv->new_channels_sig = + tp_cli_connection_interface_requests_connect_to_new_channels ( + priv->connection, new_channels_cb, NULL, NULL, G_OBJECT (list), NULL); + + /* Request the 'stored' list. */ + tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "stored"); + tp_cli_connection_interface_requests_call_ensure_channel (priv->connection, + -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list)); + + /* Request the 'publish' list. */ + tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "publish"); + tp_cli_connection_interface_requests_call_ensure_channel (priv->connection, + -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list)); + + /* Request the 'subscribe' list. */ + tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "subscribe"); + tp_cli_connection_interface_requests_call_ensure_channel (priv->connection, + -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list)); + + g_hash_table_unref (request); +out: + g_object_unref (list); +} + +static void +tp_contact_list_constructed (GObject *list) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (list); - tp_contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn), - object_path, chan_iface, - handle_type, handle, - FALSE, - list); + priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection); + + /* call GetAliasFlags */ + if (tp_proxy_has_interface_by_id (priv->connection, + TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)) { + tp_cli_connection_interface_aliasing_call_get_alias_flags ( + priv->connection, + -1, + tp_contact_list_get_alias_flags_cb, + NULL, NULL, + G_OBJECT (list)); + } - g_value_array_free (chan_struct); + /* lookup RequestableChannelClasses */ + if (tp_proxy_has_interface_by_id (priv->connection, + TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)) { + tp_cli_dbus_properties_call_get (priv->connection, + -1, + TP_IFACE_CONNECTION_INTERFACE_REQUESTS, + "RequestableChannelClasses", + tp_contact_list_get_requestablechannelclasses_cb, + NULL, NULL, + G_OBJECT (list)); + } else { + /* we just don't know... better mark the flag just in case */ + priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP; } - g_ptr_array_free (channels, TRUE); + + tp_connection_call_when_ready (priv->connection, conn_ready_cb, + g_object_ref (list)); + + tp_cli_connection_call_list_channels (priv->connection, -1, + tp_contact_list_list_channels_cb, + NULL, NULL, + list); + + tp_cli_connection_connect_to_new_channel (priv->connection, + tp_contact_list_new_channel_cb, + NULL, NULL, + list, NULL); } -EmpathyTpContactList * -empathy_tp_contact_list_new (McAccount *account) +static void +tp_contact_list_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) { - EmpathyTpContactListPriv *priv; - EmpathyTpContactList *list; - MissionControl *mc; - TpConn *tp_conn = NULL; - McProfile *profile; - const gchar *protocol_name; + EmpathyTpContactListPriv *priv = GET_PRIV (object); + + switch (param_id) { + case PROP_CONNECTION: + g_value_set_object (value, priv->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + }; +} - g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL); +static void +tp_contact_list_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (object); + + switch (param_id) { + case PROP_CONNECTION: + priv->connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + }; +} - mc = empathy_mission_control_new (); +static void +empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); - /* status==0 means CONNECTED */ - if (mission_control_get_connection_status (mc, account, NULL) == 0) { - tp_conn = mission_control_get_connection (mc, account, NULL); - } - if (!tp_conn) { - /* The account is not connected, nothing to do. */ - g_object_unref (mc); - return NULL; - } + object_class->finalize = tp_contact_list_finalize; + object_class->constructed = tp_contact_list_constructed; + object_class->get_property = tp_contact_list_get_property; + object_class->set_property = tp_contact_list_set_property; + + g_object_class_install_property (object_class, + PROP_CONNECTION, + g_param_spec_object ("connection", + "The Connection", + "The connection associated with the contact list", + TP_TYPE_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); - list = g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST, NULL); - priv = GET_PRIV (list); + g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv)); +} - priv->tp_conn = tp_conn; - priv->account = g_object_ref (account); - priv->mc = mc; - priv->contacts_groups = g_hash_table_new_full (empathy_contact_hash, - empathy_contact_equal, - (GDestroyNotify) g_object_unref, - (GDestroyNotify) tp_contact_list_group_list_free); - - /* Check for protocols that does not support contact groups. We can - * put all contacts into a special group in that case. - * FIXME: Default group should be an information in the profile */ - profile = mc_account_get_profile (account); - protocol_name = mc_profile_get_protocol_name (profile); - if (strcmp (protocol_name, "local-xmpp") == 0) { - priv->protocol_group = _("People nearby"); - } - g_object_unref (profile); - - /* Connect signals */ - dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc), - "AccountStatusChanged", - G_CALLBACK (tp_contact_list_status_changed_cb), - list, NULL); - dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel", - G_CALLBACK (tp_contact_list_newchannel_cb), - list, NULL); - g_signal_connect (priv->tp_conn, "destroy", - G_CALLBACK (tp_contact_list_destroy_cb), - list); +static void +tp_contact_list_array_free (gpointer handles) +{ + g_array_free (handles, TRUE); +} - tp_contact_list_setup (list); +static void +empathy_tp_contact_list_init (EmpathyTpContactList *list) +{ + EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list, + EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv); + + list->priv = priv; + + /* Map group's name to group's TpChannel. The group name string is owned + * by the TpChannel object */ + priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + (GDestroyNotify) g_object_unref); + + /* Map contact's handle to EmpathyContact object */ + priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify) g_object_unref); + + /* Map contact's handle to EmpathyContact object */ + priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify) g_object_unref); + + /* Map group's name to GArray of handle */ + priv->add_to_group = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + tp_contact_list_array_free); +} - return list; +EmpathyTpContactList * +empathy_tp_contact_list_new (TpConnection *connection) +{ + return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST, + "connection", connection, + NULL); } -McAccount * -empathy_tp_contact_list_get_account (EmpathyTpContactList *list) +TpConnection * +empathy_tp_contact_list_get_connection (EmpathyTpContactList *list) { EmpathyTpContactListPriv *priv; @@ -757,7 +1013,7 @@ empathy_tp_contact_list_get_account (EmpathyTpContactList *list) priv = GET_PRIV (list); - return priv->account; + return priv->connection; } static void @@ -766,12 +1022,21 @@ tp_contact_list_add (EmpathyContactList *list, const gchar *message) { EmpathyTpContactListPriv *priv = GET_PRIV (list); + TpHandle handle; + GArray handles = {(gchar *) &handle, 1}; - g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); - - empathy_tp_group_add_member (priv->subscribe, contact, message); - if (g_list_find (priv->pendings, contact)) { - empathy_tp_group_add_member (priv->publish, contact, message); + handle = empathy_contact_get_handle (contact); + if (priv->subscribe) { + tp_cli_channel_interface_group_call_add_members (priv->subscribe, + -1, &handles, message, NULL, NULL, NULL, NULL); + } + if (priv->publish) { + TpChannelGroupFlags flags = tp_channel_group_get_flags (priv->subscribe); + if (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD || + g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) { + tp_cli_channel_interface_group_call_add_members (priv->publish, + -1, &handles, message, NULL, NULL, NULL, NULL); + } } } @@ -781,55 +1046,62 @@ tp_contact_list_remove (EmpathyContactList *list, const gchar *message) { EmpathyTpContactListPriv *priv = GET_PRIV (list); + TpHandle handle; + GArray handles = {(gchar *) &handle, 1}; - g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); + handle = empathy_contact_get_handle (contact); - empathy_tp_group_remove_member (priv->subscribe, contact, message); - empathy_tp_group_remove_member (priv->publish, contact, message); + /* FIXME: this is racy if tp_contact_list_remove is called before the + * 'stored' list has been retrieved. */ + if (priv->stored != NULL) { + tp_cli_channel_interface_group_call_remove_members (priv->stored, + -1, &handles, message, NULL, NULL, NULL, NULL); + } + + if (priv->subscribe) { + tp_cli_channel_interface_group_call_remove_members (priv->subscribe, + -1, &handles, message, NULL, NULL, NULL, NULL); + } + if (priv->publish) { + tp_cli_channel_interface_group_call_remove_members (priv->publish, + -1, &handles, message, NULL, NULL, NULL, NULL); + } } static GList * tp_contact_list_get_members (EmpathyContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); + GList *ret; - g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL); - - g_list_foreach (priv->members, (GFunc) g_object_ref, NULL); - return g_list_copy (priv->members); + ret = g_hash_table_get_values (priv->members); + g_list_foreach (ret, (GFunc) g_object_ref, NULL); + return ret; } static GList * tp_contact_list_get_pendings (EmpathyContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); + GList *ret; - g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL); - - g_list_foreach (priv->pendings, (GFunc) g_object_ref, NULL); - return g_list_copy (priv->pendings); + ret = g_hash_table_get_values (priv->pendings); + g_list_foreach (ret, (GFunc) g_object_ref, NULL); + return ret; } static GList * tp_contact_list_get_all_groups (EmpathyContactList *list) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - GList *groups = NULL, *l; - - g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL); - - if (priv->protocol_group) { - groups = g_list_prepend (groups, g_strdup (priv->protocol_group)); - } - - for (l = priv->groups; l; l = l->next) { - const gchar *name; + GList *ret, *l; - name = empathy_tp_group_get_name (l->data); - groups = g_list_prepend (groups, g_strdup (name)); + ret = g_hash_table_get_keys (priv->groups); + for (l = ret; l; l = l->next) { + l->data = g_strdup (l->data); } - return groups; + return ret; } static GList * @@ -837,148 +1109,143 @@ tp_contact_list_get_groups (EmpathyContactList *list, EmpathyContact *contact) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - GList **groups; - GList *ret = NULL, *l; + GList *ret = NULL; + GHashTableIter iter; + gpointer group_name; + gpointer channel; + TpHandle handle; + + handle = empathy_contact_get_handle (contact); + g_hash_table_iter_init (&iter, priv->groups); + while (g_hash_table_iter_next (&iter, &group_name, &channel)) { + const TpIntSet *members; + + members = tp_channel_group_get_members (channel); + if (tp_intset_is_member (members, handle)) { + ret = g_list_prepend (ret, g_strdup (group_name)); + } + } - g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL); + return ret; +} - if (priv->protocol_group) { - ret = g_list_prepend (ret, g_strdup (priv->protocol_group)); - } +static void +tp_contact_list_add_to_group (EmpathyContactList *list, + EmpathyContact *contact, + const gchar *group_name) +{ + TpHandle handle; + GArray *handles; + + handle = empathy_contact_get_handle (contact); + handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1); + g_array_append_val (handles, handle); + tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list), + group_name, handles); +} - groups = g_hash_table_lookup (priv->contacts_groups, contact); - if (!groups) { - return ret; - } +static void +tp_contact_list_remove_from_group (EmpathyContactList *list, + EmpathyContact *contact, + const gchar *group_name) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (list); + TpChannel *channel; + TpHandle handle; + GArray handles = {(gchar *) &handle, 1}; - for (l = *groups; l; l = l->next) { - ret = g_list_prepend (ret, g_strdup (l->data)); + channel = g_hash_table_lookup (priv->groups, group_name); + if (channel == NULL) { + return; } + handle = empathy_contact_get_handle (contact); + DEBUG ("remove contact %s (%d) from group %s", + empathy_contact_get_id (contact), handle, group_name); - return ret; + tp_cli_channel_interface_group_call_remove_members (channel, -1, + &handles, NULL, NULL, NULL, NULL, NULL); } -static EmpathyTpGroup * -tp_contact_list_get_group (EmpathyTpContactList *list, - const gchar *group) +static void +tp_contact_list_rename_group (EmpathyContactList *list, + const gchar *old_group_name, + const gchar *new_group_name) { EmpathyTpContactListPriv *priv = GET_PRIV (list); - EmpathyTpGroup *tp_group; - gchar *object_path; - guint handle; + TpChannel *channel; + const TpIntSet *members; GArray *handles; - const char *names[2] = {group, NULL}; - GError *error = NULL; - - tp_group = tp_contact_list_find_group (list, group); - if (tp_group) { - return tp_group; - } - - empathy_debug (DEBUG_DOMAIN, "creating new group: %s", group); - if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn), - TP_HANDLE_TYPE_GROUP, - names, - &handles, - &error)) { - empathy_debug (DEBUG_DOMAIN, - "Failed to RequestHandles: %s", - error ? error->message : "No error given"); - g_clear_error (&error); - return NULL; + channel = g_hash_table_lookup (priv->groups, old_group_name); + if (channel == NULL) { + return; } - handle = g_array_index (handles, guint, 0); - g_array_free (handles, TRUE); - if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn), - TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, - TP_HANDLE_TYPE_GROUP, - handle, - FALSE, - &object_path, - &error)) { - empathy_debug (DEBUG_DOMAIN, - "Failed to RequestChannel: %s", - error ? error->message : "No error given"); - g_clear_error (&error); - return NULL; - } + DEBUG ("rename group %s to %s", old_group_name, new_group_name); - tp_contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn), - object_path, - TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, - TP_HANDLE_TYPE_GROUP, - handle, FALSE, - list); - g_free (object_path); + /* Remove all members and close the old channel */ + members = tp_channel_group_get_members (channel); + handles = tp_intset_to_array (members); + tp_cli_channel_interface_group_call_remove_members (channel, -1, + handles, NULL, NULL, NULL, NULL, NULL); + tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL); - return tp_contact_list_find_group (list, group); + tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list), + new_group_name, handles); } static void -tp_contact_list_add_to_group (EmpathyContactList *list, - EmpathyContact *contact, - const gchar *group) +tp_contact_list_remove_group (EmpathyContactList *list, + const gchar *group_name) { - EmpathyTpGroup *tp_group; + EmpathyTpContactListPriv *priv = GET_PRIV (list); + TpChannel *channel; + const TpIntSet *members; + GArray *handles; - g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); + channel = g_hash_table_lookup (priv->groups, group_name); + if (channel == NULL) { + return; + } - tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list), - group); + DEBUG ("remove group %s", group_name); - empathy_tp_group_add_member (tp_group, contact, ""); + /* Remove all members and close the channel */ + members = tp_channel_group_get_members (channel); + handles = tp_intset_to_array (members); + tp_cli_channel_interface_group_call_remove_members (channel, -1, + handles, NULL, NULL, NULL, NULL, NULL); + tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL); + g_array_free (handles, TRUE); } -static void -tp_contact_list_remove_from_group (EmpathyContactList *list, - EmpathyContact *contact, - const gchar *group) +static EmpathyContactListFlags +tp_contact_list_get_flags (EmpathyContactList *list) { - EmpathyTpGroup *tp_group; + EmpathyTpContactListPriv *priv; + EmpathyContactListFlags flags; - g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); + g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE); - tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list), - group); + priv = GET_PRIV (list); + flags = priv->flags; - if (tp_group) { - empathy_tp_group_remove_member (tp_group, contact, ""); - } -} + if (priv->subscribe != NULL) { + TpChannelGroupFlags group_flags; -static void -tp_contact_list_rename_group (EmpathyContactList *list, - const gchar *old_group, - const gchar *new_group) -{ - EmpathyTpGroup *tp_group; - GList *members; + group_flags = tp_channel_group_get_flags (priv->subscribe); - g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); + if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) { + flags |= EMPATHY_CONTACT_LIST_CAN_ADD; + } - tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list), - old_group); - if (!tp_group) { - return; + if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) { + flags |= EMPATHY_CONTACT_LIST_CAN_REMOVE; + } } - empathy_debug (DEBUG_DOMAIN, "rename group %s to %s", old_group, new_group); - - /* Remove all members from the old group */ - members = empathy_tp_group_get_members (tp_group); - empathy_tp_group_remove_members (tp_group, members, ""); - empathy_tp_group_close (tp_group); - - /* Add all members to the new group */ - tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list), - new_group); - empathy_tp_group_add_members (tp_group, members, ""); - - g_list_foreach (members, (GFunc) g_object_unref, NULL); - g_list_free (members); + return flags; } static void @@ -993,5 +1260,34 @@ tp_contact_list_iface_init (EmpathyContactListIface *iface) iface->add_to_group = tp_contact_list_add_to_group; iface->remove_from_group = tp_contact_list_remove_from_group; iface->rename_group = tp_contact_list_rename_group; + iface->remove_group = tp_contact_list_remove_group; + iface->get_flags = tp_contact_list_get_flags; +} + +void +empathy_tp_contact_list_remove_all (EmpathyTpContactList *list) +{ + EmpathyTpContactListPriv *priv = GET_PRIV (list); + GHashTableIter iter; + gpointer contact; + + g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list)); + + /* Remove all contacts */ + g_hash_table_iter_init (&iter, priv->members); + while (g_hash_table_iter_next (&iter, NULL, &contact)) { + g_signal_emit_by_name (list, "members-changed", contact, + NULL, 0, NULL, + FALSE); + } + g_hash_table_remove_all (priv->members); + + g_hash_table_iter_init (&iter, priv->pendings); + while (g_hash_table_iter_next (&iter, NULL, &contact)) { + g_signal_emit_by_name (list, "pendings-changed", contact, + NULL, 0, NULL, + FALSE); + } + g_hash_table_remove_all (priv->pendings); }