]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
Make use of tp_strdiff() to be NULL-safe. Fixes bug #509656.
[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 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.h>
27
28 #include <libtelepathy/tp-helpers.h>
29 #include <libtelepathy/tp-conn.h>
30 #include <libtelepathy/tp-chan.h>
31 #include <libtelepathy/tp-chan-type-contact-list-gen.h>
32
33 #include "empathy-tp-contact-list.h"
34 #include "empathy-contact-list.h"
35 #include "empathy-tp-group.h"
36 #include "empathy-debug.h"
37 #include "empathy-utils.h"
38
39 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
40                        EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv))
41
42 #define DEBUG_DOMAIN "TpContactList"
43
44 struct _EmpathyTpContactListPriv {
45         TpConn         *tp_conn;
46         McAccount      *account;
47         MissionControl *mc;
48         const gchar    *protocol_group;
49
50         EmpathyTpGroup *publish;
51         EmpathyTpGroup *subscribe;
52         GList          *members;
53         GList          *pendings;
54
55         GList          *groups;
56         GHashTable     *contacts_groups;
57 };
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 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass);
66 static void empathy_tp_contact_list_init       (EmpathyTpContactList      *list);
67 static void tp_contact_list_iface_init         (EmpathyContactListIface   *iface);
68
69 enum {
70         DESTROY,
71         LAST_SIGNAL
72 };
73
74 static guint signals[LAST_SIGNAL];
75
76 G_DEFINE_TYPE_WITH_CODE (EmpathyTpContactList, empathy_tp_contact_list, G_TYPE_OBJECT,
77                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
78                                                 tp_contact_list_iface_init));
79
80 static void
81 tp_contact_list_group_destroy_cb (EmpathyTpGroup       *group,
82                                   EmpathyTpContactList *list)
83 {
84         EmpathyTpContactListPriv *priv = GET_PRIV (list);
85
86         empathy_debug (DEBUG_DOMAIN, "Group destroyed: %s",
87                        empathy_tp_group_get_name (group));
88
89         priv->groups = g_list_remove (priv->groups, group);
90         g_object_unref (group);
91 }
92
93 static void
94 tp_contact_list_group_member_added_cb (EmpathyTpGroup       *group,
95                                        EmpathyContact       *contact,
96                                        EmpathyContact       *actor,
97                                        guint                 reason,
98                                        const gchar          *message,
99                                        EmpathyTpContactList *list)
100 {
101         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
102         const gchar               *group_name;
103         GList                    **groups;
104
105         if (!g_list_find (priv->members, contact)) {
106                 return;
107         }
108
109         groups = g_hash_table_lookup (priv->contacts_groups, contact);
110         if (!groups) {
111                 groups = g_slice_new0 (GList*);
112                 g_hash_table_insert (priv->contacts_groups,
113                                      g_object_ref (contact),
114                                      groups);
115         }
116
117         group_name = empathy_tp_group_get_name (group);
118         if (!g_list_find_custom (*groups, group_name, (GCompareFunc) strcmp)) {
119                 empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) added to group %s",
120                                empathy_contact_get_id (contact),
121                                empathy_contact_get_handle (contact),
122                                group_name);
123                 *groups = g_list_prepend (*groups, g_strdup (group_name));
124                 g_signal_emit_by_name (list, "groups-changed", contact,
125                                        group_name,
126                                        TRUE);
127         }
128 }
129
130 static void
131 tp_contact_list_group_member_removed_cb (EmpathyTpGroup       *group,
132                                          EmpathyContact       *contact,
133                                          EmpathyContact       *actor,
134                                          guint                 reason,
135                                          const gchar          *message,
136                                          EmpathyTpContactList *list)
137 {
138         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
139         const gchar               *group_name;
140         GList                    **groups, *l;
141
142         if (!g_list_find (priv->members, contact)) {
143                 return;
144         }
145
146         groups = g_hash_table_lookup (priv->contacts_groups, contact);
147         if (!groups) {
148                 return;
149         }
150
151         group_name = empathy_tp_group_get_name (group);
152         if ((l = g_list_find_custom (*groups, group_name, (GCompareFunc) strcmp))) {
153                 empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) removed from group %s",
154                                empathy_contact_get_id (contact),
155                                empathy_contact_get_handle (contact),
156                                group_name);
157                 *groups = g_list_delete_link (*groups, l);
158                 g_signal_emit_by_name (list, "groups-changed", contact,
159                                        group_name,
160                                        FALSE);
161         }
162 }
163
164 static EmpathyTpGroup *
165 tp_contact_list_find_group (EmpathyTpContactList *list,
166                             const gchar          *group)
167 {
168         EmpathyTpContactListPriv *priv = GET_PRIV (list);
169         GList                    *l;
170
171         for (l = priv->groups; l; l = l->next) {
172                 if (!tp_strdiff (group, empathy_tp_group_get_name (l->data))) {
173                         return l->data;
174                 }
175         }
176         return NULL;
177 }
178
179 static TpContactListType
180 tp_contact_list_get_type (EmpathyTpContactList *list,
181                           EmpathyTpGroup       *group)
182 {
183         const gchar *name;
184
185         name = empathy_tp_group_get_name (group);
186         if (!tp_strdiff (name, "subscribe")) {
187                 return TP_CONTACT_LIST_TYPE_SUBSCRIBE;
188         } else if (!tp_strdiff (name, "publish")) {
189                 return TP_CONTACT_LIST_TYPE_PUBLISH;
190         }
191
192         return TP_CONTACT_LIST_TYPE_UNKNOWN;
193 }
194
195 static void
196 tp_contact_list_add_member (EmpathyTpContactList *list,
197                             EmpathyContact       *contact,
198                             EmpathyContact       *actor,
199                             guint                 reason,
200                             const gchar          *message)
201 {
202         EmpathyTpContactListPriv *priv = GET_PRIV (list);
203         GList                    *l;
204
205         /* Add to the list and emit signal */
206         priv->members = g_list_prepend (priv->members, g_object_ref (contact));
207         g_signal_emit_by_name (list, "members-changed",
208                                contact, actor, reason, message,
209                                TRUE);
210
211         /* This contact is now member, implicitly accept pending. */
212         if (g_list_find (priv->pendings, contact)) {
213                 empathy_tp_group_add_member (priv->publish, contact, "");
214         }
215
216         /* Update groups of the contact */
217         for (l = priv->groups; l; l = l->next) {
218                 if (empathy_tp_group_is_member (l->data, contact)) {
219                         tp_contact_list_group_member_added_cb (l->data, contact,
220                                                                NULL, 0, NULL, 
221                                                                list);
222                 }
223         }
224 }
225
226 static void
227 tp_contact_list_added_cb (EmpathyTpGroup       *group,
228                           EmpathyContact       *contact,
229                           EmpathyContact       *actor,
230                           guint                 reason,
231                           const gchar          *message,
232                           EmpathyTpContactList *list)
233 {
234         EmpathyTpContactListPriv *priv = GET_PRIV (list);
235         TpContactListType         list_type;
236
237         list_type = tp_contact_list_get_type (list, group);
238         empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) added to list type %d",
239                       empathy_contact_get_id (contact),
240                       empathy_contact_get_handle (contact),
241                       list_type);
242
243         /* We now get the presence of that contact, add it to members */
244         if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE &&
245             !g_list_find (priv->members, contact)) {
246                 tp_contact_list_add_member (list, contact, actor, reason, message);
247         }
248
249         /* We now send our presence to that contact, remove it from pendings */
250         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH &&
251             g_list_find (priv->pendings, contact)) {
252                 g_signal_emit_by_name (list, "pendings-changed",
253                                        contact, actor, reason, message,
254                                        FALSE);
255                 priv->pendings = g_list_remove (priv->pendings, contact);
256                 g_object_unref (contact);
257         }
258 }
259
260 static void
261 tp_contact_list_removed_cb (EmpathyTpGroup       *group,
262                             EmpathyContact       *contact,
263                             EmpathyContact       *actor,
264                             guint                 reason,
265                             const gchar          *message,
266                             EmpathyTpContactList *list)
267 {
268         EmpathyTpContactListPriv *priv = GET_PRIV (list);
269         TpContactListType         list_type;
270
271         list_type = tp_contact_list_get_type (list, group);
272         empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) removed from list type %d",
273                       empathy_contact_get_id (contact),
274                       empathy_contact_get_handle (contact),
275                       list_type);
276
277         /* This contact refuses to send us his presence, remove from members. */
278         if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE &&
279             g_list_find (priv->members, contact)) {
280                 g_signal_emit_by_name (list, "members-changed",
281                                        contact, actor, reason, message,
282                                        FALSE);
283                 priv->members = g_list_remove (priv->members, contact);
284                 g_object_unref (contact);
285         }
286
287         /* We refuse to send our presence to that contact, remove from pendings */
288         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH &&
289             g_list_find (priv->pendings, contact)) {
290                 g_signal_emit_by_name (list, "pendings-changed",
291                                        contact, actor, reason, message,
292                                        FALSE);
293                 priv->pendings = g_list_remove (priv->pendings, contact);
294                 g_object_unref (contact);
295         }
296 }
297
298 static void
299 tp_contact_list_pending_cb (EmpathyTpGroup       *group,
300                             EmpathyContact       *contact,
301                             EmpathyContact       *actor,
302                             guint                 reason,
303                             const gchar          *message,
304                             EmpathyTpContactList *list)
305 {
306         EmpathyTpContactListPriv *priv = GET_PRIV (list);
307         TpContactListType         list_type;
308
309         list_type = tp_contact_list_get_type (list, group);
310         empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) pending in list type %d",
311                       empathy_contact_get_id (contact),
312                       empathy_contact_get_handle (contact),
313                       list_type);
314
315         /* We want this contact in our contact list but we don't get its 
316          * presence yet. Add to members anyway. */
317         if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE &&
318             !g_list_find (priv->members, contact)) {
319                 tp_contact_list_add_member (list, contact, actor, reason, message);
320         }
321
322         /* This contact wants our presence, auto accept if he is member,
323          * otherwise he is pending. */
324         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH &&
325             !g_list_find (priv->pendings, contact)) {
326                 if (g_list_find (priv->members, contact)) {
327                         empathy_tp_group_add_member (priv->publish, contact, "");
328                 } else {
329                         priv->pendings = g_list_prepend (priv->pendings,
330                                                          g_object_ref (contact));
331                         g_signal_emit_by_name (list, "pendings-changed",
332                                                contact, actor, reason, message,
333                                                TRUE);
334                 }
335         }
336 }
337
338 static void
339 tp_contact_list_newchannel_cb (DBusGProxy           *proxy,
340                                const gchar          *object_path,
341                                const gchar          *channel_type,
342                                TpHandleType          handle_type,
343                                guint                 channel_handle,
344                                gboolean              suppress_handler,
345                                EmpathyTpContactList *list)
346 {
347         EmpathyTpContactListPriv *priv = GET_PRIV (list);
348         EmpathyTpGroup           *group;
349         TpChan                   *new_chan;
350         const gchar              *bus_name;
351
352         if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 ||
353             suppress_handler) {
354                 return;
355         }
356
357         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn));
358         new_chan = tp_chan_new (tp_get_bus (),
359                                 bus_name,
360                                 object_path,
361                                 channel_type,
362                                 handle_type,
363                                 channel_handle);
364         g_return_if_fail (TELEPATHY_IS_CHAN (new_chan));
365
366         group = empathy_tp_group_new (priv->account, new_chan);
367         g_object_unref (new_chan);
368
369         if (handle_type == TP_HANDLE_TYPE_LIST) {
370                 TpContactListType  list_type;
371                 GList             *contacts, *l;
372
373                 list_type = tp_contact_list_get_type (list, group);
374                 if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && !priv->publish) {
375                         priv->publish = group;
376
377                         /* Publish is the list of contacts to who we send our
378                          * presence. Makes no sense to be in remote-pending */
379                         g_signal_connect (group, "local-pending",
380                                           G_CALLBACK (tp_contact_list_pending_cb),
381                                           list);
382
383                         contacts = empathy_tp_group_get_local_pendings (group);
384                         for (l = contacts; l; l = l->next) {
385                                 EmpathyPendingInfo *info = l->data;
386
387                                 tp_contact_list_pending_cb (group,
388                                                             info->member,
389                                                             info->actor,
390                                                             0,
391                                                             info->message,
392                                                             list);
393                                 empathy_pending_info_free (info);
394                         }
395                         g_list_free (contacts);
396                 }
397                 else if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && !priv->subscribe) {
398                         priv->subscribe = group;
399
400                         /* Subscribe is the list of contacts from who we
401                          * receive presence. Makes no sense to be in
402                          * local-pending */
403                         g_signal_connect (group, "remote-pending",
404                                           G_CALLBACK (tp_contact_list_pending_cb),
405                                           list);
406
407                         contacts = empathy_tp_group_get_remote_pendings (group);
408                         for (l = contacts; l; l = l->next) {
409                                 tp_contact_list_pending_cb (group,
410                                                             l->data,
411                                                             NULL, 0,
412                                                             NULL, list);
413                                 g_object_unref (l->data);
414                         }
415                         g_list_free (contacts);
416                 } else {
417                         empathy_debug (DEBUG_DOMAIN,
418                                       "Type of contact list channel unknown "
419                                       "or aleady have that list: %s",
420                                       empathy_tp_group_get_name (group));
421                         g_object_unref (group);
422                         return;
423                 }
424                 empathy_debug (DEBUG_DOMAIN,
425                                "New contact list channel of type: %d",
426                                list_type);
427
428                 g_signal_connect (group, "member-added",
429                                   G_CALLBACK (tp_contact_list_added_cb),
430                                   list);
431                 g_signal_connect (group, "member-removed",
432                                   G_CALLBACK (tp_contact_list_removed_cb),
433                                   list);
434
435                 contacts = empathy_tp_group_get_members (group);
436                 for (l = contacts; l; l = l->next) {
437                         tp_contact_list_added_cb (group,
438                                                   l->data,
439                                                   NULL, 0, NULL,
440                                                   list);
441                         g_object_unref (l->data);
442                 }
443                 g_list_free (contacts);
444         }
445         else if (handle_type == TP_HANDLE_TYPE_GROUP) {
446                 const gchar *group_name;
447                 GList       *contacts, *l;
448
449                 /* Check if already exists */
450                 group_name = empathy_tp_group_get_name (group);
451                 if (tp_contact_list_find_group (list, group_name)) {
452                         g_object_unref (group);
453                         return;
454                 }
455
456                 empathy_debug (DEBUG_DOMAIN, "New server-side group channel: %s",
457                                group_name);
458
459                 priv->groups = g_list_prepend (priv->groups, group);
460
461                 g_signal_connect (group, "member-added",
462                                   G_CALLBACK (tp_contact_list_group_member_added_cb),
463                                   list);
464                 g_signal_connect (group, "member-removed",
465                                   G_CALLBACK (tp_contact_list_group_member_removed_cb),
466                                   list);
467                 g_signal_connect (group, "destroy",
468                                   G_CALLBACK (tp_contact_list_group_destroy_cb),
469                                   list);
470
471                 contacts = empathy_tp_group_get_members (group);
472                 for (l = contacts; l; l = l->next) {
473                         tp_contact_list_group_member_added_cb (group, l->data,
474                                                                NULL, 0, NULL,
475                                                                list);
476                         g_object_unref (l->data);
477                 }
478                 g_list_free (contacts);
479         } else {
480                 empathy_debug (DEBUG_DOMAIN,
481                                "Unknown handle type (%d) for contact list channel",
482                                handle_type);
483                 g_object_unref (group);
484         }
485 }
486
487 static void
488 tp_contact_list_destroy_cb (TpConn               *tp_conn,
489                             EmpathyTpContactList *list)
490 {
491         EmpathyTpContactListPriv *priv = GET_PRIV (list);
492         GList                    *l;
493
494         empathy_debug (DEBUG_DOMAIN, "Account disconnected or CM crashed");
495
496         /* DBus proxie should NOT be used anymore */
497         g_object_unref (priv->tp_conn);
498         priv->tp_conn = NULL;
499
500         /* Remove all contacts */
501         for (l = priv->members; l; l = l->next) {
502                 g_signal_emit_by_name (list, "members-changed", l->data,
503                                        NULL, 0, NULL,
504                                        FALSE);
505                 g_object_unref (l->data);
506         }
507         for (l = priv->pendings; l; l = l->next) {
508                 g_signal_emit_by_name (list, "pendings-changed", l->data,
509                                        NULL, 0, NULL,
510                                        FALSE);
511                 g_object_unref (l->data);
512         }
513         g_list_free (priv->members);
514         g_list_free (priv->pendings);
515         priv->members = NULL;
516         priv->pendings = NULL;
517
518         /* Tell the world to not use us anymore */
519         g_signal_emit (list, signals[DESTROY], 0);
520 }
521
522 static void
523 tp_contact_list_disconnect (EmpathyTpContactList *list)
524 {
525         EmpathyTpContactListPriv *priv = GET_PRIV (list);
526
527         if (priv->tp_conn) {
528                 g_signal_handlers_disconnect_by_func (priv->tp_conn,
529                                                       tp_contact_list_destroy_cb,
530                                                       list);
531                 dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
532                                                 G_CALLBACK (tp_contact_list_newchannel_cb),
533                                                 list);
534         }
535 }
536
537 static void
538 tp_contact_list_status_changed_cb (MissionControl           *mc,
539                                    TpConnectionStatus        status,
540                                    McPresence                presence,
541                                    TpConnectionStatusReason  reason,
542                                    const gchar              *unique_name,
543                                    EmpathyTpContactList     *list)
544 {
545         EmpathyTpContactListPriv *priv = GET_PRIV (list);
546         McAccount                *account;
547
548         account = mc_account_lookup (unique_name);
549         if (status != TP_CONNECTION_STATUS_CONNECTED &&
550             empathy_account_equal (account, priv->account)) {
551                 /* We are disconnected */
552                 tp_contact_list_disconnect (list);
553                 tp_contact_list_destroy_cb (priv->tp_conn, list);
554         }
555
556         g_object_unref (account);
557 }
558
559 static void
560 tp_contact_list_group_list_free (GList **groups)
561 {
562         g_list_foreach (*groups, (GFunc) g_free, NULL);
563         g_list_free (*groups);
564         g_slice_free (GList*, groups);
565 }
566
567 static void
568 tp_contact_list_finalize (GObject *object)
569 {
570         EmpathyTpContactListPriv *priv;
571         EmpathyTpContactList     *list;
572
573         list = EMPATHY_TP_CONTACT_LIST (object);
574         priv = GET_PRIV (list);
575
576         empathy_debug (DEBUG_DOMAIN, "finalize: %p", object);
577
578         tp_contact_list_disconnect (list);
579
580         if (priv->mc) {
581                 dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
582                                                 "AccountStatusChanged",
583                                                 G_CALLBACK (tp_contact_list_status_changed_cb),
584                                                 list);
585                 g_object_unref (priv->mc);
586         }
587
588         if (priv->subscribe) {
589                 g_object_unref (priv->subscribe);
590         }
591         if (priv->publish) {
592                 g_object_unref (priv->publish);
593         }
594         if (priv->account) {
595                 g_object_unref (priv->account);
596         }
597         if (priv->tp_conn) {
598                 g_object_unref (priv->tp_conn);
599         }
600
601         g_hash_table_destroy (priv->contacts_groups);
602         g_list_foreach (priv->groups, (GFunc) g_object_unref, NULL);
603         g_list_free (priv->groups);
604         g_list_foreach (priv->members, (GFunc) g_object_unref, NULL);
605         g_list_free (priv->members);
606         g_list_foreach (priv->pendings, (GFunc) g_object_unref, NULL);
607         g_list_free (priv->pendings);
608
609         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
610 }
611
612 static void
613 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
614 {
615         GObjectClass *object_class = G_OBJECT_CLASS (klass);
616
617         object_class->finalize = tp_contact_list_finalize;
618
619         signals[DESTROY] =
620                 g_signal_new ("destroy",
621                               G_TYPE_FROM_CLASS (klass),
622                               G_SIGNAL_RUN_LAST,
623                               0,
624                               NULL, NULL,
625                               g_cclosure_marshal_VOID__VOID,
626                               G_TYPE_NONE,
627                               0);
628
629         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
630 }
631
632 static void
633 empathy_tp_contact_list_init (EmpathyTpContactList *list)
634 {
635 }
636
637 static void
638 tp_contact_list_setup (EmpathyTpContactList *list)
639 {
640         EmpathyTpContactListPriv *priv = GET_PRIV (list);
641         GPtrArray                *channels;
642         guint                     i;
643         GError                   *error = NULL;
644
645         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
646
647         /* Get existing channels */
648         if (!tp_conn_list_channels (DBUS_G_PROXY (priv->tp_conn),
649                                     &channels,
650                                     &error)) {
651                 empathy_debug (DEBUG_DOMAIN,
652                               "Failed to get list of open channels: %s",
653                               error ? error->message : "No error given");
654                 g_clear_error (&error);
655                 return;
656         }
657
658         for (i = 0; i < channels->len; i++) {
659                 GValueArray  *chan_struct;
660                 const gchar  *object_path;
661                 const gchar  *chan_iface;
662                 TpHandleType  handle_type;
663                 guint         handle;
664
665                 chan_struct = g_ptr_array_index (channels, i);
666                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
667                 chan_iface = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
668                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
669                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
670
671                 tp_contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
672                                                object_path, chan_iface,
673                                                handle_type, handle,
674                                                FALSE,
675                                                list);
676
677                 g_value_array_free (chan_struct);
678         }
679         g_ptr_array_free (channels, TRUE);
680 }
681
682 EmpathyTpContactList *
683 empathy_tp_contact_list_new (McAccount *account)
684 {
685         EmpathyTpContactListPriv *priv;
686         EmpathyTpContactList     *list;
687         MissionControl           *mc;
688         TpConn                   *tp_conn = NULL;
689         McProfile                *profile;
690         const gchar              *protocol_name;
691
692         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
693
694         mc = empathy_mission_control_new ();
695
696         /* status==0 means CONNECTED */
697         if (mission_control_get_connection_status (mc, account, NULL) == 0) {
698                 tp_conn = mission_control_get_connection (mc, account, NULL);
699         }
700         if (!tp_conn) {
701                 /* The account is not connected, nothing to do. */
702                 g_object_unref (mc);
703                 return NULL;
704         }
705
706         list = g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST, NULL);
707         priv = GET_PRIV (list);
708
709         priv->tp_conn = tp_conn;
710         priv->account = g_object_ref (account);
711         priv->mc = mc;
712         priv->contacts_groups = g_hash_table_new_full (empathy_contact_hash,
713                                                        empathy_contact_equal,
714                                                        (GDestroyNotify) g_object_unref,
715                                                        (GDestroyNotify) tp_contact_list_group_list_free);
716
717         /* Check for protocols that does not support contact groups. We can
718          * put all contacts into a special group in that case.
719          * FIXME: Default group should be an information in the profile */
720         profile = mc_account_get_profile (account);
721         protocol_name = mc_profile_get_protocol_name (profile);
722         if (strcmp (protocol_name, "local-xmpp") == 0) {
723                 priv->protocol_group = _("People nearby");
724         }
725         g_object_unref (profile);
726
727         /* Connect signals */
728         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc),
729                                      "AccountStatusChanged",
730                                      G_CALLBACK (tp_contact_list_status_changed_cb),
731                                      list, NULL);
732         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
733                                      G_CALLBACK (tp_contact_list_newchannel_cb),
734                                      list, NULL);
735         g_signal_connect (priv->tp_conn, "destroy",
736                           G_CALLBACK (tp_contact_list_destroy_cb),
737                           list);
738
739         tp_contact_list_setup (list);
740
741         return list;
742 }
743
744 McAccount *
745 empathy_tp_contact_list_get_account (EmpathyTpContactList *list)
746 {
747         EmpathyTpContactListPriv *priv;
748
749         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
750
751         priv = GET_PRIV (list);
752
753         return priv->account;
754 }
755
756 static void
757 tp_contact_list_add (EmpathyContactList *list,
758                      EmpathyContact     *contact,
759                      const gchar        *message)
760 {
761         EmpathyTpContactListPriv *priv = GET_PRIV (list);
762
763         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
764
765         empathy_tp_group_add_member (priv->subscribe, contact, message);
766         if (g_list_find (priv->pendings, contact)) {
767                 empathy_tp_group_add_member (priv->publish, contact, message);          
768         }
769 }
770
771 static void
772 tp_contact_list_remove (EmpathyContactList *list,
773                         EmpathyContact     *contact,
774                         const gchar        *message)
775 {
776         EmpathyTpContactListPriv *priv = GET_PRIV (list);
777
778         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
779
780         empathy_tp_group_remove_member (priv->subscribe, contact, message);
781         empathy_tp_group_remove_member (priv->publish, contact, message);               
782 }
783
784 static GList *
785 tp_contact_list_get_members (EmpathyContactList *list)
786 {
787         EmpathyTpContactListPriv *priv = GET_PRIV (list);
788
789         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
790
791         g_list_foreach (priv->members, (GFunc) g_object_ref, NULL);
792         return g_list_copy (priv->members);
793 }
794
795 static GList *
796 tp_contact_list_get_pendings (EmpathyContactList *list)
797 {
798         EmpathyTpContactListPriv *priv = GET_PRIV (list);
799
800         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
801
802         g_list_foreach (priv->pendings, (GFunc) g_object_ref, NULL);
803         return g_list_copy (priv->pendings);
804 }
805
806 static GList *
807 tp_contact_list_get_all_groups (EmpathyContactList *list)
808 {
809         EmpathyTpContactListPriv *priv = GET_PRIV (list);
810         GList                    *groups = NULL, *l;
811
812         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
813
814         if (priv->protocol_group) {
815                 groups = g_list_prepend (groups, g_strdup (priv->protocol_group));
816         }
817
818         for (l = priv->groups; l; l = l->next) {
819                 const gchar *name;
820
821                 name = empathy_tp_group_get_name (l->data);
822                 groups = g_list_prepend (groups, g_strdup (name));
823         }
824
825         return groups;
826 }
827
828 static GList *
829 tp_contact_list_get_groups (EmpathyContactList *list,
830                             EmpathyContact     *contact)
831 {
832         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
833         GList                    **groups;
834         GList                     *ret = NULL, *l;
835
836         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
837
838         if (priv->protocol_group) {
839                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
840         }
841
842         groups = g_hash_table_lookup (priv->contacts_groups, contact);
843         if (!groups) {
844                 return ret;
845         }
846
847         for (l = *groups; l; l = l->next) {
848                 ret = g_list_prepend (ret, g_strdup (l->data));
849         }
850
851
852         return ret;
853 }
854
855 static EmpathyTpGroup *
856 tp_contact_list_get_group (EmpathyTpContactList *list,
857                            const gchar          *group)
858 {
859         EmpathyTpContactListPriv *priv = GET_PRIV (list);
860         EmpathyTpGroup           *tp_group;
861         gchar                    *object_path;
862         guint                     handle;
863         GArray                   *handles;
864         const char               *names[2] = {group, NULL};
865         GError                   *error = NULL;
866
867         tp_group = tp_contact_list_find_group (list, group);
868         if (tp_group) {
869                 return tp_group;
870         }
871
872         empathy_debug (DEBUG_DOMAIN, "creating new group: %s", group);
873
874         if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
875                                       TP_HANDLE_TYPE_GROUP,
876                                       names,
877                                       &handles,
878                                       &error)) {
879                 empathy_debug (DEBUG_DOMAIN,
880                               "Failed to RequestHandles: %s",
881                               error ? error->message : "No error given");
882                 g_clear_error (&error);
883                 return NULL;
884         }
885         handle = g_array_index (handles, guint, 0);
886         g_array_free (handles, TRUE);
887
888         if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn),
889                                       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
890                                       TP_HANDLE_TYPE_GROUP,
891                                       handle,
892                                       TRUE,
893                                       &object_path,
894                                       &error)) {
895                 empathy_debug (DEBUG_DOMAIN,
896                               "Failed to RequestChannel: %s",
897                               error ? error->message : "No error given");
898                 g_clear_error (&error);
899                 return NULL;
900         }
901
902         tp_contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
903                                        object_path,
904                                        TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
905                                        TP_HANDLE_TYPE_GROUP,
906                                        handle, FALSE,
907                                        list);
908         g_free (object_path);
909
910         return tp_contact_list_find_group (list, group);
911 }
912
913 static void
914 tp_contact_list_add_to_group (EmpathyContactList *list,
915                               EmpathyContact     *contact,
916                               const gchar        *group)
917 {
918         EmpathyTpGroup *tp_group;
919
920         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
921
922         tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list),
923                                               group);
924
925         empathy_tp_group_add_member (tp_group, contact, "");
926 }
927
928 static void
929 tp_contact_list_remove_from_group (EmpathyContactList *list,
930                                    EmpathyContact     *contact,
931                                    const gchar        *group)
932 {
933         EmpathyTpGroup *tp_group;
934
935         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
936
937         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
938                                                group);
939
940         if (tp_group) {
941                 empathy_tp_group_remove_member (tp_group, contact, "");
942         }
943 }
944
945 static void
946 tp_contact_list_rename_group (EmpathyContactList *list,
947                               const gchar        *old_group,
948                               const gchar        *new_group)
949 {
950         EmpathyTpGroup *tp_group;
951         GList          *members;
952
953         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
954
955         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
956                                                old_group);
957         if (!tp_group) {
958                 return;
959         }
960
961         empathy_debug (DEBUG_DOMAIN, "rename group %s to %s", old_group, new_group);
962
963         /* Remove all members from the old group */
964         members = empathy_tp_group_get_members (tp_group);
965         empathy_tp_group_remove_members (tp_group, members, "");
966         empathy_tp_group_close (tp_group);
967
968         /* Add all members to the new group */
969         tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list),
970                                               new_group);
971         empathy_tp_group_add_members (tp_group, members, "");
972
973         g_list_foreach (members, (GFunc) g_object_unref, NULL);
974         g_list_free (members);
975 }
976
977 static void
978 tp_contact_list_remove_group (EmpathyContactList *list,
979                               const gchar *group)
980 {
981         EmpathyTpGroup *tp_group;
982         GList          *members;
983
984         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
985
986         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
987                                                group);
988         
989         if (!tp_group) {
990                 return;
991         }
992
993         empathy_debug (DEBUG_DOMAIN, "remove group %s", group);
994
995         /* Remove all members of the group */
996         members = empathy_tp_group_get_members (tp_group);
997         empathy_tp_group_remove_members (tp_group, members, "");
998         empathy_tp_group_close (tp_group);
999
1000         g_list_foreach (members, (GFunc) g_object_unref, NULL);
1001         g_list_free (members);
1002 }
1003
1004 static void
1005 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1006 {
1007         iface->add               = tp_contact_list_add;
1008         iface->remove            = tp_contact_list_remove;
1009         iface->get_members       = tp_contact_list_get_members;
1010         iface->get_pendings      = tp_contact_list_get_pendings;
1011         iface->get_all_groups    = tp_contact_list_get_all_groups;
1012         iface->get_groups        = tp_contact_list_get_groups;
1013         iface->add_to_group      = tp_contact_list_add_to_group;
1014         iface->remove_from_group = tp_contact_list_remove_from_group;
1015         iface->rename_group      = tp_contact_list_rename_group;
1016         iface->remove_group      = tp_contact_list_remove_group;
1017 }
1018