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