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;
56 } EmpathyTpContactListPriv;
59 TP_CONTACT_LIST_TYPE_PUBLISH,
60 TP_CONTACT_LIST_TYPE_SUBSCRIBE,
61 TP_CONTACT_LIST_TYPE_UNKNOWN
64 static void tp_contact_list_iface_init (EmpathyContactListIface *iface);
71 G_DEFINE_TYPE_WITH_CODE (EmpathyTpContactList, empathy_tp_contact_list, G_TYPE_OBJECT,
72 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
73 tp_contact_list_iface_init));
76 tp_contact_list_forget_group (EmpathyTpContactList *list,
79 EmpathyTpContactListPriv *priv = GET_PRIV (list);
80 const TpIntSet *members;
82 const gchar *group_name;
84 group_name = tp_channel_get_identifier (channel);
86 /* Signal that all members are not in that group anymore */
87 members = tp_channel_group_get_members (channel);
88 tp_intset_iter_init (&iter, members);
89 while (tp_intset_iter_next (&iter)) {
90 EmpathyContact *contact;
92 contact = g_hash_table_lookup (priv->members,
93 GUINT_TO_POINTER (iter.element));
94 if (contact == NULL) {
98 DEBUG ("Contact %s (%d) removed from group %s",
99 empathy_contact_get_id (contact), iter.element,
101 g_signal_emit_by_name (list, "groups-changed", contact,
108 tp_contact_list_group_invalidated_cb (TpChannel *channel,
112 EmpathyTpContactList *list)
114 EmpathyTpContactListPriv *priv = GET_PRIV (list);
115 const gchar *group_name;
117 group_name = tp_channel_get_identifier (channel);
118 DEBUG ("Group %s invalidated. Message: %s", group_name, message);
120 tp_contact_list_forget_group (list, channel);
122 g_hash_table_remove (priv->groups, group_name);
126 contacts_added_to_group (EmpathyTpContactList *list,
130 EmpathyTpContactListPriv *priv = GET_PRIV (list);
131 const gchar *group_name;
134 group_name = tp_channel_get_identifier (channel);
136 for (i = 0; i < added->len; i++) {
137 EmpathyContact *contact;
140 handle = g_array_index (added, TpHandle, i);
141 contact = g_hash_table_lookup (priv->members,
142 GUINT_TO_POINTER (handle));
143 if (contact == NULL) {
147 DEBUG ("Contact %s (%d) added to group %s",
148 empathy_contact_get_id (contact), handle, group_name);
149 g_signal_emit_by_name (list, "groups-changed", contact,
156 tp_contact_list_group_members_changed_cb (TpChannel *channel,
160 GArray *local_pending,
161 GArray *remote_pending,
164 EmpathyTpContactList *list)
166 EmpathyTpContactListPriv *priv = GET_PRIV (list);
167 const gchar *group_name;
170 contacts_added_to_group (list, channel, added);
172 group_name = tp_channel_get_identifier (channel);
174 for (i = 0; i < removed->len; i++) {
175 EmpathyContact *contact;
178 handle = g_array_index (removed, TpHandle, i);
179 contact = g_hash_table_lookup (priv->members,
180 GUINT_TO_POINTER (handle));
181 if (contact == NULL) {
185 DEBUG ("Contact %s (%d) removed from group %s",
186 empathy_contact_get_id (contact), handle, group_name);
188 g_signal_emit_by_name (list, "groups-changed", contact,
195 tp_contact_list_group_ready_cb (TpChannel *channel,
199 EmpathyTpContactListPriv *priv = GET_PRIV (list);
200 TpChannel *old_group;
201 const gchar *group_name;
202 const TpIntSet *members;
206 DEBUG ("Error: %s", error->message);
207 g_object_unref (channel);
211 group_name = tp_channel_get_identifier (channel);
213 /* If there's already a group with this name in the table, we can't
214 * just let it be replaced. Replacing it causes it to be unreffed,
215 * which causes it to be invalidated (see
216 * <https://bugs.freedesktop.org/show_bug.cgi?id=22119>), which causes
217 * it to be removed from the hash table again, which causes it to be
220 old_group = g_hash_table_lookup (priv->groups, group_name);
222 if (old_group != NULL) {
223 DEBUG ("Discarding old group %s (%p)", group_name, old_group);
224 g_hash_table_steal (priv->groups, group_name);
225 tp_contact_list_forget_group (list, old_group);
226 g_object_unref (old_group);
229 g_hash_table_insert (priv->groups, (gpointer) group_name, channel);
230 DEBUG ("Group %s added", group_name);
232 g_signal_connect (channel, "group-members-changed",
233 G_CALLBACK (tp_contact_list_group_members_changed_cb),
236 g_signal_connect (channel, "invalidated",
237 G_CALLBACK (tp_contact_list_group_invalidated_cb),
240 if (priv->add_to_group) {
243 handles = g_hash_table_lookup (priv->add_to_group, group_name);
245 DEBUG ("Adding initial members to group %s", group_name);
246 tp_cli_channel_interface_group_call_add_members (channel,
247 -1, handles, NULL, NULL, NULL, NULL, NULL);
248 g_hash_table_remove (priv->add_to_group, group_name);
252 /* Get initial members of the group */
253 members = tp_channel_group_get_members (channel);
254 g_assert (members != NULL);
255 arr = tp_intset_to_array (members);
256 contacts_added_to_group (list, channel, arr);
257 g_array_free (arr, TRUE);
261 tp_contact_list_group_add_channel (EmpathyTpContactList *list,
262 const gchar *object_path,
263 const gchar *channel_type,
264 TpHandleType handle_type,
267 EmpathyTpContactListPriv *priv = GET_PRIV (list);
270 /* Only accept server-side contact groups */
271 if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) ||
272 handle_type != TP_HANDLE_TYPE_GROUP) {
276 channel = tp_channel_new (priv->connection,
277 object_path, channel_type,
278 handle_type, handle, NULL);
280 /* Give the ref to the callback */
281 tp_channel_call_when_ready (channel,
282 tp_contact_list_group_ready_cb,
287 tp_contact_list_group_request_channel_cb (TpConnection *connection,
288 const gchar *object_path,
293 /* The new channel will be handled in NewChannel cb. Here we only
294 * handle the error if RequestChannel failed */
296 DEBUG ("Error: %s", error->message);
302 tp_contact_list_group_request_handles_cb (TpConnection *connection,
303 const GArray *handles,
308 TpHandle channel_handle;
311 DEBUG ("Error: %s", error->message);
315 channel_handle = g_array_index (handles, TpHandle, 0);
316 tp_cli_connection_call_request_channel (connection, -1,
317 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
318 TP_HANDLE_TYPE_GROUP,
321 tp_contact_list_group_request_channel_cb,
326 /* This function takes ownership of handles array */
328 tp_contact_list_group_add (EmpathyTpContactList *list,
329 const gchar *group_name,
332 EmpathyTpContactListPriv *priv = GET_PRIV (list);
334 const gchar *names[] = {group_name, NULL};
336 /* Search the channel for that group name */
337 channel = g_hash_table_lookup (priv->groups, group_name);
339 tp_cli_channel_interface_group_call_add_members (channel, -1,
340 handles, NULL, NULL, NULL, NULL, NULL);
341 g_array_free (handles, TRUE);
345 /* That group does not exist yet, we have to:
346 * 1) Request an handle for the group name
347 * 2) Request a channel
348 * 3) When NewChannel is emitted, add handles in members
350 g_hash_table_insert (priv->add_to_group,
351 g_strdup (group_name),
353 tp_cli_connection_call_request_handles (priv->connection, -1,
354 TP_HANDLE_TYPE_GROUP, names,
355 tp_contact_list_group_request_handles_cb,
361 tp_contact_list_got_added_members_cb (EmpathyTpContactFactory *factory,
363 EmpathyContact * const * contacts,
365 const TpHandle *failed,
370 EmpathyTpContactListPriv *priv = GET_PRIV (list);
374 DEBUG ("Error: %s", error->message);
378 for (i = 0; i < n_contacts; i++) {
379 EmpathyContact *contact = contacts[i];
382 handle = empathy_contact_get_handle (contact);
383 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle)))
386 /* Add to the list and emit signal */
387 g_hash_table_insert (priv->members, GUINT_TO_POINTER (handle),
388 g_object_ref (contact));
389 g_signal_emit_by_name (list, "members-changed", contact,
392 /* This contact is now member, implicitly accept pending. */
393 if (g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
394 GArray handles = {(gchar *) &handle, 1};
396 tp_cli_channel_interface_group_call_add_members (priv->publish,
397 -1, &handles, NULL, NULL, NULL, NULL, NULL);
403 tp_contact_list_got_local_pending_cb (EmpathyTpContactFactory *factory,
405 EmpathyContact * const * contacts,
407 const TpHandle *failed,
412 EmpathyTpContactListPriv *priv = GET_PRIV (list);
416 DEBUG ("Error: %s", error->message);
420 for (i = 0; i < n_contacts; i++) {
421 EmpathyContact *contact = contacts[i];
423 const gchar *message;
424 TpChannelGroupChangeReason reason;
426 handle = empathy_contact_get_handle (contact);
427 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle))) {
428 GArray handles = {(gchar *) &handle, 1};
430 /* This contact is already member, auto accept. */
431 tp_cli_channel_interface_group_call_add_members (priv->publish,
432 -1, &handles, NULL, NULL, NULL, NULL, NULL);
434 else if (tp_channel_group_get_local_pending_info (priv->publish,
439 /* Add contact to pendings */
440 g_hash_table_insert (priv->pendings, GUINT_TO_POINTER (handle),
441 g_object_ref (contact));
442 g_signal_emit_by_name (list, "pendings-changed", contact,
443 contact, reason, message, TRUE);
449 tp_contact_list_remove_handle (EmpathyTpContactList *list,
453 EmpathyTpContactListPriv *priv = GET_PRIV (list);
454 EmpathyContact *contact;
457 if (table == priv->pendings)
458 sig = "pendings-changed";
459 else if (table == priv->members)
460 sig = "members-changed";
464 contact = g_hash_table_lookup (table, GUINT_TO_POINTER (handle));
466 g_object_ref (contact);
467 g_hash_table_remove (table, GUINT_TO_POINTER (handle));
468 g_signal_emit_by_name (list, sig, contact, 0, 0, NULL,
470 g_object_unref (contact);
475 tp_contact_list_publish_group_members_changed_cb (TpChannel *channel,
479 GArray *local_pending,
480 GArray *remote_pending,
482 TpChannelGroupChangeReason reason,
483 EmpathyTpContactList *list)
485 EmpathyTpContactListPriv *priv = GET_PRIV (list);
488 /* We now send our presence to those contacts, remove them from pendings */
489 for (i = 0; i < added->len; i++) {
490 tp_contact_list_remove_handle (list, priv->pendings,
491 g_array_index (added, TpHandle, i));
494 /* We refuse to send our presence to those contacts, remove from pendings */
495 for (i = 0; i < removed->len; i++) {
496 tp_contact_list_remove_handle (list, priv->pendings,
497 g_array_index (removed, TpHandle, i));
500 /* Those contacts want our presence, auto accept those that are already
501 * member, otherwise add in pendings. */
502 if (local_pending->len > 0) {
503 empathy_tp_contact_factory_get_from_handles (priv->factory,
504 local_pending->len, (TpHandle *) local_pending->data,
505 tp_contact_list_got_local_pending_cb, NULL, NULL,
511 tp_contact_list_get_alias_flags_cb (TpConnection *connection,
517 EmpathyTpContactListPriv *priv = GET_PRIV (list);
520 DEBUG ("Error: %s", error->message);
524 if (flags & TP_CONNECTION_ALIAS_FLAG_USER_SET) {
525 priv->flags |= EMPATHY_CONTACT_LIST_CAN_ALIAS;
530 tp_contact_list_get_requestablechannelclasses_cb (TpProxy *connection,
536 EmpathyTpContactListPriv *priv = GET_PRIV (list);
541 DEBUG ("Error: %s", error->message);
545 classes = g_value_get_boxed (value);
546 for (i = 0; i < classes->len; i++) {
547 GValueArray *class = g_ptr_array_index (classes, i);
549 const char *channel_type;
552 props = g_value_get_boxed (g_value_array_get_nth (class, 0));
554 channel_type = tp_asv_get_string (props,
555 TP_IFACE_CHANNEL ".ChannelType");
556 handle_type = tp_asv_get_uint32 (props,
557 TP_IFACE_CHANNEL ".TargetHandleType", NULL);
559 if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) &&
560 handle_type == TP_HANDLE_TYPE_GROUP) {
561 DEBUG ("Got channel class for a contact group");
562 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
569 tp_contact_list_subscribe_group_members_changed_cb (TpChannel *channel,
573 GArray *local_pending,
574 GArray *remote_pending,
577 EmpathyTpContactList *list)
579 EmpathyTpContactListPriv *priv = GET_PRIV (list);
582 /* We now get the presence of those contacts, add them to members */
583 if (added->len > 0) {
584 empathy_tp_contact_factory_get_from_handles (priv->factory,
585 added->len, (TpHandle *) added->data,
586 tp_contact_list_got_added_members_cb, NULL, NULL,
590 /* Those contacts refuse to send us their presence, remove from members. */
591 for (i = 0; i < removed->len; i++) {
592 tp_contact_list_remove_handle (list, priv->members,
593 g_array_index (removed, TpHandle, i));
596 /* We want those contacts in our contact list but we don't get their
597 * presence yet. Add to members anyway. */
598 if (remote_pending->len > 0) {
599 empathy_tp_contact_factory_get_from_handles (priv->factory,
600 remote_pending->len, (TpHandle *) remote_pending->data,
601 tp_contact_list_got_added_members_cb, NULL, NULL,
607 tp_contact_list_new_channel_cb (TpConnection *proxy,
608 const gchar *object_path,
609 const gchar *channel_type,
612 gboolean suppress_handler,
616 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
617 object_path, channel_type,
618 handle_type, handle);
622 tp_contact_list_list_channels_cb (TpConnection *connection,
623 const GPtrArray *channels,
631 DEBUG ("Error: %s", error->message);
635 for (i = 0; i < channels->len; i++) {
636 GValueArray *chan_struct;
637 const gchar *object_path;
638 const gchar *channel_type;
639 TpHandleType handle_type;
642 chan_struct = g_ptr_array_index (channels, i);
643 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
644 channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
645 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
646 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
648 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
649 object_path, channel_type,
650 handle_type, handle);
655 tp_contact_list_finalize (GObject *object)
657 EmpathyTpContactListPriv *priv;
658 EmpathyTpContactList *list;
662 list = EMPATHY_TP_CONTACT_LIST (object);
663 priv = GET_PRIV (list);
665 DEBUG ("finalize: %p", object);
667 if (priv->subscribe) {
668 g_object_unref (priv->subscribe);
671 g_object_unref (priv->publish);
674 g_object_unref (priv->stored);
677 if (priv->connection) {
678 g_object_unref (priv->connection);
682 g_object_unref (priv->factory);
685 g_hash_table_iter_init (&iter, priv->groups);
686 while (g_hash_table_iter_next (&iter, NULL, &channel)) {
687 g_signal_handlers_disconnect_by_func (channel,
688 tp_contact_list_group_invalidated_cb, list);
691 g_hash_table_destroy (priv->groups);
692 g_hash_table_destroy (priv->members);
693 g_hash_table_destroy (priv->pendings);
694 g_hash_table_destroy (priv->add_to_group);
696 G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
700 list_ensure_channel_cb (TpConnection *conn,
703 GHashTable *properties,
706 GObject *weak_object)
708 EmpathyTpContactList *list = user_data;
709 EmpathyTpContactListPriv *priv = GET_PRIV (list);
714 DEBUG ("failed: %s\n", error->message);
718 /* We requested that channel by providing TargetID property, so it's
719 * guaranteed that tp_channel_get_identifier will return it. */
720 channel = tp_channel_new_from_properties (conn, path, properties, NULL);
721 id = tp_channel_get_identifier (channel);
723 /* TpChannel emits initial set of members just before being ready */
724 if (!tp_strdiff (id, "stored")) {
725 priv->stored = channel;
726 } else if (!tp_strdiff (id, "publish")) {
727 priv->publish = channel;
728 g_signal_connect (priv->publish, "group-members-changed",
729 G_CALLBACK (tp_contact_list_publish_group_members_changed_cb),
731 } else if (!tp_strdiff (id, "subscribe")) {
732 priv->subscribe = channel;
733 g_signal_connect (priv->subscribe, "group-members-changed",
734 G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb),
737 g_warn_if_reached ();
738 g_object_unref (channel);
743 conn_ready_cb (TpConnection *connection,
747 EmpathyTpContactList *list = data;
748 EmpathyTpContactListPriv *priv = GET_PRIV (list);
752 DEBUG ("failed: %s", error->message);
756 request = tp_asv_new (
757 TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
758 TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
761 /* Request the 'stored' list. */
762 tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "stored");
763 tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
764 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
766 /* Request the 'publish' list. */
767 tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "publish");
768 tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
769 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
771 /* Request the 'subscribe' list. */
772 tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "subscribe");
773 tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
774 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
776 g_hash_table_unref (request);
778 g_object_unref (list);
782 tp_contact_list_constructed (GObject *list)
784 EmpathyTpContactListPriv *priv = GET_PRIV (list);
786 priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection);
788 /* call GetAliasFlags */
789 if (tp_proxy_has_interface_by_id (priv->connection,
790 TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)) {
791 tp_cli_connection_interface_aliasing_call_get_alias_flags (
794 tp_contact_list_get_alias_flags_cb,
799 /* lookup RequestableChannelClasses */
800 if (tp_proxy_has_interface_by_id (priv->connection,
801 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)) {
802 tp_cli_dbus_properties_call_get (priv->connection,
804 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
805 "RequestableChannelClasses",
806 tp_contact_list_get_requestablechannelclasses_cb,
810 /* we just don't know... better mark the flag just in case */
811 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
814 tp_connection_call_when_ready (priv->connection, conn_ready_cb,
815 g_object_ref (list));
817 tp_cli_connection_call_list_channels (priv->connection, -1,
818 tp_contact_list_list_channels_cb,
822 tp_cli_connection_connect_to_new_channel (priv->connection,
823 tp_contact_list_new_channel_cb,
829 tp_contact_list_get_property (GObject *object,
834 EmpathyTpContactListPriv *priv = GET_PRIV (object);
837 case PROP_CONNECTION:
838 g_value_set_object (value, priv->connection);
841 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
847 tp_contact_list_set_property (GObject *object,
852 EmpathyTpContactListPriv *priv = GET_PRIV (object);
855 case PROP_CONNECTION:
856 priv->connection = g_value_dup_object (value);
859 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
865 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
867 GObjectClass *object_class = G_OBJECT_CLASS (klass);
869 object_class->finalize = tp_contact_list_finalize;
870 object_class->constructed = tp_contact_list_constructed;
871 object_class->get_property = tp_contact_list_get_property;
872 object_class->set_property = tp_contact_list_set_property;
874 g_object_class_install_property (object_class,
876 g_param_spec_object ("connection",
878 "The connection associated with the contact list",
881 G_PARAM_CONSTRUCT_ONLY));
883 g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
887 tp_contact_list_array_free (gpointer handles)
889 g_array_free (handles, TRUE);
893 empathy_tp_contact_list_init (EmpathyTpContactList *list)
895 EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
896 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
900 /* Map group's name to group's TpChannel. The group name string is owned
901 * by the TpChannel object */
902 priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal,
904 (GDestroyNotify) g_object_unref);
906 /* Map contact's handle to EmpathyContact object */
907 priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal,
909 (GDestroyNotify) g_object_unref);
911 /* Map contact's handle to EmpathyContact object */
912 priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
914 (GDestroyNotify) g_object_unref);
916 /* Map group's name to GArray of handle */
917 priv->add_to_group = g_hash_table_new_full (g_str_hash, g_str_equal,
919 tp_contact_list_array_free);
922 EmpathyTpContactList *
923 empathy_tp_contact_list_new (TpConnection *connection)
925 return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
926 "connection", connection,
931 empathy_tp_contact_list_get_connection (EmpathyTpContactList *list)
933 EmpathyTpContactListPriv *priv;
935 g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
937 priv = GET_PRIV (list);
939 return priv->connection;
943 tp_contact_list_add (EmpathyContactList *list,
944 EmpathyContact *contact,
945 const gchar *message)
947 EmpathyTpContactListPriv *priv = GET_PRIV (list);
949 GArray handles = {(gchar *) &handle, 1};
951 handle = empathy_contact_get_handle (contact);
952 if (priv->subscribe) {
953 tp_cli_channel_interface_group_call_add_members (priv->subscribe,
954 -1, &handles, message, NULL, NULL, NULL, NULL);
957 TpChannelGroupFlags flags = tp_channel_group_get_flags (priv->subscribe);
958 if (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD ||
959 g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
960 tp_cli_channel_interface_group_call_add_members (priv->publish,
961 -1, &handles, message, NULL, NULL, NULL, NULL);
967 tp_contact_list_remove (EmpathyContactList *list,
968 EmpathyContact *contact,
969 const gchar *message)
971 EmpathyTpContactListPriv *priv = GET_PRIV (list);
973 GArray handles = {(gchar *) &handle, 1};
975 handle = empathy_contact_get_handle (contact);
977 /* FIXME: this is racy if tp_contact_list_remove is called before the
978 * 'stored' list has been retrieved. */
979 if (priv->stored != NULL) {
980 tp_cli_channel_interface_group_call_remove_members (priv->stored,
981 -1, &handles, message, NULL, NULL, NULL, NULL);
984 if (priv->subscribe) {
985 tp_cli_channel_interface_group_call_remove_members (priv->subscribe,
986 -1, &handles, message, NULL, NULL, NULL, NULL);
989 tp_cli_channel_interface_group_call_remove_members (priv->publish,
990 -1, &handles, message, NULL, NULL, NULL, NULL);
995 tp_contact_list_get_members (EmpathyContactList *list)
997 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1000 ret = g_hash_table_get_values (priv->members);
1001 g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1006 tp_contact_list_get_pendings (EmpathyContactList *list)
1008 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1011 ret = g_hash_table_get_values (priv->pendings);
1012 g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1017 tp_contact_list_get_all_groups (EmpathyContactList *list)
1019 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1022 ret = g_hash_table_get_keys (priv->groups);
1023 for (l = ret; l; l = l->next) {
1024 l->data = g_strdup (l->data);
1031 tp_contact_list_get_groups (EmpathyContactList *list,
1032 EmpathyContact *contact)
1034 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1036 GHashTableIter iter;
1037 gpointer group_name;
1041 handle = empathy_contact_get_handle (contact);
1042 g_hash_table_iter_init (&iter, priv->groups);
1043 while (g_hash_table_iter_next (&iter, &group_name, &channel)) {
1044 const TpIntSet *members;
1046 members = tp_channel_group_get_members (channel);
1047 if (tp_intset_is_member (members, handle)) {
1048 ret = g_list_prepend (ret, g_strdup (group_name));
1056 tp_contact_list_add_to_group (EmpathyContactList *list,
1057 EmpathyContact *contact,
1058 const gchar *group_name)
1063 handle = empathy_contact_get_handle (contact);
1064 handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1065 g_array_append_val (handles, handle);
1066 tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1067 group_name, handles);
1071 tp_contact_list_remove_from_group (EmpathyContactList *list,
1072 EmpathyContact *contact,
1073 const gchar *group_name)
1075 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1078 GArray handles = {(gchar *) &handle, 1};
1080 channel = g_hash_table_lookup (priv->groups, group_name);
1081 if (channel == NULL) {
1085 handle = empathy_contact_get_handle (contact);
1086 DEBUG ("remove contact %s (%d) from group %s",
1087 empathy_contact_get_id (contact), handle, group_name);
1089 tp_cli_channel_interface_group_call_remove_members (channel, -1,
1090 &handles, NULL, NULL, NULL, NULL, NULL);
1094 tp_contact_list_rename_group (EmpathyContactList *list,
1095 const gchar *old_group_name,
1096 const gchar *new_group_name)
1098 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1100 const TpIntSet *members;
1103 channel = g_hash_table_lookup (priv->groups, old_group_name);
1104 if (channel == NULL) {
1108 DEBUG ("rename group %s to %s", old_group_name, new_group_name);
1110 /* Remove all members and close the old channel */
1111 members = tp_channel_group_get_members (channel);
1112 handles = tp_intset_to_array (members);
1113 tp_cli_channel_interface_group_call_remove_members (channel, -1,
1114 handles, NULL, NULL, NULL, NULL, NULL);
1115 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1117 tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1118 new_group_name, handles);
1122 tp_contact_list_remove_group (EmpathyContactList *list,
1123 const gchar *group_name)
1125 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1127 const TpIntSet *members;
1130 channel = g_hash_table_lookup (priv->groups, group_name);
1131 if (channel == NULL) {
1135 DEBUG ("remove group %s", group_name);
1137 /* Remove all members and close the channel */
1138 members = tp_channel_group_get_members (channel);
1139 handles = tp_intset_to_array (members);
1140 tp_cli_channel_interface_group_call_remove_members (channel, -1,
1141 handles, NULL, NULL, NULL, NULL, NULL);
1142 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1143 g_array_free (handles, TRUE);
1146 static EmpathyContactListFlags
1147 tp_contact_list_get_flags (EmpathyContactList *list)
1149 EmpathyTpContactListPriv *priv;
1150 EmpathyContactListFlags flags;
1152 g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1154 priv = GET_PRIV (list);
1155 flags = priv->flags;
1157 if (priv->subscribe != NULL) {
1158 TpChannelGroupFlags group_flags;
1160 group_flags = tp_channel_group_get_flags (priv->subscribe);
1162 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) {
1163 flags |= EMPATHY_CONTACT_LIST_CAN_ADD;
1166 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) {
1167 flags |= EMPATHY_CONTACT_LIST_CAN_REMOVE;
1175 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1177 iface->add = tp_contact_list_add;
1178 iface->remove = tp_contact_list_remove;
1179 iface->get_members = tp_contact_list_get_members;
1180 iface->get_pendings = tp_contact_list_get_pendings;
1181 iface->get_all_groups = tp_contact_list_get_all_groups;
1182 iface->get_groups = tp_contact_list_get_groups;
1183 iface->add_to_group = tp_contact_list_add_to_group;
1184 iface->remove_from_group = tp_contact_list_remove_from_group;
1185 iface->rename_group = tp_contact_list_rename_group;
1186 iface->remove_group = tp_contact_list_remove_group;
1187 iface->get_flags = tp_contact_list_get_flags;
1191 empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
1193 EmpathyTpContactListPriv *priv = GET_PRIV (list);
1194 GHashTableIter iter;
1197 g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1199 /* Remove all contacts */
1200 g_hash_table_iter_init (&iter, priv->members);
1201 while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1202 g_signal_emit_by_name (list, "members-changed", contact,
1206 g_hash_table_remove_all (priv->members);
1208 g_hash_table_iter_init (&iter, priv->pendings);
1209 while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1210 g_signal_emit_by_name (list, "pendings-changed", contact,
1214 g_hash_table_remove_all (priv->pendings);