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