1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
4 * Copyright (C) 2007-2009 Collabora Ltd.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 * Authors: Xavier Claessens <xclaesse@gmail.com>
26 #include <glib/gi18n-lib.h>
28 #include <telepathy-glib/channel.h>
29 #include <telepathy-glib/connection.h>
30 #include <telepathy-glib/util.h>
31 #include <telepathy-glib/dbus.h>
32 #include <telepathy-glib/interfaces.h>
34 #include "empathy-tp-contact-list.h"
35 #include "empathy-tp-contact-factory.h"
36 #include "empathy-contact-list.h"
37 #include "empathy-utils.h"
39 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT
40 #include "empathy-debug.h"
42 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactList)
44 EmpathyTpContactFactory *factory;
45 TpConnection *connection;
50 GHashTable *members; /* handle -> EmpathyContact */
51 GHashTable *pendings; /* handle -> EmpathyContact */
52 GHashTable *groups; /* group name -> TpChannel */
53 GHashTable *add_to_group; /* group name -> GArray of handles */
55 EmpathyContactListFlags flags;
57 TpProxySignalConnection *new_channels_sig;
58 } EmpathyTpContactListPriv;
61 TP_CONTACT_LIST_TYPE_PUBLISH,
62 TP_CONTACT_LIST_TYPE_SUBSCRIBE,
63 TP_CONTACT_LIST_TYPE_UNKNOWN
66 static void tp_contact_list_iface_init (EmpathyContactListIface *iface);
73 G_DEFINE_TYPE_WITH_CODE (EmpathyTpContactList, empathy_tp_contact_list, G_TYPE_OBJECT,
74 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
75 tp_contact_list_iface_init));
78 tp_contact_list_forget_group (EmpathyTpContactList *list,
81 EmpathyTpContactListPriv *priv = GET_PRIV (list);
82 const TpIntSet *members;
84 const gchar *group_name;
86 group_name = tp_channel_get_identifier (channel);
88 /* Signal that all members are not in that group anymore */
89 members = tp_channel_group_get_members (channel);
90 tp_intset_iter_init (&iter, members);
91 while (tp_intset_iter_next (&iter)) {
92 EmpathyContact *contact;
94 contact = g_hash_table_lookup (priv->members,
95 GUINT_TO_POINTER (iter.element));
96 if (contact == NULL) {
100 DEBUG ("Contact %s (%d) removed from group %s",
101 empathy_contact_get_id (contact), iter.element,
103 g_signal_emit_by_name (list, "groups-changed", contact,
110 tp_contact_list_group_invalidated_cb (TpChannel *channel,
114 EmpathyTpContactList *list)
116 EmpathyTpContactListPriv *priv = GET_PRIV (list);
117 const gchar *group_name;
119 group_name = tp_channel_get_identifier (channel);
120 DEBUG ("Group %s invalidated. Message: %s", group_name, message);
122 tp_contact_list_forget_group (list, channel);
124 g_hash_table_remove (priv->groups, group_name);
128 contacts_added_to_group (EmpathyTpContactList *list,
132 EmpathyTpContactListPriv *priv = GET_PRIV (list);
133 const gchar *group_name;
136 group_name = tp_channel_get_identifier (channel);
138 for (i = 0; i < added->len; i++) {
139 EmpathyContact *contact;
142 handle = g_array_index (added, TpHandle, i);
143 contact = g_hash_table_lookup (priv->members,
144 GUINT_TO_POINTER (handle));
145 if (contact == NULL) {
149 DEBUG ("Contact %s (%d) added to group %s",
150 empathy_contact_get_id (contact), handle, group_name);
151 g_signal_emit_by_name (list, "groups-changed", contact,
158 tp_contact_list_group_members_changed_cb (TpChannel *channel,
162 GArray *local_pending,
163 GArray *remote_pending,
166 EmpathyTpContactList *list)
168 EmpathyTpContactListPriv *priv = GET_PRIV (list);
169 const gchar *group_name;
172 contacts_added_to_group (list, channel, added);
174 group_name = tp_channel_get_identifier (channel);
176 for (i = 0; i < removed->len; i++) {
177 EmpathyContact *contact;
180 handle = g_array_index (removed, TpHandle, i);
181 contact = g_hash_table_lookup (priv->members,
182 GUINT_TO_POINTER (handle));
183 if (contact == NULL) {
187 DEBUG ("Contact %s (%d) removed from group %s",
188 empathy_contact_get_id (contact), handle, group_name);
190 g_signal_emit_by_name (list, "groups-changed", contact,
197 tp_contact_list_group_ready_cb (TpChannel *channel,
201 EmpathyTpContactListPriv *priv = GET_PRIV (list);
202 TpChannel *old_group;
203 const gchar *group_name;
204 const TpIntSet *members;
208 DEBUG ("Error: %s", error->message);
209 g_object_unref (channel);
213 group_name = tp_channel_get_identifier (channel);
215 /* If there's already a group with this name in the table, we can't
216 * just let it be replaced. Replacing it causes it to be unreffed,
217 * which causes it to be invalidated (see
218 * <https://bugs.freedesktop.org/show_bug.cgi?id=22119>), which causes
219 * it to be removed from the hash table again, which causes it to be
222 old_group = g_hash_table_lookup (priv->groups, group_name);
224 if (old_group != NULL) {
225 DEBUG ("Discarding old group %s (%p)", group_name, old_group);
226 g_hash_table_steal (priv->groups, group_name);
227 tp_contact_list_forget_group (list, old_group);
228 g_object_unref (old_group);
231 g_hash_table_insert (priv->groups, (gpointer) group_name, channel);
232 DEBUG ("Group %s added", group_name);
234 g_signal_connect (channel, "group-members-changed",
235 G_CALLBACK (tp_contact_list_group_members_changed_cb),
238 g_signal_connect (channel, "invalidated",
239 G_CALLBACK (tp_contact_list_group_invalidated_cb),
242 if (priv->add_to_group) {
245 handles = g_hash_table_lookup (priv->add_to_group, group_name);
247 DEBUG ("Adding initial members to group %s", group_name);
248 tp_cli_channel_interface_group_call_add_members (channel,
249 -1, handles, NULL, NULL, NULL, NULL, NULL);
250 g_hash_table_remove (priv->add_to_group, group_name);
254 /* Get initial members of the group */
255 members = tp_channel_group_get_members (channel);
256 g_assert (members != NULL);
257 arr = tp_intset_to_array (members);
258 contacts_added_to_group (list, channel, arr);
259 g_array_free (arr, TRUE);
263 tp_contact_list_group_add_channel (EmpathyTpContactList *list,
264 const gchar *object_path,
265 const gchar *channel_type,
266 TpHandleType handle_type,
269 EmpathyTpContactListPriv *priv = GET_PRIV (list);
272 /* Only accept server-side contact groups */
273 if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) ||
274 handle_type != TP_HANDLE_TYPE_GROUP) {
278 channel = tp_channel_new (priv->connection,
279 object_path, channel_type,
280 handle_type, handle, NULL);
282 /* Give the ref to the callback */
283 tp_channel_call_when_ready (channel,
284 tp_contact_list_group_ready_cb,
289 tp_contact_list_group_request_channel_cb (TpConnection *connection,
290 const gchar *object_path,
295 /* The new channel will be handled in NewChannel cb. Here we only
296 * handle the error if RequestChannel failed */
298 DEBUG ("Error: %s", error->message);
304 tp_contact_list_group_request_handles_cb (TpConnection *connection,
305 const GArray *handles,
310 TpHandle channel_handle;
313 DEBUG ("Error: %s", error->message);
317 channel_handle = g_array_index (handles, TpHandle, 0);
318 tp_cli_connection_call_request_channel (connection, -1,
319 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
320 TP_HANDLE_TYPE_GROUP,
323 tp_contact_list_group_request_channel_cb,
328 /* This function takes ownership of handles array */
330 tp_contact_list_group_add (EmpathyTpContactList *list,
331 const gchar *group_name,
334 EmpathyTpContactListPriv *priv = GET_PRIV (list);
336 const gchar *names[] = {group_name, NULL};
338 /* Search the channel for that group name */
339 channel = g_hash_table_lookup (priv->groups, group_name);
341 tp_cli_channel_interface_group_call_add_members (channel, -1,
342 handles, NULL, NULL, NULL, NULL, NULL);
343 g_array_free (handles, TRUE);
347 /* That group does not exist yet, we have to:
348 * 1) Request an handle for the group name
349 * 2) Request a channel
350 * 3) When NewChannel is emitted, add handles in members
352 g_hash_table_insert (priv->add_to_group,
353 g_strdup (group_name),
355 tp_cli_connection_call_request_handles (priv->connection, -1,
356 TP_HANDLE_TYPE_GROUP, names,
357 tp_contact_list_group_request_handles_cb,
363 tp_contact_list_got_added_members_cb (EmpathyTpContactFactory *factory,
365 EmpathyContact * const * contacts,
367 const TpHandle *failed,
372 EmpathyTpContactListPriv *priv = GET_PRIV (list);
376 DEBUG ("Error: %s", error->message);
380 for (i = 0; i < n_contacts; i++) {
381 EmpathyContact *contact = contacts[i];
384 handle = empathy_contact_get_handle (contact);
385 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle)))
388 /* Add to the list and emit signal */
389 g_hash_table_insert (priv->members, GUINT_TO_POINTER (handle),
390 g_object_ref (contact));
391 g_signal_emit_by_name (list, "members-changed", contact,
394 /* This contact is now member, implicitly accept pending. */
395 if (g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
396 GArray handles = {(gchar *) &handle, 1};
398 tp_cli_channel_interface_group_call_add_members (priv->publish,
399 -1, &handles, NULL, NULL, NULL, NULL, NULL);
405 tp_contact_list_got_local_pending_cb (EmpathyTpContactFactory *factory,
407 EmpathyContact * const * contacts,
409 const TpHandle *failed,
414 EmpathyTpContactListPriv *priv = GET_PRIV (list);
418 DEBUG ("Error: %s", error->message);
422 for (i = 0; i < n_contacts; i++) {
423 EmpathyContact *contact = contacts[i];
425 const gchar *message;
426 TpChannelGroupChangeReason reason;
428 handle = empathy_contact_get_handle (contact);
429 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle))) {
430 GArray handles = {(gchar *) &handle, 1};
432 /* This contact is already member, auto accept. */
433 tp_cli_channel_interface_group_call_add_members (priv->publish,
434 -1, &handles, NULL, NULL, NULL, NULL, NULL);
436 else if (tp_channel_group_get_local_pending_info (priv->publish,
441 /* Add contact to pendings */
442 g_hash_table_insert (priv->pendings, GUINT_TO_POINTER (handle),
443 g_object_ref (contact));
444 g_signal_emit_by_name (list, "pendings-changed", contact,
445 contact, reason, message, TRUE);
451 tp_contact_list_remove_handle (EmpathyTpContactList *list,
455 EmpathyTpContactListPriv *priv = GET_PRIV (list);
456 EmpathyContact *contact;
459 if (table == priv->pendings)
460 sig = "pendings-changed";
461 else if (table == priv->members)
462 sig = "members-changed";
466 contact = g_hash_table_lookup (table, GUINT_TO_POINTER (handle));
468 g_object_ref (contact);
469 g_hash_table_remove (table, GUINT_TO_POINTER (handle));
470 g_signal_emit_by_name (list, sig, contact, 0, 0, NULL,
472 g_object_unref (contact);
477 tp_contact_list_publish_group_members_changed_cb (TpChannel *channel,
481 GArray *local_pending,
482 GArray *remote_pending,
484 TpChannelGroupChangeReason reason,
485 EmpathyTpContactList *list)
487 EmpathyTpContactListPriv *priv = GET_PRIV (list);
490 /* We now send our presence to those contacts, remove them from pendings */
491 for (i = 0; i < added->len; i++) {
492 tp_contact_list_remove_handle (list, priv->pendings,
493 g_array_index (added, TpHandle, i));
496 /* We refuse to send our presence to those contacts, remove from pendings */
497 for (i = 0; i < removed->len; i++) {
498 tp_contact_list_remove_handle (list, priv->pendings,
499 g_array_index (removed, TpHandle, i));
502 /* Those contacts want our presence, auto accept those that are already
503 * member, otherwise add in pendings. */
504 if (local_pending->len > 0) {
505 empathy_tp_contact_factory_get_from_handles (priv->factory,
506 local_pending->len, (TpHandle *) local_pending->data,
507 tp_contact_list_got_local_pending_cb, NULL, NULL,
513 tp_contact_list_get_alias_flags_cb (TpConnection *connection,
519 EmpathyTpContactListPriv *priv = GET_PRIV (list);
522 DEBUG ("Error: %s", error->message);
526 if (flags & TP_CONNECTION_ALIAS_FLAG_USER_SET) {
527 priv->flags |= EMPATHY_CONTACT_LIST_CAN_ALIAS;
532 tp_contact_list_get_requestablechannelclasses_cb (TpProxy *connection,
538 EmpathyTpContactListPriv *priv = GET_PRIV (list);
543 DEBUG ("Error: %s", error->message);
547 classes = g_value_get_boxed (value);
548 for (i = 0; i < classes->len; i++) {
549 GValueArray *class = g_ptr_array_index (classes, i);
551 const char *channel_type;
554 props = g_value_get_boxed (g_value_array_get_nth (class, 0));
556 channel_type = tp_asv_get_string (props,
557 TP_IFACE_CHANNEL ".ChannelType");
558 handle_type = tp_asv_get_uint32 (props,
559 TP_IFACE_CHANNEL ".TargetHandleType", NULL);
561 if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) &&
562 handle_type == TP_HANDLE_TYPE_GROUP) {
563 DEBUG ("Got channel class for a contact group");
564 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
571 tp_contact_list_subscribe_group_members_changed_cb (TpChannel *channel,
575 GArray *local_pending,
576 GArray *remote_pending,
579 EmpathyTpContactList *list)
581 EmpathyTpContactListPriv *priv = GET_PRIV (list);
584 /* We now get the presence of those contacts, add them to members */
585 if (added->len > 0) {
586 empathy_tp_contact_factory_get_from_handles (priv->factory,
587 added->len, (TpHandle *) added->data,
588 tp_contact_list_got_added_members_cb, NULL, NULL,
592 /* Those contacts refuse to send us their presence, remove from members. */
593 for (i = 0; i < removed->len; i++) {
594 tp_contact_list_remove_handle (list, priv->members,
595 g_array_index (removed, TpHandle, i));
598 /* We want those contacts in our contact list but we don't get their
599 * presence yet. Add to members anyway. */
600 if (remote_pending->len > 0) {
601 empathy_tp_contact_factory_get_from_handles (priv->factory,
602 remote_pending->len, (TpHandle *) remote_pending->data,
603 tp_contact_list_got_added_members_cb, NULL, NULL,
609 tp_contact_list_new_channel_cb (TpConnection *proxy,
610 const gchar *object_path,
611 const gchar *channel_type,
614 gboolean suppress_handler,
618 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
619 object_path, channel_type,
620 handle_type, handle);
624 tp_contact_list_list_channels_cb (TpConnection *connection,
625 const GPtrArray *channels,
633 DEBUG ("Error: %s", error->message);
637 for (i = 0; i < channels->len; i++) {
638 GValueArray *chan_struct;
639 const gchar *object_path;
640 const gchar *channel_type;
641 TpHandleType handle_type;
644 chan_struct = g_ptr_array_index (channels, i);
645 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
646 channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
647 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
648 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
650 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
651 object_path, channel_type,
652 handle_type, handle);
657 tp_contact_list_finalize (GObject *object)
659 EmpathyTpContactListPriv *priv;
660 EmpathyTpContactList *list;
664 list = EMPATHY_TP_CONTACT_LIST (object);
665 priv = GET_PRIV (list);
667 DEBUG ("finalize: %p", object);
669 if (priv->subscribe) {
670 g_object_unref (priv->subscribe);
673 g_object_unref (priv->publish);
676 g_object_unref (priv->stored);
679 if (priv->connection) {
680 g_object_unref (priv->connection);
684 g_object_unref (priv->factory);
687 g_hash_table_iter_init (&iter, priv->groups);
688 while (g_hash_table_iter_next (&iter, NULL, &channel)) {
689 g_signal_handlers_disconnect_by_func (channel,
690 tp_contact_list_group_invalidated_cb, list);
693 g_hash_table_destroy (priv->groups);
694 g_hash_table_destroy (priv->members);
695 g_hash_table_destroy (priv->pendings);
696 g_hash_table_destroy (priv->add_to_group);
698 G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
702 received_all_list_channels (EmpathyTpContactList *self)
704 EmpathyTpContactListPriv *priv = GET_PRIV (self);
706 return (priv->stored != NULL && priv->publish != NULL &&
707 priv->subscribe != NULL);
711 got_list_channel (EmpathyTpContactList *list,
714 EmpathyTpContactListPriv *priv = GET_PRIV (list);
717 /* We requested that channel by providing TargetID property, so it's
718 * guaranteed that tp_channel_get_identifier will return it. */
719 id = tp_channel_get_identifier (channel);
721 /* TpChannel emits initial set of members just before being ready */
722 if (!tp_strdiff (id, "stored")) {
723 if (priv->stored != NULL)
725 priv->stored = g_object_ref (channel);
726 } else if (!tp_strdiff (id, "publish")) {
727 if (priv->publish != NULL)
729 priv->publish = g_object_ref (channel);
730 g_signal_connect (priv->publish, "group-members-changed",
731 G_CALLBACK (tp_contact_list_publish_group_members_changed_cb),
733 } else if (!tp_strdiff (id, "subscribe")) {
734 if (priv->subscribe != NULL)
736 priv->subscribe = g_object_ref (channel);
737 g_signal_connect (priv->subscribe, "group-members-changed",
738 G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb),
742 if (received_all_list_channels (list) && priv->new_channels_sig != NULL) {
743 /* We don't need to watch NewChannels anymore */
744 tp_proxy_signal_connection_disconnect (priv->new_channels_sig);
745 priv->new_channels_sig = NULL;
750 list_ensure_channel_cb (TpConnection *conn,
753 GHashTable *properties,
756 GObject *weak_object)
758 EmpathyTpContactList *list = user_data;
762 DEBUG ("failed: %s\n", error->message);
766 channel = tp_channel_new_from_properties (conn, path, properties, NULL);
767 got_list_channel (list, channel);
768 g_object_unref (channel);
772 new_channels_cb (TpConnection *conn,
773 const GPtrArray *channels,
775 GObject *weak_object)
777 EmpathyTpContactList *list = EMPATHY_TP_CONTACT_LIST (weak_object);
780 for (i = 0; i < channels->len ; i++) {
781 GValueArray *arr = g_ptr_array_index (channels, i);
783 GHashTable *properties;
787 path = g_value_get_boxed (g_value_array_get_nth (arr, 0));
788 properties = g_value_get_boxed (g_value_array_get_nth (arr, 1));
790 if (tp_strdiff (tp_asv_get_string (properties,
791 TP_IFACE_CHANNEL ".ChannelType"),
792 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
795 if (tp_asv_get_uint32 (properties,
796 TP_IFACE_CHANNEL ".TargetHandleType", NULL)
797 != TP_HANDLE_TYPE_LIST)
800 id = tp_asv_get_string (properties,
801 TP_IFACE_CHANNEL ".TargetID");
805 channel = tp_channel_new_from_properties (conn, path,
807 got_list_channel (list, channel);
808 g_object_unref (channel);
813 conn_ready_cb (TpConnection *connection,
817 EmpathyTpContactList *list = data;
818 EmpathyTpContactListPriv *priv = GET_PRIV (list);
822 DEBUG ("failed: %s", error->message);
826 request = tp_asv_new (
827 TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
828 TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
831 /* Watch the NewChannels signal so if ensuring list channels fails (for
832 * example because the server is slow and the D-Bus call timeouts before CM
833 * fetches the roster), we have a chance to get them later. */
834 priv->new_channels_sig =
835 tp_cli_connection_interface_requests_connect_to_new_channels (
836 priv->connection, new_channels_cb, NULL, NULL, G_OBJECT (list), NULL);
838 /* Request the 'stored' list. */
839 tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "stored");
840 tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
841 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
843 /* Request the 'publish' list. */
844 tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "publish");
845 tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
846 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
848 /* Request the 'subscribe' list. */
849 tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "subscribe");
850 tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
851 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
853 g_hash_table_unref (request);
855 g_object_unref (list);
859 tp_contact_list_constructed (GObject *list)
861 EmpathyTpContactListPriv *priv = GET_PRIV (list);
863 priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection);
865 /* call GetAliasFlags */
866 if (tp_proxy_has_interface_by_id (priv->connection,
867 TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)) {
868 tp_cli_connection_interface_aliasing_call_get_alias_flags (
871 tp_contact_list_get_alias_flags_cb,
876 /* lookup RequestableChannelClasses */
877 if (tp_proxy_has_interface_by_id (priv->connection,
878 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)) {
879 tp_cli_dbus_properties_call_get (priv->connection,
881 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
882 "RequestableChannelClasses",
883 tp_contact_list_get_requestablechannelclasses_cb,
887 /* we just don't know... better mark the flag just in case */
888 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
891 tp_connection_call_when_ready (priv->connection, conn_ready_cb,
892 g_object_ref (list));
894 tp_cli_connection_call_list_channels (priv->connection, -1,
895 tp_contact_list_list_channels_cb,
899 tp_cli_connection_connect_to_new_channel (priv->connection,
900 tp_contact_list_new_channel_cb,
906 tp_contact_list_get_property (GObject *object,
911 EmpathyTpContactListPriv *priv = GET_PRIV (object);
914 case PROP_CONNECTION:
915 g_value_set_object (value, priv->connection);
918 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
924 tp_contact_list_set_property (GObject *object,
929 EmpathyTpContactListPriv *priv = GET_PRIV (object);
932 case PROP_CONNECTION:
933 priv->connection = g_value_dup_object (value);
936 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
942 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
944 GObjectClass *object_class = G_OBJECT_CLASS (klass);
946 object_class->finalize = tp_contact_list_finalize;
947 object_class->constructed = tp_contact_list_constructed;
948 object_class->get_property = tp_contact_list_get_property;
949 object_class->set_property = tp_contact_list_set_property;
951 g_object_class_install_property (object_class,
953 g_param_spec_object ("connection",
955 "The connection associated with the contact list",
958 G_PARAM_CONSTRUCT_ONLY));
960 g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
964 tp_contact_list_array_free (gpointer handles)
966 g_array_free (handles, TRUE);
970 empathy_tp_contact_list_init (EmpathyTpContactList *list)
972 EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
973 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
977 /* Map group's name to group's TpChannel. The group name string is owned
978 * by the TpChannel object */
979 priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal,
981 (GDestroyNotify) g_object_unref);
983 /* Map contact's handle to EmpathyContact object */
984 priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal,
986 (GDestroyNotify) g_object_unref);
988 /* Map contact's handle to EmpathyContact object */
989 priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
991 (GDestroyNotify) g_object_unref);
993 /* Map group's name to GArray of handle */
994 priv->add_to_group = g_hash_table_new_full (g_str_hash, g_str_equal,
996 tp_contact_list_array_free);
999 EmpathyTpContactList *
1000 empathy_tp_contact_list_new (TpConnection *connection)
1002 return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
1003 "connection", connection,
1008 empathy_tp_contact_list_get_connection (EmpathyTpContactList *list)
1010 EmpathyTpContactListPriv *priv;
1012 g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
1014 priv = GET_PRIV (list);
1016 return priv->connection;
1020 tp_contact_list_add (EmpathyContactList *list,
1021 EmpathyContact *contact,
1022 const gchar *message)
1024 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1026 GArray handles = {(gchar *) &handle, 1};
1028 handle = empathy_contact_get_handle (contact);
1029 if (priv->subscribe) {
1030 tp_cli_channel_interface_group_call_add_members (priv->subscribe,
1031 -1, &handles, message, NULL, NULL, NULL, NULL);
1033 if (priv->publish) {
1034 TpChannelGroupFlags flags = tp_channel_group_get_flags (priv->subscribe);
1035 if (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD ||
1036 g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
1037 tp_cli_channel_interface_group_call_add_members (priv->publish,
1038 -1, &handles, message, NULL, NULL, NULL, NULL);
1044 tp_contact_list_remove (EmpathyContactList *list,
1045 EmpathyContact *contact,
1046 const gchar *message)
1048 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1050 GArray handles = {(gchar *) &handle, 1};
1052 handle = empathy_contact_get_handle (contact);
1054 /* FIXME: this is racy if tp_contact_list_remove is called before the
1055 * 'stored' list has been retrieved. */
1056 if (priv->stored != NULL) {
1057 tp_cli_channel_interface_group_call_remove_members (priv->stored,
1058 -1, &handles, message, NULL, NULL, NULL, NULL);
1061 if (priv->subscribe) {
1062 tp_cli_channel_interface_group_call_remove_members (priv->subscribe,
1063 -1, &handles, message, NULL, NULL, NULL, NULL);
1065 if (priv->publish) {
1066 tp_cli_channel_interface_group_call_remove_members (priv->publish,
1067 -1, &handles, message, NULL, NULL, NULL, NULL);
1072 tp_contact_list_get_members (EmpathyContactList *list)
1074 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1077 ret = g_hash_table_get_values (priv->members);
1078 g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1083 tp_contact_list_get_pendings (EmpathyContactList *list)
1085 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1088 ret = g_hash_table_get_values (priv->pendings);
1089 g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1094 tp_contact_list_get_all_groups (EmpathyContactList *list)
1096 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1099 ret = g_hash_table_get_keys (priv->groups);
1100 for (l = ret; l; l = l->next) {
1101 l->data = g_strdup (l->data);
1108 tp_contact_list_get_groups (EmpathyContactList *list,
1109 EmpathyContact *contact)
1111 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1113 GHashTableIter iter;
1114 gpointer group_name;
1118 handle = empathy_contact_get_handle (contact);
1119 g_hash_table_iter_init (&iter, priv->groups);
1120 while (g_hash_table_iter_next (&iter, &group_name, &channel)) {
1121 const TpIntSet *members;
1123 members = tp_channel_group_get_members (channel);
1124 if (tp_intset_is_member (members, handle)) {
1125 ret = g_list_prepend (ret, g_strdup (group_name));
1133 tp_contact_list_add_to_group (EmpathyContactList *list,
1134 EmpathyContact *contact,
1135 const gchar *group_name)
1140 handle = empathy_contact_get_handle (contact);
1141 handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1142 g_array_append_val (handles, handle);
1143 tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1144 group_name, handles);
1148 tp_contact_list_remove_from_group (EmpathyContactList *list,
1149 EmpathyContact *contact,
1150 const gchar *group_name)
1152 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1155 GArray handles = {(gchar *) &handle, 1};
1157 channel = g_hash_table_lookup (priv->groups, group_name);
1158 if (channel == NULL) {
1162 handle = empathy_contact_get_handle (contact);
1163 DEBUG ("remove contact %s (%d) from group %s",
1164 empathy_contact_get_id (contact), handle, group_name);
1166 tp_cli_channel_interface_group_call_remove_members (channel, -1,
1167 &handles, NULL, NULL, NULL, NULL, NULL);
1171 tp_contact_list_rename_group (EmpathyContactList *list,
1172 const gchar *old_group_name,
1173 const gchar *new_group_name)
1175 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1177 const TpIntSet *members;
1180 channel = g_hash_table_lookup (priv->groups, old_group_name);
1181 if (channel == NULL) {
1185 DEBUG ("rename group %s to %s", old_group_name, new_group_name);
1187 /* Remove all members and close the old channel */
1188 members = tp_channel_group_get_members (channel);
1189 handles = tp_intset_to_array (members);
1190 tp_cli_channel_interface_group_call_remove_members (channel, -1,
1191 handles, NULL, NULL, NULL, NULL, NULL);
1192 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1194 tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1195 new_group_name, handles);
1199 tp_contact_list_remove_group (EmpathyContactList *list,
1200 const gchar *group_name)
1202 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1204 const TpIntSet *members;
1207 channel = g_hash_table_lookup (priv->groups, group_name);
1208 if (channel == NULL) {
1212 DEBUG ("remove group %s", group_name);
1214 /* Remove all members and close the channel */
1215 members = tp_channel_group_get_members (channel);
1216 handles = tp_intset_to_array (members);
1217 tp_cli_channel_interface_group_call_remove_members (channel, -1,
1218 handles, NULL, NULL, NULL, NULL, NULL);
1219 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1220 g_array_free (handles, TRUE);
1223 static EmpathyContactListFlags
1224 tp_contact_list_get_flags (EmpathyContactList *list)
1226 EmpathyTpContactListPriv *priv;
1227 EmpathyContactListFlags flags;
1229 g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1231 priv = GET_PRIV (list);
1232 flags = priv->flags;
1234 if (priv->subscribe != NULL) {
1235 TpChannelGroupFlags group_flags;
1237 group_flags = tp_channel_group_get_flags (priv->subscribe);
1239 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) {
1240 flags |= EMPATHY_CONTACT_LIST_CAN_ADD;
1243 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) {
1244 flags |= EMPATHY_CONTACT_LIST_CAN_REMOVE;
1252 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1254 iface->add = tp_contact_list_add;
1255 iface->remove = tp_contact_list_remove;
1256 iface->get_members = tp_contact_list_get_members;
1257 iface->get_pendings = tp_contact_list_get_pendings;
1258 iface->get_all_groups = tp_contact_list_get_all_groups;
1259 iface->get_groups = tp_contact_list_get_groups;
1260 iface->add_to_group = tp_contact_list_add_to_group;
1261 iface->remove_from_group = tp_contact_list_remove_from_group;
1262 iface->rename_group = tp_contact_list_rename_group;
1263 iface->remove_group = tp_contact_list_remove_group;
1264 iface->get_flags = tp_contact_list_get_flags;
1268 empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
1270 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1271 GHashTableIter iter;
1274 g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1276 /* Remove all contacts */
1277 g_hash_table_iter_init (&iter, priv->members);
1278 while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1279 g_signal_emit_by_name (list, "members-changed", contact,
1283 g_hash_table_remove_all (priv->members);
1285 g_hash_table_iter_init (&iter, priv->pendings);
1286 while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1287 g_signal_emit_by_name (list, "pendings-changed", contact,
1291 g_hash_table_remove_all (priv->pendings);