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