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