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