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