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