]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
factor out add_to_members
[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         /* contact handle (TpHandle) => reffed (EmpathyContact *)
51          *
52          * Contacts which are members or remote-pending in the subscribe channel:
53          * we are receiving their presence or we asked to receive it. */
54         GHashTable     *members;
55         /* contact handle (TpHandle) => reffed (EmpathyContact *)
56          *
57          * Contacts which are local-pending in the publish channel but are NOT in
58          * the members hash table: they asked to receive our presence and we don't
59          * receive theirs or asked to.
60          * That's basically the contacts which asked to add us to their contact
61          * list and we didn't answer it. */
62         GHashTable     *pendings;
63         /* group name: borrowed (const gchar *)  => reffed (TpChannel *) */
64         GHashTable     *groups;
65         /* group name: owned (gchar *) => owned GArray of TpHandle */
66         GHashTable     *add_to_group;
67
68         EmpathyContactListFlags flags;
69
70         TpProxySignalConnection *new_channels_sig;
71 } EmpathyTpContactListPriv;
72
73 typedef enum {
74         TP_CONTACT_LIST_TYPE_PUBLISH,
75         TP_CONTACT_LIST_TYPE_SUBSCRIBE,
76         TP_CONTACT_LIST_TYPE_UNKNOWN
77 } TpContactListType;
78
79 static void tp_contact_list_iface_init         (EmpathyContactListIface   *iface);
80
81 enum {
82         PROP_0,
83         PROP_CONNECTION,
84 };
85
86 G_DEFINE_TYPE_WITH_CODE (EmpathyTpContactList, empathy_tp_contact_list, G_TYPE_OBJECT,
87                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
88                                                 tp_contact_list_iface_init));
89
90 static void
91 tp_contact_list_forget_group (EmpathyTpContactList *list,
92                               TpChannel *channel)
93 {
94         EmpathyTpContactListPriv *priv = GET_PRIV (list);
95         const TpIntSet *members;
96         TpIntSetIter iter;
97         const gchar *group_name;
98
99         group_name = tp_channel_get_identifier (channel);
100
101         /* Signal that all members are not in that group anymore */
102         members = tp_channel_group_get_members (channel);
103         tp_intset_iter_init (&iter, members);
104         while (tp_intset_iter_next (&iter)) {
105                 EmpathyContact *contact;
106
107                 contact = g_hash_table_lookup (priv->members,
108                                                GUINT_TO_POINTER (iter.element));
109                 if (contact == NULL) {
110                         continue;
111                 }
112
113                 DEBUG ("Contact %s (%d) removed from group %s",
114                         empathy_contact_get_id (contact), iter.element,
115                         group_name);
116                 g_signal_emit_by_name (list, "groups-changed", contact,
117                                        group_name,
118                                        FALSE);
119         }
120 }
121
122 static void
123 tp_contact_list_group_invalidated_cb (TpChannel *channel,
124                                       guint      domain,
125                                       gint       code,
126                                       gchar     *message,
127                                       EmpathyTpContactList *list)
128 {
129         EmpathyTpContactListPriv *priv = GET_PRIV (list);
130         const gchar *group_name;
131
132         group_name = tp_channel_get_identifier (channel);
133         DEBUG ("Group %s invalidated. Message: %s", group_name, message);
134
135         tp_contact_list_forget_group (list, channel);
136
137         g_hash_table_remove (priv->groups, group_name);
138 }
139
140 static void
141 contacts_added_to_group (EmpathyTpContactList *list,
142                          TpChannel *channel,
143                          GArray *added)
144 {
145         EmpathyTpContactListPriv *priv = GET_PRIV (list);
146         const gchar *group_name;
147         guint i;
148
149         group_name = tp_channel_get_identifier (channel);
150
151         for (i = 0; i < added->len; i++) {
152                 EmpathyContact *contact;
153                 TpHandle handle;
154
155                 handle = g_array_index (added, TpHandle, i);
156                 contact = g_hash_table_lookup (priv->members,
157                                                GUINT_TO_POINTER (handle));
158                 if (contact == NULL) {
159                         continue;
160                 }
161
162                 DEBUG ("Contact %s (%d) added to group %s",
163                         empathy_contact_get_id (contact), handle, group_name);
164                 g_signal_emit_by_name (list, "groups-changed", contact,
165                                        group_name,
166                                        TRUE);
167         }
168 }
169
170 static void
171 tp_contact_list_group_members_changed_cb (TpChannel     *channel,
172                                           gchar         *message,
173                                           GArray        *added,
174                                           GArray        *removed,
175                                           GArray        *local_pending,
176                                           GArray        *remote_pending,
177                                           guint          actor,
178                                           guint          reason,
179                                           EmpathyTpContactList *list)
180 {
181         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
182         const gchar *group_name;
183         guint i;
184
185         contacts_added_to_group (list, channel, added);
186
187         group_name = tp_channel_get_identifier (channel);
188
189         for (i = 0; i < removed->len; i++) {
190                 EmpathyContact *contact;
191                 TpHandle handle;
192
193                 handle = g_array_index (removed, TpHandle, i);
194                 contact = g_hash_table_lookup (priv->members,
195                                                GUINT_TO_POINTER (handle));
196                 if (contact == NULL) {
197                         continue;
198                 }
199
200                 DEBUG ("Contact %s (%d) removed from group %s",
201                         empathy_contact_get_id (contact), handle, group_name);
202
203                 g_signal_emit_by_name (list, "groups-changed", contact,
204                                        group_name,
205                                        FALSE);
206         }
207 }
208
209 static void
210 tp_contact_list_group_ready_cb (TpChannel *channel,
211                                 const GError *error,
212                                 gpointer list)
213 {
214         EmpathyTpContactListPriv *priv = GET_PRIV (list);
215         TpChannel *old_group;
216         const gchar *group_name;
217         const TpIntSet *members;
218         GArray *arr;
219
220         if (error) {
221                 DEBUG ("Error: %s", error->message);
222                 g_object_unref (channel);
223                 return;
224         }
225
226         group_name = tp_channel_get_identifier (channel);
227
228         /* If there's already a group with this name in the table, we can't
229          * just let it be replaced. Replacing it causes it to be unreffed,
230          * which causes it to be invalidated (see
231          * <https://bugs.freedesktop.org/show_bug.cgi?id=22119>), which causes
232          * it to be removed from the hash table again, which causes it to be
233          * unreffed again.
234          */
235         old_group = g_hash_table_lookup (priv->groups, group_name);
236
237         if (old_group != NULL) {
238                 DEBUG ("Discarding old group %s (%p)", group_name, old_group);
239                 g_hash_table_steal (priv->groups, group_name);
240                 tp_contact_list_forget_group (list, old_group);
241                 g_object_unref (old_group);
242         }
243
244         g_hash_table_insert (priv->groups, (gpointer) group_name, channel);
245         DEBUG ("Group %s added", group_name);
246
247         g_signal_connect (channel, "group-members-changed",
248                           G_CALLBACK (tp_contact_list_group_members_changed_cb),
249                           list);
250
251         g_signal_connect (channel, "invalidated",
252                           G_CALLBACK (tp_contact_list_group_invalidated_cb),
253                           list);
254
255         if (priv->add_to_group) {
256                 GArray *handles;
257
258                 handles = g_hash_table_lookup (priv->add_to_group, group_name);
259                 if (handles) {
260                         DEBUG ("Adding initial members to group %s", group_name);
261                         tp_cli_channel_interface_group_call_add_members (channel,
262                                 -1, handles, NULL, NULL, NULL, NULL, NULL);
263                         g_hash_table_remove (priv->add_to_group, group_name);
264                 }
265         }
266
267         /* Get initial members of the group */
268         members = tp_channel_group_get_members (channel);
269         g_assert (members != NULL);
270         arr = tp_intset_to_array (members);
271         contacts_added_to_group (list, channel, arr);
272         g_array_free (arr, TRUE);
273 }
274
275 static void
276 tp_contact_list_group_add_channel (EmpathyTpContactList *list,
277                                    const gchar          *object_path,
278                                    const gchar          *channel_type,
279                                    TpHandleType          handle_type,
280                                    guint                 handle)
281 {
282         EmpathyTpContactListPriv *priv = GET_PRIV (list);
283         TpChannel                *channel;
284
285         /* Only accept server-side contact groups */
286         if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) ||
287             handle_type != TP_HANDLE_TYPE_GROUP) {
288                 return;
289         }
290
291         channel = tp_channel_new (priv->connection,
292                                   object_path, channel_type,
293                                   handle_type, handle, NULL);
294
295         /* Give the ref to the callback */
296         tp_channel_call_when_ready (channel,
297                                     tp_contact_list_group_ready_cb,
298                                     list);
299 }
300
301 static void
302 tp_contact_list_group_request_channel_cb (TpConnection *connection,
303                                           const gchar  *object_path,
304                                           const GError *error,
305                                           gpointer      user_data,
306                                           GObject      *list)
307 {
308         /* The new channel will be handled in NewChannel cb. Here we only
309          * handle the error if RequestChannel failed */
310         if (error) {
311                 DEBUG ("Error: %s", error->message);
312                 return;
313         }
314 }
315
316 static void
317 tp_contact_list_group_request_handles_cb (TpConnection *connection,
318                                           const GArray *handles,
319                                           const GError *error,
320                                           gpointer      user_data,
321                                           GObject      *list)
322 {
323         TpHandle channel_handle;
324
325         if (error) {
326                 DEBUG ("Error: %s", error->message);
327                 return;
328         }
329
330         channel_handle = g_array_index (handles, TpHandle, 0);
331         tp_cli_connection_call_request_channel (connection, -1,
332                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
333                                                 TP_HANDLE_TYPE_GROUP,
334                                                 channel_handle,
335                                                 TRUE,
336                                                 tp_contact_list_group_request_channel_cb,
337                                                 NULL, NULL,
338                                                 list);
339 }
340
341 /* This function takes ownership of handles array */
342 static void
343 tp_contact_list_group_add (EmpathyTpContactList *list,
344                            const gchar          *group_name,
345                            GArray               *handles)
346 {
347         EmpathyTpContactListPriv *priv = GET_PRIV (list);
348         TpChannel                *channel;
349         const gchar              *names[] = {group_name, NULL};
350
351         /* Search the channel for that group name */
352         channel = g_hash_table_lookup (priv->groups, group_name);
353         if (channel) {
354                 tp_cli_channel_interface_group_call_add_members (channel, -1,
355                         handles, NULL, NULL, NULL, NULL, NULL);
356                 g_array_free (handles, TRUE);
357                 return;
358         }
359
360         /* That group does not exist yet, we have to:
361          * 1) Request an handle for the group name
362          * 2) Request a channel
363          * 3) When NewChannel is emitted, add handles in members
364          */
365         g_hash_table_insert (priv->add_to_group,
366                              g_strdup (group_name),
367                              handles);
368         tp_cli_connection_call_request_handles (priv->connection, -1,
369                                                 TP_HANDLE_TYPE_GROUP, names,
370                                                 tp_contact_list_group_request_handles_cb,
371                                                 NULL, NULL,
372                                                 G_OBJECT (list));
373 }
374
375 static void
376 add_to_members (EmpathyTpContactList *list,
377                 EmpathyContact *contact)
378 {
379         EmpathyTpContactListPriv *priv = GET_PRIV (list);
380         TpHandle handle;
381
382         handle = empathy_contact_get_handle (contact);
383         if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle)))
384                 return;
385
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,
390                                        0, 0, NULL, TRUE);
391 }
392
393 static void
394 tp_contact_list_got_added_members_cb (EmpathyTpContactFactory *factory,
395                                       guint                    n_contacts,
396                                       EmpathyContact * const * contacts,
397                                       guint                    n_failed,
398                                       const TpHandle          *failed,
399                                       const GError            *error,
400                                       gpointer                 user_data,
401                                       GObject                 *list)
402 {
403         EmpathyTpContactListPriv *priv = GET_PRIV (list);
404         guint i;
405
406         if (error) {
407                 DEBUG ("Error: %s", error->message);
408                 return;
409         }
410
411         for (i = 0; i < n_contacts; i++) {
412                 EmpathyContact *contact = contacts[i];
413                 TpHandle handle;
414
415                 add_to_members (EMPATHY_TP_CONTACT_LIST (list), contact);
416
417                 handle = empathy_contact_get_handle (contact);
418                 /* This contact is now member, implicitly accept pending. */
419                 if (g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
420                         GArray handles = {(gchar *) &handle, 1};
421
422                         tp_cli_channel_interface_group_call_add_members (priv->publish,
423                                 -1, &handles, NULL, NULL, NULL, NULL, NULL);
424                 }
425         }
426 }
427
428 static void
429 tp_contact_list_got_local_pending_cb (EmpathyTpContactFactory *factory,
430                                       guint                    n_contacts,
431                                       EmpathyContact * const * contacts,
432                                       guint                    n_failed,
433                                       const TpHandle          *failed,
434                                       const GError            *error,
435                                       gpointer                 user_data,
436                                       GObject                 *list)
437 {
438         EmpathyTpContactListPriv *priv = GET_PRIV (list);
439         guint i;
440
441         if (error) {
442                 DEBUG ("Error: %s", error->message);
443                 return;
444         }
445
446         for (i = 0; i < n_contacts; i++) {
447                 EmpathyContact *contact = contacts[i];
448                 TpHandle handle;
449                 const gchar *message;
450                 TpChannelGroupChangeReason reason;
451
452                 handle = empathy_contact_get_handle (contact);
453                 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle))) {
454                         GArray handles = {(gchar *) &handle, 1};
455
456                         /* This contact is already member, auto accept. */
457                         tp_cli_channel_interface_group_call_add_members (priv->publish,
458                                 -1, &handles, NULL, NULL, NULL, NULL, NULL);
459                 }
460                 else if (tp_channel_group_get_local_pending_info (priv->publish,
461                                                                   handle,
462                                                                   NULL,
463                                                                   &reason,
464                                                                   &message)) {
465                         /* Add contact to pendings */
466                         g_hash_table_insert (priv->pendings, GUINT_TO_POINTER (handle),
467                                              g_object_ref (contact));
468                         g_signal_emit_by_name (list, "pendings-changed", contact,
469                                                contact, reason, message, TRUE);
470                 }
471         }
472 }
473
474 static void
475 tp_contact_list_remove_handle (EmpathyTpContactList *list,
476                                GHashTable *table,
477                                TpHandle handle)
478 {
479         EmpathyTpContactListPriv *priv = GET_PRIV (list);
480         EmpathyContact *contact;
481         const gchar *sig;
482
483         if (table == priv->pendings)
484                 sig = "pendings-changed";
485         else if (table == priv->members)
486                 sig = "members-changed";
487         else
488                 return;
489
490         contact = g_hash_table_lookup (table, GUINT_TO_POINTER (handle));
491         if (contact) {
492                 g_object_ref (contact);
493                 g_hash_table_remove (table, GUINT_TO_POINTER (handle));
494                 g_signal_emit_by_name (list, sig, contact, 0, 0, NULL,
495                                        FALSE);
496                 g_object_unref (contact);
497         }
498 }
499
500 static void
501 tp_contact_list_publish_group_members_changed_cb (TpChannel     *channel,
502                                                   gchar         *message,
503                                                   GArray        *added,
504                                                   GArray        *removed,
505                                                   GArray        *local_pending,
506                                                   GArray        *remote_pending,
507                                                   TpHandle       actor,
508                                                   TpChannelGroupChangeReason reason,
509                                                   EmpathyTpContactList *list)
510 {
511         EmpathyTpContactListPriv *priv = GET_PRIV (list);
512         guint i;
513
514         /* We now send our presence to those contacts, remove them from pendings */
515         for (i = 0; i < added->len; i++) {
516                 tp_contact_list_remove_handle (list, priv->pendings,
517                         g_array_index (added, TpHandle, i));
518         }
519
520         /* We refuse to send our presence to those contacts, remove from pendings */
521         for (i = 0; i < removed->len; i++) {
522                 tp_contact_list_remove_handle (list, priv->pendings,
523                         g_array_index (removed, TpHandle, i));
524         }
525
526         /* Those contacts want our presence, auto accept those that are already
527          * member, otherwise add in pendings. */
528         if (local_pending->len > 0) {
529                 empathy_tp_contact_factory_get_from_handles (priv->factory,
530                         local_pending->len, (TpHandle *) local_pending->data,
531                         tp_contact_list_got_local_pending_cb, NULL, NULL,
532                         G_OBJECT (list));
533         }
534 }
535
536 static void
537 tp_contact_list_get_alias_flags_cb (TpConnection *connection,
538                                     guint         flags,
539                                     const GError *error,
540                                     gpointer      user_data,
541                                     GObject      *list)
542 {
543         EmpathyTpContactListPriv *priv = GET_PRIV (list);
544
545         if (error) {
546                 DEBUG ("Error: %s", error->message);
547                 return;
548         }
549
550         if (flags & TP_CONNECTION_ALIAS_FLAG_USER_SET) {
551                 priv->flags |= EMPATHY_CONTACT_LIST_CAN_ALIAS;
552         }
553 }
554
555 static void
556 tp_contact_list_get_requestablechannelclasses_cb (TpProxy      *connection,
557                                                   const GValue *value,
558                                                   const GError *error,
559                                                   gpointer      user_data,
560                                                   GObject      *list)
561 {
562         EmpathyTpContactListPriv *priv = GET_PRIV (list);
563         GPtrArray *classes;
564         guint i;
565
566         if (error) {
567                 DEBUG ("Error: %s", error->message);
568                 return;
569         }
570
571         classes = g_value_get_boxed (value);
572         for (i = 0; i < classes->len; i++) {
573                 GValueArray *class = g_ptr_array_index (classes, i);
574                 GHashTable *props;
575                 const char *channel_type;
576                 guint handle_type;
577
578                 props = g_value_get_boxed (g_value_array_get_nth (class, 0));
579
580                 channel_type = tp_asv_get_string (props,
581                                 TP_IFACE_CHANNEL ".ChannelType");
582                 handle_type = tp_asv_get_uint32 (props,
583                                 TP_IFACE_CHANNEL ".TargetHandleType", NULL);
584
585                 if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) &&
586                     handle_type == TP_HANDLE_TYPE_GROUP) {
587                         DEBUG ("Got channel class for a contact group");
588                         priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
589                         break;
590                 }
591         }
592 }
593
594 static void
595 tp_contact_list_subscribe_group_members_changed_cb (TpChannel     *channel,
596                                                     gchar         *message,
597                                                     GArray        *added,
598                                                     GArray        *removed,
599                                                     GArray        *local_pending,
600                                                     GArray        *remote_pending,
601                                                     guint          actor,
602                                                     guint          reason,
603                                                     EmpathyTpContactList *list)
604 {
605         EmpathyTpContactListPriv *priv = GET_PRIV (list);
606         guint i;
607
608         /* We now get the presence of those contacts, add them to members */
609         if (added->len > 0) {
610                 empathy_tp_contact_factory_get_from_handles (priv->factory,
611                         added->len, (TpHandle *) added->data,
612                         tp_contact_list_got_added_members_cb, NULL, NULL,
613                         G_OBJECT (list));
614         }
615
616         /* Those contacts refuse to send us their presence, remove from members. */
617         for (i = 0; i < removed->len; i++) {
618                 tp_contact_list_remove_handle (list, priv->members,
619                         g_array_index (removed, TpHandle, i));
620         }
621
622         /* We want those contacts in our contact list but we don't get their
623          * presence yet. Add to members anyway. */
624         if (remote_pending->len > 0) {
625                 empathy_tp_contact_factory_get_from_handles (priv->factory,
626                         remote_pending->len, (TpHandle *) remote_pending->data,
627                         tp_contact_list_got_added_members_cb, NULL, NULL,
628                         G_OBJECT (list));
629         }
630 }
631
632 static void
633 tp_contact_list_new_channel_cb (TpConnection *proxy,
634                                 const gchar  *object_path,
635                                 const gchar  *channel_type,
636                                 guint         handle_type,
637                                 guint         handle,
638                                 gboolean      suppress_handler,
639                                 gpointer      user_data,
640                                 GObject      *list)
641 {
642         tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
643                                            object_path, channel_type,
644                                            handle_type, handle);
645 }
646
647 static void
648 tp_contact_list_list_channels_cb (TpConnection    *connection,
649                                   const GPtrArray *channels,
650                                   const GError    *error,
651                                   gpointer         user_data,
652                                   GObject         *list)
653 {
654         guint i;
655
656         if (error) {
657                 DEBUG ("Error: %s", error->message);
658                 return;
659         }
660
661         for (i = 0; i < channels->len; i++) {
662                 GValueArray  *chan_struct;
663                 const gchar  *object_path;
664                 const gchar  *channel_type;
665                 TpHandleType  handle_type;
666                 guint         handle;
667
668                 chan_struct = g_ptr_array_index (channels, i);
669                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
670                 channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
671                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
672                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
673
674                 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
675                                                    object_path, channel_type,
676                                                    handle_type, handle);
677         }
678 }
679
680 static void
681 tp_contact_list_finalize (GObject *object)
682 {
683         EmpathyTpContactListPriv *priv;
684         EmpathyTpContactList     *list;
685         GHashTableIter            iter;
686         gpointer                  channel;
687
688         list = EMPATHY_TP_CONTACT_LIST (object);
689         priv = GET_PRIV (list);
690
691         DEBUG ("finalize: %p", object);
692
693         if (priv->subscribe) {
694                 g_object_unref (priv->subscribe);
695         }
696         if (priv->publish) {
697                 g_object_unref (priv->publish);
698         }
699         if (priv->stored) {
700                 g_object_unref (priv->stored);
701         }
702
703         if (priv->connection) {
704                 g_object_unref (priv->connection);
705         }
706
707         if (priv->factory) {
708                 g_object_unref (priv->factory);
709         }
710
711         g_hash_table_iter_init (&iter, priv->groups);
712         while (g_hash_table_iter_next (&iter, NULL, &channel)) {
713                 g_signal_handlers_disconnect_by_func (channel,
714                         tp_contact_list_group_invalidated_cb, list);
715         }
716
717         g_hash_table_destroy (priv->groups);
718         g_hash_table_destroy (priv->members);
719         g_hash_table_destroy (priv->pendings);
720         g_hash_table_destroy (priv->add_to_group);
721
722         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
723 }
724
725 static gboolean
726 received_all_list_channels (EmpathyTpContactList *self)
727 {
728         EmpathyTpContactListPriv *priv = GET_PRIV (self);
729
730         return (priv->stored != NULL && priv->publish != NULL &&
731                 priv->subscribe != NULL);
732 }
733
734 static void
735 got_list_channel (EmpathyTpContactList *list,
736                   TpChannel *channel)
737 {
738         EmpathyTpContactListPriv *priv = GET_PRIV (list);
739         const gchar *id;
740
741         /* We requested that channel by providing TargetID property, so it's
742          * guaranteed that tp_channel_get_identifier will return it. */
743         id = tp_channel_get_identifier (channel);
744
745         /* TpChannel emits initial set of members just before being ready */
746         if (!tp_strdiff (id, "stored")) {
747                 if (priv->stored != NULL)
748                         return;
749                 priv->stored = g_object_ref (channel);
750         } else if (!tp_strdiff (id, "publish")) {
751                 if (priv->publish != NULL)
752                         return;
753                 priv->publish = g_object_ref (channel);
754                 g_signal_connect (priv->publish, "group-members-changed",
755                                   G_CALLBACK (tp_contact_list_publish_group_members_changed_cb),
756                                   list);
757         } else if (!tp_strdiff (id, "subscribe")) {
758                 if (priv->subscribe != NULL)
759                         return;
760                 priv->subscribe = g_object_ref (channel);
761                 g_signal_connect (priv->subscribe, "group-members-changed",
762                                   G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb),
763                                   list);
764         }
765
766         if (received_all_list_channels (list) && priv->new_channels_sig != NULL) {
767                 /* We don't need to watch NewChannels anymore */
768                 tp_proxy_signal_connection_disconnect (priv->new_channels_sig);
769                 priv->new_channels_sig = NULL;
770         }
771 }
772
773 static void
774 list_ensure_channel_cb (TpConnection *conn,
775                         gboolean yours,
776                         const gchar *path,
777                         GHashTable *properties,
778                         const GError *error,
779                         gpointer user_data,
780                         GObject *weak_object)
781 {
782         EmpathyTpContactList *list = user_data;
783         TpChannel *channel;
784
785         if (error != NULL) {
786                 DEBUG ("failed: %s\n", error->message);
787                 return;
788         }
789
790         channel = tp_channel_new_from_properties (conn, path, properties, NULL);
791         got_list_channel (list, channel);
792         g_object_unref (channel);
793 }
794
795 static void
796 new_channels_cb (TpConnection *conn,
797                  const GPtrArray *channels,
798                  gpointer user_data,
799                  GObject *weak_object)
800 {
801         EmpathyTpContactList *list = EMPATHY_TP_CONTACT_LIST (weak_object);
802         guint i;
803
804         for (i = 0; i < channels->len ; i++) {
805                 GValueArray *arr = g_ptr_array_index (channels, i);
806                 const gchar *path;
807                 GHashTable *properties;
808                 const gchar *id;
809                 TpChannel *channel;
810
811                 path = g_value_get_boxed (g_value_array_get_nth (arr, 0));
812                 properties = g_value_get_boxed (g_value_array_get_nth (arr, 1));
813
814                 if (tp_strdiff (tp_asv_get_string (properties,
815                                 TP_IFACE_CHANNEL ".ChannelType"),
816                     TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
817                         return;
818
819                 if (tp_asv_get_uint32 (properties,
820                                        TP_IFACE_CHANNEL ".TargetHandleType", NULL)
821                     != TP_HANDLE_TYPE_LIST)
822                         return;
823
824                 id = tp_asv_get_string (properties,
825                                         TP_IFACE_CHANNEL ".TargetID");
826                 if (id == NULL)
827                         return;
828
829                 channel = tp_channel_new_from_properties (conn, path,
830                                                           properties, NULL);
831                 got_list_channel (list, channel);
832                 g_object_unref (channel);
833         }
834 }
835
836 static void
837 conn_ready_cb (TpConnection *connection,
838                const GError *error,
839                gpointer data)
840 {
841         EmpathyTpContactList *list = data;
842         EmpathyTpContactListPriv *priv = GET_PRIV (list);
843         GHashTable *request;
844
845         if (error != NULL) {
846                 DEBUG ("failed: %s", error->message);
847                 goto out;
848         }
849
850         request = tp_asv_new (
851                 TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
852                 TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
853                 NULL);
854
855         /* Watch the NewChannels signal so if ensuring list channels fails (for
856          * example because the server is slow and the D-Bus call timeouts before CM
857          * fetches the roster), we have a chance to get them later. */
858         priv->new_channels_sig =
859           tp_cli_connection_interface_requests_connect_to_new_channels (
860                 priv->connection, new_channels_cb, NULL, NULL, G_OBJECT (list), NULL);
861
862         /* Request the 'stored' list. */
863         tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "stored");
864         tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
865                 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
866
867         /* Request the 'publish' list. */
868         tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "publish");
869         tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
870                 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
871
872         /* Request the 'subscribe' list. */
873         tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "subscribe");
874         tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
875                 -1, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
876
877         g_hash_table_unref (request);
878 out:
879         g_object_unref (list);
880 }
881
882 static void
883 tp_contact_list_constructed (GObject *list)
884 {
885         EmpathyTpContactListPriv *priv = GET_PRIV (list);
886
887         priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection);
888
889         /* call GetAliasFlags */
890         if (tp_proxy_has_interface_by_id (priv->connection,
891                                 TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)) {
892                 tp_cli_connection_interface_aliasing_call_get_alias_flags (
893                                 priv->connection,
894                                 -1,
895                                 tp_contact_list_get_alias_flags_cb,
896                                 NULL, NULL,
897                                 G_OBJECT (list));
898         }
899
900         /* lookup RequestableChannelClasses */
901         if (tp_proxy_has_interface_by_id (priv->connection,
902                                 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)) {
903                 tp_cli_dbus_properties_call_get (priv->connection,
904                                 -1,
905                                 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
906                                 "RequestableChannelClasses",
907                                 tp_contact_list_get_requestablechannelclasses_cb,
908                                 NULL, NULL,
909                                 G_OBJECT (list));
910         } else {
911                 /* we just don't know... better mark the flag just in case */
912                 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
913         }
914
915         tp_connection_call_when_ready (priv->connection, conn_ready_cb,
916                 g_object_ref (list));
917
918         tp_cli_connection_call_list_channels (priv->connection, -1,
919                                               tp_contact_list_list_channels_cb,
920                                               NULL, NULL,
921                                               list);
922
923         tp_cli_connection_connect_to_new_channel (priv->connection,
924                                                   tp_contact_list_new_channel_cb,
925                                                   NULL, NULL,
926                                                   list, NULL);
927 }
928
929 static void
930 tp_contact_list_get_property (GObject    *object,
931                               guint       param_id,
932                               GValue     *value,
933                               GParamSpec *pspec)
934 {
935         EmpathyTpContactListPriv *priv = GET_PRIV (object);
936
937         switch (param_id) {
938         case PROP_CONNECTION:
939                 g_value_set_object (value, priv->connection);
940                 break;
941         default:
942                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
943                 break;
944         };
945 }
946
947 static void
948 tp_contact_list_set_property (GObject      *object,
949                               guint         param_id,
950                               const GValue *value,
951                               GParamSpec   *pspec)
952 {
953         EmpathyTpContactListPriv *priv = GET_PRIV (object);
954
955         switch (param_id) {
956         case PROP_CONNECTION:
957                 priv->connection = g_value_dup_object (value);
958                 break;
959         default:
960                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
961                 break;
962         };
963 }
964
965 static void
966 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
967 {
968         GObjectClass *object_class = G_OBJECT_CLASS (klass);
969
970         object_class->finalize = tp_contact_list_finalize;
971         object_class->constructed = tp_contact_list_constructed;
972         object_class->get_property = tp_contact_list_get_property;
973         object_class->set_property = tp_contact_list_set_property;
974
975         g_object_class_install_property (object_class,
976                                          PROP_CONNECTION,
977                                          g_param_spec_object ("connection",
978                                                               "The Connection",
979                                                               "The connection associated with the contact list",
980                                                               TP_TYPE_CONNECTION,
981                                                               G_PARAM_READWRITE |
982                                                               G_PARAM_CONSTRUCT_ONLY));
983
984         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
985 }
986
987 static void
988 tp_contact_list_array_free (gpointer handles)
989 {
990         g_array_free (handles, TRUE);
991 }
992
993 static void
994 empathy_tp_contact_list_init (EmpathyTpContactList *list)
995 {
996         EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
997                 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
998
999         list->priv = priv;
1000
1001         /* Map group's name to group's TpChannel. The group name string is owned
1002          * by the TpChannel object */
1003         priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1004                                               NULL,
1005                                               (GDestroyNotify) g_object_unref);
1006
1007         /* Map contact's handle to EmpathyContact object */
1008         priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal,
1009                                                NULL,
1010                                                (GDestroyNotify) g_object_unref);
1011
1012         /* Map contact's handle to EmpathyContact object */
1013         priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
1014                                                 NULL,
1015                                                 (GDestroyNotify) g_object_unref);
1016
1017         /* Map group's name to GArray of handle */
1018         priv->add_to_group = g_hash_table_new_full (g_str_hash, g_str_equal,
1019                                                     g_free,
1020                                                     tp_contact_list_array_free);
1021 }
1022
1023 EmpathyTpContactList *
1024 empathy_tp_contact_list_new (TpConnection *connection)
1025 {
1026         return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
1027                              "connection", connection,
1028                              NULL);
1029 }
1030
1031 TpConnection *
1032 empathy_tp_contact_list_get_connection (EmpathyTpContactList *list)
1033 {
1034         EmpathyTpContactListPriv *priv;
1035
1036         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
1037
1038         priv = GET_PRIV (list);
1039
1040         return priv->connection;
1041 }
1042
1043 static void
1044 tp_contact_list_add (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         if (priv->subscribe) {
1054                 tp_cli_channel_interface_group_call_add_members (priv->subscribe,
1055                         -1, &handles, message, NULL, NULL, NULL, NULL);
1056         }
1057         if (priv->publish) {
1058                 TpChannelGroupFlags flags = tp_channel_group_get_flags (priv->subscribe);
1059                 if (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD ||
1060                     g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
1061                         tp_cli_channel_interface_group_call_add_members (priv->publish,
1062                                 -1, &handles, message, NULL, NULL, NULL, NULL);
1063                 }
1064         }
1065 }
1066
1067 static void
1068 tp_contact_list_remove (EmpathyContactList *list,
1069                         EmpathyContact     *contact,
1070                         const gchar        *message)
1071 {
1072         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1073         TpHandle handle;
1074         GArray handles = {(gchar *) &handle, 1};
1075
1076         handle = empathy_contact_get_handle (contact);
1077
1078         /* FIXME: this is racy if tp_contact_list_remove is called before the
1079          * 'stored' list has been retrieved. */
1080         if (priv->stored != NULL) {
1081                 tp_cli_channel_interface_group_call_remove_members (priv->stored,
1082                         -1, &handles, message, NULL, NULL, NULL, NULL);
1083         }
1084
1085         if (priv->subscribe) {
1086                 tp_cli_channel_interface_group_call_remove_members (priv->subscribe,
1087                         -1, &handles, message, NULL, NULL, NULL, NULL);
1088         }
1089         if (priv->publish) {
1090                 tp_cli_channel_interface_group_call_remove_members (priv->publish,
1091                         -1, &handles, message, NULL, NULL, NULL, NULL);
1092         }
1093 }
1094
1095 static GList *
1096 tp_contact_list_get_members (EmpathyContactList *list)
1097 {
1098         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1099         GList *ret;
1100
1101         ret = g_hash_table_get_values (priv->members);
1102         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1103         return ret;
1104 }
1105
1106 static GList *
1107 tp_contact_list_get_pendings (EmpathyContactList *list)
1108 {
1109         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1110         GList *ret;
1111
1112         ret = g_hash_table_get_values (priv->pendings);
1113         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1114         return ret;
1115 }
1116
1117 static GList *
1118 tp_contact_list_get_all_groups (EmpathyContactList *list)
1119 {
1120         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1121         GList                    *ret, *l;
1122
1123         ret = g_hash_table_get_keys (priv->groups);
1124         for (l = ret; l; l = l->next) {
1125                 l->data = g_strdup (l->data);
1126         }
1127
1128         return ret;
1129 }
1130
1131 static GList *
1132 tp_contact_list_get_groups (EmpathyContactList *list,
1133                             EmpathyContact     *contact)
1134 {
1135         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
1136         GList                     *ret = NULL;
1137         GHashTableIter             iter;
1138         gpointer                   group_name;
1139         gpointer                   channel;
1140         TpHandle                   handle;
1141
1142         handle = empathy_contact_get_handle (contact);
1143         g_hash_table_iter_init (&iter, priv->groups);
1144         while (g_hash_table_iter_next (&iter, &group_name, &channel)) {
1145                 const TpIntSet *members;
1146
1147                 members = tp_channel_group_get_members (channel);
1148                 if (tp_intset_is_member (members, handle)) {
1149                         ret = g_list_prepend (ret, g_strdup (group_name));
1150                 }
1151         }
1152
1153         return ret;
1154 }
1155
1156 static void
1157 tp_contact_list_add_to_group (EmpathyContactList *list,
1158                               EmpathyContact     *contact,
1159                               const gchar        *group_name)
1160 {
1161         TpHandle handle;
1162         GArray *handles;
1163
1164         handle = empathy_contact_get_handle (contact);
1165         handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1166         g_array_append_val (handles, handle);
1167         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1168                                    group_name, handles);
1169 }
1170
1171 static void
1172 tp_contact_list_remove_from_group (EmpathyContactList *list,
1173                                    EmpathyContact     *contact,
1174                                    const gchar        *group_name)
1175 {
1176         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1177         TpChannel                *channel;
1178         TpHandle                  handle;
1179         GArray                    handles = {(gchar *) &handle, 1};
1180
1181         channel = g_hash_table_lookup (priv->groups, group_name);
1182         if (channel == NULL) {
1183                 return;
1184         }
1185
1186         handle = empathy_contact_get_handle (contact);
1187         DEBUG ("remove contact %s (%d) from group %s",
1188                 empathy_contact_get_id (contact), handle, group_name);
1189
1190         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1191                 &handles, NULL, NULL, NULL, NULL, NULL);
1192 }
1193
1194 static void
1195 tp_contact_list_rename_group (EmpathyContactList *list,
1196                               const gchar        *old_group_name,
1197                               const gchar        *new_group_name)
1198 {
1199         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1200         TpChannel                *channel;
1201         const TpIntSet           *members;
1202         GArray                   *handles;
1203
1204         channel = g_hash_table_lookup (priv->groups, old_group_name);
1205         if (channel == NULL) {
1206                 return;
1207         }
1208
1209         DEBUG ("rename group %s to %s", old_group_name, new_group_name);
1210
1211         /* Remove all members and close the old channel */
1212         members = tp_channel_group_get_members (channel);
1213         handles = tp_intset_to_array (members);
1214         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1215                 handles, NULL, NULL, NULL, NULL, NULL);
1216         tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1217
1218         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1219                                    new_group_name, handles);
1220 }
1221
1222 static void
1223 tp_contact_list_remove_group (EmpathyContactList *list,
1224                               const gchar *group_name)
1225 {
1226         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1227         TpChannel                *channel;
1228         const TpIntSet           *members;
1229         GArray                   *handles;
1230
1231         channel = g_hash_table_lookup (priv->groups, group_name);
1232         if (channel == NULL) {
1233                 return;
1234         }
1235
1236         DEBUG ("remove group %s", group_name);
1237
1238         /* Remove all members and close the channel */
1239         members = tp_channel_group_get_members (channel);
1240         handles = tp_intset_to_array (members);
1241         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1242                 handles, NULL, NULL, NULL, NULL, NULL);
1243         tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1244         g_array_free (handles, TRUE);
1245 }
1246
1247 static EmpathyContactListFlags
1248 tp_contact_list_get_flags (EmpathyContactList *list)
1249 {
1250         EmpathyTpContactListPriv *priv;
1251         EmpathyContactListFlags flags;
1252
1253         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1254
1255         priv = GET_PRIV (list);
1256         flags = priv->flags;
1257
1258         if (priv->subscribe != NULL) {
1259                 TpChannelGroupFlags group_flags;
1260
1261                 group_flags = tp_channel_group_get_flags (priv->subscribe);
1262
1263                 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) {
1264                         flags |= EMPATHY_CONTACT_LIST_CAN_ADD;
1265                 }
1266
1267                 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) {
1268                         flags |= EMPATHY_CONTACT_LIST_CAN_REMOVE;
1269                 }
1270         }
1271
1272         return flags;
1273 }
1274
1275 static void
1276 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1277 {
1278         iface->add               = tp_contact_list_add;
1279         iface->remove            = tp_contact_list_remove;
1280         iface->get_members       = tp_contact_list_get_members;
1281         iface->get_pendings      = tp_contact_list_get_pendings;
1282         iface->get_all_groups    = tp_contact_list_get_all_groups;
1283         iface->get_groups        = tp_contact_list_get_groups;
1284         iface->add_to_group      = tp_contact_list_add_to_group;
1285         iface->remove_from_group = tp_contact_list_remove_from_group;
1286         iface->rename_group      = tp_contact_list_rename_group;
1287         iface->remove_group      = tp_contact_list_remove_group;
1288         iface->get_flags         = tp_contact_list_get_flags;
1289 }
1290
1291 void
1292 empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
1293 {
1294         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1295         GHashTableIter            iter;
1296         gpointer                  contact;
1297
1298         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1299
1300         /* Remove all contacts */
1301         g_hash_table_iter_init (&iter, priv->members);
1302         while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1303                 g_signal_emit_by_name (list, "members-changed", contact,
1304                                        NULL, 0, NULL,
1305                                        FALSE);
1306         }
1307         g_hash_table_remove_all (priv->members);
1308
1309         g_hash_table_iter_init (&iter, priv->pendings);
1310         while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1311                 g_signal_emit_by_name (list, "pendings-changed", contact,
1312                                        NULL, 0, NULL,
1313                                        FALSE);
1314         }
1315         g_hash_table_remove_all (priv->pendings);
1316 }
1317