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