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