]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
Merge branch 'people-nearby-fake-group-613558'
[empathy.git] / libempathy / empathy-tp-contact-list.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
4  * Copyright (C) 2007-2009 Collabora Ltd.
5  *
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.
10  *
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.
15  *
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
19  *
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <glib/gi18n-lib.h>
27
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>
33
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"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT
40 #include "empathy-debug.h"
41
42 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactList)
43 typedef struct {
44         EmpathyTpContactFactory *factory;
45         TpConnection   *connection;
46
47         TpChannel      *publish;
48         TpChannel      *subscribe;
49         TpChannel      *stored;
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 */
54
55         EmpathyContactListFlags flags;
56
57         TpProxySignalConnection *new_channels_sig;
58 } EmpathyTpContactListPriv;
59
60 typedef enum {
61         TP_CONTACT_LIST_TYPE_PUBLISH,
62         TP_CONTACT_LIST_TYPE_SUBSCRIBE,
63         TP_CONTACT_LIST_TYPE_UNKNOWN
64 } TpContactListType;
65
66 static void tp_contact_list_iface_init         (EmpathyContactListIface   *iface);
67
68 enum {
69         PROP_0,
70         PROP_CONNECTION,
71 };
72
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));
76
77 static void
78 tp_contact_list_forget_group (EmpathyTpContactList *list,
79                               TpChannel *channel)
80 {
81         EmpathyTpContactListPriv *priv = GET_PRIV (list);
82         const TpIntSet *members;
83         TpIntSetIter iter;
84         const gchar *group_name;
85
86         group_name = tp_channel_get_identifier (channel);
87
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;
93
94                 contact = g_hash_table_lookup (priv->members,
95                                                GUINT_TO_POINTER (iter.element));
96                 if (contact == NULL) {
97                         continue;
98                 }
99
100                 DEBUG ("Contact %s (%d) removed from group %s",
101                         empathy_contact_get_id (contact), iter.element,
102                         group_name);
103                 g_signal_emit_by_name (list, "groups-changed", contact,
104                                        group_name,
105                                        FALSE);
106         }
107 }
108
109 static void
110 tp_contact_list_group_invalidated_cb (TpChannel *channel,
111                                       guint      domain,
112                                       gint       code,
113                                       gchar     *message,
114                                       EmpathyTpContactList *list)
115 {
116         EmpathyTpContactListPriv *priv = GET_PRIV (list);
117         const gchar *group_name;
118
119         group_name = tp_channel_get_identifier (channel);
120         DEBUG ("Group %s invalidated. Message: %s", group_name, message);
121
122         tp_contact_list_forget_group (list, channel);
123
124         g_hash_table_remove (priv->groups, group_name);
125 }
126
127 static void
128 contacts_added_to_group (EmpathyTpContactList *list,
129                          TpChannel *channel,
130                          GArray *added)
131 {
132         EmpathyTpContactListPriv *priv = GET_PRIV (list);
133         const gchar *group_name;
134         guint i;
135
136         group_name = tp_channel_get_identifier (channel);
137
138         for (i = 0; i < added->len; i++) {
139                 EmpathyContact *contact;
140                 TpHandle handle;
141
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) {
146                         continue;
147                 }
148
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,
152                                        group_name,
153                                        TRUE);
154         }
155 }
156
157 static void
158 tp_contact_list_group_members_changed_cb (TpChannel     *channel,
159                                           gchar         *message,
160                                           GArray        *added,
161                                           GArray        *removed,
162                                           GArray        *local_pending,
163                                           GArray        *remote_pending,
164                                           guint          actor,
165                                           guint          reason,
166                                           EmpathyTpContactList *list)
167 {
168         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
169         const gchar *group_name;
170         guint i;
171
172         contacts_added_to_group (list, channel, added);
173
174         group_name = tp_channel_get_identifier (channel);
175
176         for (i = 0; i < removed->len; i++) {
177                 EmpathyContact *contact;
178                 TpHandle handle;
179
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) {
184                         continue;
185                 }
186
187                 DEBUG ("Contact %s (%d) removed from group %s",
188                         empathy_contact_get_id (contact), handle, group_name);
189
190                 g_signal_emit_by_name (list, "groups-changed", contact,
191                                        group_name,
192                                        FALSE);
193         }
194 }
195
196 static void
197 tp_contact_list_group_ready_cb (TpChannel *channel,
198                                 const GError *error,
199                                 gpointer list)
200 {
201         EmpathyTpContactListPriv *priv = GET_PRIV (list);
202         TpChannel *old_group;
203         const gchar *group_name;
204         const TpIntSet *members;
205         GArray *arr;
206
207         if (error) {
208                 DEBUG ("Error: %s", error->message);
209                 g_object_unref (channel);
210                 return;
211         }
212
213         group_name = tp_channel_get_identifier (channel);
214
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
220          * unreffed again.
221          */
222         old_group = g_hash_table_lookup (priv->groups, group_name);
223
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);
229         }
230
231         g_hash_table_insert (priv->groups, (gpointer) group_name, channel);
232         DEBUG ("Group %s added", group_name);
233
234         g_signal_connect (channel, "group-members-changed",
235                           G_CALLBACK (tp_contact_list_group_members_changed_cb),
236                           list);
237
238         g_signal_connect (channel, "invalidated",
239                           G_CALLBACK (tp_contact_list_group_invalidated_cb),
240                           list);
241
242         if (priv->add_to_group) {
243                 GArray *handles;
244
245                 handles = g_hash_table_lookup (priv->add_to_group, group_name);
246                 if (handles) {
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);
251                 }
252         }
253
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);
260 }
261
262 static void
263 tp_contact_list_group_add_channel (EmpathyTpContactList *list,
264                                    const gchar          *object_path,
265                                    const gchar          *channel_type,
266                                    TpHandleType          handle_type,
267                                    guint                 handle)
268 {
269         EmpathyTpContactListPriv *priv = GET_PRIV (list);
270         TpChannel                *channel;
271
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) {
275                 return;
276         }
277
278         channel = tp_channel_new (priv->connection,
279                                   object_path, channel_type,
280                                   handle_type, handle, NULL);
281
282         /* Give the ref to the callback */
283         tp_channel_call_when_ready (channel,
284                                     tp_contact_list_group_ready_cb,
285                                     list);
286 }
287
288 static void
289 tp_contact_list_group_request_channel_cb (TpConnection *connection,
290                                           const gchar  *object_path,
291                                           const GError *error,
292                                           gpointer      user_data,
293                                           GObject      *list)
294 {
295         /* The new channel will be handled in NewChannel cb. Here we only
296          * handle the error if RequestChannel failed */
297         if (error) {
298                 DEBUG ("Error: %s", error->message);
299                 return;
300         }
301 }
302
303 static void
304 tp_contact_list_group_request_handles_cb (TpConnection *connection,
305                                           const GArray *handles,
306                                           const GError *error,
307                                           gpointer      user_data,
308                                           GObject      *list)
309 {
310         TpHandle channel_handle;
311
312         if (error) {
313                 DEBUG ("Error: %s", error->message);
314                 return;
315         }
316
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,
321                                                 channel_handle,
322                                                 TRUE,
323                                                 tp_contact_list_group_request_channel_cb,
324                                                 NULL, NULL,
325                                                 list);
326 }
327
328 /* This function takes ownership of handles array */
329 static void
330 tp_contact_list_group_add (EmpathyTpContactList *list,
331                            const gchar          *group_name,
332                            GArray               *handles)
333 {
334         EmpathyTpContactListPriv *priv = GET_PRIV (list);
335         TpChannel                *channel;
336         const gchar              *names[] = {group_name, NULL};
337
338         /* Search the channel for that group name */
339         channel = g_hash_table_lookup (priv->groups, group_name);
340         if (channel) {
341                 tp_cli_channel_interface_group_call_add_members (channel, -1,
342                         handles, NULL, NULL, NULL, NULL, NULL);
343                 g_array_free (handles, TRUE);
344                 return;
345         }
346
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
351          */
352         g_hash_table_insert (priv->add_to_group,
353                              g_strdup (group_name),
354                              handles);
355         tp_cli_connection_call_request_handles (priv->connection, -1,
356                                                 TP_HANDLE_TYPE_GROUP, names,
357                                                 tp_contact_list_group_request_handles_cb,
358                                                 NULL, NULL,
359                                                 G_OBJECT (list));
360 }
361
362 static void
363 tp_contact_list_got_added_members_cb (EmpathyTpContactFactory *factory,
364                                       guint                    n_contacts,
365                                       EmpathyContact * const * contacts,
366                                       guint                    n_failed,
367                                       const TpHandle          *failed,
368                                       const GError            *error,
369                                       gpointer                 user_data,
370                                       GObject                 *list)
371 {
372         EmpathyTpContactListPriv *priv = GET_PRIV (list);
373         guint i;
374
375         if (error) {
376                 DEBUG ("Error: %s", error->message);
377                 return;
378         }
379
380         for (i = 0; i < n_contacts; i++) {
381                 EmpathyContact *contact = contacts[i];
382                 TpHandle handle;
383
384                 handle = empathy_contact_get_handle (contact);
385                 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle)))
386                         continue;
387
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,
392                                        0, 0, NULL, TRUE);
393
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};
397
398                         tp_cli_channel_interface_group_call_add_members (priv->publish,
399                                 -1, &handles, NULL, NULL, NULL, NULL, NULL);
400                 }
401         }
402 }
403
404 static void
405 tp_contact_list_got_local_pending_cb (EmpathyTpContactFactory *factory,
406                                       guint                    n_contacts,
407                                       EmpathyContact * const * contacts,
408                                       guint                    n_failed,
409                                       const TpHandle          *failed,
410                                       const GError            *error,
411                                       gpointer                 user_data,
412                                       GObject                 *list)
413 {
414         EmpathyTpContactListPriv *priv = GET_PRIV (list);
415         guint i;
416
417         if (error) {
418                 DEBUG ("Error: %s", error->message);
419                 return;
420         }
421
422         for (i = 0; i < n_contacts; i++) {
423                 EmpathyContact *contact = contacts[i];
424                 TpHandle handle;
425                 const gchar *message;
426                 TpChannelGroupChangeReason reason;
427
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};
431
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);
435                 }
436                 else if (tp_channel_group_get_local_pending_info (priv->publish,
437                                                                   handle,
438                                                                   NULL,
439                                                                   &reason,
440                                                                   &message)) {
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);
446                 }
447         }
448 }
449
450 static void
451 tp_contact_list_remove_handle (EmpathyTpContactList *list,
452                                GHashTable *table,
453                                TpHandle handle)
454 {
455         EmpathyTpContactListPriv *priv = GET_PRIV (list);
456         EmpathyContact *contact;
457         const gchar *sig;
458
459         if (table == priv->pendings)
460                 sig = "pendings-changed";
461         else if (table == priv->members)
462                 sig = "members-changed";
463         else
464                 return;
465
466         contact = g_hash_table_lookup (table, GUINT_TO_POINTER (handle));
467         if (contact) {
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,
471                                        FALSE);
472                 g_object_unref (contact);
473         }
474 }
475
476 static void
477 tp_contact_list_publish_group_members_changed_cb (TpChannel     *channel,
478                                                   gchar         *message,
479                                                   GArray        *added,
480                                                   GArray        *removed,
481                                                   GArray        *local_pending,
482                                                   GArray        *remote_pending,
483                                                   TpHandle       actor,
484                                                   TpChannelGroupChangeReason reason,
485                                                   EmpathyTpContactList *list)
486 {
487         EmpathyTpContactListPriv *priv = GET_PRIV (list);
488         guint i;
489
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));
494         }
495
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));
500         }
501
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,
508                         G_OBJECT (list));
509         }
510 }
511
512 static void
513 tp_contact_list_get_alias_flags_cb (TpConnection *connection,
514                                     guint         flags,
515                                     const GError *error,
516                                     gpointer      user_data,
517                                     GObject      *list)
518 {
519         EmpathyTpContactListPriv *priv = GET_PRIV (list);
520
521         if (error) {
522                 DEBUG ("Error: %s", error->message);
523                 return;
524         }
525
526         if (flags & TP_CONNECTION_ALIAS_FLAG_USER_SET) {
527                 priv->flags |= EMPATHY_CONTACT_LIST_CAN_ALIAS;
528         }
529 }
530
531 static void
532 tp_contact_list_get_requestablechannelclasses_cb (TpProxy      *connection,
533                                                   const GValue *value,
534                                                   const GError *error,
535                                                   gpointer      user_data,
536                                                   GObject      *list)
537 {
538         EmpathyTpContactListPriv *priv = GET_PRIV (list);
539         GPtrArray *classes;
540         guint i;
541
542         if (error) {
543                 DEBUG ("Error: %s", error->message);
544                 return;
545         }
546
547         classes = g_value_get_boxed (value);
548         for (i = 0; i < classes->len; i++) {
549                 GValueArray *class = g_ptr_array_index (classes, i);
550                 GHashTable *props;
551                 const char *channel_type;
552                 guint handle_type;
553
554                 props = g_value_get_boxed (g_value_array_get_nth (class, 0));
555
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);
560
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;
565                         break;
566                 }
567         }
568 }
569
570 static void
571 tp_contact_list_subscribe_group_members_changed_cb (TpChannel     *channel,
572                                                     gchar         *message,
573                                                     GArray        *added,
574                                                     GArray        *removed,
575                                                     GArray        *local_pending,
576                                                     GArray        *remote_pending,
577                                                     guint          actor,
578                                                     guint          reason,
579                                                     EmpathyTpContactList *list)
580 {
581         EmpathyTpContactListPriv *priv = GET_PRIV (list);
582         guint i;
583
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,
589                         G_OBJECT (list));
590         }
591
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));
596         }
597
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,
604                         G_OBJECT (list));
605         }
606 }
607
608 static void
609 tp_contact_list_new_channel_cb (TpConnection *proxy,
610                                 const gchar  *object_path,
611                                 const gchar  *channel_type,
612                                 guint         handle_type,
613                                 guint         handle,
614                                 gboolean      suppress_handler,
615                                 gpointer      user_data,
616                                 GObject      *list)
617 {
618         tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
619                                            object_path, channel_type,
620                                            handle_type, handle);
621 }
622
623 static void
624 tp_contact_list_list_channels_cb (TpConnection    *connection,
625                                   const GPtrArray *channels,
626                                   const GError    *error,
627                                   gpointer         user_data,
628                                   GObject         *list)
629 {
630         guint i;
631
632         if (error) {
633                 DEBUG ("Error: %s", error->message);
634                 return;
635         }
636
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;
642                 guint         handle;
643
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));
649
650                 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
651                                                    object_path, channel_type,
652                                                    handle_type, handle);
653         }
654 }
655
656 static void
657 tp_contact_list_finalize (GObject *object)
658 {
659         EmpathyTpContactListPriv *priv;
660         EmpathyTpContactList     *list;
661         GHashTableIter            iter;
662         gpointer                  channel;
663
664         list = EMPATHY_TP_CONTACT_LIST (object);
665         priv = GET_PRIV (list);
666
667         DEBUG ("finalize: %p", object);
668
669         if (priv->subscribe) {
670                 g_object_unref (priv->subscribe);
671         }
672         if (priv->publish) {
673                 g_object_unref (priv->publish);
674         }
675         if (priv->stored) {
676                 g_object_unref (priv->stored);
677         }
678
679         if (priv->connection) {
680                 g_object_unref (priv->connection);
681         }
682
683         if (priv->factory) {
684                 g_object_unref (priv->factory);
685         }
686
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);
691         }
692
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);
697
698         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
699 }
700
701 static gboolean
702 received_all_list_channels (EmpathyTpContactList *self)
703 {
704         EmpathyTpContactListPriv *priv = GET_PRIV (self);
705
706         return (priv->stored != NULL && priv->publish != NULL &&
707                 priv->subscribe != NULL);
708 }
709
710 static void
711 got_list_channel (EmpathyTpContactList *list,
712                   TpChannel *channel)
713 {
714         EmpathyTpContactListPriv *priv = GET_PRIV (list);
715         const gchar *id;
716
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);
720
721         /* TpChannel emits initial set of members just before being ready */
722         if (!tp_strdiff (id, "stored")) {
723                 if (priv->stored != NULL)
724                         return;
725                 priv->stored = g_object_ref (channel);
726         } else if (!tp_strdiff (id, "publish")) {
727                 if (priv->publish != NULL)
728                         return;
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),
732                                   list);
733         } else if (!tp_strdiff (id, "subscribe")) {
734                 if (priv->subscribe != NULL)
735                         return;
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),
739                                   list);
740         }
741
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;
746         }
747 }
748
749 static void
750 list_ensure_channel_cb (TpConnection *conn,
751                         gboolean yours,
752                         const gchar *path,
753                         GHashTable *properties,
754                         const GError *error,
755                         gpointer user_data,
756                         GObject *weak_object)
757 {
758         EmpathyTpContactList *list = user_data;
759         TpChannel *channel;
760
761         if (error != NULL) {
762                 DEBUG ("failed: %s\n", error->message);
763                 return;
764         }
765
766         channel = tp_channel_new_from_properties (conn, path, properties, NULL);
767         got_list_channel (list, channel);
768         g_object_unref (channel);
769 }
770
771 static void
772 new_channels_cb (TpConnection *conn,
773                  const GPtrArray *channels,
774                  gpointer user_data,
775                  GObject *weak_object)
776 {
777         EmpathyTpContactList *list = EMPATHY_TP_CONTACT_LIST (weak_object);
778         guint i;
779
780         for (i = 0; i < channels->len ; i++) {
781                 GValueArray *arr = g_ptr_array_index (channels, i);
782                 const gchar *path;
783                 GHashTable *properties;
784                 const gchar *id;
785                 TpChannel *channel;
786
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));
789
790                 if (tp_strdiff (tp_asv_get_string (properties,
791                                 TP_IFACE_CHANNEL ".ChannelType"),
792                     TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
793                         return;
794
795                 if (tp_asv_get_uint32 (properties,
796                                        TP_IFACE_CHANNEL ".TargetHandleType", NULL)
797                     != TP_HANDLE_TYPE_LIST)
798                         return;
799
800                 id = tp_asv_get_string (properties,
801                                         TP_IFACE_CHANNEL ".TargetID");
802                 if (id == NULL)
803                         return;
804
805                 channel = tp_channel_new_from_properties (conn, path,
806                                                           properties, NULL);
807                 got_list_channel (list, channel);
808                 g_object_unref (channel);
809         }
810 }
811
812 static void
813 conn_ready_cb (TpConnection *connection,
814                const GError *error,
815                gpointer data)
816 {
817         EmpathyTpContactList *list = data;
818         EmpathyTpContactListPriv *priv = GET_PRIV (list);
819         GHashTable *request;
820
821         if (error != NULL) {
822                 DEBUG ("failed: %s", error->message);
823                 goto out;
824         }
825
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,
829                 NULL);
830
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);
837
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));
842
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));
847
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));
852
853         g_hash_table_unref (request);
854 out:
855         g_object_unref (list);
856 }
857
858 static void
859 tp_contact_list_constructed (GObject *list)
860 {
861         EmpathyTpContactListPriv *priv = GET_PRIV (list);
862
863         priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection);
864
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 (
869                                 priv->connection,
870                                 -1,
871                                 tp_contact_list_get_alias_flags_cb,
872                                 NULL, NULL,
873                                 G_OBJECT (list));
874         }
875
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,
880                                 -1,
881                                 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
882                                 "RequestableChannelClasses",
883                                 tp_contact_list_get_requestablechannelclasses_cb,
884                                 NULL, NULL,
885                                 G_OBJECT (list));
886         } else {
887                 /* we just don't know... better mark the flag just in case */
888                 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
889         }
890
891         tp_connection_call_when_ready (priv->connection, conn_ready_cb,
892                 g_object_ref (list));
893
894         tp_cli_connection_call_list_channels (priv->connection, -1,
895                                               tp_contact_list_list_channels_cb,
896                                               NULL, NULL,
897                                               list);
898
899         tp_cli_connection_connect_to_new_channel (priv->connection,
900                                                   tp_contact_list_new_channel_cb,
901                                                   NULL, NULL,
902                                                   list, NULL);
903 }
904
905 static void
906 tp_contact_list_get_property (GObject    *object,
907                               guint       param_id,
908                               GValue     *value,
909                               GParamSpec *pspec)
910 {
911         EmpathyTpContactListPriv *priv = GET_PRIV (object);
912
913         switch (param_id) {
914         case PROP_CONNECTION:
915                 g_value_set_object (value, priv->connection);
916                 break;
917         default:
918                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
919                 break;
920         };
921 }
922
923 static void
924 tp_contact_list_set_property (GObject      *object,
925                               guint         param_id,
926                               const GValue *value,
927                               GParamSpec   *pspec)
928 {
929         EmpathyTpContactListPriv *priv = GET_PRIV (object);
930
931         switch (param_id) {
932         case PROP_CONNECTION:
933                 priv->connection = g_value_dup_object (value);
934                 break;
935         default:
936                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
937                 break;
938         };
939 }
940
941 static void
942 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
943 {
944         GObjectClass *object_class = G_OBJECT_CLASS (klass);
945
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;
950
951         g_object_class_install_property (object_class,
952                                          PROP_CONNECTION,
953                                          g_param_spec_object ("connection",
954                                                               "The Connection",
955                                                               "The connection associated with the contact list",
956                                                               TP_TYPE_CONNECTION,
957                                                               G_PARAM_READWRITE |
958                                                               G_PARAM_CONSTRUCT_ONLY));
959
960         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
961 }
962
963 static void
964 tp_contact_list_array_free (gpointer handles)
965 {
966         g_array_free (handles, TRUE);
967 }
968
969 static void
970 empathy_tp_contact_list_init (EmpathyTpContactList *list)
971 {
972         EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
973                 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
974
975         list->priv = priv;
976
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,
980                                               NULL,
981                                               (GDestroyNotify) g_object_unref);
982
983         /* Map contact's handle to EmpathyContact object */
984         priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal,
985                                                NULL,
986                                                (GDestroyNotify) g_object_unref);
987
988         /* Map contact's handle to EmpathyContact object */
989         priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
990                                                 NULL,
991                                                 (GDestroyNotify) g_object_unref);
992
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,
995                                                     g_free,
996                                                     tp_contact_list_array_free);
997 }
998
999 EmpathyTpContactList *
1000 empathy_tp_contact_list_new (TpConnection *connection)
1001 {
1002         return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
1003                              "connection", connection,
1004                              NULL);
1005 }
1006
1007 TpConnection *
1008 empathy_tp_contact_list_get_connection (EmpathyTpContactList *list)
1009 {
1010         EmpathyTpContactListPriv *priv;
1011
1012         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
1013
1014         priv = GET_PRIV (list);
1015
1016         return priv->connection;
1017 }
1018
1019 static void
1020 tp_contact_list_add (EmpathyContactList *list,
1021                      EmpathyContact     *contact,
1022                      const gchar        *message)
1023 {
1024         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1025         TpHandle handle;
1026         GArray handles = {(gchar *) &handle, 1};
1027
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);
1032         }
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);
1039                 }
1040         }
1041 }
1042
1043 static void
1044 tp_contact_list_remove (EmpathyContactList *list,
1045                         EmpathyContact     *contact,
1046                         const gchar        *message)
1047 {
1048         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1049         TpHandle handle;
1050         GArray handles = {(gchar *) &handle, 1};
1051
1052         handle = empathy_contact_get_handle (contact);
1053
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);
1059         }
1060
1061         if (priv->subscribe) {
1062                 tp_cli_channel_interface_group_call_remove_members (priv->subscribe,
1063                         -1, &handles, message, NULL, NULL, NULL, NULL);
1064         }
1065         if (priv->publish) {
1066                 tp_cli_channel_interface_group_call_remove_members (priv->publish,
1067                         -1, &handles, message, NULL, NULL, NULL, NULL);
1068         }
1069 }
1070
1071 static GList *
1072 tp_contact_list_get_members (EmpathyContactList *list)
1073 {
1074         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1075         GList *ret;
1076
1077         ret = g_hash_table_get_values (priv->members);
1078         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1079         return ret;
1080 }
1081
1082 static GList *
1083 tp_contact_list_get_pendings (EmpathyContactList *list)
1084 {
1085         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1086         GList *ret;
1087
1088         ret = g_hash_table_get_values (priv->pendings);
1089         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1090         return ret;
1091 }
1092
1093 static GList *
1094 tp_contact_list_get_all_groups (EmpathyContactList *list)
1095 {
1096         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1097         GList                    *ret, *l;
1098
1099         ret = g_hash_table_get_keys (priv->groups);
1100         for (l = ret; l; l = l->next) {
1101                 l->data = g_strdup (l->data);
1102         }
1103
1104         return ret;
1105 }
1106
1107 static GList *
1108 tp_contact_list_get_groups (EmpathyContactList *list,
1109                             EmpathyContact     *contact)
1110 {
1111         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
1112         GList                     *ret = NULL;
1113         GHashTableIter             iter;
1114         gpointer                   group_name;
1115         gpointer                   channel;
1116         TpHandle                   handle;
1117
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;
1122
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));
1126                 }
1127         }
1128
1129         return ret;
1130 }
1131
1132 static void
1133 tp_contact_list_add_to_group (EmpathyContactList *list,
1134                               EmpathyContact     *contact,
1135                               const gchar        *group_name)
1136 {
1137         TpHandle handle;
1138         GArray *handles;
1139
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);
1145 }
1146
1147 static void
1148 tp_contact_list_remove_from_group (EmpathyContactList *list,
1149                                    EmpathyContact     *contact,
1150                                    const gchar        *group_name)
1151 {
1152         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1153         TpChannel                *channel;
1154         TpHandle                  handle;
1155         GArray                    handles = {(gchar *) &handle, 1};
1156
1157         channel = g_hash_table_lookup (priv->groups, group_name);
1158         if (channel == NULL) {
1159                 return;
1160         }
1161
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);
1165
1166         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1167                 &handles, NULL, NULL, NULL, NULL, NULL);
1168 }
1169
1170 static void
1171 tp_contact_list_rename_group (EmpathyContactList *list,
1172                               const gchar        *old_group_name,
1173                               const gchar        *new_group_name)
1174 {
1175         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1176         TpChannel                *channel;
1177         const TpIntSet           *members;
1178         GArray                   *handles;
1179
1180         channel = g_hash_table_lookup (priv->groups, old_group_name);
1181         if (channel == NULL) {
1182                 return;
1183         }
1184
1185         DEBUG ("rename group %s to %s", old_group_name, new_group_name);
1186
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);
1193
1194         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1195                                    new_group_name, handles);
1196 }
1197
1198 static void
1199 tp_contact_list_remove_group (EmpathyContactList *list,
1200                               const gchar *group_name)
1201 {
1202         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1203         TpChannel                *channel;
1204         const TpIntSet           *members;
1205         GArray                   *handles;
1206
1207         channel = g_hash_table_lookup (priv->groups, group_name);
1208         if (channel == NULL) {
1209                 return;
1210         }
1211
1212         DEBUG ("remove group %s", group_name);
1213
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);
1221 }
1222
1223 static EmpathyContactListFlags
1224 tp_contact_list_get_flags (EmpathyContactList *list)
1225 {
1226         EmpathyTpContactListPriv *priv;
1227         EmpathyContactListFlags flags;
1228
1229         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1230
1231         priv = GET_PRIV (list);
1232         flags = priv->flags;
1233
1234         if (priv->subscribe != NULL) {
1235                 TpChannelGroupFlags group_flags;
1236
1237                 group_flags = tp_channel_group_get_flags (priv->subscribe);
1238
1239                 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) {
1240                         flags |= EMPATHY_CONTACT_LIST_CAN_ADD;
1241                 }
1242
1243                 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) {
1244                         flags |= EMPATHY_CONTACT_LIST_CAN_REMOVE;
1245                 }
1246         }
1247
1248         return flags;
1249 }
1250
1251 static void
1252 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1253 {
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;
1265 }
1266
1267 void
1268 empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
1269 {
1270         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1271         GHashTableIter            iter;
1272         gpointer                  contact;
1273
1274         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1275
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,
1280                                        NULL, 0, NULL,
1281                                        FALSE);
1282         }
1283         g_hash_table_remove_all (priv->members);
1284
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,
1288                                        NULL, 0, NULL,
1289                                        FALSE);
1290         }
1291         g_hash_table_remove_all (priv->pendings);
1292 }
1293