]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
Remove last references to mission-control 4
[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-2009 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 #include <telepathy-glib/interfaces.h>
33
34 #include "empathy-tp-contact-list.h"
35 #include "empathy-tp-contact-factory.h"
36 #include "empathy-contact-list.h"
37 #include "empathy-utils.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CONTACT
40 #include "empathy-debug.h"
41
42 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpContactList)
43 typedef struct {
44         EmpathyTpContactFactory *factory;
45         TpConnection   *connection;
46         const gchar    *protocol_group;
47
48         TpChannel      *publish;
49         TpChannel      *subscribe;
50         GHashTable     *members; /* handle -> EmpathyContact */
51         GHashTable     *pendings; /* handle -> EmpathyContact */
52         GHashTable     *groups; /* group name -> TpChannel */
53         GHashTable     *add_to_group; /* group name -> GArray of handles */
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_forget_group (EmpathyTpContactList *list,
75                               TpChannel *channel)
76 {
77         EmpathyTpContactListPriv *priv = GET_PRIV (list);
78         const TpIntSet *members;
79         TpIntSetIter iter;
80         const gchar *group_name;
81
82         group_name = tp_channel_get_identifier (channel);
83
84         /* Signal that all members are not in that group anymore */
85         members = tp_channel_group_get_members (channel);
86         tp_intset_iter_init (&iter, members);
87         while (tp_intset_iter_next (&iter)) {
88                 EmpathyContact *contact;
89
90                 contact = g_hash_table_lookup (priv->members,
91                                                GUINT_TO_POINTER (iter.element));
92                 if (contact == NULL) {
93                         continue;
94                 }
95
96                 DEBUG ("Contact %s (%d) removed from group %s",
97                         empathy_contact_get_id (contact), iter.element,
98                         group_name);
99                 g_signal_emit_by_name (list, "groups-changed", contact,
100                                        group_name,
101                                        FALSE);
102         }
103 }
104
105 static void
106 tp_contact_list_group_invalidated_cb (TpChannel *channel,
107                                       guint      domain,
108                                       gint       code,
109                                       gchar     *message,
110                                       EmpathyTpContactList *list)
111 {
112         EmpathyTpContactListPriv *priv = GET_PRIV (list);
113         const gchar *group_name;
114
115         group_name = tp_channel_get_identifier (channel);
116         DEBUG ("Group %s invalidated. Message: %s", group_name, message);
117
118         tp_contact_list_forget_group (list, channel);
119
120         g_hash_table_remove (priv->groups, group_name);
121 }
122
123 static void
124 tp_contact_list_group_ready_cb (TpChannel *channel,
125                                 const GError *error,
126                                 gpointer list)
127 {
128         EmpathyTpContactListPriv *priv = GET_PRIV (list);
129         TpChannel *old_group;
130         const gchar *group_name;
131
132         if (error) {
133                 DEBUG ("Error: %s", error->message);
134                 g_object_unref (channel);
135                 return;
136         }
137
138         group_name = tp_channel_get_identifier (channel);
139
140         /* If there's already a group with this name in the table, we can't
141          * just let it be replaced. Replacing it causes it to be unreffed,
142          * which causes it to be invalidated (see
143          * <https://bugs.freedesktop.org/show_bug.cgi?id=22119>), which causes
144          * it to be removed from the hash table again, which causes it to be
145          * unreffed again.
146          */
147         old_group = g_hash_table_lookup (priv->groups, group_name);
148
149         if (old_group != NULL) {
150                 DEBUG ("Discarding old group %s (%p)", group_name, old_group);
151                 g_hash_table_steal (priv->groups, group_name);
152                 tp_contact_list_forget_group (list, old_group);
153                 g_object_unref (old_group);
154         }
155
156         g_hash_table_insert (priv->groups, (gpointer) group_name, channel);
157         DEBUG ("Group %s added", group_name);
158
159         g_signal_connect (channel, "invalidated",
160                           G_CALLBACK (tp_contact_list_group_invalidated_cb),
161                           list);
162
163         if (priv->add_to_group) {
164                 GArray *handles;
165
166                 handles = g_hash_table_lookup (priv->add_to_group, group_name);
167                 if (handles) {
168                         DEBUG ("Adding initial members to group %s", group_name);
169                         tp_cli_channel_interface_group_call_add_members (channel,
170                                 -1, handles, NULL, NULL, NULL, NULL, NULL);
171                         g_hash_table_remove (priv->add_to_group, group_name);
172                 }
173         }
174 }
175
176 static void
177 tp_contact_list_group_members_changed_cb (TpChannel     *channel,
178                                           gchar         *message,
179                                           GArray        *added,
180                                           GArray        *removed,
181                                           GArray        *local_pending,
182                                           GArray        *remote_pending,
183                                           guint          actor,
184                                           guint          reason,
185                                           EmpathyTpContactList *list)
186 {
187         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
188         const gchar *group_name;
189         gint i;
190
191         group_name = tp_channel_get_identifier (channel);
192
193         for (i = 0; i < added->len; i++) {
194                 EmpathyContact *contact;
195                 TpHandle handle;
196
197                 handle = g_array_index (added, TpHandle, i);
198                 contact = g_hash_table_lookup (priv->members,
199                                                GUINT_TO_POINTER (handle));
200                 if (contact == NULL) {
201                         continue;
202                 }
203
204                 DEBUG ("Contact %s (%d) added to group %s",
205                         empathy_contact_get_id (contact), handle, group_name);
206                 g_signal_emit_by_name (list, "groups-changed", contact,
207                                        group_name,
208                                        TRUE);
209         }
210
211         for (i = 0; i < removed->len; i++) {
212                 EmpathyContact *contact;
213                 TpHandle handle;
214
215                 handle = g_array_index (removed, TpHandle, i);
216                 contact = g_hash_table_lookup (priv->members,
217                                                GUINT_TO_POINTER (handle));
218                 if (contact == NULL) {
219                         continue;
220                 }
221
222                 DEBUG ("Contact %s (%d) removed from group %s",
223                         empathy_contact_get_id (contact), handle, group_name);
224
225                 g_signal_emit_by_name (list, "groups-changed", contact,
226                                        group_name,
227                                        FALSE);
228         }
229 }
230
231 static void
232 tp_contact_list_group_add_channel (EmpathyTpContactList *list,
233                                    const gchar          *object_path,
234                                    const gchar          *channel_type,
235                                    TpHandleType          handle_type,
236                                    guint                 handle)
237 {
238         EmpathyTpContactListPriv *priv = GET_PRIV (list);
239         TpChannel                *channel;
240
241         /* Only accept server-side contact groups */
242         if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) ||
243             handle_type != TP_HANDLE_TYPE_GROUP) {
244                 return;
245         }
246
247         channel = tp_channel_new (priv->connection,
248                                   object_path, channel_type,
249                                   handle_type, handle, NULL);
250
251         /* TpChannel emits initial set of members just before being ready */
252         g_signal_connect (channel, "group-members-changed",
253                           G_CALLBACK (tp_contact_list_group_members_changed_cb),
254                           list);
255
256         /* Give the ref to the callback */
257         tp_channel_call_when_ready (channel,
258                                     tp_contact_list_group_ready_cb,
259                                     list);
260 }
261
262 static void
263 tp_contact_list_group_request_channel_cb (TpConnection *connection,
264                                           const gchar  *object_path,
265                                           const GError *error,
266                                           gpointer      user_data,
267                                           GObject      *list)
268 {
269         /* The new channel will be handled in NewChannel cb. Here we only
270          * handle the error if RequestChannel failed */
271         if (error) {
272                 DEBUG ("Error: %s", error->message);
273                 return;
274         }
275 }
276
277 static void
278 tp_contact_list_group_request_handles_cb (TpConnection *connection,
279                                           const GArray *handles,
280                                           const GError *error,
281                                           gpointer      user_data,
282                                           GObject      *list)
283 {
284         TpHandle channel_handle;
285
286         if (error) {
287                 DEBUG ("Error: %s", error->message);
288                 return;
289         }
290
291         channel_handle = g_array_index (handles, TpHandle, 0);
292         tp_cli_connection_call_request_channel (connection, -1,
293                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
294                                                 TP_HANDLE_TYPE_GROUP,
295                                                 channel_handle,
296                                                 TRUE,
297                                                 tp_contact_list_group_request_channel_cb,
298                                                 NULL, NULL,
299                                                 list);
300 }
301
302 /* This function takes ownership of handles array */
303 static void
304 tp_contact_list_group_add (EmpathyTpContactList *list,
305                            const gchar          *group_name,
306                            GArray               *handles)
307 {
308         EmpathyTpContactListPriv *priv = GET_PRIV (list);
309         TpChannel                *channel;
310         const gchar              *names[] = {group_name, NULL};
311
312         /* Search the channel for that group name */
313         channel = g_hash_table_lookup (priv->groups, group_name);
314         if (channel) {
315                 tp_cli_channel_interface_group_call_add_members (channel, -1,
316                         handles, NULL, NULL, NULL, NULL, NULL);
317                 g_array_free (handles, TRUE);
318                 return;
319         }
320
321         /* That group does not exist yet, we have to:
322          * 1) Request an handle for the group name
323          * 2) Request a channel
324          * 3) When NewChannel is emitted, add handles in members
325          */
326         g_hash_table_insert (priv->add_to_group,
327                              g_strdup (group_name),
328                              handles);
329         tp_cli_connection_call_request_handles (priv->connection, -1,
330                                                 TP_HANDLE_TYPE_GROUP, names,
331                                                 tp_contact_list_group_request_handles_cb,
332                                                 NULL, NULL,
333                                                 G_OBJECT (list));
334 }
335
336 static void
337 tp_contact_list_got_added_members_cb (EmpathyTpContactFactory *factory,
338                                       guint                    n_contacts,
339                                       EmpathyContact * const * contacts,
340                                       guint                    n_failed,
341                                       const TpHandle          *failed,
342                                       const GError            *error,
343                                       gpointer                 user_data,
344                                       GObject                 *list)
345 {
346         EmpathyTpContactListPriv *priv = GET_PRIV (list);
347         guint i;
348
349         if (error) {
350                 DEBUG ("Error: %s", error->message);
351                 return;
352         }
353
354         for (i = 0; i < n_contacts; i++) {
355                 EmpathyContact *contact = contacts[i];
356                 TpHandle handle;
357
358                 handle = empathy_contact_get_handle (contact);
359                 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle)))
360                         continue;
361
362                 /* Add to the list and emit signal */
363                 g_hash_table_insert (priv->members, GUINT_TO_POINTER (handle),
364                                      g_object_ref (contact));
365                 g_signal_emit_by_name (list, "members-changed", contact,
366                                        0, 0, NULL, TRUE);
367
368                 /* This contact is now member, implicitly accept pending. */
369                 if (g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
370                         GArray handles = {(gchar *) &handle, 1};
371
372                         tp_cli_channel_interface_group_call_add_members (priv->publish,
373                                 -1, &handles, NULL, NULL, NULL, NULL, NULL);
374                 }
375         }
376 }
377
378 static void
379 tp_contact_list_got_local_pending_cb (EmpathyTpContactFactory *factory,
380                                       guint                    n_contacts,
381                                       EmpathyContact * const * contacts,
382                                       guint                    n_failed,
383                                       const TpHandle          *failed,
384                                       const GError            *error,
385                                       gpointer                 user_data,
386                                       GObject                 *list)
387 {
388         EmpathyTpContactListPriv *priv = GET_PRIV (list);
389         guint i;
390
391         if (error) {
392                 DEBUG ("Error: %s", error->message);
393                 return;
394         }
395
396         for (i = 0; i < n_contacts; i++) {
397                 EmpathyContact *contact = contacts[i];
398                 TpHandle handle;
399                 const gchar *message;
400                 TpChannelGroupChangeReason reason;
401
402                 handle = empathy_contact_get_handle (contact);
403                 if (g_hash_table_lookup (priv->members, GUINT_TO_POINTER (handle))) {
404                         GArray handles = {(gchar *) &handle, 1};
405
406                         /* This contact is already member, auto accept. */
407                         tp_cli_channel_interface_group_call_add_members (priv->publish,
408                                 -1, &handles, NULL, NULL, NULL, NULL, NULL);
409                 }
410                 else if (tp_channel_group_get_local_pending_info (priv->publish,
411                                                                   handle,
412                                                                   NULL,
413                                                                   &reason,
414                                                                   &message)) {
415                         /* Add contact to pendings */
416                         g_hash_table_insert (priv->pendings, GUINT_TO_POINTER (handle),
417                                              g_object_ref (contact));
418                         g_signal_emit_by_name (list, "pendings-changed", contact,
419                                                contact, reason, message, TRUE);
420                 }
421         }
422 }
423
424 static void
425 tp_contact_list_remove_handle (EmpathyTpContactList *list,
426                                GHashTable *table,
427                                TpHandle handle)
428 {
429         EmpathyTpContactListPriv *priv = GET_PRIV (list);
430         EmpathyContact *contact;
431         const gchar *signal;
432
433         if (table == priv->pendings)
434                 signal = "pendings-changed";
435         else if (table == priv->members)
436                 signal = "members-changed";
437         else
438                 return;
439
440         contact = g_hash_table_lookup (table, GUINT_TO_POINTER (handle));
441         if (contact) {
442                 g_object_ref (contact);
443                 g_hash_table_remove (table, GUINT_TO_POINTER (handle));
444                 g_signal_emit_by_name (list, signal, contact, 0, 0, NULL,
445                                        FALSE);
446                 g_object_unref (contact);
447         }
448 }
449
450 static void
451 tp_contact_list_publish_group_members_changed_cb (TpChannel     *channel,
452                                                   gchar         *message,
453                                                   GArray        *added,
454                                                   GArray        *removed,
455                                                   GArray        *local_pending,
456                                                   GArray        *remote_pending,
457                                                   TpHandle       actor,
458                                                   TpChannelGroupChangeReason reason,
459                                                   EmpathyTpContactList *list)
460 {
461         EmpathyTpContactListPriv *priv = GET_PRIV (list);
462         guint i;
463
464         /* We now send our presence to those contacts, remove them from pendings */
465         for (i = 0; i < added->len; i++) {
466                 tp_contact_list_remove_handle (list, priv->pendings,
467                         g_array_index (added, TpHandle, i));
468         }
469
470         /* We refuse to send our presence to those contacts, remove from pendings */
471         for (i = 0; i < removed->len; i++) {
472                 tp_contact_list_remove_handle (list, priv->pendings,
473                         g_array_index (added, TpHandle, i));
474         }
475
476         /* Those contacts want our presence, auto accept those that are already
477          * member, otherwise add in pendings. */
478         if (local_pending->len > 0) {
479                 empathy_tp_contact_factory_get_from_handles (priv->factory,
480                         local_pending->len, (TpHandle *) local_pending->data,
481                         tp_contact_list_got_local_pending_cb, NULL, NULL,
482                         G_OBJECT (list));
483         }
484 }
485
486 static void
487 tp_contact_list_publish_request_channel_cb (TpConnection *connection,
488                                             const gchar  *object_path,
489                                             const GError *error,
490                                             gpointer      user_data,
491                                             GObject      *list)
492 {
493         EmpathyTpContactListPriv *priv = GET_PRIV (list);
494
495         if (error) {
496                 DEBUG ("Error: %s", error->message);
497                 return;
498         }
499
500         priv->publish = tp_channel_new (connection, object_path,
501                                         TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
502                                         TP_HANDLE_TYPE_LIST,
503                                         GPOINTER_TO_UINT (user_data),
504                                         NULL);
505
506         /* TpChannel emits initial set of members just before being ready */
507         g_signal_connect (priv->publish, "group-members-changed",
508                           G_CALLBACK (tp_contact_list_publish_group_members_changed_cb),
509                           list);
510 }
511
512 static void
513 tp_contact_list_publish_request_handle_cb (TpConnection *connection,
514                                            const GArray *handles,
515                                            const GError *error,
516                                            gpointer      user_data,
517                                            GObject      *list)
518 {
519         TpHandle handle;
520
521         if (error) {
522                 DEBUG ("Error: %s", error->message);
523                 return;
524         }
525
526         handle = g_array_index (handles, TpHandle, 0);
527         tp_cli_connection_call_request_channel (connection, -1,
528                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
529                                                 TP_HANDLE_TYPE_LIST,
530                                                 handle,
531                                                 TRUE,
532                                                 tp_contact_list_publish_request_channel_cb,
533                                                 GUINT_TO_POINTER (handle), NULL,
534                                                 list);
535 }
536
537 static void
538 tp_contact_list_subscribe_group_members_changed_cb (TpChannel     *channel,
539                                                     gchar         *message,
540                                                     GArray        *added,
541                                                     GArray        *removed,
542                                                     GArray        *local_pending,
543                                                     GArray        *remote_pending,
544                                                     guint          actor,
545                                                     guint          reason,
546                                                     EmpathyTpContactList *list)
547 {
548         EmpathyTpContactListPriv *priv = GET_PRIV (list);
549         guint i;
550
551         /* We now get the presence of those contacts, add them to members */
552         if (added->len > 0) {
553                 empathy_tp_contact_factory_get_from_handles (priv->factory,
554                         added->len, (TpHandle *) added->data,
555                         tp_contact_list_got_added_members_cb, NULL, NULL,
556                         G_OBJECT (list));
557         }
558
559         /* Those contacts refuse to send us their presence, remove from members. */
560         for (i = 0; i < removed->len; i++) {
561                 tp_contact_list_remove_handle (list, priv->members,
562                         g_array_index (added, TpHandle, i));
563         }
564
565         /* We want those contacts in our contact list but we don't get their
566          * presence yet. Add to members anyway. */
567         if (remote_pending->len > 0) {
568                 empathy_tp_contact_factory_get_from_handles (priv->factory,
569                         remote_pending->len, (TpHandle *) remote_pending->data,
570                         tp_contact_list_got_added_members_cb, NULL, NULL,
571                         G_OBJECT (list));
572         }
573 }
574
575 static void
576 tp_contact_list_subscribe_request_channel_cb (TpConnection *connection,
577                                               const gchar  *object_path,
578                                               const GError *error,
579                                               gpointer      user_data,
580                                               GObject      *list)
581 {
582         EmpathyTpContactListPriv *priv = GET_PRIV (list);
583
584         if (error) {
585                 DEBUG ("Error: %s", error->message);
586                 return;
587         }
588
589         priv->subscribe = tp_channel_new (connection, object_path,
590                                           TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
591                                           TP_HANDLE_TYPE_LIST,
592                                           GPOINTER_TO_UINT (user_data),
593                                           NULL);
594
595         /* TpChannel emits initial set of members just before being ready */
596         g_signal_connect (priv->subscribe, "group-members-changed",
597                           G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb),
598                           list);
599 }
600
601 static void
602 tp_contact_list_subscribe_request_handle_cb (TpConnection *connection,
603                                              const GArray *handles,
604                                              const GError *error,
605                                              gpointer      user_data,
606                                              GObject      *list)
607 {
608         TpHandle handle;
609
610         if (error) {
611                 DEBUG ("Error: %s", error->message);
612                 return;
613         }
614
615         handle = g_array_index (handles, TpHandle, 0);
616         tp_cli_connection_call_request_channel (connection, -1,
617                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
618                                                 TP_HANDLE_TYPE_LIST,
619                                                 handle,
620                                                 TRUE,
621                                                 tp_contact_list_subscribe_request_channel_cb,
622                                                 GUINT_TO_POINTER (handle), NULL,
623                                                 list);
624 }
625
626 static void
627 tp_contact_list_new_channel_cb (TpConnection *proxy,
628                                 const gchar  *object_path,
629                                 const gchar  *channel_type,
630                                 guint         handle_type,
631                                 guint         handle,
632                                 gboolean      suppress_handler,
633                                 gpointer      user_data,
634                                 GObject      *list)
635 {
636         tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
637                                            object_path, channel_type,
638                                            handle_type, handle);
639 }
640
641 static void
642 tp_contact_list_list_channels_cb (TpConnection    *connection,
643                                   const GPtrArray *channels,
644                                   const GError    *error,
645                                   gpointer         user_data,
646                                   GObject         *list)
647 {
648         guint i;
649
650         if (error) {
651                 DEBUG ("Error: %s", error->message);
652                 return;
653         }
654
655         for (i = 0; i < channels->len; i++) {
656                 GValueArray  *chan_struct;
657                 const gchar  *object_path;
658                 const gchar  *channel_type;
659                 TpHandleType  handle_type;
660                 guint         handle;
661
662                 chan_struct = g_ptr_array_index (channels, i);
663                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
664                 channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
665                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
666                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
667
668                 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
669                                                    object_path, channel_type,
670                                                    handle_type, handle);
671         }
672 }
673
674 static void
675 tp_contact_list_finalize (GObject *object)
676 {
677         EmpathyTpContactListPriv *priv;
678         EmpathyTpContactList     *list;
679         GHashTableIter            iter;
680         gpointer                  channel;
681
682         list = EMPATHY_TP_CONTACT_LIST (object);
683         priv = GET_PRIV (list);
684
685         DEBUG ("finalize: %p", object);
686
687         if (priv->subscribe) {
688                 g_object_unref (priv->subscribe);
689         }
690         if (priv->publish) {
691                 g_object_unref (priv->publish);
692         }
693
694         if (priv->connection) {
695                 g_object_unref (priv->connection);
696         }
697
698         if (priv->factory) {
699                 g_object_unref (priv->factory);
700         }
701
702         g_hash_table_iter_init (&iter, priv->groups);
703         while (g_hash_table_iter_next (&iter, NULL, &channel)) {
704                 g_signal_handlers_disconnect_by_func (channel,
705                         tp_contact_list_group_invalidated_cb, list);
706         }
707
708         g_hash_table_destroy (priv->groups);
709         g_hash_table_destroy (priv->members);
710         g_hash_table_destroy (priv->pendings);
711         g_hash_table_destroy (priv->add_to_group);
712
713         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
714 }
715
716 static void
717 tp_contact_list_constructed (GObject *list)
718 {
719
720         EmpathyTpContactListPriv *priv = GET_PRIV (list);
721         gchar                    *protocol_name = NULL;
722         const gchar              *names[] = {NULL, NULL};
723
724         priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection);
725
726         names[0] = "publish";
727         tp_cli_connection_call_request_handles (priv->connection,
728                                                 -1,
729                                                 TP_HANDLE_TYPE_LIST,
730                                                 names,
731                                                 tp_contact_list_publish_request_handle_cb,
732                                                 NULL, NULL,
733                                                 G_OBJECT (list));
734         names[0] = "subscribe";
735         tp_cli_connection_call_request_handles (priv->connection,
736                                                 -1,
737                                                 TP_HANDLE_TYPE_LIST,
738                                                 names,
739                                                 tp_contact_list_subscribe_request_handle_cb,
740                                                 NULL, NULL,
741                                                 G_OBJECT (list));
742
743         tp_cli_connection_call_list_channels (priv->connection, -1,
744                                               tp_contact_list_list_channels_cb,
745                                               NULL, NULL,
746                                               list);
747
748         tp_cli_connection_connect_to_new_channel (priv->connection,
749                                                   tp_contact_list_new_channel_cb,
750                                                   NULL, NULL,
751                                                   list, NULL);
752
753         /* Check for protocols that does not support contact groups. We can
754          * put all contacts into a special group in that case.
755          * FIXME: Default group should be an information in the profile */
756         tp_connection_parse_object_path (priv->connection, &protocol_name, NULL);
757         if (!tp_strdiff (protocol_name, "local-xmpp")) {
758                 priv->protocol_group = _("People nearby");
759         }
760         g_free (protocol_name);
761 }
762
763 static void
764 tp_contact_list_get_property (GObject    *object,
765                               guint       param_id,
766                               GValue     *value,
767                               GParamSpec *pspec)
768 {
769         EmpathyTpContactListPriv *priv = GET_PRIV (object);
770
771         switch (param_id) {
772         case PROP_CONNECTION:
773                 g_value_set_object (value, priv->connection);
774                 break;
775         default:
776                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
777                 break;
778         };
779 }
780
781 static void
782 tp_contact_list_set_property (GObject      *object,
783                               guint         param_id,
784                               const GValue *value,
785                               GParamSpec   *pspec)
786 {
787         EmpathyTpContactListPriv *priv = GET_PRIV (object);
788
789         switch (param_id) {
790         case PROP_CONNECTION:
791                 priv->connection = g_value_dup_object (value);
792                 break;
793         default:
794                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
795                 break;
796         };
797 }
798
799 static void
800 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
801 {
802         GObjectClass *object_class = G_OBJECT_CLASS (klass);
803
804         object_class->finalize = tp_contact_list_finalize;
805         object_class->constructed = tp_contact_list_constructed;
806         object_class->get_property = tp_contact_list_get_property;
807         object_class->set_property = tp_contact_list_set_property;
808
809         g_object_class_install_property (object_class,
810                                          PROP_CONNECTION,
811                                          g_param_spec_object ("connection",
812                                                               "The Connection",
813                                                               "The connection associated with the contact list",
814                                                               TP_TYPE_CONNECTION,
815                                                               G_PARAM_READWRITE |
816                                                               G_PARAM_CONSTRUCT_ONLY));
817
818         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
819 }
820
821 static void
822 tp_contact_list_array_free (gpointer handles)
823 {
824         g_array_free (handles, TRUE);
825 }
826
827 static void
828 empathy_tp_contact_list_init (EmpathyTpContactList *list)
829 {
830         EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
831                 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
832
833         list->priv = priv;
834
835         /* Map group's name to group's TpChannel. The group name string is owned
836          * by the TpChannel object */
837         priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal,
838                                               NULL,
839                                               (GDestroyNotify) g_object_unref);
840
841         /* Map contact's handle to EmpathyContact object */
842         priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal,
843                                                NULL,
844                                                (GDestroyNotify) g_object_unref);
845
846         /* Map contact's handle to EmpathyContact object */
847         priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
848                                                 NULL,
849                                                 (GDestroyNotify) g_object_unref);
850
851         /* Map group's name to GArray of handle */
852         priv->add_to_group = g_hash_table_new_full (g_str_hash, g_str_equal,
853                                                     g_free,
854                                                     tp_contact_list_array_free);
855 }
856
857 EmpathyTpContactList *
858 empathy_tp_contact_list_new (TpConnection *connection)
859 {
860         return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
861                              "connection", connection,
862                              NULL);
863 }
864
865 TpConnection *
866 empathy_tp_contact_list_get_connection (EmpathyTpContactList *list)
867 {
868         EmpathyTpContactListPriv *priv;
869
870         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
871
872         priv = GET_PRIV (list);
873
874         return priv->connection;
875 }
876
877 static void
878 tp_contact_list_add (EmpathyContactList *list,
879                      EmpathyContact     *contact,
880                      const gchar        *message)
881 {
882         EmpathyTpContactListPriv *priv = GET_PRIV (list);
883         TpHandle handle;
884         GArray handles = {(gchar *) &handle, 1};
885
886         handle = empathy_contact_get_handle (contact);
887         if (priv->subscribe) {
888                 tp_cli_channel_interface_group_call_add_members (priv->subscribe,
889                         -1, &handles, message, NULL, NULL, NULL, NULL);
890         }
891         if (priv->publish &&
892             g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
893                 tp_cli_channel_interface_group_call_add_members (priv->publish,
894                         -1, &handles, message, NULL, NULL, NULL, NULL);
895         }
896 }
897
898 static void
899 tp_contact_list_remove (EmpathyContactList *list,
900                         EmpathyContact     *contact,
901                         const gchar        *message)
902 {
903         EmpathyTpContactListPriv *priv = GET_PRIV (list);
904         TpHandle handle;
905         GArray handles = {(gchar *) &handle, 1};
906
907         handle = empathy_contact_get_handle (contact);
908         if (priv->subscribe) {
909                 tp_cli_channel_interface_group_call_remove_members (priv->subscribe,
910                         -1, &handles, message, NULL, NULL, NULL, NULL);
911         }
912         if (priv->publish) {
913                 tp_cli_channel_interface_group_call_remove_members (priv->publish,
914                         -1, &handles, message, NULL, NULL, NULL, NULL);
915         }
916 }
917
918 static GList *
919 tp_contact_list_get_members (EmpathyContactList *list)
920 {
921         EmpathyTpContactListPriv *priv = GET_PRIV (list);
922         GList *ret;
923
924         ret = g_hash_table_get_values (priv->members);
925         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
926         return ret;
927 }
928
929 static GList *
930 tp_contact_list_get_pendings (EmpathyContactList *list)
931 {
932         EmpathyTpContactListPriv *priv = GET_PRIV (list);
933         GList *ret;
934
935         ret = g_hash_table_get_values (priv->pendings);
936         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
937         return ret;
938 }
939
940 static GList *
941 tp_contact_list_get_all_groups (EmpathyContactList *list)
942 {
943         EmpathyTpContactListPriv *priv = GET_PRIV (list);
944         GList                    *ret, *l;
945
946         ret = g_hash_table_get_keys (priv->groups);
947         for (l = ret; l; l = l->next) {
948                 l->data = g_strdup (l->data);
949         }
950
951         if (priv->protocol_group) {
952                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
953         }
954
955         return ret;
956 }
957
958 static GList *
959 tp_contact_list_get_groups (EmpathyContactList *list,
960                             EmpathyContact     *contact)
961 {
962         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
963         GList                     *ret = NULL;
964         GHashTableIter             iter;
965         gpointer                   group_name;
966         gpointer                   channel;
967         TpHandle                   handle;
968
969         handle = empathy_contact_get_handle (contact);
970         g_hash_table_iter_init (&iter, priv->groups);
971         while (g_hash_table_iter_next (&iter, &group_name, &channel)) {
972                 const TpIntSet *members;
973
974                 members = tp_channel_group_get_members (channel);
975                 if (tp_intset_is_member (members, handle)) {
976                         ret = g_list_prepend (ret, g_strdup (group_name));
977                 }
978         }
979
980         if (priv->protocol_group) {
981                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
982         }
983
984         return ret;
985 }
986
987 static void
988 tp_contact_list_add_to_group (EmpathyContactList *list,
989                               EmpathyContact     *contact,
990                               const gchar        *group_name)
991 {
992         TpHandle handle;
993         GArray *handles;
994
995         handle = empathy_contact_get_handle (contact);
996         handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
997         g_array_append_val (handles, handle);
998         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
999                                    group_name, handles);
1000 }
1001
1002 static void
1003 tp_contact_list_remove_from_group (EmpathyContactList *list,
1004                                    EmpathyContact     *contact,
1005                                    const gchar        *group_name)
1006 {
1007         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1008         TpChannel                *channel;
1009         TpHandle                  handle;
1010         GArray                    handles = {(gchar *) &handle, 1};
1011
1012         channel = g_hash_table_lookup (priv->groups, group_name);
1013         if (channel == NULL) {
1014                 return;
1015         }
1016
1017         handle = empathy_contact_get_handle (contact);
1018         DEBUG ("remove contact %s (%d) from group %s",
1019                 empathy_contact_get_id (contact), handle, group_name);
1020
1021         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1022                 &handles, NULL, NULL, NULL, NULL, NULL);
1023 }
1024
1025 static void
1026 tp_contact_list_rename_group (EmpathyContactList *list,
1027                               const gchar        *old_group_name,
1028                               const gchar        *new_group_name)
1029 {
1030         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1031         TpChannel                *channel;
1032         const TpIntSet           *members;
1033         GArray                   *handles;
1034
1035         channel = g_hash_table_lookup (priv->groups, old_group_name);
1036         if (channel == NULL) {
1037                 return;
1038         }
1039
1040         DEBUG ("rename group %s to %s", old_group_name, new_group_name);
1041
1042         /* Remove all members and close the old channel */
1043         members = tp_channel_group_get_members (channel);
1044         handles = tp_intset_to_array (members);
1045         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1046                 handles, NULL, NULL, NULL, NULL, NULL);
1047         tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1048
1049         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1050                                    new_group_name, handles);
1051 }
1052
1053 static void
1054 tp_contact_list_remove_group (EmpathyContactList *list,
1055                               const gchar *group_name)
1056 {
1057         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1058         TpChannel                *channel;
1059         const TpIntSet           *members;
1060         GArray                   *handles;
1061
1062         channel = g_hash_table_lookup (priv->groups, group_name);
1063         if (channel == NULL) {
1064                 return;
1065         }
1066
1067         DEBUG ("remove group %s", group_name);
1068
1069         /* Remove all members and close the channel */
1070         members = tp_channel_group_get_members (channel);
1071         handles = tp_intset_to_array (members);
1072         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1073                 handles, NULL, NULL, NULL, NULL, NULL);
1074         tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1075         g_array_free (handles, TRUE);
1076 }
1077
1078 static void
1079 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1080 {
1081         iface->add               = tp_contact_list_add;
1082         iface->remove            = tp_contact_list_remove;
1083         iface->get_members       = tp_contact_list_get_members;
1084         iface->get_pendings      = tp_contact_list_get_pendings;
1085         iface->get_all_groups    = tp_contact_list_get_all_groups;
1086         iface->get_groups        = tp_contact_list_get_groups;
1087         iface->add_to_group      = tp_contact_list_add_to_group;
1088         iface->remove_from_group = tp_contact_list_remove_from_group;
1089         iface->rename_group      = tp_contact_list_rename_group;
1090         iface->remove_group      = tp_contact_list_remove_group;
1091 }
1092
1093 gboolean
1094 empathy_tp_contact_list_can_add (EmpathyTpContactList *list)
1095 {
1096         EmpathyTpContactListPriv *priv;
1097         TpChannelGroupFlags       flags;
1098
1099         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1100
1101         priv = GET_PRIV (list);
1102
1103         if (priv->subscribe == NULL)
1104                 return FALSE;
1105
1106         flags = tp_channel_group_get_flags (priv->subscribe);
1107         return (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) != 0;
1108 }
1109
1110 void
1111 empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
1112 {
1113         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1114         GHashTableIter            iter;
1115         gpointer                  contact;
1116
1117         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1118
1119         /* Remove all contacts */
1120         g_hash_table_iter_init (&iter, priv->members);
1121         while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1122                 g_signal_emit_by_name (list, "members-changed", contact,
1123                                        NULL, 0, NULL,
1124                                        FALSE);
1125         }
1126         g_hash_table_remove_all (priv->members);
1127
1128         g_hash_table_iter_init (&iter, priv->pendings);
1129         while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1130                 g_signal_emit_by_name (list, "pendings-changed", contact,
1131                                        NULL, 0, NULL,
1132                                        FALSE);
1133         }
1134         g_hash_table_remove_all (priv->pendings);
1135 }
1136