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