]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
d4b99e4c4f1d91582d8ef411aa988e58a20679de
[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-2008 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  * 
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <glib/gi18n-lib.h>
27
28 #include <telepathy-glib/channel.h>
29 #include <telepathy-glib/connection.h>
30 #include <telepathy-glib/util.h>
31 #include <telepathy-glib/dbus.h>
32
33 #include "empathy-tp-contact-list.h"
34 #include "empathy-contact-list.h"
35 #include "empathy-tp-group.h"
36 #include "empathy-utils.h"
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT
39 #include "empathy-debug.h"
40
41 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactList)
42 typedef struct {
43         McAccount      *account;
44         TpConnection   *connection;
45         const gchar    *protocol_group;
46         gboolean        ready;
47
48         EmpathyTpGroup *publish;
49         EmpathyTpGroup *subscribe;
50         GList          *members;
51         GList          *pendings;
52
53         GList          *groups;
54         GHashTable     *contacts_groups;
55 } EmpathyTpContactListPriv;
56
57 typedef enum {
58         TP_CONTACT_LIST_TYPE_PUBLISH,
59         TP_CONTACT_LIST_TYPE_SUBSCRIBE,
60         TP_CONTACT_LIST_TYPE_UNKNOWN
61 } TpContactListType;
62
63 static void tp_contact_list_iface_init         (EmpathyContactListIface   *iface);
64
65 enum {
66         DESTROY,
67         LAST_SIGNAL
68 };
69
70 enum {
71         PROP_0,
72         PROP_ACCOUNT,
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         DEBUG ("Group destroyed: %s", 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                 DEBUG ("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                 DEBUG ("Contact %s (%d) removed from group %s",
154                         empathy_contact_get_id (contact),
155                         empathy_contact_get_handle (contact),
156                         group_name);
157                 g_free (l->data);
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         DEBUG ("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         DEBUG ("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         DEBUG ("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_invalidated_cb (TpConnection         *connection,
341                                 guint                 domain,
342                                 gint                  code,
343                                 gchar                *message,
344                                 EmpathyTpContactList *list)
345 {
346         EmpathyTpContactListPriv *priv = GET_PRIV (list);
347         GList                    *l;
348
349         DEBUG ("Connection invalidated");
350
351         /* Remove all contacts */
352         for (l = priv->members; l; l = l->next) {
353                 g_signal_emit_by_name (list, "members-changed", l->data,
354                                        NULL, 0, NULL,
355                                        FALSE);
356                 g_object_unref (l->data);
357         }
358         for (l = priv->pendings; l; l = l->next) {
359                 g_signal_emit_by_name (list, "pendings-changed", l->data,
360                                        NULL, 0, NULL,
361                                        FALSE);
362                 g_object_unref (l->data);
363         }
364         g_list_free (priv->members);
365         g_list_free (priv->pendings);
366         priv->members = NULL;
367         priv->pendings = NULL;
368
369         /* Tell the world to not use us anymore */
370         g_signal_emit (list, signals[DESTROY], 0);
371 }
372
373 static void
374 tp_contact_list_group_list_free (GList **groups)
375 {
376         g_list_foreach (*groups, (GFunc) g_free, NULL);
377         g_list_free (*groups);
378         g_slice_free (GList*, groups);
379 }
380
381 static void
382 tp_contact_list_add_channel (EmpathyTpContactList *list,
383                              const gchar          *object_path,
384                              const gchar          *channel_type,
385                              TpHandleType          handle_type,
386                              guint                 handle)
387 {
388         EmpathyTpContactListPriv *priv = GET_PRIV (list);
389         TpChannel                *channel;
390         EmpathyTpGroup           *group;
391         const gchar              *group_name;
392         GList                    *contacts, *l;
393
394         if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 ||
395             handle_type != TP_HANDLE_TYPE_GROUP) {
396                 return;
397         }
398
399         channel = tp_channel_new (priv->connection,
400                                   object_path, channel_type,
401                                   handle_type, handle, NULL);
402
403         group = empathy_tp_group_new (channel);
404         empathy_run_until_ready (group);
405         g_object_unref (channel);
406
407         /* Check if already exists */
408         group_name = empathy_tp_group_get_name (group);
409         if (tp_contact_list_find_group (list, group_name)) {
410                 g_object_unref (group);
411                 return;
412         }
413
414         /* Add the group */
415         DEBUG ("New server-side group: %s", group_name);
416         priv->groups = g_list_prepend (priv->groups, group);
417         g_signal_connect (group, "member-added",
418                           G_CALLBACK (tp_contact_list_group_member_added_cb),
419                           list);
420         g_signal_connect (group, "member-removed",
421                           G_CALLBACK (tp_contact_list_group_member_removed_cb),
422                           list);
423         g_signal_connect (group, "destroy",
424                           G_CALLBACK (tp_contact_list_group_destroy_cb),
425                           list);
426
427         /* Get initial members */
428         contacts = empathy_tp_group_get_members (group);
429         for (l = contacts; l; l = l->next) {
430                 tp_contact_list_group_member_added_cb (group, l->data,
431                                                        NULL, 0, NULL,
432                                                        list);
433                 g_object_unref (l->data);
434         }
435         g_list_free (contacts);
436 }
437
438 static void
439 tp_contact_list_new_channel_cb (TpConnection *proxy,
440                                 const gchar  *object_path,
441                                 const gchar  *channel_type,
442                                 guint         handle_type,
443                                 guint         handle,
444                                 gboolean      suppress_handler,
445                                 gpointer      user_data,
446                                 GObject      *list)
447 {
448         EmpathyTpContactListPriv *priv = GET_PRIV (list);
449
450         if (!suppress_handler && priv->ready) {
451                 tp_contact_list_add_channel (EMPATHY_TP_CONTACT_LIST (list),
452                                              object_path, channel_type,
453                                              handle_type, handle);
454         }
455 }
456
457 static void
458 tp_contact_list_list_channels_cb (TpConnection    *connection,
459                                   const GPtrArray *channels,
460                                   const GError    *error,
461                                   gpointer         user_data,
462                                   GObject         *list)
463 {
464         EmpathyTpContactListPriv *priv = GET_PRIV (list);
465         guint                     i;
466
467         if (error) {
468                 DEBUG ("Error: %s", error->message);
469                 return;
470         }
471
472         for (i = 0; i < channels->len; i++) {
473                 GValueArray  *chan_struct;
474                 const gchar  *object_path;
475                 const gchar  *channel_type;
476                 TpHandleType  handle_type;
477                 guint         handle;
478
479                 chan_struct = g_ptr_array_index (channels, i);
480                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
481                 channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
482                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
483                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
484
485                 tp_contact_list_add_channel (EMPATHY_TP_CONTACT_LIST (list),
486                                              object_path, channel_type,
487                                              handle_type, handle);
488         }
489
490         priv->ready = TRUE;
491 }
492
493 static void
494 tp_contact_list_request_channel_cb (TpConnection *connection,
495                                     const gchar  *object_path,
496                                     const GError *error,
497                                     gpointer      user_data,
498                                     GObject      *weak_object)
499 {
500         EmpathyTpContactList     *list = EMPATHY_TP_CONTACT_LIST (weak_object);
501         EmpathyTpContactListPriv *priv = GET_PRIV (list);
502         EmpathyTpGroup           *group;
503         TpChannel                *channel;
504         TpContactListType         list_type;
505         GList                    *contacts, *l;
506
507         if (error) {
508                 DEBUG ("Error: %s", error->message);
509                 return;
510         }
511
512         channel = tp_channel_new (connection, object_path,
513                                   TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
514                                   TP_HANDLE_TYPE_LIST,
515                                   GPOINTER_TO_UINT (user_data),
516                                   NULL);
517         group = empathy_tp_group_new (channel);
518         empathy_run_until_ready (group);
519
520         list_type = tp_contact_list_get_type (list, group);
521         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && !priv->publish) {
522                 DEBUG ("Got publish list");
523                 priv->publish = group;
524
525                 /* Publish is the list of contacts to who we send our
526                  * presence. Makes no sense to be in remote-pending */
527                 g_signal_connect (group, "local-pending",
528                                   G_CALLBACK (tp_contact_list_pending_cb),
529                                   list);
530
531                 contacts = empathy_tp_group_get_local_pendings (group);
532                 for (l = contacts; l; l = l->next) {
533                         EmpathyPendingInfo *info = l->data;
534                                 tp_contact_list_pending_cb (group,
535                                                     info->member,
536                                                     info->actor,
537                                                     0,
538                                                     info->message,
539                                                     list);
540                         empathy_pending_info_free (info);
541                 }
542                 g_list_free (contacts);
543         }
544         else if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && !priv->subscribe) {
545                 DEBUG ("Got subscribe list");
546                 priv->subscribe = group;
547
548                 /* Subscribe is the list of contacts from who we
549                  * receive presence. Makes no sense to be in
550                  * local-pending */
551                 g_signal_connect (group, "remote-pending",
552                                   G_CALLBACK (tp_contact_list_pending_cb),
553                                   list);
554
555                 contacts = empathy_tp_group_get_remote_pendings (group);
556                 for (l = contacts; l; l = l->next) {
557                         tp_contact_list_pending_cb (group,
558                                                     l->data,
559                                                     NULL, 0,
560                                                     NULL, list);
561                         g_object_unref (l->data);
562                 }
563                 g_list_free (contacts);
564         } else {
565                 DEBUG ("Type of contact list channel unknown or aleady "
566                         "have that list: %s",
567                         empathy_tp_group_get_name (group));
568                 g_object_unref (group);
569                 return;
570         }
571
572         /* For all list types when need to get members */
573         g_signal_connect (group, "member-added",
574                           G_CALLBACK (tp_contact_list_added_cb),
575                           list);
576         g_signal_connect (group, "member-removed",
577                           G_CALLBACK (tp_contact_list_removed_cb),
578                           list);
579
580         contacts = empathy_tp_group_get_members (group);
581         for (l = contacts; l; l = l->next) {
582                 tp_contact_list_added_cb (group,
583                                           l->data,
584                                           NULL, 0, NULL,
585                                           list);
586                 g_object_unref (l->data);
587         }
588         g_list_free (contacts);
589 }
590
591 static void
592 tp_contact_list_request_handle_cb (TpConnection *connection,
593                                    const GArray *handles,
594                                    const GError *error,
595                                    gpointer      user_data,
596                                    GObject      *list)
597 {
598         guint handle;
599
600         if (error) {
601                 DEBUG ("Error: %s", error->message);
602                 return;
603         }
604
605         handle = g_array_index (handles, guint, 0);
606         tp_cli_connection_call_request_channel (connection, -1,
607                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
608                                                 TP_HANDLE_TYPE_LIST,
609                                                 handle,
610                                                 TRUE,
611                                                 tp_contact_list_request_channel_cb,
612                                                 GUINT_TO_POINTER (handle), NULL,
613                                                 list);
614 }
615
616 static void
617 tp_contact_list_request_list (EmpathyTpContactList *list,
618                               const gchar          *type)
619 {
620         EmpathyTpContactListPriv *priv = GET_PRIV (list);
621         const gchar *names[] = {type, NULL};
622
623         tp_cli_connection_call_request_handles (priv->connection,
624                                                 -1,
625                                                 TP_HANDLE_TYPE_LIST,
626                                                 names,
627                                                 tp_contact_list_request_handle_cb,
628                                                 NULL, NULL,
629                                                 G_OBJECT (list));
630 }
631
632 static void
633 tp_contact_list_finalize (GObject *object)
634 {
635         EmpathyTpContactListPriv *priv;
636         EmpathyTpContactList     *list;
637
638         list = EMPATHY_TP_CONTACT_LIST (object);
639         priv = GET_PRIV (list);
640
641         DEBUG ("finalize: %p", object);
642
643         if (priv->subscribe) {
644                 g_object_unref (priv->subscribe);
645         }
646         if (priv->publish) {
647                 g_object_unref (priv->publish);
648         }
649         if (priv->account) {
650                 g_object_unref (priv->account);
651         }
652         if (priv->connection) {
653                 g_signal_handlers_disconnect_by_func (priv->connection,
654                                                       tp_contact_list_invalidated_cb,
655                                                       object);
656                 g_object_unref (priv->connection);
657         }
658
659         g_hash_table_destroy (priv->contacts_groups);
660         g_list_foreach (priv->groups, (GFunc) g_object_unref, NULL);
661         g_list_free (priv->groups);
662         g_list_foreach (priv->members, (GFunc) g_object_unref, NULL);
663         g_list_free (priv->members);
664         g_list_foreach (priv->pendings, (GFunc) g_object_unref, NULL);
665         g_list_free (priv->pendings);
666
667         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
668 }
669
670 static void
671 tp_contact_list_connection_ready (TpConnection *connection,
672                                   const GError *error,
673                                   gpointer      list)
674 {
675         EmpathyTpContactListPriv *priv = GET_PRIV (list);
676
677         if (error) {
678                 tp_contact_list_invalidated_cb (connection,
679                                                 error->domain,
680                                                 error->code,
681                                                 error->message,
682                                                 EMPATHY_TP_CONTACT_LIST (list));
683                 return;
684         }
685
686         g_signal_connect (priv->connection, "invalidated",
687                           G_CALLBACK (tp_contact_list_invalidated_cb),
688                           list);
689
690         tp_contact_list_request_list (list, "publish");
691         tp_contact_list_request_list (list, "subscribe");
692
693         tp_cli_connection_call_list_channels (priv->connection, -1,
694                                               tp_contact_list_list_channels_cb,
695                                               NULL, NULL,
696                                               list);
697
698         tp_cli_connection_connect_to_new_channel (priv->connection,
699                                                   tp_contact_list_new_channel_cb,
700                                                   NULL, NULL,
701                                                   list, NULL);
702 }
703
704 static void
705 tp_contact_list_constructed (GObject *list)
706 {
707
708         EmpathyTpContactListPriv *priv = GET_PRIV (list);
709         MissionControl           *mc;
710         guint                     status;
711         McProfile                *profile;
712         const gchar              *protocol_name;
713
714         /* Get the connection. status==0 means CONNECTED */
715         mc = empathy_mission_control_dup_singleton ();
716         status = mission_control_get_connection_status (mc, priv->account, NULL);
717         g_return_if_fail (status == 0);
718         priv->connection = mission_control_get_tpconnection (mc, priv->account, NULL);
719         g_return_if_fail (priv->connection != NULL);
720         g_object_unref (mc);
721
722         tp_connection_call_when_ready (priv->connection,
723                                        tp_contact_list_connection_ready,
724                                        list);
725
726         /* Check for protocols that does not support contact groups. We can
727          * put all contacts into a special group in that case.
728          * FIXME: Default group should be an information in the profile */
729         profile = mc_account_get_profile (priv->account);
730         protocol_name = mc_profile_get_protocol_name (profile);
731         if (strcmp (protocol_name, "local-xmpp") == 0) {
732                 priv->protocol_group = _("People nearby");
733         }
734         g_object_unref (profile);
735 }
736
737 static void
738 tp_contact_list_get_property (GObject    *object,
739                               guint       param_id,
740                               GValue     *value,
741                               GParamSpec *pspec)
742 {
743         EmpathyTpContactListPriv *priv = GET_PRIV (object);
744
745         switch (param_id) {
746         case PROP_ACCOUNT:
747                 g_value_set_object (value, priv->account);
748                 break;
749         default:
750                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
751                 break;
752         };
753 }
754
755 static void
756 tp_contact_list_set_property (GObject      *object,
757                               guint         param_id,
758                               const GValue *value,
759                               GParamSpec   *pspec)
760 {
761         EmpathyTpContactListPriv *priv = GET_PRIV (object);
762
763         switch (param_id) {
764         case PROP_ACCOUNT:
765                 priv->account = g_object_ref (g_value_get_object (value));
766                 break;
767         default:
768                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
769                 break;
770         };
771 }
772
773 static void
774 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
775 {
776         GObjectClass *object_class = G_OBJECT_CLASS (klass);
777
778         object_class->finalize = tp_contact_list_finalize;
779         object_class->constructed = tp_contact_list_constructed;
780         object_class->get_property = tp_contact_list_get_property;
781         object_class->set_property = tp_contact_list_set_property;
782
783         g_object_class_install_property (object_class,
784                                          PROP_ACCOUNT,
785                                          g_param_spec_object ("account",
786                                                               "The Account",
787                                                               "The account associated with the contact list",
788                                                               MC_TYPE_ACCOUNT,
789                                                               G_PARAM_READWRITE |
790                                                               G_PARAM_CONSTRUCT_ONLY));
791
792         signals[DESTROY] =
793                 g_signal_new ("destroy",
794                               G_TYPE_FROM_CLASS (klass),
795                               G_SIGNAL_RUN_LAST,
796                               0,
797                               NULL, NULL,
798                               g_cclosure_marshal_VOID__VOID,
799                               G_TYPE_NONE,
800                               0);
801
802         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
803 }
804
805 static void
806 empathy_tp_contact_list_init (EmpathyTpContactList *list)
807 {
808         EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
809                 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
810
811         list->priv = priv;
812         priv->contacts_groups = g_hash_table_new_full (g_direct_hash,
813                                                        g_direct_equal,
814                                                        (GDestroyNotify) g_object_unref,
815                                                        (GDestroyNotify) tp_contact_list_group_list_free);
816 }
817
818 EmpathyTpContactList *
819 empathy_tp_contact_list_new (McAccount *account)
820 {
821         return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
822                              "account", account,
823                              NULL);
824 }
825
826 McAccount *
827 empathy_tp_contact_list_get_account (EmpathyTpContactList *list)
828 {
829         EmpathyTpContactListPriv *priv;
830
831         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
832
833         priv = GET_PRIV (list);
834
835         return priv->account;
836 }
837
838 static void
839 tp_contact_list_add (EmpathyContactList *list,
840                      EmpathyContact     *contact,
841                      const gchar        *message)
842 {
843         EmpathyTpContactListPriv *priv = GET_PRIV (list);
844
845         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
846
847         if (priv->subscribe) {
848                 empathy_tp_group_add_member (priv->subscribe, contact, message);
849         }
850
851         if (priv->publish && g_list_find (priv->pendings, contact)) {
852                 empathy_tp_group_add_member (priv->publish, contact, message);          
853         }
854 }
855
856 static void
857 tp_contact_list_remove (EmpathyContactList *list,
858                         EmpathyContact     *contact,
859                         const gchar        *message)
860 {
861         EmpathyTpContactListPriv *priv = GET_PRIV (list);
862
863         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
864
865         if (priv->subscribe) {
866                 empathy_tp_group_remove_member (priv->subscribe, contact, message);
867         }
868         if (priv->publish) {
869                 empathy_tp_group_remove_member (priv->publish, contact, message);               
870         }
871 }
872
873 static GList *
874 tp_contact_list_get_members (EmpathyContactList *list)
875 {
876         EmpathyTpContactListPriv *priv = GET_PRIV (list);
877
878         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
879
880         g_list_foreach (priv->members, (GFunc) g_object_ref, NULL);
881         return g_list_copy (priv->members);
882 }
883
884 static GList *
885 tp_contact_list_get_pendings (EmpathyContactList *list)
886 {
887         EmpathyTpContactListPriv *priv = GET_PRIV (list);
888
889         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
890
891         g_list_foreach (priv->pendings, (GFunc) g_object_ref, NULL);
892         return g_list_copy (priv->pendings);
893 }
894
895 static GList *
896 tp_contact_list_get_all_groups (EmpathyContactList *list)
897 {
898         EmpathyTpContactListPriv *priv = GET_PRIV (list);
899         GList                    *groups = NULL, *l;
900
901         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
902
903         if (priv->protocol_group) {
904                 groups = g_list_prepend (groups, g_strdup (priv->protocol_group));
905         }
906
907         for (l = priv->groups; l; l = l->next) {
908                 const gchar *name;
909
910                 name = empathy_tp_group_get_name (l->data);
911                 groups = g_list_prepend (groups, g_strdup (name));
912         }
913
914         return groups;
915 }
916
917 static GList *
918 tp_contact_list_get_groups (EmpathyContactList *list,
919                             EmpathyContact     *contact)
920 {
921         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
922         GList                    **groups;
923         GList                     *ret = NULL, *l;
924
925         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
926
927         if (priv->protocol_group) {
928                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
929         }
930
931         groups = g_hash_table_lookup (priv->contacts_groups, contact);
932         if (!groups) {
933                 return ret;
934         }
935
936         for (l = *groups; l; l = l->next) {
937                 ret = g_list_prepend (ret, g_strdup (l->data));
938         }
939
940
941         return ret;
942 }
943
944 static EmpathyTpGroup *
945 tp_contact_list_get_group (EmpathyTpContactList *list,
946                            const gchar          *group)
947 {
948         EmpathyTpContactListPriv *priv = GET_PRIV (list);
949         EmpathyTpGroup           *tp_group;
950         gchar                    *object_path;
951         guint                     handle;
952         GArray                   *handles;
953         const char               *names[2] = {group, NULL};
954         GError                   *error = NULL;
955
956         tp_group = tp_contact_list_find_group (list, group);
957         if (tp_group) {
958                 return tp_group;
959         }
960
961         DEBUG ("creating new group: %s", group);
962
963         if (!tp_cli_connection_run_request_handles (priv->connection, -1,
964                                                     TP_HANDLE_TYPE_GROUP,
965                                                     names,
966                                                     &handles,
967                                                     &error, NULL)) {
968                 DEBUG ("Failed to RequestHandles: %s",
969                         error ? error->message : "No error given");
970                 g_clear_error (&error);
971                 return NULL;
972         }
973         handle = g_array_index (handles, guint, 0);
974         g_array_free (handles, TRUE);
975
976         if (!tp_cli_connection_run_request_channel (priv->connection, -1,
977                                                     TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
978                                                     TP_HANDLE_TYPE_GROUP,
979                                                     handle,
980                                                     TRUE,
981                                                     &object_path,
982                                                     &error, NULL)) {
983                 DEBUG ("Failed to RequestChannel: %s",
984                         error ? error->message : "No error given");
985                 g_clear_error (&error);
986                 return NULL;
987         }
988
989         tp_contact_list_add_channel (EMPATHY_TP_CONTACT_LIST (list),
990                                      object_path,
991                                      TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
992                                      TP_HANDLE_TYPE_GROUP, handle);
993
994         g_free (object_path);
995
996         return tp_contact_list_find_group (list, group);
997 }
998
999 static void
1000 tp_contact_list_add_to_group (EmpathyContactList *list,
1001                               EmpathyContact     *contact,
1002                               const gchar        *group)
1003 {
1004         EmpathyTpGroup *tp_group;
1005
1006         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1007
1008         tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list),
1009                                               group);
1010
1011         if (tp_group) {
1012                 empathy_tp_group_add_member (tp_group, contact, "");
1013         }
1014 }
1015
1016 static void
1017 tp_contact_list_remove_from_group (EmpathyContactList *list,
1018                                    EmpathyContact     *contact,
1019                                    const gchar        *group)
1020 {
1021         EmpathyTpGroup *tp_group;
1022
1023         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1024
1025         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
1026                                                group);
1027
1028         if (tp_group) {
1029                 empathy_tp_group_remove_member (tp_group, contact, "");
1030         }
1031 }
1032
1033 static void
1034 tp_contact_list_rename_group (EmpathyContactList *list,
1035                               const gchar        *old_group,
1036                               const gchar        *new_group)
1037 {
1038         EmpathyTpGroup *tp_group;
1039         GList          *members;
1040
1041         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1042
1043         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
1044                                                old_group);
1045         if (!tp_group) {
1046                 return;
1047         }
1048
1049         DEBUG ("rename group %s to %s", old_group, new_group);
1050
1051         /* Remove all members from the old group */
1052         members = empathy_tp_group_get_members (tp_group);
1053         empathy_tp_group_remove_members (tp_group, members, "");
1054         empathy_tp_group_close (tp_group);
1055
1056         /* Add all members to the new group */
1057         tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list),
1058                                               new_group);
1059         empathy_tp_group_add_members (tp_group, members, "");
1060
1061         g_list_foreach (members, (GFunc) g_object_unref, NULL);
1062         g_list_free (members);
1063 }
1064
1065 static void
1066 tp_contact_list_remove_group (EmpathyContactList *list,
1067                               const gchar *group)
1068 {
1069         EmpathyTpGroup *tp_group;
1070         GList          *members;
1071
1072         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1073
1074         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
1075                                                group);
1076         
1077         if (!tp_group) {
1078                 return;
1079         }
1080
1081         DEBUG ("remove group %s", group);
1082
1083         /* Remove all members of the group */
1084         members = empathy_tp_group_get_members (tp_group);
1085         empathy_tp_group_remove_members (tp_group, members, "");
1086         empathy_tp_group_close (tp_group);
1087
1088         g_list_foreach (members, (GFunc) g_object_unref, NULL);
1089         g_list_free (members);
1090 }
1091
1092 static void
1093 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1094 {
1095         iface->add               = tp_contact_list_add;
1096         iface->remove            = tp_contact_list_remove;
1097         iface->get_members       = tp_contact_list_get_members;
1098         iface->get_pendings      = tp_contact_list_get_pendings;
1099         iface->get_all_groups    = tp_contact_list_get_all_groups;
1100         iface->get_groups        = tp_contact_list_get_groups;
1101         iface->add_to_group      = tp_contact_list_add_to_group;
1102         iface->remove_from_group = tp_contact_list_remove_from_group;
1103         iface->rename_group      = tp_contact_list_rename_group;
1104         iface->remove_group      = tp_contact_list_remove_group;
1105 }
1106
1107 gboolean
1108 empathy_tp_contact_list_can_add (EmpathyTpContactList *list)
1109 {
1110         EmpathyTpContactListPriv *priv;
1111         TpChannelGroupFlags       flags;
1112
1113         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1114
1115         priv = GET_PRIV (list);
1116
1117         if (priv->subscribe == NULL)
1118                 return FALSE;
1119
1120         flags = empathy_tp_group_get_flags (priv->subscribe);
1121         return (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) != 0;
1122 }