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