]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
tp_contact_list_remove: remove from the stored list if possible (#604181)
[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_publish_request_channel_cb (TpConnection *connection,
491                                             const gchar  *object_path,
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         priv->publish = tp_channel_new (connection, object_path,
504                                         TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
505                                         TP_HANDLE_TYPE_LIST,
506                                         GPOINTER_TO_UINT (user_data),
507                                         NULL);
508
509         /* TpChannel emits initial set of members just before being ready */
510         g_signal_connect (priv->publish, "group-members-changed",
511                           G_CALLBACK (tp_contact_list_publish_group_members_changed_cb),
512                           list);
513 }
514
515 static void
516 tp_contact_list_get_alias_flags_cb (TpConnection *connection,
517                                     guint         flags,
518                                     const GError *error,
519                                     gpointer      user_data,
520                                     GObject      *list)
521 {
522         EmpathyTpContactListPriv *priv = GET_PRIV (list);
523
524         if (error) {
525                 DEBUG ("Error: %s", error->message);
526                 return;
527         }
528
529         if (flags & TP_CONNECTION_ALIAS_FLAG_USER_SET) {
530                 priv->flags |= EMPATHY_CONTACT_LIST_CAN_ALIAS;
531         }
532 }
533
534 static void
535 tp_contact_list_get_requestablechannelclasses_cb (TpProxy      *connection,
536                                                   const GValue *value,
537                                                   const GError *error,
538                                                   gpointer      user_data,
539                                                   GObject      *list)
540 {
541         EmpathyTpContactListPriv *priv = GET_PRIV (list);
542         GPtrArray *classes;
543         guint i;
544
545         if (error) {
546                 DEBUG ("Error: %s", error->message);
547                 return;
548         }
549
550         classes = g_value_get_boxed (value);
551         for (i = 0; i < classes->len; i++) {
552                 GValueArray *class = g_ptr_array_index (classes, i);
553                 GHashTable *props;
554                 const char *channel_type;
555                 guint handle_type;
556
557                 props = g_value_get_boxed (g_value_array_get_nth (class, 0));
558
559                 channel_type = tp_asv_get_string (props,
560                                 TP_IFACE_CHANNEL ".ChannelType");
561                 handle_type = tp_asv_get_uint32 (props,
562                                 TP_IFACE_CHANNEL ".TargetHandleType", NULL);
563
564                 if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) &&
565                     handle_type == TP_HANDLE_TYPE_GROUP) {
566                         DEBUG ("Got channel class for a contact group");
567                         priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
568                         break;
569                 }
570         }
571 }
572
573 static void
574 tp_contact_list_publish_request_handle_cb (TpConnection *connection,
575                                            const GArray *handles,
576                                            const GError *error,
577                                            gpointer      user_data,
578                                            GObject      *list)
579 {
580         TpHandle handle;
581
582         if (error) {
583                 DEBUG ("Error: %s", error->message);
584                 return;
585         }
586
587         handle = g_array_index (handles, TpHandle, 0);
588         tp_cli_connection_call_request_channel (connection, -1,
589                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
590                                                 TP_HANDLE_TYPE_LIST,
591                                                 handle,
592                                                 TRUE,
593                                                 tp_contact_list_publish_request_channel_cb,
594                                                 GUINT_TO_POINTER (handle), NULL,
595                                                 list);
596 }
597
598 static void
599 tp_contact_list_subscribe_group_members_changed_cb (TpChannel     *channel,
600                                                     gchar         *message,
601                                                     GArray        *added,
602                                                     GArray        *removed,
603                                                     GArray        *local_pending,
604                                                     GArray        *remote_pending,
605                                                     guint          actor,
606                                                     guint          reason,
607                                                     EmpathyTpContactList *list)
608 {
609         EmpathyTpContactListPriv *priv = GET_PRIV (list);
610         guint i;
611
612         /* We now get the presence of those contacts, add them to members */
613         if (added->len > 0) {
614                 empathy_tp_contact_factory_get_from_handles (priv->factory,
615                         added->len, (TpHandle *) added->data,
616                         tp_contact_list_got_added_members_cb, NULL, NULL,
617                         G_OBJECT (list));
618         }
619
620         /* Those contacts refuse to send us their presence, remove from members. */
621         for (i = 0; i < removed->len; i++) {
622                 tp_contact_list_remove_handle (list, priv->members,
623                         g_array_index (removed, TpHandle, i));
624         }
625
626         /* We want those contacts in our contact list but we don't get their
627          * presence yet. Add to members anyway. */
628         if (remote_pending->len > 0) {
629                 empathy_tp_contact_factory_get_from_handles (priv->factory,
630                         remote_pending->len, (TpHandle *) remote_pending->data,
631                         tp_contact_list_got_added_members_cb, NULL, NULL,
632                         G_OBJECT (list));
633         }
634 }
635
636 static void
637 tp_contact_list_subscribe_request_channel_cb (TpConnection *connection,
638                                               const gchar  *object_path,
639                                               const GError *error,
640                                               gpointer      user_data,
641                                               GObject      *list)
642 {
643         EmpathyTpContactListPriv *priv = GET_PRIV (list);
644
645         if (error) {
646                 DEBUG ("Error: %s", error->message);
647                 return;
648         }
649
650         priv->subscribe = tp_channel_new (connection, object_path,
651                                           TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
652                                           TP_HANDLE_TYPE_LIST,
653                                           GPOINTER_TO_UINT (user_data),
654                                           NULL);
655
656         /* TpChannel emits initial set of members just before being ready */
657         g_signal_connect (priv->subscribe, "group-members-changed",
658                           G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb),
659                           list);
660 }
661
662 static void
663 tp_contact_list_subscribe_request_handle_cb (TpConnection *connection,
664                                              const GArray *handles,
665                                              const GError *error,
666                                              gpointer      user_data,
667                                              GObject      *list)
668 {
669         TpHandle handle;
670
671         if (error) {
672                 DEBUG ("Error: %s", error->message);
673                 return;
674         }
675
676         handle = g_array_index (handles, TpHandle, 0);
677         tp_cli_connection_call_request_channel (connection, -1,
678                                                 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
679                                                 TP_HANDLE_TYPE_LIST,
680                                                 handle,
681                                                 TRUE,
682                                                 tp_contact_list_subscribe_request_channel_cb,
683                                                 GUINT_TO_POINTER (handle), NULL,
684                                                 list);
685 }
686
687 static void
688 tp_contact_list_new_channel_cb (TpConnection *proxy,
689                                 const gchar  *object_path,
690                                 const gchar  *channel_type,
691                                 guint         handle_type,
692                                 guint         handle,
693                                 gboolean      suppress_handler,
694                                 gpointer      user_data,
695                                 GObject      *list)
696 {
697         tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
698                                            object_path, channel_type,
699                                            handle_type, handle);
700 }
701
702 static void
703 tp_contact_list_list_channels_cb (TpConnection    *connection,
704                                   const GPtrArray *channels,
705                                   const GError    *error,
706                                   gpointer         user_data,
707                                   GObject         *list)
708 {
709         guint i;
710
711         if (error) {
712                 DEBUG ("Error: %s", error->message);
713                 return;
714         }
715
716         for (i = 0; i < channels->len; i++) {
717                 GValueArray  *chan_struct;
718                 const gchar  *object_path;
719                 const gchar  *channel_type;
720                 TpHandleType  handle_type;
721                 guint         handle;
722
723                 chan_struct = g_ptr_array_index (channels, i);
724                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
725                 channel_type = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
726                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
727                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
728
729                 tp_contact_list_group_add_channel (EMPATHY_TP_CONTACT_LIST (list),
730                                                    object_path, channel_type,
731                                                    handle_type, handle);
732         }
733 }
734
735 static void
736 tp_contact_list_finalize (GObject *object)
737 {
738         EmpathyTpContactListPriv *priv;
739         EmpathyTpContactList     *list;
740         GHashTableIter            iter;
741         gpointer                  channel;
742
743         list = EMPATHY_TP_CONTACT_LIST (object);
744         priv = GET_PRIV (list);
745
746         DEBUG ("finalize: %p", object);
747
748         if (priv->subscribe) {
749                 g_object_unref (priv->subscribe);
750         }
751         if (priv->publish) {
752                 g_object_unref (priv->publish);
753         }
754         if (priv->stored) {
755                 g_object_unref (priv->stored);
756         }
757
758         if (priv->connection) {
759                 g_object_unref (priv->connection);
760         }
761
762         if (priv->factory) {
763                 g_object_unref (priv->factory);
764         }
765
766         g_hash_table_iter_init (&iter, priv->groups);
767         while (g_hash_table_iter_next (&iter, NULL, &channel)) {
768                 g_signal_handlers_disconnect_by_func (channel,
769                         tp_contact_list_group_invalidated_cb, list);
770         }
771
772         g_hash_table_destroy (priv->groups);
773         g_hash_table_destroy (priv->members);
774         g_hash_table_destroy (priv->pendings);
775         g_hash_table_destroy (priv->add_to_group);
776
777         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
778 }
779
780 static void
781 store_create_channel_cb (TpConnection *conn,
782                          const gchar *path,
783                          GHashTable *properties,
784                          const GError *error,
785                          gpointer user_data,
786                          GObject *weak_object)
787 {
788         EmpathyTpContactList *list = user_data;
789         EmpathyTpContactListPriv *priv = GET_PRIV (list);
790
791         if (error != NULL) {
792                 DEBUG ("failed: %s\n", error->message);
793                 return;
794         }
795
796         priv->stored = tp_channel_new_from_properties (conn, path, properties, NULL);
797 }
798
799 static void
800 conn_ready_cb (TpConnection *connection,
801                const GError *error,
802                gpointer data)
803 {
804         EmpathyTpContactList *list = data;
805         EmpathyTpContactListPriv *priv = GET_PRIV (list);
806         GHashTable *request;
807         GValue *value;
808
809         if (error != NULL) {
810                 DEBUG ("failed: %s", error->message);
811                 goto out;
812         }
813
814         /* Try to request the 'stored' list. */
815         request = g_hash_table_new_full (g_str_hash, g_str_equal,
816                 NULL, (GDestroyNotify) tp_g_value_slice_free);
817
818         /* org.freedesktop.Telepathy.Channel.ChannelType */
819         value = tp_g_value_slice_new_string (TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
820         g_hash_table_insert (request, TP_IFACE_CHANNEL ".ChannelType", value);
821
822         /* org.freedesktop.Telepathy.Channel.TargetHandleType */
823         value = tp_g_value_slice_new_uint (TP_HANDLE_TYPE_LIST);
824         g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetHandleType", value);
825
826         /* org.freedesktop.Telepathy.Channel.TargetID */
827         value = tp_g_value_slice_new_string ("stored");
828         g_hash_table_insert (request, TP_IFACE_CHANNEL ".TargetID", value);
829
830         tp_cli_connection_interface_requests_call_create_channel (priv->connection,
831                 -1, request, store_create_channel_cb, list, NULL, G_OBJECT (list));
832
833         g_hash_table_unref (request);
834
835 out:
836         g_object_unref (list);
837 }
838
839 static void
840 tp_contact_list_constructed (GObject *list)
841 {
842         EmpathyTpContactListPriv *priv = GET_PRIV (list);
843         gchar                    *protocol_name = NULL;
844         const gchar              *names[] = {NULL, NULL};
845
846         priv->factory = empathy_tp_contact_factory_dup_singleton (priv->connection);
847
848         /* call GetAliasFlags */
849         if (tp_proxy_has_interface_by_id (priv->connection,
850                                 TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING)) {
851                 tp_cli_connection_interface_aliasing_call_get_alias_flags (
852                                 priv->connection,
853                                 -1,
854                                 tp_contact_list_get_alias_flags_cb,
855                                 NULL, NULL,
856                                 G_OBJECT (list));
857         }
858
859         /* lookup RequestableChannelClasses */
860         if (tp_proxy_has_interface_by_id (priv->connection,
861                                 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)) {
862                 tp_cli_dbus_properties_call_get (priv->connection,
863                                 -1,
864                                 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
865                                 "RequestableChannelClasses",
866                                 tp_contact_list_get_requestablechannelclasses_cb,
867                                 NULL, NULL,
868                                 G_OBJECT (list));
869         } else {
870                 /* we just don't know... better mark the flag just in case */
871                 priv->flags |= EMPATHY_CONTACT_LIST_CAN_GROUP;
872         }
873
874         names[0] = "publish";
875         tp_cli_connection_call_request_handles (priv->connection,
876                                                 -1,
877                                                 TP_HANDLE_TYPE_LIST,
878                                                 names,
879                                                 tp_contact_list_publish_request_handle_cb,
880                                                 NULL, NULL,
881                                                 G_OBJECT (list));
882         names[0] = "subscribe";
883         tp_cli_connection_call_request_handles (priv->connection,
884                                                 -1,
885                                                 TP_HANDLE_TYPE_LIST,
886                                                 names,
887                                                 tp_contact_list_subscribe_request_handle_cb,
888                                                 NULL, NULL,
889                                                 G_OBJECT (list));
890
891         g_object_ref (list);
892         tp_connection_call_when_ready (priv->connection, conn_ready_cb, list);
893
894         tp_cli_connection_call_list_channels (priv->connection, -1,
895                                               tp_contact_list_list_channels_cb,
896                                               NULL, NULL,
897                                               list);
898
899         tp_cli_connection_connect_to_new_channel (priv->connection,
900                                                   tp_contact_list_new_channel_cb,
901                                                   NULL, NULL,
902                                                   list, NULL);
903
904         /* Check for protocols that does not support contact groups. We can
905          * put all contacts into a special group in that case.
906          * FIXME: Default group should be an information in the profile */
907         tp_connection_parse_object_path (priv->connection, &protocol_name, NULL);
908         if (!tp_strdiff (protocol_name, "local-xmpp")) {
909                 priv->protocol_group = _("People nearby");
910         }
911         g_free (protocol_name);
912 }
913
914 static void
915 tp_contact_list_get_property (GObject    *object,
916                               guint       param_id,
917                               GValue     *value,
918                               GParamSpec *pspec)
919 {
920         EmpathyTpContactListPriv *priv = GET_PRIV (object);
921
922         switch (param_id) {
923         case PROP_CONNECTION:
924                 g_value_set_object (value, priv->connection);
925                 break;
926         default:
927                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
928                 break;
929         };
930 }
931
932 static void
933 tp_contact_list_set_property (GObject      *object,
934                               guint         param_id,
935                               const GValue *value,
936                               GParamSpec   *pspec)
937 {
938         EmpathyTpContactListPriv *priv = GET_PRIV (object);
939
940         switch (param_id) {
941         case PROP_CONNECTION:
942                 priv->connection = g_value_dup_object (value);
943                 break;
944         default:
945                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
946                 break;
947         };
948 }
949
950 static void
951 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
952 {
953         GObjectClass *object_class = G_OBJECT_CLASS (klass);
954
955         object_class->finalize = tp_contact_list_finalize;
956         object_class->constructed = tp_contact_list_constructed;
957         object_class->get_property = tp_contact_list_get_property;
958         object_class->set_property = tp_contact_list_set_property;
959
960         g_object_class_install_property (object_class,
961                                          PROP_CONNECTION,
962                                          g_param_spec_object ("connection",
963                                                               "The Connection",
964                                                               "The connection associated with the contact list",
965                                                               TP_TYPE_CONNECTION,
966                                                               G_PARAM_READWRITE |
967                                                               G_PARAM_CONSTRUCT_ONLY));
968
969         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
970 }
971
972 static void
973 tp_contact_list_array_free (gpointer handles)
974 {
975         g_array_free (handles, TRUE);
976 }
977
978 static void
979 empathy_tp_contact_list_init (EmpathyTpContactList *list)
980 {
981         EmpathyTpContactListPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (list,
982                 EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv);
983
984         list->priv = priv;
985
986         /* Map group's name to group's TpChannel. The group name string is owned
987          * by the TpChannel object */
988         priv->groups = g_hash_table_new_full (g_str_hash, g_str_equal,
989                                               NULL,
990                                               (GDestroyNotify) g_object_unref);
991
992         /* Map contact's handle to EmpathyContact object */
993         priv->members = g_hash_table_new_full (g_direct_hash, g_direct_equal,
994                                                NULL,
995                                                (GDestroyNotify) g_object_unref);
996
997         /* Map contact's handle to EmpathyContact object */
998         priv->pendings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
999                                                 NULL,
1000                                                 (GDestroyNotify) g_object_unref);
1001
1002         /* Map group's name to GArray of handle */
1003         priv->add_to_group = g_hash_table_new_full (g_str_hash, g_str_equal,
1004                                                     g_free,
1005                                                     tp_contact_list_array_free);
1006 }
1007
1008 EmpathyTpContactList *
1009 empathy_tp_contact_list_new (TpConnection *connection)
1010 {
1011         return g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST,
1012                              "connection", connection,
1013                              NULL);
1014 }
1015
1016 TpConnection *
1017 empathy_tp_contact_list_get_connection (EmpathyTpContactList *list)
1018 {
1019         EmpathyTpContactListPriv *priv;
1020
1021         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
1022
1023         priv = GET_PRIV (list);
1024
1025         return priv->connection;
1026 }
1027
1028 static void
1029 tp_contact_list_add (EmpathyContactList *list,
1030                      EmpathyContact     *contact,
1031                      const gchar        *message)
1032 {
1033         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1034         TpHandle handle;
1035         GArray handles = {(gchar *) &handle, 1};
1036
1037         handle = empathy_contact_get_handle (contact);
1038         if (priv->subscribe) {
1039                 tp_cli_channel_interface_group_call_add_members (priv->subscribe,
1040                         -1, &handles, message, NULL, NULL, NULL, NULL);
1041         }
1042         if (priv->publish) {
1043                 TpChannelGroupFlags flags = tp_channel_group_get_flags (priv->subscribe);
1044                 if (flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD ||
1045                     g_hash_table_lookup (priv->pendings, GUINT_TO_POINTER (handle))) {
1046                         tp_cli_channel_interface_group_call_add_members (priv->publish,
1047                                 -1, &handles, message, NULL, NULL, NULL, NULL);
1048                 }
1049         }
1050 }
1051
1052 static void
1053 tp_contact_list_remove (EmpathyContactList *list,
1054                         EmpathyContact     *contact,
1055                         const gchar        *message)
1056 {
1057         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1058         TpHandle handle;
1059         GArray handles = {(gchar *) &handle, 1};
1060
1061         handle = empathy_contact_get_handle (contact);
1062
1063         if (priv->stored != NULL) {
1064                 tp_cli_channel_interface_group_call_remove_members (priv->stored,
1065                         -1, &handles, message, NULL, NULL, NULL, NULL);
1066                 /* Contact will be removed from 'publish' and 'subscribe' too */
1067                 return;
1068         }
1069
1070         if (priv->subscribe) {
1071                 tp_cli_channel_interface_group_call_remove_members (priv->subscribe,
1072                         -1, &handles, message, NULL, NULL, NULL, NULL);
1073         }
1074         if (priv->publish) {
1075                 tp_cli_channel_interface_group_call_remove_members (priv->publish,
1076                         -1, &handles, message, NULL, NULL, NULL, NULL);
1077         }
1078 }
1079
1080 static GList *
1081 tp_contact_list_get_members (EmpathyContactList *list)
1082 {
1083         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1084         GList *ret;
1085
1086         ret = g_hash_table_get_values (priv->members);
1087         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1088         return ret;
1089 }
1090
1091 static GList *
1092 tp_contact_list_get_pendings (EmpathyContactList *list)
1093 {
1094         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1095         GList *ret;
1096
1097         ret = g_hash_table_get_values (priv->pendings);
1098         g_list_foreach (ret, (GFunc) g_object_ref, NULL);
1099         return ret;
1100 }
1101
1102 static GList *
1103 tp_contact_list_get_all_groups (EmpathyContactList *list)
1104 {
1105         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1106         GList                    *ret, *l;
1107
1108         ret = g_hash_table_get_keys (priv->groups);
1109         for (l = ret; l; l = l->next) {
1110                 l->data = g_strdup (l->data);
1111         }
1112
1113         if (priv->protocol_group) {
1114                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
1115         }
1116
1117         return ret;
1118 }
1119
1120 static GList *
1121 tp_contact_list_get_groups (EmpathyContactList *list,
1122                             EmpathyContact     *contact)
1123 {
1124         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
1125         GList                     *ret = NULL;
1126         GHashTableIter             iter;
1127         gpointer                   group_name;
1128         gpointer                   channel;
1129         TpHandle                   handle;
1130
1131         handle = empathy_contact_get_handle (contact);
1132         g_hash_table_iter_init (&iter, priv->groups);
1133         while (g_hash_table_iter_next (&iter, &group_name, &channel)) {
1134                 const TpIntSet *members;
1135
1136                 members = tp_channel_group_get_members (channel);
1137                 if (tp_intset_is_member (members, handle)) {
1138                         ret = g_list_prepend (ret, g_strdup (group_name));
1139                 }
1140         }
1141
1142         if (priv->protocol_group) {
1143                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
1144         }
1145
1146         return ret;
1147 }
1148
1149 static void
1150 tp_contact_list_add_to_group (EmpathyContactList *list,
1151                               EmpathyContact     *contact,
1152                               const gchar        *group_name)
1153 {
1154         TpHandle handle;
1155         GArray *handles;
1156
1157         handle = empathy_contact_get_handle (contact);
1158         handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
1159         g_array_append_val (handles, handle);
1160         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1161                                    group_name, handles);
1162 }
1163
1164 static void
1165 tp_contact_list_remove_from_group (EmpathyContactList *list,
1166                                    EmpathyContact     *contact,
1167                                    const gchar        *group_name)
1168 {
1169         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1170         TpChannel                *channel;
1171         TpHandle                  handle;
1172         GArray                    handles = {(gchar *) &handle, 1};
1173
1174         channel = g_hash_table_lookup (priv->groups, group_name);
1175         if (channel == NULL) {
1176                 return;
1177         }
1178
1179         handle = empathy_contact_get_handle (contact);
1180         DEBUG ("remove contact %s (%d) from group %s",
1181                 empathy_contact_get_id (contact), handle, group_name);
1182
1183         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1184                 &handles, NULL, NULL, NULL, NULL, NULL);
1185 }
1186
1187 static void
1188 tp_contact_list_rename_group (EmpathyContactList *list,
1189                               const gchar        *old_group_name,
1190                               const gchar        *new_group_name)
1191 {
1192         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1193         TpChannel                *channel;
1194         const TpIntSet           *members;
1195         GArray                   *handles;
1196
1197         channel = g_hash_table_lookup (priv->groups, old_group_name);
1198         if (channel == NULL) {
1199                 return;
1200         }
1201
1202         DEBUG ("rename group %s to %s", old_group_name, new_group_name);
1203
1204         /* Remove all members and close the old channel */
1205         members = tp_channel_group_get_members (channel);
1206         handles = tp_intset_to_array (members);
1207         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1208                 handles, NULL, NULL, NULL, NULL, NULL);
1209         tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1210
1211         tp_contact_list_group_add (EMPATHY_TP_CONTACT_LIST (list),
1212                                    new_group_name, handles);
1213 }
1214
1215 static void
1216 tp_contact_list_remove_group (EmpathyContactList *list,
1217                               const gchar *group_name)
1218 {
1219         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1220         TpChannel                *channel;
1221         const TpIntSet           *members;
1222         GArray                   *handles;
1223
1224         channel = g_hash_table_lookup (priv->groups, group_name);
1225         if (channel == NULL) {
1226                 return;
1227         }
1228
1229         DEBUG ("remove group %s", group_name);
1230
1231         /* Remove all members and close the channel */
1232         members = tp_channel_group_get_members (channel);
1233         handles = tp_intset_to_array (members);
1234         tp_cli_channel_interface_group_call_remove_members (channel, -1,
1235                 handles, NULL, NULL, NULL, NULL, NULL);
1236         tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
1237         g_array_free (handles, TRUE);
1238 }
1239
1240 static EmpathyContactListFlags
1241 tp_contact_list_get_flags (EmpathyContactList *list)
1242 {
1243         EmpathyTpContactListPriv *priv;
1244         EmpathyContactListFlags flags;
1245
1246         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), FALSE);
1247
1248         priv = GET_PRIV (list);
1249         flags = priv->flags;
1250
1251         if (priv->subscribe != NULL) {
1252                 TpChannelGroupFlags group_flags;
1253
1254                 group_flags = tp_channel_group_get_flags (priv->subscribe);
1255
1256                 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) {
1257                         flags |= EMPATHY_CONTACT_LIST_CAN_ADD;
1258                 }
1259
1260                 if (group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) {
1261                         flags |= EMPATHY_CONTACT_LIST_CAN_REMOVE;
1262                 }
1263         }
1264
1265         return flags;
1266 }
1267
1268 static void
1269 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1270 {
1271         iface->add               = tp_contact_list_add;
1272         iface->remove            = tp_contact_list_remove;
1273         iface->get_members       = tp_contact_list_get_members;
1274         iface->get_pendings      = tp_contact_list_get_pendings;
1275         iface->get_all_groups    = tp_contact_list_get_all_groups;
1276         iface->get_groups        = tp_contact_list_get_groups;
1277         iface->add_to_group      = tp_contact_list_add_to_group;
1278         iface->remove_from_group = tp_contact_list_remove_from_group;
1279         iface->rename_group      = tp_contact_list_rename_group;
1280         iface->remove_group      = tp_contact_list_remove_group;
1281         iface->get_flags         = tp_contact_list_get_flags;
1282 }
1283
1284 void
1285 empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
1286 {
1287         EmpathyTpContactListPriv *priv = GET_PRIV (list);
1288         GHashTableIter            iter;
1289         gpointer                  contact;
1290
1291         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1292
1293         /* Remove all contacts */
1294         g_hash_table_iter_init (&iter, priv->members);
1295         while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1296                 g_signal_emit_by_name (list, "members-changed", contact,
1297                                        NULL, 0, NULL,
1298                                        FALSE);
1299         }
1300         g_hash_table_remove_all (priv->members);
1301
1302         g_hash_table_iter_init (&iter, priv->pendings);
1303         while (g_hash_table_iter_next (&iter, NULL, &contact)) {
1304                 g_signal_emit_by_name (list, "pendings-changed", contact,
1305                                        NULL, 0, NULL,
1306                                        FALSE);
1307         }
1308         g_hash_table_remove_all (priv->pendings);
1309 }
1310