]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-group.c
Make use of tp-glib debug system.
[empathy.git] / libempathy / empathy-tp-group.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2006 Xavier Claessens <xclaesse@gmail.com>
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <libmissioncontrol/mc-account.h>
26
27 #include <telepathy-glib/channel.h>
28 #include <telepathy-glib/util.h>
29 #include <telepathy-glib/interfaces.h>
30
31 #include "empathy-tp-group.h"
32 #include "empathy-contact-factory.h"
33 #include "empathy-utils.h"
34 #include "empathy-marshal.h"
35
36 #define DEBUG_FLAG EMPATHY_DEBUG_TP
37 #include "empathy-debug.h"
38
39 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
40                        EMPATHY_TYPE_TP_GROUP, EmpathyTpGroupPriv))
41
42 struct _EmpathyTpGroupPriv {
43         TpChannel             *channel;
44         gboolean               ready;
45
46         EmpathyContactFactory *factory;
47         McAccount             *account;
48         gchar                 *group_name;
49         guint                  self_handle;
50         GList                 *members;
51         GList                 *local_pendings;
52         GList                 *remote_pendings;
53 };
54
55 static void empathy_tp_group_class_init (EmpathyTpGroupClass *klass);
56 static void empathy_tp_group_init       (EmpathyTpGroup      *group);
57
58 enum {
59         MEMBER_ADDED,
60         MEMBER_REMOVED,
61         LOCAL_PENDING,
62         REMOTE_PENDING,
63         DESTROY,
64         LAST_SIGNAL
65 };
66
67 enum {
68         PROP_0,
69         PROP_CHANNEL,
70         PROP_READY
71 };
72
73 static guint signals[LAST_SIGNAL];
74
75 G_DEFINE_TYPE (EmpathyTpGroup, empathy_tp_group, G_TYPE_OBJECT);
76
77 static EmpathyContact *
78 tp_group_get_contact (EmpathyTpGroup *group,
79                       guint           handle)
80 {
81         EmpathyTpGroupPriv *priv = GET_PRIV (group);
82         EmpathyContact     *contact = NULL;
83         
84         if (handle != 0) {
85                 contact = empathy_contact_factory_get_from_handle (priv->factory,
86                                                                    priv->account,
87                                                                    handle);
88         }
89
90         if (contact && handle == priv->self_handle) {
91                 empathy_contact_set_is_user (contact, TRUE);
92         }
93
94         return contact;
95 }
96
97 static GList *
98 tp_group_get_contacts (EmpathyTpGroup *group,
99                        const GArray   *handles)
100 {
101         EmpathyTpGroupPriv *priv = GET_PRIV (group);
102         GList              *contacts,  *l;
103
104         if (!handles) {
105                 return NULL;
106         }
107
108         contacts = empathy_contact_factory_get_from_handles (priv->factory,
109                                                              priv->account,
110                                                              handles);
111
112         /* FIXME: Only useful if the group has a different self handle than
113          * the connection, otherwise the contact factory already set that
114          * property. That can be known using group flags. */
115         for (l = contacts; l; l = l->next) {
116                 if (empathy_contact_get_handle (l->data) == priv->self_handle) {
117                         empathy_contact_set_is_user (l->data, TRUE);
118                 }
119         }
120
121         return contacts;
122 }
123
124 EmpathyPendingInfo *
125 empathy_pending_info_new (EmpathyContact *member,
126                           EmpathyContact *actor,
127                           const gchar    *message)
128 {
129         EmpathyPendingInfo *info;
130
131         info = g_slice_new0 (EmpathyPendingInfo);
132
133         if (member) {
134                 info->member = g_object_ref (member);
135         }
136         if (actor) {
137                 info->actor = g_object_ref (actor);
138         }
139         if (message) {
140                 info->message = g_strdup (message);
141         }
142
143         return info;
144 }
145
146 void
147 empathy_pending_info_free (EmpathyPendingInfo *info)
148 {
149         if (!info) {
150                 return;
151         }
152
153         if (info->member) {
154                 g_object_unref (info->member);
155         }
156         if (info->actor) {
157                 g_object_unref (info->actor);
158         }
159         g_free (info->message);
160
161         g_slice_free (EmpathyPendingInfo, info);
162 }
163
164 static gint
165 tp_group_local_pending_find (gconstpointer a,
166                              gconstpointer b)
167 {
168         const EmpathyPendingInfo *info = a;
169
170         return (info->member != b);
171 }
172
173 static void
174 tp_group_remove_from_pendings (EmpathyTpGroup *group,
175                                EmpathyContact *contact)
176 {
177         EmpathyTpGroupPriv *priv = GET_PRIV (group);
178         GList              *l;
179
180         /* local pending */
181         l = g_list_find_custom (priv->local_pendings,
182                                 contact,
183                                 tp_group_local_pending_find);
184         if (l) {
185                 empathy_pending_info_free (l->data);
186                 priv->local_pendings = g_list_delete_link (priv->local_pendings, l);
187         }
188
189         /* remote pending */
190         l = g_list_find (priv->remote_pendings, contact);
191         if (l) {
192                 g_object_unref (l->data);
193                 priv->remote_pendings = g_list_delete_link (priv->remote_pendings, l);
194         }
195 }
196
197 static void
198 tp_group_update_members (EmpathyTpGroup *group,
199                          const gchar    *message,
200                          const GArray   *added,
201                          const GArray   *removed,
202                          const GArray   *local_pending,
203                          const GArray   *remote_pending,
204                          guint           actor,
205                          guint           reason)
206 {
207         EmpathyTpGroupPriv *priv = GET_PRIV (group);
208         EmpathyContact     *actor_contact = NULL;
209         GList              *contacts, *l, *ll;
210
211         actor_contact = tp_group_get_contact (group, actor);
212
213         DEBUG ("Members changed for list %s:\n"
214                 "  added-len=%d, current-len=%d\n"
215                 "  removed-len=%d\n"
216                 "  local-pending-len=%d, current-len=%d\n"
217                 "  remote-pending-len=%d, current-len=%d",
218                 priv->group_name, added ? added->len : 0,
219                 g_list_length (priv->members), removed ? removed->len : 0,
220                 local_pending ? local_pending->len : 0,
221                 g_list_length (priv->local_pendings),
222                 remote_pending ? remote_pending->len : 0,
223                 g_list_length (priv->remote_pendings));
224
225         /* Contacts added */
226         contacts = tp_group_get_contacts (group, added);
227         for (l = contacts; l; l = l->next) {
228                 tp_group_remove_from_pendings (group, l->data);
229
230                 /* If the contact is not yet member, add it and emit signal */
231                 if (!g_list_find (priv->members, l->data)) {
232                         priv->members = g_list_prepend (priv->members,
233                                                         g_object_ref (l->data));
234                         g_signal_emit (group, signals[MEMBER_ADDED], 0,
235                                        l->data, actor_contact, reason, message);
236                 }
237                 g_object_unref (l->data);
238         }
239         g_list_free (contacts);
240
241         /* Contacts removed */
242         contacts = tp_group_get_contacts (group, removed);
243         for (l = contacts; l; l = l->next) {
244                 tp_group_remove_from_pendings (group, l->data);
245
246                 /* If the contact is member, remove it and emit signal */
247                 if ((ll = g_list_find (priv->members, l->data))) {
248                         g_object_unref (ll->data);
249                         priv->members = g_list_delete_link (priv->members, ll);
250                         g_signal_emit (group, signals[MEMBER_REMOVED], 0,
251                                        l->data, actor_contact, reason, message);
252                 }
253                 g_object_unref (l->data);
254         }
255         g_list_free (contacts);
256
257         /* Contacts local pending */
258         contacts = tp_group_get_contacts (group, local_pending);
259         for (l = contacts; l; l = l->next) {
260                 /* If the contact is not yet local-pending, add it and emit signal */
261                 if (!g_list_find_custom (priv->local_pendings, l->data,
262                                          tp_group_local_pending_find)) {
263                         EmpathyPendingInfo *info;
264
265                         info = empathy_pending_info_new (l->data,
266                                                          actor_contact,
267                                                          message);
268
269                         priv->local_pendings = g_list_prepend (priv->local_pendings, info);
270                         g_signal_emit (group, signals[LOCAL_PENDING], 0,
271                                        l->data, actor_contact, reason, message);
272                 }
273                 g_object_unref (l->data);
274         }
275         g_list_free (contacts);
276
277         /* Contacts remote pending */
278         contacts = tp_group_get_contacts (group, remote_pending);
279         for (l = contacts; l; l = l->next) {
280                 /* If the contact is not yet remote-pending, add it and emit signal */
281                 if (!g_list_find (priv->remote_pendings, l->data)) {
282                         priv->remote_pendings = g_list_prepend (priv->remote_pendings,
283                                                                 g_object_ref (l->data));
284                         g_signal_emit (group, signals[REMOTE_PENDING], 0,
285                                        l->data, actor_contact, reason, message);
286                 }
287                 g_object_unref (l->data);
288         }
289         g_list_free (contacts);
290
291         if (actor_contact) {
292                 g_object_unref (actor_contact);
293         }
294
295         DEBUG ("Members changed done for list %s:\n"
296                 "  members-len=%d\n"
297                 "  local-pendings-len=%d\n"
298                 "  remote-pendings-len=%d",
299                 priv->group_name, g_list_length (priv->members),
300                 g_list_length (priv->local_pendings),
301                 g_list_length (priv->remote_pendings));
302 }
303
304 static void
305 tp_group_members_changed_cb (TpChannel    *channel,
306                              const gchar  *message,
307                              const GArray *added,
308                              const GArray *removed,
309                              const GArray *local_pending,
310                              const GArray *remote_pending,
311                              guint         actor,
312                              guint         reason,
313                              gpointer      user_data,
314                              GObject      *group)
315 {
316         EmpathyTpGroupPriv *priv = GET_PRIV (group);
317
318         if (priv->ready) {
319                 tp_group_update_members (EMPATHY_TP_GROUP (group), message,
320                                          added, removed,
321                                          local_pending, remote_pending,
322                                          actor, reason);
323         }
324 }
325
326 static void
327 tp_group_get_members_cb (TpChannel    *channel,
328                          const GArray *handles,
329                          const GError *error,
330                          gpointer      user_data,
331                          GObject      *group)
332 {
333         EmpathyTpGroupPriv *priv = GET_PRIV (group);
334
335         if (error) {
336                 DEBUG ("Failed to get members: %s", error->message);
337                 return;
338         }
339
340         tp_group_update_members (EMPATHY_TP_GROUP (group),
341                                  NULL,    /* message */
342                                  handles, /* added */
343                                  NULL,    /* removed */
344                                  NULL,    /* local_pending */
345                                  NULL,    /* remote_pending */
346                                  0,       /* actor */
347                                  0);      /* reason */
348
349         DEBUG ("Ready");
350         priv->ready = TRUE;
351         g_object_notify (group, "ready");
352 }
353
354 static void
355 tp_group_get_local_pending_cb (TpChannel        *channel,
356                                const GPtrArray  *array,
357                                const GError     *error,
358                                gpointer          user_data,
359                                GObject          *group)
360 {
361         GArray *handles;
362         guint   i = 0;
363         
364         if (error) {
365                 DEBUG ("Failed to get local pendings: %s", error->message);
366                 return;
367         }
368
369         handles = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
370         g_array_append_val (handles, i);
371         for (i = 0; array->len > i; i++) {
372                 GValueArray *pending_struct;
373                 const gchar *message;
374                 guint        member_handle;
375                 guint        actor_handle;
376                 guint        reason;
377
378                 pending_struct = g_ptr_array_index (array, i);
379                 member_handle = g_value_get_uint (g_value_array_get_nth (pending_struct, 0));
380                 actor_handle = g_value_get_uint (g_value_array_get_nth (pending_struct, 1));
381                 reason = g_value_get_uint (g_value_array_get_nth (pending_struct, 2));
382                 message = g_value_get_string (g_value_array_get_nth (pending_struct, 3));
383
384                 g_array_index (handles, guint, 0) = member_handle;
385
386                 tp_group_update_members (EMPATHY_TP_GROUP (group),
387                                          message,      /* message */
388                                          NULL,         /* added */
389                                          NULL,         /* removed */
390                                          handles,      /* local_pending */
391                                          NULL,         /* remote_pending */
392                                          actor_handle, /* actor */
393                                          reason);      /* reason */
394         }
395         g_array_free (handles, TRUE);
396 }
397
398 static void
399 tp_group_get_remote_pending_cb (TpChannel    *channel,
400                                 const GArray *handles,
401                                 const GError *error,
402                                 gpointer      user_data,
403                                 GObject      *group)
404 {
405         if (error) {
406                 DEBUG ("Failed to get remote pendings: %s", error->message);
407                 return;
408         }
409
410         tp_group_update_members (EMPATHY_TP_GROUP (group),
411                                  NULL,    /* message */
412                                  NULL,    /* added */
413                                  NULL,    /* removed */
414                                  NULL,    /* local_pending */
415                                  handles, /* remote_pending */
416                                  0,       /* actor */
417                                  0);      /* reason */
418 }
419
420 static void
421 tp_group_inspect_handles_cb (TpConnection  *connection,
422                              const gchar  **names,
423                              const GError  *error,
424                              gpointer       user_data,
425                              GObject       *group)
426 {
427         EmpathyTpGroupPriv *priv = GET_PRIV (group);
428
429         if (error) {
430                 DEBUG ("Failed to inspect channel handle: %s", error->message);
431                 return;
432         }
433
434         priv->group_name = g_strdup (*names);
435 }
436
437 static void
438 tp_group_invalidated_cb (TpProxy        *proxy,
439                          guint           domain,
440                          gint            code,
441                          gchar          *message,
442                          EmpathyTpGroup *group)
443 {
444         DEBUG ("Channel invalidated: %s", message);
445         g_signal_emit (group, signals[DESTROY], 0);
446 }
447
448 static void
449 tp_group_get_self_handle_cb (TpChannel    *proxy,
450                              guint         handle,
451                              const GError *error,
452                              gpointer      user_data,
453                              GObject      *group)
454 {
455         EmpathyTpGroupPriv *priv = GET_PRIV (group);
456         TpConnection       *connection;
457         guint               channel_handle;
458         guint               channel_handle_type;
459         GArray             *handles;
460
461         if (error) {
462                 DEBUG ("Failed to get self handle: %s", error->message);
463                 return;
464         }
465
466         priv->self_handle = handle;
467         tp_cli_channel_interface_group_connect_to_members_changed (priv->channel,
468                                                                    tp_group_members_changed_cb,
469                                                                    NULL, NULL,
470                                                                    group, NULL);
471
472         /* GetMembers is called last, so it will be the last to get the reply,
473          * so we'll be ready once that call return. */
474         g_object_get (priv->channel,
475                       "connection", &connection,
476                       "handle-type", &channel_handle_type,
477                       "handle", &channel_handle,
478                       NULL);
479         handles = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
480         g_array_prepend_val (handles, channel_handle);
481         tp_cli_connection_call_inspect_handles (connection, -1,
482                                                 channel_handle_type,
483                                                 handles,
484                                                 tp_group_inspect_handles_cb,
485                                                 NULL, NULL,
486                                                 group);
487         g_array_free (handles, TRUE);
488
489         tp_cli_channel_interface_group_call_get_local_pending_members_with_info
490                                                         (priv->channel, -1,
491                                                          tp_group_get_local_pending_cb,
492                                                          NULL, NULL, 
493                                                          group);
494         tp_cli_channel_interface_group_call_get_remote_pending_members
495                                                         (priv->channel, -1,
496                                                          tp_group_get_remote_pending_cb,
497                                                          NULL, NULL, 
498                                                          group);
499         tp_cli_channel_interface_group_call_get_members (priv->channel, -1,
500                                                          tp_group_get_members_cb,
501                                                          NULL, NULL, 
502                                                          group);
503 }
504
505 static void
506 tp_group_factory_ready_cb (EmpathyTpGroup *group)
507 {
508         EmpathyTpGroupPriv      *priv = GET_PRIV (group);
509         EmpathyTpContactFactory *tp_factory;
510
511         tp_factory = empathy_contact_factory_get_tp_factory (priv->factory, priv->account);
512         g_signal_handlers_disconnect_by_func (tp_factory, tp_group_factory_ready_cb, group);
513         tp_cli_channel_interface_group_call_get_self_handle (priv->channel, -1,
514                                                              tp_group_get_self_handle_cb,
515                                                              NULL, NULL,
516                                                              G_OBJECT (group));
517 }
518
519 static void
520 tp_group_channel_ready_cb (EmpathyTpGroup *group)
521 {
522         EmpathyTpGroupPriv      *priv = GET_PRIV (group);
523         EmpathyTpContactFactory *tp_factory;
524
525         tp_factory = empathy_contact_factory_get_tp_factory (priv->factory,
526                                                              priv->account);
527         if (empathy_tp_contact_factory_is_ready (tp_factory)) {
528                 tp_group_factory_ready_cb (group);
529         } else {
530                 g_signal_connect_swapped (tp_factory, "notify::ready",
531                                           G_CALLBACK (tp_group_factory_ready_cb),
532                                           group);
533         }
534 }
535
536 static void
537 tp_group_finalize (GObject *object)
538 {
539         EmpathyTpGroupPriv      *priv = GET_PRIV (object);
540         EmpathyTpContactFactory *tp_factory;
541
542         DEBUG ("finalize: %p", object);
543
544         tp_factory = empathy_contact_factory_get_tp_factory (priv->factory, priv->account);
545         g_signal_handlers_disconnect_by_func (tp_factory, tp_group_factory_ready_cb, object);
546
547         if (priv->channel) {
548                 g_signal_handlers_disconnect_by_func (priv->channel,
549                                                       tp_group_invalidated_cb,
550                                                       object);
551                 g_object_unref (priv->channel);
552         }
553         if (priv->account) {
554                 g_object_unref (priv->account);
555         }
556         if (priv->factory) {
557                 g_object_unref (priv->factory);
558         }
559         g_free (priv->group_name);
560
561         g_list_foreach (priv->members, (GFunc) g_object_unref, NULL);
562         g_list_free (priv->members);
563
564         g_list_foreach (priv->local_pendings, (GFunc) empathy_pending_info_free, NULL);
565         g_list_free (priv->local_pendings);
566
567         g_list_foreach (priv->remote_pendings, (GFunc) g_object_unref, NULL);
568         g_list_free (priv->remote_pendings);
569
570         G_OBJECT_CLASS (empathy_tp_group_parent_class)->finalize (object);
571 }
572
573 static void
574 tp_group_constructed (GObject *group)
575 {
576         EmpathyTpGroupPriv *priv = GET_PRIV (group);
577         gboolean            channel_ready;
578
579         priv->factory = empathy_contact_factory_new ();
580         priv->account = empathy_channel_get_account (priv->channel);
581
582         g_signal_connect (priv->channel, "invalidated",
583                           G_CALLBACK (tp_group_invalidated_cb),
584                           group);
585
586         g_object_get (priv->channel, "channel-ready", &channel_ready, NULL);
587         if (channel_ready) {
588                 tp_group_channel_ready_cb (EMPATHY_TP_GROUP (group));
589         } else {
590                 g_signal_connect_swapped (priv->channel, "notify::channel-ready",
591                                           G_CALLBACK (tp_group_channel_ready_cb),
592                                           group);
593         }
594 }
595
596 static void
597 tp_group_get_property (GObject    *object,
598                        guint       param_id,
599                        GValue     *value,
600                        GParamSpec *pspec)
601 {
602         EmpathyTpGroupPriv *priv = GET_PRIV (object);
603
604         switch (param_id) {
605         case PROP_CHANNEL:
606                 g_value_set_object (value, priv->channel);
607                 break;
608         case PROP_READY:
609                 g_value_set_boolean (value, priv->ready);
610                 break;
611         default:
612                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
613                 break;
614         };
615 }
616
617 static void
618 tp_group_set_property (GObject      *object,
619                        guint         param_id,
620                        const GValue *value,
621                        GParamSpec   *pspec)
622 {
623         EmpathyTpGroupPriv *priv = GET_PRIV (object);
624
625         switch (param_id) {
626         case PROP_CHANNEL:
627                 priv->channel = g_object_ref (g_value_get_object (value));
628                 break;
629         default:
630                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
631                 break;
632         };
633 }
634
635 static void
636 empathy_tp_group_class_init (EmpathyTpGroupClass *klass)
637 {
638         GObjectClass *object_class = G_OBJECT_CLASS (klass);
639
640         object_class->finalize = tp_group_finalize;
641         object_class->constructed = tp_group_constructed;
642         object_class->get_property = tp_group_get_property;
643         object_class->set_property = tp_group_set_property;
644
645         g_object_class_install_property (object_class,
646                                          PROP_CHANNEL,
647                                          g_param_spec_object ("channel",
648                                                               "telepathy channel",
649                                                               "The channel for the group",
650                                                               TP_TYPE_CHANNEL,
651                                                               G_PARAM_READWRITE |
652                                                               G_PARAM_CONSTRUCT_ONLY));
653         g_object_class_install_property (object_class,
654                                          PROP_READY,
655                                          g_param_spec_boolean ("ready",
656                                                                "Is the object ready",
657                                                                "This object can't be used until this becomes true",
658                                                                FALSE,
659                                                                G_PARAM_READABLE));
660
661         signals[MEMBER_ADDED] =
662                 g_signal_new ("member-added",
663                               G_TYPE_FROM_CLASS (klass),
664                               G_SIGNAL_RUN_LAST,
665                               0,
666                               NULL, NULL,
667                               _empathy_marshal_VOID__OBJECT_OBJECT_UINT_STRING,
668                               G_TYPE_NONE,
669                               4, EMPATHY_TYPE_CONTACT, EMPATHY_TYPE_CONTACT, G_TYPE_UINT, G_TYPE_STRING);
670
671         signals[MEMBER_REMOVED] =
672                 g_signal_new ("member-removed",
673                               G_TYPE_FROM_CLASS (klass),
674                               G_SIGNAL_RUN_LAST,
675                               0,
676                               NULL, NULL,
677                               _empathy_marshal_VOID__OBJECT_OBJECT_UINT_STRING,
678                               G_TYPE_NONE,
679                               4, EMPATHY_TYPE_CONTACT, EMPATHY_TYPE_CONTACT, G_TYPE_UINT, G_TYPE_STRING);
680
681         signals[LOCAL_PENDING] =
682                 g_signal_new ("local-pending",
683                               G_TYPE_FROM_CLASS (klass),
684                               G_SIGNAL_RUN_LAST,
685                               0,
686                               NULL, NULL,
687                               _empathy_marshal_VOID__OBJECT_OBJECT_UINT_STRING,
688                               G_TYPE_NONE,
689                               4, EMPATHY_TYPE_CONTACT, EMPATHY_TYPE_CONTACT, G_TYPE_UINT, G_TYPE_STRING);
690
691         signals[REMOTE_PENDING] =
692                 g_signal_new ("remote-pending",
693                               G_TYPE_FROM_CLASS (klass),
694                               G_SIGNAL_RUN_LAST,
695                               0,
696                               NULL, NULL,
697                               _empathy_marshal_VOID__OBJECT_OBJECT_UINT_STRING,
698                               G_TYPE_NONE,
699                               4, EMPATHY_TYPE_CONTACT, EMPATHY_TYPE_CONTACT, G_TYPE_UINT, G_TYPE_STRING);
700
701         signals[DESTROY] =
702                 g_signal_new ("destroy",
703                               G_TYPE_FROM_CLASS (klass),
704                               G_SIGNAL_RUN_LAST,
705                               0,
706                               NULL, NULL,
707                               g_cclosure_marshal_VOID__VOID,
708                               G_TYPE_NONE,
709                               0);
710
711         g_type_class_add_private (object_class, sizeof (EmpathyTpGroupPriv));
712 }
713
714 static void
715 empathy_tp_group_init (EmpathyTpGroup *group)
716 {
717 }
718
719 EmpathyTpGroup *
720 empathy_tp_group_new (TpChannel *channel)
721 {
722         g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
723
724         return g_object_new (EMPATHY_TYPE_TP_GROUP, 
725                              "channel", channel,
726                              NULL);
727 }
728
729 static void
730 tp_group_async_cb (TpChannel    *channel,
731                    const GError *error,
732                    gpointer      user_data,
733                    GObject      *weak_object)
734 {
735         const gchar *msg = user_data;
736
737         if (error) {
738                 DEBUG ("%s: %s", msg, error->message);
739         }
740 }
741
742 void
743 empathy_tp_group_close (EmpathyTpGroup *group)
744 {
745         EmpathyTpGroupPriv *priv = GET_PRIV (group);
746
747         g_return_if_fail (EMPATHY_IS_TP_GROUP (group));
748         g_return_if_fail (priv->ready);
749
750         tp_cli_channel_call_close (priv->channel, -1,
751                                    tp_group_async_cb,
752                                    "Failed to close", NULL,
753                                    G_OBJECT (group));
754 }
755
756 static GArray *
757 tp_group_get_handles (GList *contacts)
758 {
759         GArray *handles;
760         guint   length;
761         GList  *l;
762
763         length = g_list_length (contacts);
764         handles = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
765
766         for (l = contacts; l; l = l->next) {
767                 guint handle;
768
769                 handle = empathy_contact_get_handle (l->data);
770                 g_array_append_val (handles, handle);
771         }
772
773         return handles;
774 }
775
776 void
777 empathy_tp_group_add_members (EmpathyTpGroup *group,
778                               GList          *contacts,
779                               const gchar    *message)
780 {
781         EmpathyTpGroupPriv *priv = GET_PRIV (group);
782         GArray             *handles;
783
784         g_return_if_fail (EMPATHY_IS_TP_GROUP (group));
785         g_return_if_fail (contacts != NULL);
786         g_return_if_fail (priv->ready);
787
788         handles = tp_group_get_handles (contacts);
789         tp_cli_channel_interface_group_call_add_members (priv->channel, -1,
790                                                          handles,
791                                                          message,
792                                                          tp_group_async_cb,
793                                                          "Failed to add members", NULL,
794                                                          G_OBJECT (group));
795         g_array_free (handles, TRUE);
796 }
797
798 void
799 empathy_tp_group_remove_members (EmpathyTpGroup *group,
800                                  GList          *contacts,
801                                  const gchar    *message)
802 {
803         EmpathyTpGroupPriv *priv = GET_PRIV (group);
804         GArray             *handles;
805
806         g_return_if_fail (EMPATHY_IS_TP_GROUP (group));
807         g_return_if_fail (contacts != NULL);
808         g_return_if_fail (priv->ready);
809
810         handles = tp_group_get_handles (contacts);
811         tp_cli_channel_interface_group_call_remove_members (priv->channel, -1,
812                                                             handles,
813                                                             message,
814                                                             tp_group_async_cb,
815                                                             "Failed to remove members", NULL,
816                                                             G_OBJECT (group));
817         g_array_free (handles, TRUE);
818 }
819
820 void
821 empathy_tp_group_add_member (EmpathyTpGroup *group,
822                              EmpathyContact *contact,
823                              const gchar    *message)
824 {
825         GList *contacts;
826
827         contacts = g_list_prepend (NULL, contact);
828         empathy_tp_group_add_members (group, contacts, message);
829         g_list_free (contacts);
830 }
831
832 void
833 empathy_tp_group_remove_member (EmpathyTpGroup *group,
834                                 EmpathyContact *contact,
835                                 const gchar    *message)
836 {
837         GList *contacts;
838
839         contacts = g_list_prepend (NULL, contact);
840         empathy_tp_group_remove_members (group, contacts, message);
841         g_list_free (contacts);
842 }
843
844 GList *
845 empathy_tp_group_get_members (EmpathyTpGroup *group)
846 {
847         EmpathyTpGroupPriv *priv = GET_PRIV (group);
848
849         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), NULL);
850
851         g_list_foreach (priv->members, (GFunc) g_object_ref, NULL);
852
853         return g_list_copy (priv->members);
854 }
855
856 GList *
857 empathy_tp_group_get_local_pendings (EmpathyTpGroup *group)
858 {
859         EmpathyTpGroupPriv *priv = GET_PRIV (group);
860         GList              *pendings = NULL, *l;
861
862         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), NULL);
863
864         for (l = priv->local_pendings; l; l = l->next) {
865                 EmpathyPendingInfo *info;
866                 EmpathyPendingInfo *new_info;
867
868                 info = l->data;
869                 new_info = empathy_pending_info_new (info->member,
870                                                      info->actor,
871                                                      info->message);
872                 pendings = g_list_prepend (pendings, new_info);
873         }
874
875         return pendings;
876 }
877
878 GList *
879 empathy_tp_group_get_remote_pendings (EmpathyTpGroup *group)
880 {
881         EmpathyTpGroupPriv *priv = GET_PRIV (group);
882
883         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), NULL);
884
885         g_list_foreach (priv->remote_pendings, (GFunc) g_object_ref, NULL);
886
887         return g_list_copy (priv->remote_pendings);
888 }
889
890 const gchar *
891 empathy_tp_group_get_name (EmpathyTpGroup *group)
892 {
893         EmpathyTpGroupPriv *priv = GET_PRIV (group);
894
895         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), NULL);
896         g_return_val_if_fail (priv->ready, NULL);
897
898         return priv->group_name;
899 }
900
901 EmpathyContact *
902 empathy_tp_group_get_self_contact (EmpathyTpGroup *group)
903 {
904         EmpathyTpGroupPriv *priv = GET_PRIV (group);
905
906         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), NULL);
907         g_return_val_if_fail (priv->ready, NULL);
908
909         return tp_group_get_contact (group, priv->self_handle);
910 }
911
912 gboolean
913 empathy_tp_group_is_member (EmpathyTpGroup *group,
914                             EmpathyContact *contact)
915 {
916         EmpathyTpGroupPriv *priv = GET_PRIV (group);
917
918         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), FALSE);
919         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), FALSE);
920
921         return g_list_find (priv->members, contact) != NULL;
922 }
923
924 gboolean
925 empathy_tp_group_is_ready (EmpathyTpGroup *group)
926 {
927         EmpathyTpGroupPriv *priv = GET_PRIV (group);
928
929         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), FALSE);
930
931         return priv->ready;
932 }
933
934 EmpathyPendingInfo *
935 empathy_tp_group_get_invitation (EmpathyTpGroup  *group,
936                                  EmpathyContact **remote_contact)
937 {
938         EmpathyTpGroupPriv *priv = GET_PRIV (group);
939         EmpathyContact     *contact = NULL;
940         EmpathyPendingInfo *invitation = NULL;
941         GList              *l;
942
943         g_return_val_if_fail (EMPATHY_IS_TP_GROUP (group), FALSE);
944         g_return_val_if_fail (priv->ready, NULL);
945
946         for (l = priv->local_pendings; l; l = l->next) {
947                 EmpathyPendingInfo *info = l->data;
948
949                 if (empathy_contact_is_user (info->member)) {
950                         invitation = info;
951                         break;
952                 }
953         }
954
955         if (invitation && priv->members && !priv->members->next) {
956                 contact = priv->members->data;
957         }
958         if (!invitation && priv->remote_pendings && !priv->remote_pendings->next) {
959                 contact = priv->remote_pendings->data;
960         }
961
962         if (remote_contact && contact) {
963                 *remote_contact = g_object_ref (contact);
964         }
965
966         return invitation;
967 }
968