1 /*
2  * group-mixin.c - Source for TpGroupMixin
3  *
4  * Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
5  * Copyright (C) 2006-2007 Nokia Corporation
6  *   @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
7  *   @author Robert McQueen <robert.mcqueen@collabora.co.uk>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23 
24 /**
25  * SECTION:group-mixin
26  * @title: TpGroupMixin
27  * @short_description: a mixin implementation of the groups interface
28  * @see_also: #TpSvcChannelInterfaceGroup
29  *
30  * This mixin can be added to a channel GObject class to implement the
31  * groups interface in a general way.
32  *
33  * To use the group mixin, include a #TpGroupMixinClass somewhere in your
34  * class structure and a #TpGroupMixin somewhere in your instance structure,
35  * and call tp_group_mixin_class_init() from your class_init function,
36  * tp_group_mixin_init() from your init function or constructor, and
37  * tp_group_mixin_finalize() from your dispose or finalize function.
38  *
39  * To use the group mixin as the implementation of
40  * #TpSvcChannelInterfaceGroup, call
41  * <literal>G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
42  * tp_group_mixin_iface_init)</literal> in the fourth argument to
43  * <literal>G_DEFINE_TYPE_WITH_CODE</literal>.
44  *
45  * Since 0.5.13 you can also implement the group interface by forwarding all
46  * group operations to the group mixin of an associated object (mainly useful
47  * for Tubes channels). To do this, call tp_external_group_mixin_init()
48  * in the constructor after the associated object has been set,
49  * tp_external_group_mixin_finalize() in the dispose or finalize function, and
50  * <literal>G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
51  * tp_external_group_mixin_iface_init)</literal> in the fourth argument to
52  * <literal>G_DEFINE_TYPE_WITH_CODE</literal>.
53  *
54  * Since 0.7.10 you can also implement the properties of Group channels,
55  * by calling tp_group_mixin_init_dbus_properties() or
56  * tp_external_group_mixin_init_dbus_properties() (as appropriate).
57  */
58 
59 #include "config.h"
60 
61 #include <telepathy-glib/group-mixin.h>
62 
63 #include <dbus/dbus-glib.h>
64 #include <stdio.h>
65 #include <string.h>
66 
67 #include <telepathy-glib/dbus.h>
68 #include <telepathy-glib/debug-ansi.h>
69 #include <telepathy-glib/errors.h>
70 #include <telepathy-glib/gtypes.h>
71 #include <telepathy-glib/interfaces.h>
72 
73 #define DEBUG_FLAG TP_DEBUG_GROUPS
74 
75 #include "debug-internal.h"
76 
77 static const char *
group_change_reason_str(guint reason)78 group_change_reason_str (guint reason)
79 {
80   switch (reason)
81     {
82     case TP_CHANNEL_GROUP_CHANGE_REASON_NONE:
83       return "unspecified reason";
84     case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
85       return "offline";
86     case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
87       return "kicked";
88     case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY:
89       return "busy";
90     case TP_CHANNEL_GROUP_CHANGE_REASON_INVITED:
91       return "invited";
92     case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
93       return "banned";
94     case TP_CHANNEL_GROUP_CHANGE_REASON_ERROR:
95       return "error";
96     case TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT:
97       return "invalid contact";
98     case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER:
99       return "no answer";
100     case TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED:
101       return "renamed";
102     case TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED:
103       return "permission denied";
104     case TP_CHANNEL_GROUP_CHANGE_REASON_SEPARATED:
105       return "separated";
106     default:
107       return "(unknown reason code)";
108     }
109 }
110 
111 typedef struct {
112   TpHandle actor;
113   guint reason;
114   const gchar *message;
115   TpHandleRepoIface *repo;
116 } LocalPendingInfo;
117 
118 static LocalPendingInfo *
local_pending_info_new(TpHandleRepoIface * repo,TpHandle actor,guint reason,const gchar * message)119 local_pending_info_new (TpHandleRepoIface *repo,
120                         TpHandle actor,
121                         guint reason,
122                         const gchar *message)
123 {
124   LocalPendingInfo *info = g_slice_new0 (LocalPendingInfo);
125   info->reason = reason;
126   info->message = g_strdup (message);
127   info->repo = repo;
128 
129   if (actor != 0)
130     info->actor = actor;
131 
132   return info;
133 }
134 
135 static void
local_pending_info_free(LocalPendingInfo * info)136 local_pending_info_free (LocalPendingInfo *info)
137 {
138   g_free ((gchar *) info->message);
139   g_slice_free (LocalPendingInfo, info);
140 }
141 
142 struct _TpGroupMixinClassPrivate {
143     TpGroupMixinRemMemberWithReasonFunc remove_with_reason;
144     unsigned allow_self_removal : 1;
145 };
146 
147 struct _TpGroupMixinPrivate {
148     TpHandleSet *actors;
149     GHashTable *handle_owners;
150     GHashTable *local_pending_info;
151     GPtrArray *externals;
152 };
153 
154 /**
155  * TP_HAS_GROUP_MIXIN:
156  * @o: a #GObject instance
157  *
158  * <!-- -->
159  *
160  * Returns: %TRUE if @o (or one of its parent classes) has the group mixin.
161  *
162  * Since: 0.13.9
163  */
164 
165 /**
166  * TP_HAS_GROUP_MIXIN_CLASS:
167  * @cls: a #GObjectClass structure
168  *
169  * <!-- -->
170  *
171  * Returns: %TRUE if @cls (or one of its parent classes) has the group mixin.
172  *
173  * Since: 0.13.9
174  */
175 
176 /**
177  * tp_group_mixin_class_get_offset_quark: (skip)
178  *
179  * <!--Returns: says it all-->
180  *
181  * Returns: the quark used for storing mixin offset on a GObjectClass
182  */
183 GQuark
tp_group_mixin_class_get_offset_quark()184 tp_group_mixin_class_get_offset_quark ()
185 {
186   static GQuark offset_quark = 0;
187   if (!offset_quark)
188     offset_quark = g_quark_from_static_string ("TpGroupMixinClassOffsetQuark");
189   return offset_quark;
190 }
191 
192 /**
193  * tp_group_mixin_get_offset_quark: (skip)
194  *
195  * <!--Returns: says it all-->
196  *
197  * Returns: the quark used for storing mixin offset on a GObject
198  */
199 GQuark
tp_group_mixin_get_offset_quark()200 tp_group_mixin_get_offset_quark ()
201 {
202   static GQuark offset_quark = 0;
203   if (!offset_quark)
204     offset_quark = g_quark_from_static_string ("TpGroupMixinOffsetQuark");
205   return offset_quark;
206 }
207 
208 /**
209  * tp_group_mixin_class_set_remove_with_reason_func: (skip)
210  * @cls: The class of an object implementing the group interface using this
211  *  mixin
212  * @func: A callback to be used to remove contacts from this group with a
213  *  specified reason.
214  *
215  * Set a callback to be used to implement RemoveMembers() and
216  * RemoveMembersWithReason(). If this function is called during class
217  * initialization, the given callback will be used instead of the remove
218  * callback passed to tp_group_mixin_class_init() (which must be %NULL
219  * in this case).
220  *
221  * Since: 0.5.13
222  */
223 void
tp_group_mixin_class_set_remove_with_reason_func(GObjectClass * cls,TpGroupMixinRemMemberWithReasonFunc func)224 tp_group_mixin_class_set_remove_with_reason_func (GObjectClass *cls,
225                                       TpGroupMixinRemMemberWithReasonFunc func)
226 {
227   TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (cls);
228 
229   g_return_if_fail (mixin_cls->remove_member == NULL);
230   g_return_if_fail (mixin_cls->priv->remove_with_reason == NULL);
231   mixin_cls->priv->remove_with_reason = func;
232 }
233 
234 /**
235  * tp_group_mixin_class_init: (skip)
236  * @obj_cls: The class of an object implementing the group interface using this
237  *  mixin
238  * @offset: The offset of the TpGroupMixinClass structure within the class
239  *  structure
240  * @add_func: A callback to be used to add contacts to this group
241  * @rem_func: A callback to be used to remove contacts from this group.
242  *  This must be %NULL if you will subsequently call
243  *  tp_group_mixin_class_set_remove_with_reason_func().
244  *
245  * Configure the mixin for use with the given class.
246  */
247 void
tp_group_mixin_class_init(GObjectClass * obj_cls,glong offset,TpGroupMixinAddMemberFunc add_func,TpGroupMixinRemMemberFunc rem_func)248 tp_group_mixin_class_init (GObjectClass *obj_cls,
249                            glong offset,
250                            TpGroupMixinAddMemberFunc add_func,
251                            TpGroupMixinRemMemberFunc rem_func)
252 {
253   TpGroupMixinClass *mixin_cls;
254 
255   g_assert (G_IS_OBJECT_CLASS (obj_cls));
256 
257   g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
258                     TP_GROUP_MIXIN_CLASS_OFFSET_QUARK,
259                     GINT_TO_POINTER (offset));
260 
261   mixin_cls = TP_GROUP_MIXIN_CLASS (obj_cls);
262 
263   mixin_cls->add_member = add_func;
264   mixin_cls->remove_member = rem_func;
265 
266   mixin_cls->priv = g_slice_new0 (TpGroupMixinClassPrivate);
267 }
268 
269 /**
270  * tp_group_mixin_class_allow_self_removal: (skip)
271  * @obj_cls: The class of an object implementing the group interface using this
272  *  mixin
273  *
274  * Configure the mixin to allow attempts to remove the SelfHandle from this
275  * Group, even if the group flags would otherwise disallow this. The
276  * channel's #TpGroupMixinRemMemberFunc or
277  * #TpGroupMixinRemMemberWithReasonFunc will be called as usual for such
278  * attempts, and may make them fail with %TP_ERROR_PERMISSION_DENIED if
279  * required.
280  *
281  * This function should be called from the GObject @class_init callback,
282  * after calling tp_group_mixin_class_init().
283  *
284  * (Recent telepathy-spec changes make it valid to try to remove the
285  * self-handle at all times, regardless of group flags. However, if this was
286  * implemented automatically in TpGroupMixin, this would risk crashing
287  * connection manager implementations that assume that TpGroupMixin will
288  * enforce the group flags strictly. As a result, connection managers should
289  * call this function to indicate to the TpGroupMixin that it may call their
290  * removal callback with the self-handle regardless of flag settings.)
291  *
292  * Since: 0.7.27
293  */
294 void
tp_group_mixin_class_allow_self_removal(GObjectClass * obj_cls)295 tp_group_mixin_class_allow_self_removal (GObjectClass *obj_cls)
296 {
297   TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (obj_cls);
298 
299   mixin_cls->priv->allow_self_removal = TRUE;
300 }
301 
302 /**
303  * tp_group_mixin_init: (skip)
304  * @obj: An object implementing the group interface using this mixin
305  * @offset: The offset of the TpGroupMixin structure within the instance
306  *  structure
307  * @handle_repo: The connection's handle repository for contacts
308  * @self_handle: The handle of the local user in this group, if any
309  *
310  * Initialize the mixin.
311  */
312 void
tp_group_mixin_init(GObject * obj,glong offset,TpHandleRepoIface * handle_repo,TpHandle self_handle)313 tp_group_mixin_init (GObject *obj,
314                      glong offset,
315                      TpHandleRepoIface *handle_repo,
316                      TpHandle self_handle)
317 {
318   TpGroupMixin *mixin;
319 
320   g_assert (G_IS_OBJECT (obj));
321 
322   g_type_set_qdata (G_OBJECT_TYPE (obj),
323                     TP_GROUP_MIXIN_OFFSET_QUARK,
324                     GINT_TO_POINTER (offset));
325 
326   mixin = TP_GROUP_MIXIN (obj);
327 
328   mixin->handle_repo = handle_repo;
329 
330   if (self_handle != 0)
331     mixin->self_handle = self_handle;
332 
333   mixin->group_flags = TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED;
334 
335   mixin->members = tp_handle_set_new (handle_repo);
336   mixin->local_pending = tp_handle_set_new (handle_repo);
337   mixin->remote_pending = tp_handle_set_new (handle_repo);
338 
339   mixin->priv = g_slice_new0 (TpGroupMixinPrivate);
340   mixin->priv->handle_owners = g_hash_table_new (NULL, NULL);
341   mixin->priv->local_pending_info = g_hash_table_new_full (NULL, NULL, NULL,
342       (GDestroyNotify)local_pending_info_free);
343   mixin->priv->actors = tp_handle_set_new (handle_repo);
344   mixin->priv->externals = NULL;
345 }
346 
347 static void
tp_group_mixin_add_external(GObject * obj,GObject * external)348 tp_group_mixin_add_external (GObject *obj, GObject *external)
349 {
350   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
351 
352   if (mixin->priv->externals == NULL)
353     mixin->priv->externals = g_ptr_array_new ();
354   g_ptr_array_add (mixin->priv->externals, external);
355 }
356 
357 static void
tp_group_mixin_remove_external(GObject * obj,GObject * external)358 tp_group_mixin_remove_external (GObject *obj, GObject *external)
359 {
360   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
361 
362   /* we can't have added it if we have no array to add it to... */
363   g_return_if_fail (mixin->priv->externals != NULL);
364   g_ptr_array_remove_fast (mixin->priv->externals, external);
365 }
366 
367 /**
368  * tp_group_mixin_finalize: (skip)
369  * @obj: An object implementing the group interface using this mixin
370  *
371  * Unreference handles and free resources used by this mixin.
372  */
373 void
tp_group_mixin_finalize(GObject * obj)374 tp_group_mixin_finalize (GObject *obj)
375 {
376   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
377 
378   tp_handle_set_destroy (mixin->priv->actors);
379 
380   g_hash_table_unref (mixin->priv->handle_owners);
381   g_hash_table_unref (mixin->priv->local_pending_info);
382 
383   if (mixin->priv->externals)
384     g_ptr_array_unref (mixin->priv->externals);
385 
386   g_slice_free (TpGroupMixinPrivate, mixin->priv);
387 
388   tp_handle_set_destroy (mixin->members);
389   tp_handle_set_destroy (mixin->local_pending);
390   tp_handle_set_destroy (mixin->remote_pending);
391 }
392 
393 /**
394  * tp_group_mixin_get_self_handle: (skip)
395  * @obj: An object implementing the group mixin using this interface
396  * @ret: Used to return the local user's handle in this group
397  * @error: Unused
398  *
399  * Set the guint pointed to by ret to the local user's handle in this
400  * group, or to 0 if the local user is not present in this group.
401  *
402  * Returns: %TRUE.
403  */
404 gboolean
tp_group_mixin_get_self_handle(GObject * obj,guint * ret,GError ** error)405 tp_group_mixin_get_self_handle (GObject *obj,
406                                 guint *ret,
407                                 GError **error)
408 {
409   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
410 
411   if (tp_handle_set_is_member (mixin->members, mixin->self_handle) ||
412       tp_handle_set_is_member (mixin->local_pending, mixin->self_handle) ||
413       tp_handle_set_is_member (mixin->remote_pending, mixin->self_handle))
414     {
415       *ret = mixin->self_handle;
416     }
417   else
418     {
419       *ret = 0;
420     }
421 
422   return TRUE;
423 }
424 
425 
426 /**
427  * tp_group_mixin_change_self_handle: (skip)
428  * @obj: An object implementing the group interface using this mixin
429  * @new_self_handle: The new self-handle for this group
430  *
431  * Change the self-handle for this group to the given value.
432  */
433 void
tp_group_mixin_change_self_handle(GObject * obj,TpHandle new_self_handle)434 tp_group_mixin_change_self_handle (GObject *obj,
435                                    TpHandle new_self_handle)
436 {
437   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
438   const gchar *new_self_id = tp_handle_inspect (mixin->handle_repo,
439       new_self_handle);
440 
441   DEBUG ("%u '%s'", new_self_handle, new_self_id);
442 
443   mixin->self_handle = new_self_handle;
444 
445   tp_svc_channel_interface_group_emit_self_handle_changed (obj,
446       new_self_handle);
447   tp_svc_channel_interface_group_emit_self_contact_changed (obj,
448       new_self_handle, new_self_id);
449 }
450 
451 
452 static void
tp_group_mixin_get_self_handle_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)453 tp_group_mixin_get_self_handle_async (TpSvcChannelInterfaceGroup *obj,
454                                       DBusGMethodInvocation *context)
455 {
456   guint ret;
457   GError *error = NULL;
458 
459   if (tp_group_mixin_get_self_handle ((GObject *) obj, &ret, &error))
460     {
461       tp_svc_channel_interface_group_return_from_get_self_handle (
462           context, ret);
463     }
464   else
465     {
466       dbus_g_method_return_error (context, error);
467       g_error_free (error);
468     }
469 }
470 
471 /**
472  * tp_group_mixin_get_group_flags: (skip)
473  * @obj: An object implementing the group mixin using this interface
474  * @ret: Used to return the flags
475  * @error: Unused
476  *
477  * Set the guint pointed to by ret to this group's flags, to be
478  * interpreted according to TpChannelGroupFlags.
479  *
480  * Returns: %TRUE
481  */
482 gboolean
tp_group_mixin_get_group_flags(GObject * obj,guint * ret,GError ** error)483 tp_group_mixin_get_group_flags (GObject *obj,
484                                 guint *ret,
485                                 GError **error)
486 {
487   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
488 
489   *ret = mixin->group_flags;
490 
491   return TRUE;
492 }
493 
494 static void
tp_group_mixin_get_group_flags_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)495 tp_group_mixin_get_group_flags_async (TpSvcChannelInterfaceGroup *obj,
496                                       DBusGMethodInvocation *context)
497 {
498   guint ret;
499   GError *error = NULL;
500 
501   if (tp_group_mixin_get_group_flags ((GObject *) obj, &ret, &error))
502     {
503       tp_svc_channel_interface_group_return_from_get_group_flags (
504           context, ret);
505     }
506   else
507     {
508       dbus_g_method_return_error (context, error);
509       g_error_free (error);
510     }
511 }
512 
513 /**
514  * tp_group_mixin_add_members: (skip)
515  * @obj: An object implementing the group interface using this mixin
516  * @contacts: A GArray of guint representing contacts
517  * @message: A message associated with the addition request, if supported
518  * @error: Used to return an error if %FALSE is returned
519  *
520  * Request that the given contacts be added to the group as if in response
521  * to user action. If the group's flags prohibit this, raise
522  * PermissionDenied. If any of the handles is invalid, raise InvalidHandle.
523  * Otherwise attempt to add the contacts by calling the callbacks provided
524  * by the channel implementation.
525  *
526  * Returns: %TRUE on success
527  */
528 gboolean
tp_group_mixin_add_members(GObject * obj,const GArray * contacts,const gchar * message,GError ** error)529 tp_group_mixin_add_members (GObject *obj,
530                             const GArray *contacts,
531                             const gchar *message,
532                             GError **error)
533 {
534   TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
535   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
536   guint i;
537   TpHandle handle;
538 
539   /* reject invalid handles */
540   if (!tp_handles_are_valid (mixin->handle_repo, contacts, FALSE, error))
541     return FALSE;
542 
543   /* check that adding is allowed by flags */
544   for (i = 0; i < contacts->len; i++)
545     {
546       handle = g_array_index (contacts, TpHandle, i);
547 
548       if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) == 0 &&
549           !tp_handle_set_is_member (mixin->members, handle) &&
550           !tp_handle_set_is_member (mixin->local_pending, handle))
551         {
552           DEBUG ("handle %u cannot be added to members without "
553               "GROUP_FLAG_CAN_ADD", handle);
554 
555           g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
556               "handle %u cannot be added to members without "
557               "GROUP_FLAG_CAN_ADD", handle);
558 
559           return FALSE;
560         }
561     }
562 
563   /* add handle by handle */
564   for (i = 0; i < contacts->len; i++)
565     {
566       handle = g_array_index (contacts, TpHandle, i);
567 
568       if (tp_handle_set_is_member (mixin->members, handle))
569         {
570           DEBUG ("handle %u is already a member, skipping", handle);
571 
572           continue;
573         }
574 
575       if (mixin_cls->add_member == NULL)
576         {
577           g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
578               "Adding members to this Group channel is not possible");
579           return FALSE;
580         }
581       if (!mixin_cls->add_member (obj, handle, message, error))
582         {
583           return FALSE;
584         }
585     }
586 
587   return TRUE;
588 }
589 
590 static void
tp_group_mixin_add_members_async(TpSvcChannelInterfaceGroup * obj,const GArray * contacts,const gchar * message,DBusGMethodInvocation * context)591 tp_group_mixin_add_members_async (TpSvcChannelInterfaceGroup *obj,
592                                   const GArray *contacts,
593                                   const gchar *message,
594                                   DBusGMethodInvocation *context)
595 {
596   GError *error = NULL;
597 
598   if (tp_group_mixin_add_members ((GObject *) obj, contacts, message, &error))
599     {
600       tp_svc_channel_interface_group_return_from_add_members (context);
601     }
602   else
603     {
604       dbus_g_method_return_error (context, error);
605       g_error_free (error);
606     }
607 }
608 
609 /**
610  * tp_group_mixin_remove_members: (skip)
611  * @obj: An object implementing the group interface using this mixin
612  * @contacts: A GArray of guint representing contacts
613  * @message: A message to be sent to those contacts, if supported
614  * @error: Used to return an error if %FALSE is returned
615  *
616  * Request that the given contacts be removed from the group as if in response
617  * to user action. If the group's flags prohibit this, raise
618  * PermissionDenied. If any of the handles is invalid, raise InvalidHandle.
619  * If any of the handles is absent from the group, raise NotAvailable.
620  * Otherwise attempt to remove the contacts by calling the callbacks provided
621  * by the channel implementation.
622  *
623  * Returns: %TRUE on success
624  */
625 gboolean
tp_group_mixin_remove_members(GObject * obj,const GArray * contacts,const gchar * message,GError ** error)626 tp_group_mixin_remove_members (GObject *obj,
627                                const GArray *contacts,
628                                const gchar *message,
629                                GError **error)
630 {
631   return tp_group_mixin_remove_members_with_reason (obj, contacts, message,
632       TP_CHANNEL_GROUP_CHANGE_REASON_NONE, error);
633 }
634 
635 /**
636  * tp_group_mixin_remove_members_with_reason: (skip)
637  * @obj: An object implementing the group interface using this mixin
638  * @contacts: A GArray of guint representing contacts
639  * @message: A message to be sent to those contacts, if supported
640  * @reason: A #TpChannelGroupChangeReason
641  * @error: Used to return an error if %FALSE is returned
642  *
643  * Request that the given contacts be removed from the group as if in response
644  * to user action. If the group's flags prohibit this, raise
645  * PermissionDenied. If any of the handles is invalid, raise InvalidHandle.
646  * If any of the handles is absent from the group, raise NotAvailable.
647  * Otherwise attempt to remove the contacts by calling the callbacks provided
648  * by the channel implementation.
649  *
650  * Returns: %TRUE on success
651  */
652 gboolean
tp_group_mixin_remove_members_with_reason(GObject * obj,const GArray * contacts,const gchar * message,guint reason,GError ** error)653 tp_group_mixin_remove_members_with_reason (GObject *obj,
654                                const GArray *contacts,
655                                const gchar *message,
656                                guint reason,
657                                GError **error)
658 {
659   TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
660   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
661   guint i;
662   TpHandle handle;
663 
664   /* reject invalid handles */
665   if (!tp_handles_are_valid (mixin->handle_repo, contacts, FALSE, error))
666     return FALSE;
667 
668   /* check removing is allowed by flags */
669   for (i = 0; i < contacts->len; i++)
670     {
671       handle = g_array_index (contacts, TpHandle, i);
672 
673       if (mixin_cls->priv->allow_self_removal &&
674           handle == mixin->self_handle &&
675           (tp_handle_set_is_member (mixin->members, handle) ||
676            tp_handle_set_is_member (mixin->remote_pending, handle) ||
677            tp_handle_set_is_member (mixin->local_pending, handle)))
678         {
679           /* don't check the flags - attempting to remove the self-handle
680            * is explicitly always allowed by this channel */
681         }
682       else if (tp_handle_set_is_member (mixin->members, handle))
683         {
684           if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) == 0)
685             {
686               DEBUG ("handle %u cannot be removed from members without "
687                   "GROUP_FLAG_CAN_REMOVE", handle);
688 
689               g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
690                   "handle %u cannot be removed from members without "
691                   "GROUP_FLAG_CAN_REMOVE", handle);
692 
693               return FALSE;
694             }
695         }
696       else if (tp_handle_set_is_member (mixin->remote_pending, handle))
697         {
698           if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CAN_RESCIND) == 0)
699             {
700               DEBUG ("handle %u cannot be removed from remote pending "
701                   "without GROUP_FLAG_CAN_RESCIND", handle);
702 
703               g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
704                   "handle %u cannot be removed from remote pending without "
705                   "GROUP_FLAG_CAN_RESCIND", handle);
706 
707               return FALSE;
708             }
709         }
710       else if (!tp_handle_set_is_member (mixin->local_pending, handle))
711         {
712           DEBUG ("handle %u is not a current or pending member",
713                    handle);
714           /* we'll skip this handle during the second pass */
715         }
716     }
717 
718   /* remove handle by handle */
719   for (i = 0; i < contacts->len; i++)
720     {
721       handle = g_array_index (contacts, TpHandle, i);
722 
723       if (!tp_handle_set_is_member (mixin->members, handle) &&
724           !tp_handle_set_is_member (mixin->remote_pending, handle) &&
725           !tp_handle_set_is_member (mixin->local_pending, handle))
726         continue;
727 
728       if (mixin_cls->priv->remove_with_reason != NULL)
729         {
730           if (!mixin_cls->priv->remove_with_reason (obj, handle, message,
731                                                     reason, error))
732             {
733               return FALSE;
734             }
735         }
736       else if (mixin_cls->remove_member != NULL)
737         {
738           if (!mixin_cls->remove_member (obj, handle, message, error))
739             {
740               return FALSE;
741             }
742         }
743       else
744         {
745           g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
746               "Removing contacts from this Group channel is not possible");
747           return FALSE;
748         }
749     }
750 
751   return TRUE;
752 }
753 
754 static void
tp_group_mixin_remove_members_with_reason_async(TpSvcChannelInterfaceGroup * obj,const GArray * contacts,const gchar * message,guint reason,DBusGMethodInvocation * context)755 tp_group_mixin_remove_members_with_reason_async
756     (TpSvcChannelInterfaceGroup *obj,
757      const GArray *contacts,
758      const gchar *message,
759      guint reason,
760      DBusGMethodInvocation *context)
761 {
762   GError *error = NULL;
763 
764   if (tp_group_mixin_remove_members_with_reason ((GObject *) obj, contacts,
765         message, reason, &error))
766     {
767       tp_svc_channel_interface_group_return_from_remove_members_with_reason
768         (context);
769     }
770   else
771     {
772       dbus_g_method_return_error (context, error);
773       g_error_free (error);
774     }
775 }
776 
777 static void
tp_group_mixin_remove_members_async(TpSvcChannelInterfaceGroup * obj,const GArray * contacts,const gchar * message,DBusGMethodInvocation * context)778 tp_group_mixin_remove_members_async (TpSvcChannelInterfaceGroup *obj,
779                                      const GArray *contacts,
780                                      const gchar *message,
781                                      DBusGMethodInvocation *context)
782 {
783   GError *error = NULL;
784 
785   if (tp_group_mixin_remove_members_with_reason ((GObject *) obj, contacts,
786         message, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, &error))
787     {
788       tp_svc_channel_interface_group_return_from_remove_members (context);
789     }
790   else
791     {
792       dbus_g_method_return_error (context, error);
793       g_error_free (error);
794     }
795 }
796 
797 /**
798  * tp_group_mixin_get_members: (skip)
799  * @obj: An object implementing the group interface using this mixin
800  * @ret: Used to return a newly-allocated GArray of guint contact handles
801  * @error: Unused
802  *
803  * Get the group's current members
804  *
805  * Returns: %TRUE
806  */
807 gboolean
tp_group_mixin_get_members(GObject * obj,GArray ** ret,GError ** error)808 tp_group_mixin_get_members (GObject *obj,
809                             GArray **ret,
810                             GError **error)
811 {
812   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
813 
814   *ret = tp_handle_set_to_array (mixin->members);
815 
816   return TRUE;
817 }
818 
819 static void
tp_group_mixin_get_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)820 tp_group_mixin_get_members_async (TpSvcChannelInterfaceGroup *obj,
821                                   DBusGMethodInvocation *context)
822 {
823   GArray *ret;
824   GError *error = NULL;
825 
826   if (tp_group_mixin_get_members ((GObject *) obj, &ret, &error))
827     {
828       tp_svc_channel_interface_group_return_from_get_members (
829           context, ret);
830       g_array_unref (ret);
831     }
832   else
833     {
834       dbus_g_method_return_error (context, error);
835       g_error_free (error);
836     }
837 }
838 
839 /**
840  * tp_group_mixin_get_local_pending_members: (skip)
841  * @obj: An object implementing the group interface using this mixin
842  * @ret: Used to return a newly-allocated GArray of guint contact handles
843  * @error: Unused
844  *
845  * Get the group's local-pending members.
846  *
847  * Returns: %TRUE
848  */
849 gboolean
tp_group_mixin_get_local_pending_members(GObject * obj,GArray ** ret,GError ** error)850 tp_group_mixin_get_local_pending_members (GObject *obj,
851                                           GArray **ret,
852                                           GError **error)
853 {
854   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
855 
856   *ret = tp_handle_set_to_array (mixin->local_pending);
857 
858   return TRUE;
859 }
860 
861 static void
tp_group_mixin_get_local_pending_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)862 tp_group_mixin_get_local_pending_members_async (TpSvcChannelInterfaceGroup *obj,
863                                                 DBusGMethodInvocation *context)
864 {
865   GArray *ret;
866   GError *error = NULL;
867 
868   if (tp_group_mixin_get_local_pending_members ((GObject *) obj, &ret, &error))
869     {
870       tp_svc_channel_interface_group_return_from_get_local_pending_members (
871           context, ret);
872       g_array_unref (ret);
873     }
874   else
875     {
876       dbus_g_method_return_error (context, error);
877       g_error_free (error);
878     }
879 }
880 
881 typedef struct {
882     TpGroupMixin *mixin;
883     GPtrArray *array;
884 } _mixin_and_array_of_info;
885 
886 static void
local_pending_members_with_info_foreach(TpHandleSet * set,TpHandle i,gpointer userdata)887 local_pending_members_with_info_foreach (TpHandleSet *set,
888                                          TpHandle i,
889                                          gpointer userdata)
890 {
891   _mixin_and_array_of_info *data = userdata;
892   TpGroupMixinPrivate *priv = data->mixin->priv;
893   GType info_type = TP_STRUCT_TYPE_LOCAL_PENDING_INFO;
894   GValue entry = { 0, };
895   LocalPendingInfo *info = g_hash_table_lookup (priv->local_pending_info,
896                                                 GUINT_TO_POINTER(i));
897   g_assert (info != NULL);
898 
899   g_value_init (&entry, info_type);
900   g_value_take_boxed (&entry, dbus_g_type_specialized_construct (info_type));
901 
902   dbus_g_type_struct_set (&entry,
903       0, i,
904       1, info->actor,
905       2, info->reason,
906       3, info->message,
907       G_MAXUINT);
908 
909   g_ptr_array_add (data->array, g_value_get_boxed (&entry));
910 }
911 
912 /**
913  * tp_group_mixin_get_local_pending_members_with_info: (skip)
914  * @obj: An object implementing the group interface using this mixin
915  * @ret: Used to return a newly-allocated GPtrArray of D-Bus structures each
916  * containing the handle of a local-pending contact, the handle of a contact
917  *  responsible for adding them to the group (or 0), the reason code
918  *  and a related message (e.g. their request to join the group)
919  * @error: Unused
920  *
921  * Get the group's local-pending members and information about their
922  * requests to join the channel.
923  *
924  * Returns: %TRUE
925  */
926 gboolean
tp_group_mixin_get_local_pending_members_with_info(GObject * obj,GPtrArray ** ret,GError ** error)927 tp_group_mixin_get_local_pending_members_with_info (
928                                                GObject *obj,
929                                                GPtrArray **ret,
930                                                GError **error)
931 {
932   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
933   _mixin_and_array_of_info data = { mixin, NULL };
934 
935   *ret = g_ptr_array_new ();
936   data.array = *ret;
937 
938   tp_handle_set_foreach (mixin->local_pending,
939       local_pending_members_with_info_foreach, &data);
940 
941   return TRUE;
942 }
943 
944 static void
tp_group_mixin_get_local_pending_members_with_info_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)945 tp_group_mixin_get_local_pending_members_with_info_async (
946                                                 TpSvcChannelInterfaceGroup *obj,
947                                                 DBusGMethodInvocation *context)
948 {
949   GPtrArray *ret;
950   GError *error = NULL;
951 
952   if (tp_group_mixin_get_local_pending_members_with_info ((GObject *) obj,
953         &ret, &error))
954     {
955       guint i;
956       tp_svc_channel_interface_group_return_from_get_local_pending_members_with_info (
957           context, ret);
958       for (i = 0 ; i < ret->len; i++) {
959         tp_value_array_free (g_ptr_array_index (ret,i));
960       }
961       g_ptr_array_unref (ret);
962     }
963   else
964     {
965       dbus_g_method_return_error (context, error);
966       g_error_free (error);
967     }
968 }
969 
970 /**
971  * tp_group_mixin_get_remote_pending_members: (skip)
972  * @obj: An object implementing the group interface using this mixin
973  * @ret: Used to return a newly-allocated GArray of guint representing the
974  * handles of the group's remote pending members
975  * @error: Unused
976  *
977  * Get the group's remote-pending members.
978  *
979  * Returns: %TRUE
980  */
981 gboolean
tp_group_mixin_get_remote_pending_members(GObject * obj,GArray ** ret,GError ** error)982 tp_group_mixin_get_remote_pending_members (GObject *obj,
983                                            GArray **ret,
984                                            GError **error)
985 {
986   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
987 
988   *ret = tp_handle_set_to_array (mixin->remote_pending);
989 
990   return TRUE;
991 }
992 
993 static void
tp_group_mixin_get_remote_pending_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)994 tp_group_mixin_get_remote_pending_members_async (TpSvcChannelInterfaceGroup *obj,
995                                                  DBusGMethodInvocation *context)
996 {
997   GArray *ret;
998   GError *error = NULL;
999 
1000   if (tp_group_mixin_get_remote_pending_members ((GObject *) obj,
1001         &ret, &error))
1002     {
1003       tp_svc_channel_interface_group_return_from_get_remote_pending_members (
1004           context, ret);
1005       g_array_unref (ret);
1006     }
1007   else
1008     {
1009       dbus_g_method_return_error (context, error);
1010       g_error_free (error);
1011     }
1012 }
1013 
1014 /**
1015  * tp_group_mixin_get_all_members: (skip)
1016  * @obj: An object implementing the group interface using this mixin
1017  * @members: Used to return a newly-allocated GArray of guint representing
1018  * the handles of the group's members
1019  * @local_pending: Used to return a newly-allocated GArray of guint
1020  * representing the handles of the group's local pending members
1021  * @remote_pending: Used to return a newly-allocated GArray of guint
1022  * representing the handles of the group's remote pending members
1023  * @error: Unused
1024  *
1025  * Get the group's current and pending members.
1026  *
1027  * Returns: %TRUE
1028  */
1029 gboolean
tp_group_mixin_get_all_members(GObject * obj,GArray ** members,GArray ** local_pending,GArray ** remote_pending,GError ** error)1030 tp_group_mixin_get_all_members (GObject *obj,
1031                                 GArray **members,
1032                                 GArray **local_pending,
1033                                 GArray **remote_pending,
1034                                 GError **error)
1035 {
1036   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
1037 
1038   *members = tp_handle_set_to_array (mixin->members);
1039   *local_pending = tp_handle_set_to_array (mixin->local_pending);
1040   *remote_pending = tp_handle_set_to_array (mixin->remote_pending);
1041 
1042   return TRUE;
1043 }
1044 
1045 static void
tp_group_mixin_get_all_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)1046 tp_group_mixin_get_all_members_async (TpSvcChannelInterfaceGroup *obj,
1047                                       DBusGMethodInvocation *context)
1048 {
1049   GArray *mem, *local, *remote;
1050   GError *error = NULL;
1051 
1052   if (tp_group_mixin_get_all_members ((GObject *) obj, &mem, &local, &remote,
1053         &error))
1054     {
1055       tp_svc_channel_interface_group_return_from_get_all_members (
1056           context, mem, local, remote);
1057       g_array_unref (mem);
1058       g_array_unref (local);
1059       g_array_unref (remote);
1060     }
1061   else
1062     {
1063       dbus_g_method_return_error (context, error);
1064       g_error_free (error);
1065     }
1066 }
1067 
1068 /**
1069  * tp_group_mixin_get_handle_owners: (skip)
1070  * @obj: An object implementing the group interface with this mixin
1071  * @handles: An array of guint representing locally valid handles
1072  * @ret: Used to return an array of guint representing globally valid
1073  *  handles, or 0 where unavailable, if %TRUE is returned
1074  * @error: Used to return an error if %FALSE is returned
1075  *
1076  * If the mixin has the flag %TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES,
1077  * return the global owners of the given local handles, or 0 where
1078  * unavailable.
1079  *
1080  * Returns: %TRUE (setting @ret) on success, %FALSE (setting @error) on
1081  * failure
1082  */
1083 gboolean
tp_group_mixin_get_handle_owners(GObject * obj,const GArray * handles,GArray ** ret,GError ** error)1084 tp_group_mixin_get_handle_owners (GObject *obj,
1085                                   const GArray *handles,
1086                                   GArray **ret,
1087                                   GError **error)
1088 {
1089   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
1090   TpGroupMixinPrivate *priv = mixin->priv;
1091   guint i;
1092 
1093   if ((mixin->group_flags &
1094         TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) == 0)
1095     {
1096       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1097           "channel doesn't have channel specific handles");
1098 
1099       return FALSE;
1100     }
1101 
1102   if (!tp_handles_are_valid (mixin->handle_repo, handles, FALSE, error))
1103     {
1104       return FALSE;
1105     }
1106 
1107   *ret = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), handles->len);
1108 
1109   for (i = 0; i < handles->len; i++)
1110     {
1111       TpHandle local_handle = g_array_index (handles, TpHandle, i);
1112       TpHandle owner_handle;
1113 
1114       if (!tp_handle_set_is_member (mixin->members, local_handle))
1115         {
1116           g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1117               "handle %u is not a member", local_handle);
1118 
1119           g_array_unref (*ret);
1120           *ret = NULL;
1121 
1122           return FALSE;
1123         }
1124 
1125       owner_handle = GPOINTER_TO_UINT (
1126           g_hash_table_lookup (priv->handle_owners,
1127                                GUINT_TO_POINTER (local_handle)));
1128 
1129       g_array_append_val (*ret, owner_handle);
1130     }
1131 
1132   return TRUE;
1133 }
1134 
1135 static void
tp_group_mixin_get_handle_owners_async(TpSvcChannelInterfaceGroup * obj,const GArray * handles,DBusGMethodInvocation * context)1136 tp_group_mixin_get_handle_owners_async (TpSvcChannelInterfaceGroup *obj,
1137                                         const GArray *handles,
1138                                         DBusGMethodInvocation *context)
1139 {
1140   GArray *ret;
1141   GError *error = NULL;
1142 
1143   if (tp_group_mixin_get_handle_owners ((GObject *) obj, handles,
1144         &ret, &error))
1145     {
1146       tp_svc_channel_interface_group_return_from_get_handle_owners (
1147           context, ret);
1148       g_array_unref (ret);
1149     }
1150   else
1151     {
1152       dbus_g_method_return_error (context, error);
1153       g_error_free (error);
1154     }
1155 }
1156 
1157 #define GFTS_APPEND_FLAG_IF_SET(flag) \
1158   if (flags & flag) \
1159     { \
1160       if (i++ > 0) \
1161         g_string_append (str, "|"); \
1162       g_string_append (str, #flag + 22); \
1163       flags &= ~flag; \
1164     }
1165 
1166 static gchar *
group_flags_to_string(TpChannelGroupFlags flags)1167 group_flags_to_string (TpChannelGroupFlags flags)
1168 {
1169   gint i = 0;
1170   GString *str;
1171 
1172   str = g_string_new ("[");
1173 
1174   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CAN_ADD);
1175   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CAN_REMOVE);
1176   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
1177   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD);
1178   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE);
1179   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT);
1180   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_REJECT);
1181   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND);
1182   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES);
1183   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_ONLY_ONE_GROUP);
1184   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_HANDLE_OWNERS_NOT_AVAILABLE);
1185   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_PROPERTIES);
1186   GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED);
1187 
1188   /* Print out any remaining flags that weren't removed in the above cases
1189    * numerically.
1190    */
1191   if (flags != 0)
1192     {
1193       if (i > 0)
1194         g_string_append (str, "|");
1195 
1196       g_string_append_printf (str, "%u", flags);
1197     }
1198 
1199   g_string_append (str, "]");
1200 
1201   return g_string_free (str, FALSE);
1202 }
1203 
1204 /**
1205  * tp_group_mixin_change_flags: (skip)
1206  * @obj: An object implementing the groups interface using this mixin
1207  * @add: Flags to be added
1208  * @del: Flags to be removed
1209  *
1210  * Request a change to be made to the flags. If any flags were actually
1211  * set or cleared, emits the GroupFlagsChanged signal with the changes.
1212  *
1213  * It is an error to set any of the same bits in both @add and @del.
1214  *
1215  * Changed in 0.7.7: the signal is not emitted if adding @add and
1216  *  removing @del had no effect on the existing group flags.
1217  */
1218 void
tp_group_mixin_change_flags(GObject * obj,TpChannelGroupFlags add,TpChannelGroupFlags del)1219 tp_group_mixin_change_flags (GObject *obj,
1220                              TpChannelGroupFlags add,
1221                              TpChannelGroupFlags del)
1222 {
1223   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
1224   TpChannelGroupFlags added, removed;
1225 
1226   /* It's meaningless to want to add and remove the same capability */
1227   g_return_if_fail ((add & del) == 0);
1228 
1229   added = add & ~mixin->group_flags;
1230   mixin->group_flags |= added;
1231 
1232   removed = del & mixin->group_flags;
1233   mixin->group_flags &= ~removed;
1234 
1235   if (added == 0 && removed == 0)
1236     {
1237       DEBUG ("No change: %u includes all the bits of %u and none of %u",
1238           mixin->group_flags, add, del);
1239     }
1240   else
1241     {
1242       gchar *str_added, *str_removed, *str_flags;
1243 
1244       if (DEBUGGING)
1245         {
1246           str_added = group_flags_to_string (added);
1247           str_removed = group_flags_to_string (removed);
1248           str_flags = group_flags_to_string (mixin->group_flags);
1249 
1250           DEBUG ("emitting group flags changed\n"
1251                   "  added    : %s\n"
1252                   "  removed  : %s\n"
1253                   "  flags now: %s\n",
1254                   str_added, str_removed, str_flags);
1255 
1256           g_free (str_added);
1257           g_free (str_removed);
1258           g_free (str_flags);
1259         }
1260 
1261       tp_svc_channel_interface_group_emit_group_flags_changed (obj, added,
1262           removed);
1263       if (mixin->priv->externals != NULL)
1264         {
1265           guint i;
1266 
1267           for (i = 0; i < mixin->priv->externals->len; i++)
1268             {
1269               tp_svc_channel_interface_group_emit_group_flags_changed
1270                 ((GObject *) g_ptr_array_index (mixin->priv->externals, i),
1271                  added, removed);
1272             }
1273         }
1274     }
1275 }
1276 
1277 static gchar *
member_array_to_string(TpHandleRepoIface * repo,const GArray * array)1278 member_array_to_string (TpHandleRepoIface *repo,
1279                         const GArray *array)
1280 {
1281   GString *str;
1282   guint i;
1283 
1284   str = g_string_new ("[");
1285 
1286   for (i = 0; i < array->len; i++)
1287     {
1288       TpHandle handle;
1289       const gchar *handle_str;
1290 
1291       handle = g_array_index (array, guint, i);
1292       handle_str = tp_handle_inspect (repo, handle);
1293 
1294       g_string_append_printf (str, "%s%u (%s)",
1295       /* indent to:   "  remote_pending: [" */
1296           (i > 0) ? "\n                   "
1297                   : "",
1298           handle, handle_str);
1299     }
1300 
1301   g_string_append (str, "]");
1302 
1303   return g_string_free (str, FALSE);
1304 }
1305 
1306 static GArray *remove_handle_owners_if_exist (GObject *obj,
1307     GArray *array) G_GNUC_WARN_UNUSED_RESULT;
1308 
1309 typedef struct {
1310     TpGroupMixin *mixin;
1311     LocalPendingInfo *info;
1312 } _mixin_and_info;
1313 
1314 static void
local_pending_added_foreach(guint i,gpointer userdata)1315 local_pending_added_foreach (guint i,
1316                              gpointer userdata)
1317 {
1318   _mixin_and_info *data = userdata;
1319   TpGroupMixinPrivate *priv = data->mixin->priv;
1320 
1321   g_hash_table_insert (priv->local_pending_info,
1322       GUINT_TO_POINTER (i),
1323       local_pending_info_new (data->mixin->handle_repo,
1324       data->info->actor, data->info->reason, data->info->message));
1325 }
1326 
1327 static void
local_pending_added(TpGroupMixin * mixin,const TpIntset * added,TpHandle actor,guint reason,const gchar * message)1328 local_pending_added (TpGroupMixin *mixin,
1329     const TpIntset *added,
1330     TpHandle actor,
1331     guint reason,
1332     const gchar *message)
1333 {
1334   LocalPendingInfo info;
1335   _mixin_and_info data = { mixin, &info };
1336   info.actor = actor;
1337   info.reason = reason;
1338   info.message = message;
1339 
1340   tp_intset_foreach (added, local_pending_added_foreach, &data);
1341 }
1342 
1343 static void
local_pending_remove_foreach(guint i,gpointer userdata)1344 local_pending_remove_foreach (guint i,
1345                              gpointer userdata)
1346 {
1347   TpGroupMixin *mixin = (TpGroupMixin *) userdata;
1348   TpGroupMixinPrivate *priv = mixin->priv;
1349 
1350   g_hash_table_remove (priv->local_pending_info, GUINT_TO_POINTER(i));
1351 }
1352 
1353 static void
local_pending_remove(TpGroupMixin * mixin,TpIntset * removed)1354 local_pending_remove (TpGroupMixin *mixin,
1355                      TpIntset *removed)
1356 {
1357   tp_intset_foreach (removed, local_pending_remove_foreach, mixin);
1358 }
1359 
1360 
1361 static void
add_members_in_array(GHashTable * contact_ids,TpHandleRepoIface * repo,const GArray * handles)1362 add_members_in_array (GHashTable *contact_ids,
1363                       TpHandleRepoIface *repo,
1364                       const GArray *handles)
1365 {
1366   guint i;
1367 
1368   for (i = 0; i < handles->len; i++)
1369     {
1370       TpHandle handle = g_array_index (handles, TpHandle, i);
1371       const gchar *id = tp_handle_inspect (repo, handle);
1372 
1373       g_hash_table_insert (contact_ids, GUINT_TO_POINTER (handle), (gchar *) id);
1374     }
1375 }
1376 
1377 
1378 static gboolean
maybe_add_contact_ids(TpGroupMixin * mixin,const GArray * add,const GArray * local_pending,const GArray * remote_pending,TpHandle actor,GHashTable * details)1379 maybe_add_contact_ids (TpGroupMixin *mixin,
1380                       const GArray *add,
1381                       const GArray *local_pending,
1382                       const GArray *remote_pending,
1383                       TpHandle actor,
1384                       GHashTable *details)
1385 {
1386   GHashTable *contact_ids;
1387 
1388   /* If the library user had its own ideas about which members' IDs to include
1389    * in the change details, we'll leave that intact.
1390    */
1391   if (tp_asv_lookup (details, "contact-ids") != NULL)
1392     return FALSE;
1393 
1394   /* The library user didn't include the new members' IDs in details; let's add
1395    * the IDs of the handles being added to the group (but not removed, as per
1396    * the spec) and of the actor.
1397    */
1398   contact_ids = g_hash_table_new (NULL, NULL);
1399 
1400   add_members_in_array (contact_ids, mixin->handle_repo, add);
1401   add_members_in_array (contact_ids, mixin->handle_repo, local_pending);
1402   add_members_in_array (contact_ids, mixin->handle_repo, remote_pending);
1403 
1404   if (actor != 0)
1405     {
1406       const gchar *id = tp_handle_inspect (mixin->handle_repo, actor);
1407 
1408       g_hash_table_insert (contact_ids, GUINT_TO_POINTER (actor), (gchar *) id);
1409     }
1410 
1411   g_hash_table_insert (details, "contact-ids",
1412       tp_g_value_slice_new_take_boxed (TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP,
1413           contact_ids));
1414 
1415   return TRUE;
1416 }
1417 
1418 
1419 static void
remove_contact_ids(GHashTable * details)1420 remove_contact_ids (GHashTable *details)
1421 {
1422   GValue *contact_ids_v = g_hash_table_lookup (details, "contact-ids");
1423 
1424   g_assert (contact_ids_v != NULL);
1425   g_hash_table_steal (details, "contact-ids");
1426 
1427   tp_g_value_slice_free (contact_ids_v);
1428 }
1429 
1430 
1431 static void
emit_members_changed_signals(GObject * channel,const gchar * message,const GArray * add,const GArray * del,const GArray * local_pending,const GArray * remote_pending,TpHandle actor,TpChannelGroupChangeReason reason,const GHashTable * details)1432 emit_members_changed_signals (GObject *channel,
1433                               const gchar *message,
1434                               const GArray *add,
1435                               const GArray *del,
1436                               const GArray *local_pending,
1437                               const GArray *remote_pending,
1438                               TpHandle actor,
1439                               TpChannelGroupChangeReason reason,
1440                               const GHashTable *details)
1441 {
1442   TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
1443   GHashTable *details_ = (GHashTable *) details; /* Cast the pain away! */
1444   gboolean added_contact_ids;
1445 
1446   if (DEBUGGING)
1447     {
1448       gchar *add_str, *rem_str, *local_str, *remote_str;
1449 
1450       add_str = member_array_to_string (mixin->handle_repo, add);
1451       rem_str = member_array_to_string (mixin->handle_repo, del);
1452       local_str = member_array_to_string (mixin->handle_repo, local_pending);
1453       remote_str = member_array_to_string (mixin->handle_repo, remote_pending);
1454 
1455       DEBUG ("emitting members changed\n"
1456               "  message       : \"%s\"\n"
1457               "  added         : %s\n"
1458               "  removed       : %s\n"
1459               "  local_pending : %s\n"
1460               "  remote_pending: %s\n"
1461               "  actor         : %u\n"
1462               "  reason        : %u: %s\n",
1463               message, add_str, rem_str, local_str, remote_str,
1464               actor, reason, group_change_reason_str (reason));
1465 
1466       g_free (add_str);
1467       g_free (rem_str);
1468       g_free (local_str);
1469       g_free (remote_str);
1470     }
1471 
1472   added_contact_ids = maybe_add_contact_ids (mixin, add, local_pending,
1473       remote_pending, actor, details_);
1474 
1475   tp_svc_channel_interface_group_emit_members_changed (channel, message,
1476       add, del, local_pending, remote_pending, actor, reason);
1477   tp_svc_channel_interface_group_emit_members_changed_detailed (channel,
1478       add, del, local_pending, remote_pending, details_);
1479 
1480   if (mixin->priv->externals != NULL)
1481     {
1482       guint i;
1483 
1484       for (i = 0; i < mixin->priv->externals->len; i++)
1485         {
1486           GObject *external = g_ptr_array_index (mixin->priv->externals, i);
1487 
1488           tp_svc_channel_interface_group_emit_members_changed (external,
1489               message, add, del, local_pending, remote_pending, actor, reason);
1490           tp_svc_channel_interface_group_emit_members_changed_detailed (
1491               external, add, del, local_pending, remote_pending, details_);
1492         }
1493     }
1494 
1495   if (added_contact_ids)
1496     remove_contact_ids (details_);
1497 }
1498 
1499 
1500 static gboolean
change_members(GObject * obj,const gchar * message,const TpIntset * add,const TpIntset * del,const TpIntset * add_local_pending,const TpIntset * add_remote_pending,TpHandle actor,TpChannelGroupChangeReason reason,const GHashTable * details)1501 change_members (GObject *obj,
1502                 const gchar *message,
1503                 const TpIntset *add,
1504                 const TpIntset *del,
1505                 const TpIntset *add_local_pending,
1506                 const TpIntset *add_remote_pending,
1507                 TpHandle actor,
1508                 TpChannelGroupChangeReason reason,
1509                 const GHashTable *details)
1510 {
1511   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
1512   TpIntset *new_add, *new_remove, *new_local_pending,
1513            *new_remote_pending, *tmp, *tmp2, *empty;
1514   gboolean ret;
1515 
1516   empty = tp_intset_new ();
1517 
1518   if (message == NULL)
1519     message = "";
1520 
1521   if (add == NULL)
1522     add = empty;
1523 
1524   if (del == NULL)
1525     del = empty;
1526 
1527   if (add_local_pending == NULL)
1528     add_local_pending = empty;
1529 
1530   if (add_remote_pending == NULL)
1531     add_remote_pending = empty;
1532 
1533   /* remember the actor handle before any handle unreffing happens */
1534   if (actor)
1535     {
1536       tp_handle_set_add (mixin->priv->actors, actor);
1537     }
1538 
1539   /* members + add */
1540   new_add = tp_handle_set_update (mixin->members, add);
1541 
1542   /* members - del */
1543   new_remove = tp_handle_set_difference_update (mixin->members, del);
1544 
1545   /* members - add_local_pending */
1546   tmp = tp_handle_set_difference_update (mixin->members, add_local_pending);
1547   tp_intset_destroy (tmp);
1548 
1549   /* members - add_remote_pending */
1550   tmp = tp_handle_set_difference_update (mixin->members, add_remote_pending);
1551   tp_intset_destroy (tmp);
1552 
1553 
1554   /* local pending + add_local_pending */
1555   new_local_pending = tp_handle_set_update (mixin->local_pending,
1556       add_local_pending);
1557   local_pending_added (mixin, add_local_pending, actor, reason, message);
1558 
1559   /* local pending - add */
1560   tmp = tp_handle_set_difference_update (mixin->local_pending, add);
1561   local_pending_remove (mixin, tmp);
1562   tp_intset_destroy (tmp);
1563 
1564   /* local pending - del */
1565   tmp = tp_handle_set_difference_update (mixin->local_pending, del);
1566   local_pending_remove (mixin, tmp);
1567 
1568   tmp2 = tp_intset_union (new_remove, tmp);
1569   tp_intset_destroy (new_remove);
1570   tp_intset_destroy (tmp);
1571   new_remove = tmp2;
1572 
1573   /* local pending - add_remote_pending */
1574   tmp = tp_handle_set_difference_update (mixin->local_pending,
1575       add_remote_pending);
1576   local_pending_remove (mixin, tmp);
1577   tp_intset_destroy (tmp);
1578 
1579 
1580   /* remote pending + add_remote_pending */
1581   new_remote_pending = tp_handle_set_update (mixin->remote_pending,
1582       add_remote_pending);
1583 
1584   /* remote pending - add */
1585   tmp = tp_handle_set_difference_update (mixin->remote_pending, add);
1586   tp_intset_destroy (tmp);
1587 
1588   /* remote pending - del */
1589   tmp = tp_handle_set_difference_update (mixin->remote_pending, del);
1590   tmp2 = tp_intset_union (new_remove, tmp);
1591   tp_intset_destroy (new_remove);
1592   tp_intset_destroy (tmp);
1593   new_remove = tmp2;
1594 
1595   /* remote pending - local_pending */
1596   tmp = tp_handle_set_difference_update (mixin->remote_pending,
1597       add_local_pending);
1598   tp_intset_destroy (tmp);
1599 
1600   if (tp_intset_size (new_add) > 0 ||
1601       tp_intset_size (new_remove) > 0 ||
1602       tp_intset_size (new_local_pending) > 0 ||
1603       tp_intset_size (new_remote_pending) > 0)
1604     {
1605       GArray *arr_add, *arr_remove, *arr_local, *arr_remote;
1606       GArray *arr_owners_removed;
1607 
1608       /* translate intsets to arrays */
1609       arr_add = tp_intset_to_array (new_add);
1610       arr_remove = tp_intset_to_array (new_remove);
1611       arr_local = tp_intset_to_array (new_local_pending);
1612       arr_remote = tp_intset_to_array (new_remote_pending);
1613 
1614       /* remove any handle owner mappings */
1615       arr_owners_removed = remove_handle_owners_if_exist (obj, arr_remove);
1616 
1617       /* emit signals */
1618       emit_members_changed_signals (obj, message, arr_add, arr_remove,
1619           arr_local, arr_remote, actor, reason, details);
1620 
1621       if (arr_owners_removed->len > 0)
1622         {
1623           GHashTable *empty_hash_table = g_hash_table_new (NULL, NULL);
1624 
1625           tp_svc_channel_interface_group_emit_handle_owners_changed (obj,
1626               empty_hash_table, arr_owners_removed);
1627           tp_svc_channel_interface_group_emit_handle_owners_changed_detailed (
1628               obj, empty_hash_table, arr_owners_removed, empty_hash_table);
1629 
1630           if (mixin->priv->externals != NULL)
1631             {
1632               guint i;
1633 
1634               for (i = 0; i < mixin->priv->externals->len; i++)
1635                 {
1636                   tp_svc_channel_interface_group_emit_handle_owners_changed (
1637                       g_ptr_array_index (mixin->priv->externals, i),
1638                       empty_hash_table, arr_owners_removed);
1639                   tp_svc_channel_interface_group_emit_handle_owners_changed_detailed (
1640                       g_ptr_array_index (mixin->priv->externals, i),
1641                       empty_hash_table, arr_owners_removed, empty_hash_table);
1642                 }
1643             }
1644 
1645           g_hash_table_unref (empty_hash_table);
1646         }
1647 
1648       /* free arrays */
1649       g_array_unref (arr_add);
1650       g_array_unref (arr_remove);
1651       g_array_unref (arr_local);
1652       g_array_unref (arr_remote);
1653       g_array_unref (arr_owners_removed);
1654 
1655       ret = TRUE;
1656     }
1657   else
1658     {
1659       DEBUG ("not emitting signal, nothing changed");
1660 
1661       ret = FALSE;
1662     }
1663 
1664   /* free intsets */
1665   tp_intset_destroy (new_add);
1666   tp_intset_destroy (new_remove);
1667   tp_intset_destroy (new_local_pending);
1668   tp_intset_destroy (new_remote_pending);
1669   tp_intset_destroy (empty);
1670 
1671   return ret;
1672 }
1673 
1674 
1675 /**
1676  * tp_group_mixin_change_members: (skip)
1677  * @obj: An object implementing the group interface using this mixin
1678  * @message: A message to be sent to the affected contacts if possible;
1679  *  %NULL is allowed, and is mapped to an empty string
1680  * @add: A set of contact handles to be added to the members (if not
1681  *  already present) and removed from local pending and remote pending
1682  *  (if present)
1683  * @del: A set of contact handles to be removed from members,
1684  *  local pending or remote pending, wherever they are present
1685  * @add_local_pending: A set of contact handles to be added to local pending,
1686  *  and removed from members and remote pending
1687  * @add_remote_pending: A set of contact handles to be added to remote pending,
1688  *  and removed from members and local pending
1689  * @actor: The handle of the contact responsible for this change
1690  * @reason: The reason for this change
1691  *
1692  * Change the sets of members as given by the arguments, and emit the
1693  * MembersChanged and MembersChangedDetailed signals if the changes were not a
1694  * no-op.
1695  *
1696  * This function must be called in response to events on the underlying
1697  * IM protocol, and must not be called in direct response to user input;
1698  * it does not respect the permissions flags, but changes the group directly.
1699  *
1700  * If any two of add, del, add_local_pending and add_remote_pending have
1701  * a non-empty intersection, the result is undefined. Don't do that.
1702  *
1703  * Each of the TpIntset arguments may be %NULL, which is treated as
1704  * equivalent to an empty set.
1705  *
1706  * Returns: %TRUE if the group was changed and the MembersChanged(Detailed)
1707  *  signals were emitted; %FALSE if nothing actually changed and the signals
1708  *  were suppressed.
1709  */
1710 gboolean
tp_group_mixin_change_members(GObject * obj,const gchar * message,const TpIntset * add,const TpIntset * del,const TpIntset * add_local_pending,const TpIntset * add_remote_pending,TpHandle actor,TpChannelGroupChangeReason reason)1711 tp_group_mixin_change_members (GObject *obj,
1712                                const gchar *message,
1713                                const TpIntset *add,
1714                                const TpIntset *del,
1715                                const TpIntset *add_local_pending,
1716                                const TpIntset *add_remote_pending,
1717                                TpHandle actor,
1718                                TpChannelGroupChangeReason reason)
1719 {
1720   GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal,
1721       NULL, (GDestroyNotify) tp_g_value_slice_free);
1722   gboolean ret;
1723 
1724   if (actor != 0)
1725     {
1726       g_hash_table_insert (details, "actor",
1727           tp_g_value_slice_new_uint (actor));
1728     }
1729 
1730   if (reason != TP_CHANNEL_GROUP_CHANGE_REASON_NONE)
1731     {
1732       g_hash_table_insert (details, "change-reason",
1733           tp_g_value_slice_new_uint (reason));
1734     }
1735 
1736   if (message != NULL && message[0] != '\0')
1737     {
1738       g_hash_table_insert (details, "message",
1739           tp_g_value_slice_new_string (message));
1740     }
1741 
1742   ret = change_members (obj, message, add, del, add_local_pending,
1743       add_remote_pending, actor, reason, details);
1744 
1745   g_hash_table_unref (details);
1746   return ret;
1747 }
1748 
1749 /**
1750  * tp_group_mixin_change_members_detailed: (skip)
1751  * @obj: An object implementing the group interface using this mixin
1752  * @add: A set of contact handles to be added to the members (if not
1753  *  already present) and removed from local pending and remote pending
1754  *  (if present)
1755  * @del: A set of contact handles to be removed from members,
1756  *  local pending or remote pending, wherever they are present
1757  * @add_local_pending: A set of contact handles to be added to local pending,
1758  *  and removed from members and remote pending
1759  * @add_remote_pending: A set of contact handles to be added to remote pending,
1760  *  and removed from members and local pending
1761  * @details: a map from strings to GValues detailing the change
1762  *
1763  * Change the sets of members as given by the arguments, and emit the
1764  * MembersChanged and MembersChangedDetailed signals if the changes were not a
1765  * no-op.
1766  *
1767  * This function must be called in response to events on the underlying
1768  * IM protocol, and must not be called in direct response to user input;
1769  * it does not respect the permissions flags, but changes the group directly.
1770  *
1771  * If any two of add, del, add_local_pending and add_remote_pending have
1772  * a non-empty intersection, the result is undefined. Don't do that.
1773  *
1774  * Each of the TpIntset arguments may be %NULL, which is treated as
1775  * equivalent to an empty set.
1776  *
1777  * details may contain, among other entries, the well-known
1778  * keys (and corresponding type, wrapped in a GValue) defined by the
1779  * Group.MembersChangedDetailed signal's specification; these include "actor"
1780  * (a handle as G_TYPE_UINT), "change-reason" (an element of
1781  * #TpChannelGroupChangeReason as G_TYPE_UINT), "message" (G_TYPE_STRING),
1782  * "error" (G_TYPE_STRING), "debug-message" (G_TYPE_STRING).
1783  *
1784  * If all of the information in details could be passed to
1785  * tp_group_mixin_change_members() then calling this function instead provides
1786  * no benefit. Calling this function without setting
1787  * #TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED with
1788  * tp_group_mixin_change_members() first is not very useful, as clients will
1789  * not know to listen for MembersChangedDetailed and thus will miss the
1790  * details.
1791  *
1792  * Returns: %TRUE if the group was changed and the MembersChanged(Detailed)
1793  *  signals were emitted; %FALSE if nothing actually changed and the signals
1794  *  were suppressed.
1795  *
1796  * Since: 0.7.21
1797  */
1798 gboolean
tp_group_mixin_change_members_detailed(GObject * obj,const TpIntset * add,const TpIntset * del,const TpIntset * add_local_pending,const TpIntset * add_remote_pending,const GHashTable * details)1799 tp_group_mixin_change_members_detailed (GObject *obj,
1800                                         const TpIntset *add,
1801                                         const TpIntset *del,
1802                                         const TpIntset *add_local_pending,
1803                                         const TpIntset *add_remote_pending,
1804                                         const GHashTable *details)
1805 {
1806   const gchar *message;
1807   TpHandle actor;
1808   TpChannelGroupChangeReason reason;
1809   gboolean valid;
1810 
1811   g_return_val_if_fail (details != NULL, FALSE);
1812 
1813   /* For each detail we're extracting for the benefit of old-school
1814    * MembersChanged, warn if it's present but badly typed.
1815    */
1816 
1817   message = tp_asv_get_string (details, "message");
1818   g_warn_if_fail (message != NULL || tp_asv_lookup (details, "message") == NULL);
1819 
1820   /* change_members will cry (via tp_handle_set_add) if actor is non-zero and
1821    * invalid.
1822    */
1823   actor = tp_asv_get_uint32 (details, "actor", &valid);
1824   g_warn_if_fail (valid || tp_asv_lookup (details, "actor") == NULL);
1825 
1826   reason = tp_asv_get_uint32 (details, "change-reason", &valid);
1827   g_warn_if_fail (valid || tp_asv_lookup (details, "change-reason") == NULL);
1828 
1829   return change_members (obj, message, add, del, add_local_pending,
1830       add_remote_pending, actor, reason, details);
1831 }
1832 
1833 /**
1834  * tp_group_mixin_add_handle_owner: (skip)
1835  * @obj: A GObject implementing the group interface with this mixin
1836  * @local_handle: A contact handle valid within this group (may not be 0)
1837  * @owner_handle: A contact handle valid globally, or 0 if the owner of the
1838  *  @local_handle is unknown
1839  *
1840  * Note that the given local handle is an alias within this group
1841  * for the given globally-valid handle. It will be returned from subsequent
1842  * GetHandleOwner queries where appropriate.
1843  *
1844  * Changed in 0.7.10: The @owner_handle may be 0. To comply with telepathy-spec
1845  *  0.17.6, before adding any channel-specific handle to the members,
1846  *  local-pending members or remote-pending members, you must call either
1847  *  this function or tp_group_mixin_add_handle_owners().
1848  */
1849 void
tp_group_mixin_add_handle_owner(GObject * obj,TpHandle local_handle,TpHandle owner_handle)1850 tp_group_mixin_add_handle_owner (GObject *obj,
1851                                  TpHandle local_handle,
1852                                  TpHandle owner_handle)
1853 {
1854   GHashTable *tmp;
1855 
1856   g_return_if_fail (local_handle != 0);
1857 
1858   tmp = g_hash_table_new (g_direct_hash, g_direct_equal);
1859   g_hash_table_insert (tmp, GUINT_TO_POINTER (local_handle),
1860       GUINT_TO_POINTER (owner_handle));
1861 
1862   tp_group_mixin_add_handle_owners (obj, tmp);
1863 
1864   g_hash_table_unref (tmp);
1865 }
1866 
1867 
1868 static void
add_us_mapping_for_handleset(GHashTable * map,TpHandleRepoIface * repo,TpHandleSet * handles)1869 add_us_mapping_for_handleset (GHashTable *map,
1870     TpHandleRepoIface *repo,
1871     TpHandleSet *handles)
1872 {
1873   TpIntset *set;
1874   TpIntsetFastIter iter;
1875   TpHandle handle;
1876 
1877   set = tp_handle_set_peek (handles);
1878   tp_intset_fast_iter_init (&iter, set);
1879   while (tp_intset_fast_iter_next (&iter, &handle))
1880     g_hash_table_insert (map, GUINT_TO_POINTER (handle),
1881         (gchar *) tp_handle_inspect (repo, handle));
1882 }
1883 
1884 static void
add_us_mapping_for_owners_map(GHashTable * map,TpHandleRepoIface * repo,GHashTable * owners)1885 add_us_mapping_for_owners_map (GHashTable *map,
1886     TpHandleRepoIface *repo,
1887     GHashTable *owners)
1888 {
1889   GHashTableIter iter;
1890   gpointer key, value;
1891 
1892   g_hash_table_iter_init (&iter, owners);
1893   while (g_hash_table_iter_next (&iter, &key, &value))
1894     {
1895       TpHandle local_handle = GPOINTER_TO_UINT (key);
1896       TpHandle owner_handle = GPOINTER_TO_UINT (value);
1897 
1898       g_hash_table_insert (map, key,
1899           (gchar *) tp_handle_inspect (repo, local_handle));
1900 
1901       if (owner_handle != 0)
1902         g_hash_table_insert (map, value,
1903             (gchar *) tp_handle_inspect (repo, owner_handle));
1904     }
1905 }
1906 
1907 static void
add_handle_owners_helper(gpointer key,gpointer value,gpointer user_data)1908 add_handle_owners_helper (gpointer key,
1909                           gpointer value,
1910                           gpointer user_data)
1911 {
1912   TpHandle local_handle = GPOINTER_TO_UINT (key);
1913   TpGroupMixin *mixin = user_data;
1914 
1915   g_return_if_fail (local_handle != 0);
1916 
1917   g_hash_table_insert (mixin->priv->handle_owners, key, value);
1918 }
1919 
1920 /**
1921  * tp_group_mixin_add_handle_owners: (skip)
1922  * @obj: A GObject implementing the group interface with this mixin
1923  * @local_to_owner_handle: A map from contact handles valid within this group
1924  *  (which may not be 0) to either contact handles valid globally, or 0 if the
1925  *  owner of the corresponding key is unknown; all handles are stored using
1926  *  GUINT_TO_POINTER
1927  *
1928  * Note that the given local handles are aliases within this group
1929  * for the given globally-valid handles.
1930  *
1931  * To comply with telepathy-spec 0.17.6, before adding any channel-specific
1932  * handle to the members, local-pending members or remote-pending members, you
1933  * must call either this function or tp_group_mixin_add_handle_owner().
1934  *
1935  * Since: 0.7.10
1936  */
1937 void
tp_group_mixin_add_handle_owners(GObject * obj,GHashTable * local_to_owner_handle)1938 tp_group_mixin_add_handle_owners (GObject *obj,
1939                                   GHashTable *local_to_owner_handle)
1940 {
1941   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
1942   GArray *empty_array;
1943   GHashTable *ids = g_hash_table_new (NULL, NULL);
1944 
1945   if (g_hash_table_size (local_to_owner_handle) == 0)
1946     return;
1947 
1948   empty_array = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0);
1949 
1950   g_hash_table_foreach (local_to_owner_handle, add_handle_owners_helper,
1951       mixin);
1952 
1953   tp_svc_channel_interface_group_emit_handle_owners_changed (obj,
1954       local_to_owner_handle, empty_array);
1955 
1956   add_us_mapping_for_owners_map (ids, mixin->handle_repo, local_to_owner_handle);
1957   tp_svc_channel_interface_group_emit_handle_owners_changed_detailed (obj,
1958       local_to_owner_handle, empty_array, ids);
1959 
1960   g_array_unref (empty_array);
1961   g_hash_table_unref (ids);
1962 }
1963 
1964 
1965 static GArray *
remove_handle_owners_if_exist(GObject * obj,GArray * array)1966 remove_handle_owners_if_exist (GObject *obj,
1967                                GArray *array)
1968 {
1969   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
1970   TpGroupMixinPrivate *priv = mixin->priv;
1971   guint i;
1972   GArray *ret;
1973 
1974   ret = g_array_sized_new (FALSE, FALSE, sizeof (guint), array->len);
1975 
1976   for (i = 0; i < array->len; i++)
1977     {
1978       TpHandle handle = g_array_index (array, guint, i);
1979       gpointer local_handle, owner_handle;
1980 
1981       g_assert (handle != 0);
1982 
1983       if (g_hash_table_lookup_extended (priv->handle_owners,
1984                                         GUINT_TO_POINTER (handle),
1985                                         &local_handle,
1986                                         &owner_handle))
1987         {
1988           g_assert (GPOINTER_TO_UINT (local_handle) == handle);
1989           g_array_append_val (ret, handle);
1990           g_hash_table_remove (priv->handle_owners, GUINT_TO_POINTER (handle));
1991         }
1992     }
1993 
1994   return ret;
1995 }
1996 
1997 static GHashTable *
dup_member_identifiers(GObject * obj)1998 dup_member_identifiers (GObject *obj)
1999 {
2000   TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
2001   GHashTable *ret = g_hash_table_new (NULL, NULL);
2002 
2003   g_hash_table_insert (ret, GUINT_TO_POINTER (mixin->self_handle),
2004       (gchar *) tp_handle_inspect (mixin->handle_repo, mixin->self_handle));
2005 
2006   add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->priv->actors);
2007   add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->members);
2008   add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->local_pending);
2009   add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->remote_pending);
2010 
2011   add_us_mapping_for_owners_map (ret, mixin->handle_repo,
2012       mixin->priv->handle_owners);
2013 
2014   return ret;
2015 }
2016 
2017 /**
2018  * tp_group_mixin_iface_init: (skip)
2019  * @g_iface: A #TpSvcChannelInterfaceGroupClass
2020  * @iface_data: Unused
2021  *
2022  * Fill in the vtable entries needed to implement the group interface using
2023  * this mixin. This function should usually be called via
2024  * G_IMPLEMENT_INTERFACE.
2025  */
2026 void
tp_group_mixin_iface_init(gpointer g_iface,gpointer iface_data)2027 tp_group_mixin_iface_init (gpointer g_iface, gpointer iface_data)
2028 {
2029   TpSvcChannelInterfaceGroupClass *klass = g_iface;
2030 
2031 #define IMPLEMENT(x) tp_svc_channel_interface_group_implement_##x (klass,\
2032     tp_group_mixin_##x##_async)
2033   IMPLEMENT(add_members);
2034   IMPLEMENT(get_all_members);
2035   IMPLEMENT(get_group_flags);
2036   IMPLEMENT(get_handle_owners);
2037   IMPLEMENT(get_local_pending_members);
2038   IMPLEMENT(get_local_pending_members_with_info);
2039   IMPLEMENT(get_members);
2040   IMPLEMENT(get_remote_pending_members);
2041   IMPLEMENT(get_self_handle);
2042   IMPLEMENT(remove_members);
2043   IMPLEMENT(remove_members_with_reason);
2044 #undef IMPLEMENT
2045 }
2046 
2047 
2048 enum {
2049     MIXIN_DP_GROUP_FLAGS,
2050     MIXIN_DP_HANDLE_OWNERS,
2051     MIXIN_DP_LOCAL_PENDING_MEMBERS,
2052     MIXIN_DP_MEMBERS,
2053     MIXIN_DP_REMOTE_PENDING_MEMBERS,
2054     MIXIN_DP_SELF_HANDLE,
2055     MIXIN_DP_MEMBER_IDENTIFIERS,
2056     NUM_MIXIN_DBUS_PROPERTIES
2057 };
2058 
2059 
2060 /**
2061  * tp_group_mixin_get_dbus_property: (skip)
2062  * @object: An object with this mixin
2063  * @interface: Must be %TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP
2064  * @name: A quark representing the D-Bus property name, either
2065  *  "GroupFlags", "HandleOwners", "LocalPendingMembers", "Members",
2066  *  "RemotePendingMembers" or "SelfHandle"
2067  * @value: A GValue pre-initialized to the right type, into which to put the
2068  *  value
2069  * @unused: Ignored
2070  *
2071  * An implementation of #TpDBusPropertiesMixinGetter which assumes that the
2072  * @object has the group mixin. It can only be used for the Group interface.
2073  *
2074  * Since: 0.7.10
2075  */
2076 void
tp_group_mixin_get_dbus_property(GObject * object,GQuark interface,GQuark name,GValue * value,gpointer unused G_GNUC_UNUSED)2077 tp_group_mixin_get_dbus_property (GObject *object,
2078                                   GQuark interface,
2079                                   GQuark name,
2080                                   GValue *value,
2081                                   gpointer unused G_GNUC_UNUSED)
2082 {
2083   TpGroupMixin *mixin;
2084   static GQuark q[NUM_MIXIN_DBUS_PROPERTIES] = { 0 };
2085 
2086   if (G_UNLIKELY (q[0] == 0))
2087     {
2088       q[MIXIN_DP_GROUP_FLAGS] = g_quark_from_static_string ("GroupFlags");
2089       q[MIXIN_DP_HANDLE_OWNERS] = g_quark_from_static_string ("HandleOwners");
2090       q[MIXIN_DP_LOCAL_PENDING_MEMBERS] = g_quark_from_static_string (
2091           "LocalPendingMembers");
2092       q[MIXIN_DP_MEMBERS] = g_quark_from_static_string ("Members");
2093       q[MIXIN_DP_REMOTE_PENDING_MEMBERS] = g_quark_from_static_string (
2094           "RemotePendingMembers");
2095       q[MIXIN_DP_SELF_HANDLE] = g_quark_from_static_string ("SelfHandle");
2096       q[MIXIN_DP_MEMBER_IDENTIFIERS] = g_quark_from_static_string ("MemberIdentifiers");
2097     }
2098 
2099   g_return_if_fail (object != NULL);
2100   mixin = TP_GROUP_MIXIN (object);
2101   g_return_if_fail (mixin != NULL);
2102   g_return_if_fail (interface == TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);
2103   g_return_if_fail (name != 0);
2104   g_return_if_fail (value != NULL);
2105 
2106   if (name == q[MIXIN_DP_GROUP_FLAGS])
2107     {
2108       g_return_if_fail (G_VALUE_HOLDS_UINT (value));
2109       g_value_set_uint (value, mixin->group_flags);
2110     }
2111   else if (name == q[MIXIN_DP_HANDLE_OWNERS])
2112     {
2113       g_return_if_fail (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_OWNER_MAP));
2114       g_value_set_boxed (value, mixin->priv->handle_owners);
2115     }
2116   else if (name == q[MIXIN_DP_LOCAL_PENDING_MEMBERS])
2117     {
2118       GPtrArray *ret = NULL;
2119       gboolean success;
2120 
2121       g_return_if_fail (G_VALUE_HOLDS_BOXED (value));
2122       success = tp_group_mixin_get_local_pending_members_with_info (object,
2123           &ret, NULL);
2124       g_assert (success);     /* as of 0.7.8, cannot fail */
2125       g_value_take_boxed (value, ret);
2126     }
2127   else if (name == q[MIXIN_DP_MEMBERS])
2128     {
2129       GArray *ret = NULL;
2130       gboolean success;
2131 
2132       g_return_if_fail (G_VALUE_HOLDS_BOXED (value));
2133       success = tp_group_mixin_get_members (object, &ret, NULL);
2134       g_assert (success);     /* as of 0.7.8, cannot fail */
2135       g_value_take_boxed (value, ret);
2136     }
2137   else if (name == q[MIXIN_DP_REMOTE_PENDING_MEMBERS])
2138     {
2139       GArray *ret = NULL;
2140       gboolean success;
2141 
2142       g_return_if_fail (G_VALUE_HOLDS_BOXED (value));
2143       success = tp_group_mixin_get_remote_pending_members (object,
2144           &ret, NULL);
2145       g_assert (success);     /* as of 0.7.8, cannot fail */
2146       g_value_take_boxed (value, ret);
2147     }
2148   else if (name == q[MIXIN_DP_SELF_HANDLE])
2149     {
2150       g_return_if_fail (G_VALUE_HOLDS_UINT (value));
2151       g_value_set_uint (value, mixin->self_handle);
2152     }
2153   else if (name == q[MIXIN_DP_MEMBER_IDENTIFIERS])
2154     {
2155       g_return_if_fail (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP));
2156       g_value_take_boxed (value, dup_member_identifiers (object));
2157     }
2158   else
2159     {
2160       g_return_if_reached ();
2161     }
2162 }
2163 
2164 static TpDBusPropertiesMixinPropImpl known_group_props[] = {
2165     { "GroupFlags", NULL, NULL },
2166     { "HandleOwners", NULL, NULL },
2167     { "LocalPendingMembers", NULL, NULL },
2168     { "Members", NULL, NULL },
2169     { "RemotePendingMembers", NULL, NULL },
2170     { "SelfHandle", NULL, NULL },
2171     { "MemberIdentifiers", NULL, NULL },
2172     { NULL }
2173 };
2174 
2175 /**
2176  * tp_group_mixin_init_dbus_properties: (skip)
2177  * @cls: The class of an object with this mixin
2178  *
2179  * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of
2180  * the Group interface's properties.
2181  *
2182  * This uses tp_group_mixin_get_dbus_property() as the property getter and
2183  * sets up a list of the supported properties for it.  Having called this, you
2184  * should add #TP_CHANNEL_GROUP_FLAG_PROPERTIES to any channels of this class
2185  * with tp_group_mixin_change_flags() to indicate that the DBus properties are
2186  * available.
2187  *
2188  * Since: 0.7.10
2189  */
2190 void
tp_group_mixin_init_dbus_properties(GObjectClass * cls)2191 tp_group_mixin_init_dbus_properties (GObjectClass *cls)
2192 {
2193 
2194   tp_dbus_properties_mixin_implement_interface (cls,
2195       TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP, tp_group_mixin_get_dbus_property,
2196       NULL, known_group_props);
2197 }
2198 
2199 
2200 #define TP_EXTERNAL_GROUP_MIXIN_OBJ(o) \
2201     ((GObject *) g_object_get_qdata (o, \
2202       _external_group_mixin_get_obj_quark ()))
2203 
2204 static GQuark
_external_group_mixin_get_obj_quark(void)2205 _external_group_mixin_get_obj_quark (void)
2206 {
2207   static GQuark quark = 0;
2208   if (!quark)
2209     quark = g_quark_from_static_string
2210         ("TpExternalGroupMixinQuark");
2211   return quark;
2212 }
2213 
2214 /**
2215  * tp_external_group_mixin_init: (skip)
2216  * @obj: An object implementing the groups interface using an external group
2217  *    mixin
2218  * @obj_with_mixin: A GObject with the group mixin
2219  *
2220  * Fill in the qdata needed to implement the group interface using
2221  * the group mixin of another object. This function should usually be called
2222  * in the instance constructor.
2223  *
2224  * Since: 0.5.13
2225  */
2226 void
tp_external_group_mixin_init(GObject * obj,GObject * obj_with_mixin)2227 tp_external_group_mixin_init (GObject *obj, GObject *obj_with_mixin)
2228 {
2229   g_object_ref (obj_with_mixin);
2230   g_object_set_qdata (obj, _external_group_mixin_get_obj_quark (),
2231       obj_with_mixin);
2232   tp_group_mixin_add_external (obj_with_mixin, obj);
2233 }
2234 
2235 /**
2236  * tp_external_group_mixin_finalize: (skip)
2237  * @obj: An object implementing the groups interface using an external group
2238  *    mixin
2239  *
2240  * Remove the external group mixin. This function should usually be called
2241  * in the dispose or finalize function.
2242  *
2243  * Since: 0.5.13
2244  */
2245 void
tp_external_group_mixin_finalize(GObject * obj)2246 tp_external_group_mixin_finalize (GObject *obj)
2247 {
2248   GObject *obj_with_mixin = g_object_steal_qdata (obj,
2249       _external_group_mixin_get_obj_quark ());
2250 
2251   tp_group_mixin_remove_external (obj_with_mixin, obj);
2252   g_object_unref (obj_with_mixin);
2253 }
2254 
2255 /**
2256  * tp_external_group_mixin_init_dbus_properties: (skip)
2257  * @cls: The class of an object with this mixin
2258  *
2259  * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of
2260  * the Group interface's properties.
2261  *
2262  * This uses tp_group_mixin_get_dbus_property() as the property getter and
2263  * sets up a list of the supported properties for it.  Having called this, you
2264  * should add #TP_CHANNEL_GROUP_FLAG_PROPERTIES to channels containing the
2265  * mixin used by this class with tp_group_mixin_change_flags() to indicate that
2266  * the DBus properties are available.
2267  *
2268  * Since: 0.7.10
2269  */
2270 void
tp_external_group_mixin_init_dbus_properties(GObjectClass * cls)2271 tp_external_group_mixin_init_dbus_properties (GObjectClass *cls)
2272 {
2273 
2274   tp_dbus_properties_mixin_implement_interface (cls,
2275       TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP,
2276       tp_external_group_mixin_get_dbus_property,
2277       NULL, known_group_props);
2278 }
2279 
2280 /**
2281  * tp_external_group_mixin_get_dbus_property: (skip)
2282  * @object: An object with this mixin
2283  * @interface: Must be %TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP
2284  * @name: A quark representing the D-Bus property name, either
2285  *  "GroupFlags", "HandleOwners", "LocalPendingMembers", "Members",
2286  *  "RemotePendingMembers" or "SelfHandle"
2287  * @value: A GValue pre-initialized to the right type, into which to put the
2288  *  value
2289  * @unused: Ignored
2290  *
2291  * An implementation of #TpDBusPropertiesMixinGetter which assumes that the
2292  * @object has the external group mixin. It can only be used for the Group
2293  * interface.
2294  *
2295  * Since: 0.7.10
2296  */
2297 void
tp_external_group_mixin_get_dbus_property(GObject * object,GQuark interface,GQuark name,GValue * value,gpointer unused G_GNUC_UNUSED)2298 tp_external_group_mixin_get_dbus_property (GObject *object,
2299                                            GQuark interface,
2300                                            GQuark name,
2301                                            GValue *value,
2302                                            gpointer unused G_GNUC_UNUSED)
2303 {
2304   GObject *group = TP_EXTERNAL_GROUP_MIXIN_OBJ (object);
2305 
2306   if (group != NULL)
2307     {
2308       tp_group_mixin_get_dbus_property (group, interface, name, value, NULL);
2309     }
2310   else if (G_VALUE_HOLDS_BOXED (value))
2311     {
2312       /* for certain boxed types we need to supply an empty value */
2313 
2314       if (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_OWNER_MAP))
2315         g_value_take_boxed (value, g_hash_table_new (NULL, NULL));
2316       else if (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP))
2317         g_value_take_boxed (value, g_hash_table_new (NULL, NULL));
2318       else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_UINT_ARRAY))
2319         g_value_take_boxed (value, g_array_sized_new (FALSE, FALSE,
2320               sizeof (guint), 0));
2321       else if (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_LOCAL_PENDING_INFO_LIST))
2322         g_value_take_boxed (value, g_ptr_array_sized_new (0));
2323     }
2324 }
2325 
2326 #define EXTERNAL_OR_DIE(var) \
2327     GObject *var = TP_EXTERNAL_GROUP_MIXIN_OBJ ((GObject *) obj); \
2328     \
2329     if (var == NULL) \
2330       { \
2331         GError na = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "I'm sure I " \
2332                       "had a group object around here somewhere?" };\
2333         \
2334         dbus_g_method_return_error (context, &na); \
2335         return; \
2336       } \
2337 
2338 static void
tp_external_group_mixin_add_members_async(TpSvcChannelInterfaceGroup * obj,const GArray * contacts,const gchar * message,DBusGMethodInvocation * context)2339 tp_external_group_mixin_add_members_async (TpSvcChannelInterfaceGroup *obj,
2340                                            const GArray *contacts,
2341                                            const gchar *message,
2342                                            DBusGMethodInvocation *context)
2343 {
2344   EXTERNAL_OR_DIE (group)
2345   tp_group_mixin_add_members_async ((TpSvcChannelInterfaceGroup *) group,
2346       contacts, message, context);
2347 }
2348 
2349 static void
tp_external_group_mixin_get_self_handle_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2350 tp_external_group_mixin_get_self_handle_async (TpSvcChannelInterfaceGroup *obj,
2351                                                DBusGMethodInvocation *context)
2352 {
2353   EXTERNAL_OR_DIE (group)
2354   tp_group_mixin_get_self_handle_async ((TpSvcChannelInterfaceGroup *) group,
2355       context);
2356 }
2357 
2358 static void
tp_external_group_mixin_get_group_flags_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2359 tp_external_group_mixin_get_group_flags_async (TpSvcChannelInterfaceGroup *obj,
2360                                                DBusGMethodInvocation *context)
2361 {
2362   EXTERNAL_OR_DIE (group)
2363   tp_group_mixin_get_group_flags_async ((TpSvcChannelInterfaceGroup *) group,
2364       context);
2365 }
2366 
2367 static void
tp_external_group_mixin_get_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2368 tp_external_group_mixin_get_members_async (TpSvcChannelInterfaceGroup *obj,
2369                                                DBusGMethodInvocation *context)
2370 {
2371   EXTERNAL_OR_DIE (group)
2372   tp_group_mixin_get_members_async ((TpSvcChannelInterfaceGroup *) group,
2373       context);
2374 }
2375 
2376 static void
tp_external_group_mixin_get_local_pending_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2377 tp_external_group_mixin_get_local_pending_members_async
2378     (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context)
2379 {
2380   EXTERNAL_OR_DIE (group)
2381   tp_group_mixin_get_local_pending_members_async
2382       ((TpSvcChannelInterfaceGroup *) group, context);
2383 }
2384 
2385 static void
tp_external_group_mixin_get_local_pending_members_with_info_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2386 tp_external_group_mixin_get_local_pending_members_with_info_async
2387     (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context)
2388 {
2389   EXTERNAL_OR_DIE (group)
2390   tp_group_mixin_get_local_pending_members_with_info_async
2391       ((TpSvcChannelInterfaceGroup *) group, context);
2392 }
2393 
2394 static void
tp_external_group_mixin_get_remote_pending_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2395 tp_external_group_mixin_get_remote_pending_members_async
2396     (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context)
2397 {
2398   EXTERNAL_OR_DIE (group)
2399   tp_group_mixin_get_remote_pending_members_async
2400       ((TpSvcChannelInterfaceGroup *) group, context);
2401 }
2402 
2403 static void
tp_external_group_mixin_get_all_members_async(TpSvcChannelInterfaceGroup * obj,DBusGMethodInvocation * context)2404 tp_external_group_mixin_get_all_members_async (TpSvcChannelInterfaceGroup *obj,
2405                                                DBusGMethodInvocation *context)
2406 {
2407   EXTERNAL_OR_DIE (group)
2408   tp_group_mixin_get_all_members_async ((TpSvcChannelInterfaceGroup *) group,
2409       context);
2410 }
2411 
2412 static void
tp_external_group_mixin_get_handle_owners_async(TpSvcChannelInterfaceGroup * obj,const GArray * handles,DBusGMethodInvocation * context)2413 tp_external_group_mixin_get_handle_owners_async
2414     (TpSvcChannelInterfaceGroup *obj,
2415      const GArray *handles,
2416      DBusGMethodInvocation *context)
2417 {
2418   EXTERNAL_OR_DIE (group)
2419   tp_group_mixin_get_handle_owners_async ((TpSvcChannelInterfaceGroup *) group,
2420       handles, context);
2421 }
2422 
2423 static void
tp_external_group_mixin_remove_members_async(TpSvcChannelInterfaceGroup * obj,const GArray * contacts,const gchar * message,DBusGMethodInvocation * context)2424 tp_external_group_mixin_remove_members_async (TpSvcChannelInterfaceGroup *obj,
2425                                               const GArray *contacts,
2426                                               const gchar *message,
2427                                               DBusGMethodInvocation *context)
2428 {
2429   EXTERNAL_OR_DIE (group)
2430   tp_group_mixin_remove_members_with_reason_async
2431       ((TpSvcChannelInterfaceGroup *) group, contacts, message,
2432        TP_CHANNEL_GROUP_CHANGE_REASON_NONE, context);
2433 }
2434 
2435 
2436 static void
tp_external_group_mixin_remove_members_with_reason_async(TpSvcChannelInterfaceGroup * obj,const GArray * contacts,const gchar * message,guint reason,DBusGMethodInvocation * context)2437 tp_external_group_mixin_remove_members_with_reason_async
2438     (TpSvcChannelInterfaceGroup *obj,
2439      const GArray *contacts,
2440      const gchar *message,
2441      guint reason,
2442      DBusGMethodInvocation *context)
2443 {
2444   EXTERNAL_OR_DIE (group)
2445   tp_group_mixin_remove_members_with_reason_async
2446       ((TpSvcChannelInterfaceGroup *) group, contacts, message, reason,
2447        context);
2448 }
2449 /**
2450  * tp_external_group_mixin_iface_init: (skip)
2451  * @g_iface: A #TpSvcChannelInterfaceGroupClass
2452  * @iface_data: Unused
2453  *
2454  * Fill in the vtable entries needed to implement the group interface using
2455  * the group mixin of another object. This function should usually be called
2456  * via G_IMPLEMENT_INTERFACE.
2457  *
2458  * Since: 0.5.13
2459  */
2460 void
tp_external_group_mixin_iface_init(gpointer g_iface,gpointer iface_data)2461 tp_external_group_mixin_iface_init (gpointer g_iface,
2462                                     gpointer iface_data)
2463 {
2464   TpSvcChannelInterfaceGroupClass *klass = g_iface;
2465 
2466 #define IMPLEMENT(x) tp_svc_channel_interface_group_implement_##x (klass,\
2467     tp_external_group_mixin_##x##_async)
2468   IMPLEMENT(add_members);
2469   IMPLEMENT(get_all_members);
2470   IMPLEMENT(get_group_flags);
2471   IMPLEMENT(get_handle_owners);
2472   IMPLEMENT(get_local_pending_members);
2473   IMPLEMENT(get_local_pending_members_with_info);
2474   IMPLEMENT(get_members);
2475   IMPLEMENT(get_remote_pending_members);
2476   IMPLEMENT(get_self_handle);
2477   IMPLEMENT(remove_members);
2478   IMPLEMENT(remove_members_with_reason);
2479 #undef IMPLEMENT
2480 }
2481