/* * group-mixin.c - Source for TpGroupMixin * * Copyright (C) 2006-2007 Collabora Ltd. * Copyright (C) 2006-2007 Nokia Corporation * @author Ole Andre Vadla Ravnaas * @author Robert McQueen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * SECTION:group-mixin * @title: TpGroupMixin * @short_description: a mixin implementation of the groups interface * @see_also: #TpSvcChannelInterfaceGroup * * This mixin can be added to a channel GObject class to implement the * groups interface in a general way. * * To use the group mixin, include a #TpGroupMixinClass somewhere in your * class structure and a #TpGroupMixin somewhere in your instance structure, * and call tp_group_mixin_class_init() from your class_init function, * tp_group_mixin_init() from your init function or constructor, and * tp_group_mixin_finalize() from your dispose or finalize function. * * To use the group mixin as the implementation of * #TpSvcChannelInterfaceGroup, call * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, * tp_group_mixin_iface_init) in the fourth argument to * G_DEFINE_TYPE_WITH_CODE. * * Since 0.5.13 you can also implement the group interface by forwarding all * group operations to the group mixin of an associated object (mainly useful * for Tubes channels). To do this, call tp_external_group_mixin_init() * in the constructor after the associated object has been set, * tp_external_group_mixin_finalize() in the dispose or finalize function, and * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, * tp_external_group_mixin_iface_init) in the fourth argument to * G_DEFINE_TYPE_WITH_CODE. * * Since 0.7.10 you can also implement the properties of Group channels, * by calling tp_group_mixin_init_dbus_properties() or * tp_external_group_mixin_init_dbus_properties() (as appropriate). */ #include "config.h" #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_GROUPS #include "debug-internal.h" static const char * group_change_reason_str (guint reason) { switch (reason) { case TP_CHANNEL_GROUP_CHANGE_REASON_NONE: return "unspecified reason"; case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE: return "offline"; case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED: return "kicked"; case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY: return "busy"; case TP_CHANNEL_GROUP_CHANGE_REASON_INVITED: return "invited"; case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED: return "banned"; case TP_CHANNEL_GROUP_CHANGE_REASON_ERROR: return "error"; case TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT: return "invalid contact"; case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: return "no answer"; case TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED: return "renamed"; case TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED: return "permission denied"; case TP_CHANNEL_GROUP_CHANGE_REASON_SEPARATED: return "separated"; default: return "(unknown reason code)"; } } typedef struct { TpHandle actor; guint reason; const gchar *message; TpHandleRepoIface *repo; } LocalPendingInfo; static LocalPendingInfo * local_pending_info_new (TpHandleRepoIface *repo, TpHandle actor, guint reason, const gchar *message) { LocalPendingInfo *info = g_slice_new0 (LocalPendingInfo); info->reason = reason; info->message = g_strdup (message); info->repo = repo; if (actor != 0) info->actor = actor; return info; } static void local_pending_info_free (LocalPendingInfo *info) { g_free ((gchar *) info->message); g_slice_free (LocalPendingInfo, info); } struct _TpGroupMixinClassPrivate { TpGroupMixinRemMemberWithReasonFunc remove_with_reason; unsigned allow_self_removal : 1; }; struct _TpGroupMixinPrivate { TpHandleSet *actors; GHashTable *handle_owners; GHashTable *local_pending_info; GPtrArray *externals; }; /** * TP_HAS_GROUP_MIXIN: * @o: a #GObject instance * * * * Returns: %TRUE if @o (or one of its parent classes) has the group mixin. * * Since: 0.13.9 */ /** * TP_HAS_GROUP_MIXIN_CLASS: * @cls: a #GObjectClass structure * * * * Returns: %TRUE if @cls (or one of its parent classes) has the group mixin. * * Since: 0.13.9 */ /** * tp_group_mixin_class_get_offset_quark: (skip) * * * * Returns: the quark used for storing mixin offset on a GObjectClass */ GQuark tp_group_mixin_class_get_offset_quark () { static GQuark offset_quark = 0; if (!offset_quark) offset_quark = g_quark_from_static_string ("TpGroupMixinClassOffsetQuark"); return offset_quark; } /** * tp_group_mixin_get_offset_quark: (skip) * * * * Returns: the quark used for storing mixin offset on a GObject */ GQuark tp_group_mixin_get_offset_quark () { static GQuark offset_quark = 0; if (!offset_quark) offset_quark = g_quark_from_static_string ("TpGroupMixinOffsetQuark"); return offset_quark; } /** * tp_group_mixin_class_set_remove_with_reason_func: (skip) * @cls: The class of an object implementing the group interface using this * mixin * @func: A callback to be used to remove contacts from this group with a * specified reason. * * Set a callback to be used to implement RemoveMembers() and * RemoveMembersWithReason(). If this function is called during class * initialization, the given callback will be used instead of the remove * callback passed to tp_group_mixin_class_init() (which must be %NULL * in this case). * * Since: 0.5.13 */ void tp_group_mixin_class_set_remove_with_reason_func (GObjectClass *cls, TpGroupMixinRemMemberWithReasonFunc func) { TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (cls); g_return_if_fail (mixin_cls->remove_member == NULL); g_return_if_fail (mixin_cls->priv->remove_with_reason == NULL); mixin_cls->priv->remove_with_reason = func; } /** * tp_group_mixin_class_init: (skip) * @obj_cls: The class of an object implementing the group interface using this * mixin * @offset: The offset of the TpGroupMixinClass structure within the class * structure * @add_func: A callback to be used to add contacts to this group * @rem_func: A callback to be used to remove contacts from this group. * This must be %NULL if you will subsequently call * tp_group_mixin_class_set_remove_with_reason_func(). * * Configure the mixin for use with the given class. */ void tp_group_mixin_class_init (GObjectClass *obj_cls, glong offset, TpGroupMixinAddMemberFunc add_func, TpGroupMixinRemMemberFunc rem_func) { TpGroupMixinClass *mixin_cls; g_assert (G_IS_OBJECT_CLASS (obj_cls)); g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls), TP_GROUP_MIXIN_CLASS_OFFSET_QUARK, GINT_TO_POINTER (offset)); mixin_cls = TP_GROUP_MIXIN_CLASS (obj_cls); mixin_cls->add_member = add_func; mixin_cls->remove_member = rem_func; mixin_cls->priv = g_slice_new0 (TpGroupMixinClassPrivate); } /** * tp_group_mixin_class_allow_self_removal: (skip) * @obj_cls: The class of an object implementing the group interface using this * mixin * * Configure the mixin to allow attempts to remove the SelfHandle from this * Group, even if the group flags would otherwise disallow this. The * channel's #TpGroupMixinRemMemberFunc or * #TpGroupMixinRemMemberWithReasonFunc will be called as usual for such * attempts, and may make them fail with %TP_ERROR_PERMISSION_DENIED if * required. * * This function should be called from the GObject @class_init callback, * after calling tp_group_mixin_class_init(). * * (Recent telepathy-spec changes make it valid to try to remove the * self-handle at all times, regardless of group flags. However, if this was * implemented automatically in TpGroupMixin, this would risk crashing * connection manager implementations that assume that TpGroupMixin will * enforce the group flags strictly. As a result, connection managers should * call this function to indicate to the TpGroupMixin that it may call their * removal callback with the self-handle regardless of flag settings.) * * Since: 0.7.27 */ void tp_group_mixin_class_allow_self_removal (GObjectClass *obj_cls) { TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (obj_cls); mixin_cls->priv->allow_self_removal = TRUE; } /** * tp_group_mixin_init: (skip) * @obj: An object implementing the group interface using this mixin * @offset: The offset of the TpGroupMixin structure within the instance * structure * @handle_repo: The connection's handle repository for contacts * @self_handle: The handle of the local user in this group, if any * * Initialize the mixin. */ void tp_group_mixin_init (GObject *obj, glong offset, TpHandleRepoIface *handle_repo, TpHandle self_handle) { TpGroupMixin *mixin; g_assert (G_IS_OBJECT (obj)); g_type_set_qdata (G_OBJECT_TYPE (obj), TP_GROUP_MIXIN_OFFSET_QUARK, GINT_TO_POINTER (offset)); mixin = TP_GROUP_MIXIN (obj); mixin->handle_repo = handle_repo; if (self_handle != 0) mixin->self_handle = self_handle; mixin->group_flags = TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED; mixin->members = tp_handle_set_new (handle_repo); mixin->local_pending = tp_handle_set_new (handle_repo); mixin->remote_pending = tp_handle_set_new (handle_repo); mixin->priv = g_slice_new0 (TpGroupMixinPrivate); mixin->priv->handle_owners = g_hash_table_new (NULL, NULL); mixin->priv->local_pending_info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)local_pending_info_free); mixin->priv->actors = tp_handle_set_new (handle_repo); mixin->priv->externals = NULL; } static void tp_group_mixin_add_external (GObject *obj, GObject *external) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); if (mixin->priv->externals == NULL) mixin->priv->externals = g_ptr_array_new (); g_ptr_array_add (mixin->priv->externals, external); } static void tp_group_mixin_remove_external (GObject *obj, GObject *external) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); /* we can't have added it if we have no array to add it to... */ g_return_if_fail (mixin->priv->externals != NULL); g_ptr_array_remove_fast (mixin->priv->externals, external); } /** * tp_group_mixin_finalize: (skip) * @obj: An object implementing the group interface using this mixin * * Unreference handles and free resources used by this mixin. */ void tp_group_mixin_finalize (GObject *obj) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); tp_handle_set_destroy (mixin->priv->actors); g_hash_table_unref (mixin->priv->handle_owners); g_hash_table_unref (mixin->priv->local_pending_info); if (mixin->priv->externals) g_ptr_array_unref (mixin->priv->externals); g_slice_free (TpGroupMixinPrivate, mixin->priv); tp_handle_set_destroy (mixin->members); tp_handle_set_destroy (mixin->local_pending); tp_handle_set_destroy (mixin->remote_pending); } /** * tp_group_mixin_get_self_handle: (skip) * @obj: An object implementing the group mixin using this interface * @ret: Used to return the local user's handle in this group * @error: Unused * * Set the guint pointed to by ret to the local user's handle in this * group, or to 0 if the local user is not present in this group. * * Returns: %TRUE. */ gboolean tp_group_mixin_get_self_handle (GObject *obj, guint *ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); if (tp_handle_set_is_member (mixin->members, mixin->self_handle) || tp_handle_set_is_member (mixin->local_pending, mixin->self_handle) || tp_handle_set_is_member (mixin->remote_pending, mixin->self_handle)) { *ret = mixin->self_handle; } else { *ret = 0; } return TRUE; } /** * tp_group_mixin_change_self_handle: (skip) * @obj: An object implementing the group interface using this mixin * @new_self_handle: The new self-handle for this group * * Change the self-handle for this group to the given value. */ void tp_group_mixin_change_self_handle (GObject *obj, TpHandle new_self_handle) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); const gchar *new_self_id = tp_handle_inspect (mixin->handle_repo, new_self_handle); DEBUG ("%u '%s'", new_self_handle, new_self_id); mixin->self_handle = new_self_handle; tp_svc_channel_interface_group_emit_self_handle_changed (obj, new_self_handle); tp_svc_channel_interface_group_emit_self_contact_changed (obj, new_self_handle, new_self_id); } static void tp_group_mixin_get_self_handle_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { guint ret; GError *error = NULL; if (tp_group_mixin_get_self_handle ((GObject *) obj, &ret, &error)) { tp_svc_channel_interface_group_return_from_get_self_handle ( context, ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_get_group_flags: (skip) * @obj: An object implementing the group mixin using this interface * @ret: Used to return the flags * @error: Unused * * Set the guint pointed to by ret to this group's flags, to be * interpreted according to TpChannelGroupFlags. * * Returns: %TRUE */ gboolean tp_group_mixin_get_group_flags (GObject *obj, guint *ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); *ret = mixin->group_flags; return TRUE; } static void tp_group_mixin_get_group_flags_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { guint ret; GError *error = NULL; if (tp_group_mixin_get_group_flags ((GObject *) obj, &ret, &error)) { tp_svc_channel_interface_group_return_from_get_group_flags ( context, ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_add_members: (skip) * @obj: An object implementing the group interface using this mixin * @contacts: A GArray of guint representing contacts * @message: A message associated with the addition request, if supported * @error: Used to return an error if %FALSE is returned * * Request that the given contacts be added to the group as if in response * to user action. If the group's flags prohibit this, raise * PermissionDenied. If any of the handles is invalid, raise InvalidHandle. * Otherwise attempt to add the contacts by calling the callbacks provided * by the channel implementation. * * Returns: %TRUE on success */ gboolean tp_group_mixin_add_members (GObject *obj, const GArray *contacts, const gchar *message, GError **error) { TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); guint i; TpHandle handle; /* reject invalid handles */ if (!tp_handles_are_valid (mixin->handle_repo, contacts, FALSE, error)) return FALSE; /* check that adding is allowed by flags */ for (i = 0; i < contacts->len; i++) { handle = g_array_index (contacts, TpHandle, i); if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CAN_ADD) == 0 && !tp_handle_set_is_member (mixin->members, handle) && !tp_handle_set_is_member (mixin->local_pending, handle)) { DEBUG ("handle %u cannot be added to members without " "GROUP_FLAG_CAN_ADD", handle); g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED, "handle %u cannot be added to members without " "GROUP_FLAG_CAN_ADD", handle); return FALSE; } } /* add handle by handle */ for (i = 0; i < contacts->len; i++) { handle = g_array_index (contacts, TpHandle, i); if (tp_handle_set_is_member (mixin->members, handle)) { DEBUG ("handle %u is already a member, skipping", handle); continue; } if (mixin_cls->add_member == NULL) { g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Adding members to this Group channel is not possible"); return FALSE; } if (!mixin_cls->add_member (obj, handle, message, error)) { return FALSE; } } return TRUE; } static void tp_group_mixin_add_members_async (TpSvcChannelInterfaceGroup *obj, const GArray *contacts, const gchar *message, DBusGMethodInvocation *context) { GError *error = NULL; if (tp_group_mixin_add_members ((GObject *) obj, contacts, message, &error)) { tp_svc_channel_interface_group_return_from_add_members (context); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_remove_members: (skip) * @obj: An object implementing the group interface using this mixin * @contacts: A GArray of guint representing contacts * @message: A message to be sent to those contacts, if supported * @error: Used to return an error if %FALSE is returned * * Request that the given contacts be removed from the group as if in response * to user action. If the group's flags prohibit this, raise * PermissionDenied. If any of the handles is invalid, raise InvalidHandle. * If any of the handles is absent from the group, raise NotAvailable. * Otherwise attempt to remove the contacts by calling the callbacks provided * by the channel implementation. * * Returns: %TRUE on success */ gboolean tp_group_mixin_remove_members (GObject *obj, const GArray *contacts, const gchar *message, GError **error) { return tp_group_mixin_remove_members_with_reason (obj, contacts, message, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, error); } /** * tp_group_mixin_remove_members_with_reason: (skip) * @obj: An object implementing the group interface using this mixin * @contacts: A GArray of guint representing contacts * @message: A message to be sent to those contacts, if supported * @reason: A #TpChannelGroupChangeReason * @error: Used to return an error if %FALSE is returned * * Request that the given contacts be removed from the group as if in response * to user action. If the group's flags prohibit this, raise * PermissionDenied. If any of the handles is invalid, raise InvalidHandle. * If any of the handles is absent from the group, raise NotAvailable. * Otherwise attempt to remove the contacts by calling the callbacks provided * by the channel implementation. * * Returns: %TRUE on success */ gboolean tp_group_mixin_remove_members_with_reason (GObject *obj, const GArray *contacts, const gchar *message, guint reason, GError **error) { TpGroupMixinClass *mixin_cls = TP_GROUP_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); guint i; TpHandle handle; /* reject invalid handles */ if (!tp_handles_are_valid (mixin->handle_repo, contacts, FALSE, error)) return FALSE; /* check removing is allowed by flags */ for (i = 0; i < contacts->len; i++) { handle = g_array_index (contacts, TpHandle, i); if (mixin_cls->priv->allow_self_removal && handle == mixin->self_handle && (tp_handle_set_is_member (mixin->members, handle) || tp_handle_set_is_member (mixin->remote_pending, handle) || tp_handle_set_is_member (mixin->local_pending, handle))) { /* don't check the flags - attempting to remove the self-handle * is explicitly always allowed by this channel */ } else if (tp_handle_set_is_member (mixin->members, handle)) { if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CAN_REMOVE) == 0) { DEBUG ("handle %u cannot be removed from members without " "GROUP_FLAG_CAN_REMOVE", handle); g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED, "handle %u cannot be removed from members without " "GROUP_FLAG_CAN_REMOVE", handle); return FALSE; } } else if (tp_handle_set_is_member (mixin->remote_pending, handle)) { if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CAN_RESCIND) == 0) { DEBUG ("handle %u cannot be removed from remote pending " "without GROUP_FLAG_CAN_RESCIND", handle); g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED, "handle %u cannot be removed from remote pending without " "GROUP_FLAG_CAN_RESCIND", handle); return FALSE; } } else if (!tp_handle_set_is_member (mixin->local_pending, handle)) { DEBUG ("handle %u is not a current or pending member", handle); /* we'll skip this handle during the second pass */ } } /* remove handle by handle */ for (i = 0; i < contacts->len; i++) { handle = g_array_index (contacts, TpHandle, i); if (!tp_handle_set_is_member (mixin->members, handle) && !tp_handle_set_is_member (mixin->remote_pending, handle) && !tp_handle_set_is_member (mixin->local_pending, handle)) continue; if (mixin_cls->priv->remove_with_reason != NULL) { if (!mixin_cls->priv->remove_with_reason (obj, handle, message, reason, error)) { return FALSE; } } else if (mixin_cls->remove_member != NULL) { if (!mixin_cls->remove_member (obj, handle, message, error)) { return FALSE; } } else { g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Removing contacts from this Group channel is not possible"); return FALSE; } } return TRUE; } static void tp_group_mixin_remove_members_with_reason_async (TpSvcChannelInterfaceGroup *obj, const GArray *contacts, const gchar *message, guint reason, DBusGMethodInvocation *context) { GError *error = NULL; if (tp_group_mixin_remove_members_with_reason ((GObject *) obj, contacts, message, reason, &error)) { tp_svc_channel_interface_group_return_from_remove_members_with_reason (context); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } static void tp_group_mixin_remove_members_async (TpSvcChannelInterfaceGroup *obj, const GArray *contacts, const gchar *message, DBusGMethodInvocation *context) { GError *error = NULL; if (tp_group_mixin_remove_members_with_reason ((GObject *) obj, contacts, message, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, &error)) { tp_svc_channel_interface_group_return_from_remove_members (context); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_get_members: (skip) * @obj: An object implementing the group interface using this mixin * @ret: Used to return a newly-allocated GArray of guint contact handles * @error: Unused * * Get the group's current members * * Returns: %TRUE */ gboolean tp_group_mixin_get_members (GObject *obj, GArray **ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); *ret = tp_handle_set_to_array (mixin->members); return TRUE; } static void tp_group_mixin_get_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { GArray *ret; GError *error = NULL; if (tp_group_mixin_get_members ((GObject *) obj, &ret, &error)) { tp_svc_channel_interface_group_return_from_get_members ( context, ret); g_array_unref (ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_get_local_pending_members: (skip) * @obj: An object implementing the group interface using this mixin * @ret: Used to return a newly-allocated GArray of guint contact handles * @error: Unused * * Get the group's local-pending members. * * Returns: %TRUE */ gboolean tp_group_mixin_get_local_pending_members (GObject *obj, GArray **ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); *ret = tp_handle_set_to_array (mixin->local_pending); return TRUE; } static void tp_group_mixin_get_local_pending_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { GArray *ret; GError *error = NULL; if (tp_group_mixin_get_local_pending_members ((GObject *) obj, &ret, &error)) { tp_svc_channel_interface_group_return_from_get_local_pending_members ( context, ret); g_array_unref (ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } typedef struct { TpGroupMixin *mixin; GPtrArray *array; } _mixin_and_array_of_info; static void local_pending_members_with_info_foreach (TpHandleSet *set, TpHandle i, gpointer userdata) { _mixin_and_array_of_info *data = userdata; TpGroupMixinPrivate *priv = data->mixin->priv; GType info_type = TP_STRUCT_TYPE_LOCAL_PENDING_INFO; GValue entry = { 0, }; LocalPendingInfo *info = g_hash_table_lookup (priv->local_pending_info, GUINT_TO_POINTER(i)); g_assert (info != NULL); g_value_init (&entry, info_type); g_value_take_boxed (&entry, dbus_g_type_specialized_construct (info_type)); dbus_g_type_struct_set (&entry, 0, i, 1, info->actor, 2, info->reason, 3, info->message, G_MAXUINT); g_ptr_array_add (data->array, g_value_get_boxed (&entry)); } /** * tp_group_mixin_get_local_pending_members_with_info: (skip) * @obj: An object implementing the group interface using this mixin * @ret: Used to return a newly-allocated GPtrArray of D-Bus structures each * containing the handle of a local-pending contact, the handle of a contact * responsible for adding them to the group (or 0), the reason code * and a related message (e.g. their request to join the group) * @error: Unused * * Get the group's local-pending members and information about their * requests to join the channel. * * Returns: %TRUE */ gboolean tp_group_mixin_get_local_pending_members_with_info ( GObject *obj, GPtrArray **ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); _mixin_and_array_of_info data = { mixin, NULL }; *ret = g_ptr_array_new (); data.array = *ret; tp_handle_set_foreach (mixin->local_pending, local_pending_members_with_info_foreach, &data); return TRUE; } static void tp_group_mixin_get_local_pending_members_with_info_async ( TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { GPtrArray *ret; GError *error = NULL; if (tp_group_mixin_get_local_pending_members_with_info ((GObject *) obj, &ret, &error)) { guint i; tp_svc_channel_interface_group_return_from_get_local_pending_members_with_info ( context, ret); for (i = 0 ; i < ret->len; i++) { tp_value_array_free (g_ptr_array_index (ret,i)); } g_ptr_array_unref (ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_get_remote_pending_members: (skip) * @obj: An object implementing the group interface using this mixin * @ret: Used to return a newly-allocated GArray of guint representing the * handles of the group's remote pending members * @error: Unused * * Get the group's remote-pending members. * * Returns: %TRUE */ gboolean tp_group_mixin_get_remote_pending_members (GObject *obj, GArray **ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); *ret = tp_handle_set_to_array (mixin->remote_pending); return TRUE; } static void tp_group_mixin_get_remote_pending_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { GArray *ret; GError *error = NULL; if (tp_group_mixin_get_remote_pending_members ((GObject *) obj, &ret, &error)) { tp_svc_channel_interface_group_return_from_get_remote_pending_members ( context, ret); g_array_unref (ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_get_all_members: (skip) * @obj: An object implementing the group interface using this mixin * @members: Used to return a newly-allocated GArray of guint representing * the handles of the group's members * @local_pending: Used to return a newly-allocated GArray of guint * representing the handles of the group's local pending members * @remote_pending: Used to return a newly-allocated GArray of guint * representing the handles of the group's remote pending members * @error: Unused * * Get the group's current and pending members. * * Returns: %TRUE */ gboolean tp_group_mixin_get_all_members (GObject *obj, GArray **members, GArray **local_pending, GArray **remote_pending, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); *members = tp_handle_set_to_array (mixin->members); *local_pending = tp_handle_set_to_array (mixin->local_pending); *remote_pending = tp_handle_set_to_array (mixin->remote_pending); return TRUE; } static void tp_group_mixin_get_all_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { GArray *mem, *local, *remote; GError *error = NULL; if (tp_group_mixin_get_all_members ((GObject *) obj, &mem, &local, &remote, &error)) { tp_svc_channel_interface_group_return_from_get_all_members ( context, mem, local, remote); g_array_unref (mem); g_array_unref (local); g_array_unref (remote); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * tp_group_mixin_get_handle_owners: (skip) * @obj: An object implementing the group interface with this mixin * @handles: An array of guint representing locally valid handles * @ret: Used to return an array of guint representing globally valid * handles, or 0 where unavailable, if %TRUE is returned * @error: Used to return an error if %FALSE is returned * * If the mixin has the flag %TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, * return the global owners of the given local handles, or 0 where * unavailable. * * Returns: %TRUE (setting @ret) on success, %FALSE (setting @error) on * failure */ gboolean tp_group_mixin_get_handle_owners (GObject *obj, const GArray *handles, GArray **ret, GError **error) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); TpGroupMixinPrivate *priv = mixin->priv; guint i; if ((mixin->group_flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) == 0) { g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, "channel doesn't have channel specific handles"); return FALSE; } if (!tp_handles_are_valid (mixin->handle_repo, handles, FALSE, error)) { return FALSE; } *ret = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), handles->len); for (i = 0; i < handles->len; i++) { TpHandle local_handle = g_array_index (handles, TpHandle, i); TpHandle owner_handle; if (!tp_handle_set_is_member (mixin->members, local_handle)) { g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "handle %u is not a member", local_handle); g_array_unref (*ret); *ret = NULL; return FALSE; } owner_handle = GPOINTER_TO_UINT ( g_hash_table_lookup (priv->handle_owners, GUINT_TO_POINTER (local_handle))); g_array_append_val (*ret, owner_handle); } return TRUE; } static void tp_group_mixin_get_handle_owners_async (TpSvcChannelInterfaceGroup *obj, const GArray *handles, DBusGMethodInvocation *context) { GArray *ret; GError *error = NULL; if (tp_group_mixin_get_handle_owners ((GObject *) obj, handles, &ret, &error)) { tp_svc_channel_interface_group_return_from_get_handle_owners ( context, ret); g_array_unref (ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } #define GFTS_APPEND_FLAG_IF_SET(flag) \ if (flags & flag) \ { \ if (i++ > 0) \ g_string_append (str, "|"); \ g_string_append (str, #flag + 22); \ flags &= ~flag; \ } static gchar * group_flags_to_string (TpChannelGroupFlags flags) { gint i = 0; GString *str; str = g_string_new ("["); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CAN_ADD); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CAN_REMOVE); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CAN_RESCIND); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_REJECT); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_ONLY_ONE_GROUP); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_HANDLE_OWNERS_NOT_AVAILABLE); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_PROPERTIES); GFTS_APPEND_FLAG_IF_SET (TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED); /* Print out any remaining flags that weren't removed in the above cases * numerically. */ if (flags != 0) { if (i > 0) g_string_append (str, "|"); g_string_append_printf (str, "%u", flags); } g_string_append (str, "]"); return g_string_free (str, FALSE); } /** * tp_group_mixin_change_flags: (skip) * @obj: An object implementing the groups interface using this mixin * @add: Flags to be added * @del: Flags to be removed * * Request a change to be made to the flags. If any flags were actually * set or cleared, emits the GroupFlagsChanged signal with the changes. * * It is an error to set any of the same bits in both @add and @del. * * Changed in 0.7.7: the signal is not emitted if adding @add and * removing @del had no effect on the existing group flags. */ void tp_group_mixin_change_flags (GObject *obj, TpChannelGroupFlags add, TpChannelGroupFlags del) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); TpChannelGroupFlags added, removed; /* It's meaningless to want to add and remove the same capability */ g_return_if_fail ((add & del) == 0); added = add & ~mixin->group_flags; mixin->group_flags |= added; removed = del & mixin->group_flags; mixin->group_flags &= ~removed; if (added == 0 && removed == 0) { DEBUG ("No change: %u includes all the bits of %u and none of %u", mixin->group_flags, add, del); } else { gchar *str_added, *str_removed, *str_flags; if (DEBUGGING) { str_added = group_flags_to_string (added); str_removed = group_flags_to_string (removed); str_flags = group_flags_to_string (mixin->group_flags); DEBUG ("emitting group flags changed\n" " added : %s\n" " removed : %s\n" " flags now: %s\n", str_added, str_removed, str_flags); g_free (str_added); g_free (str_removed); g_free (str_flags); } tp_svc_channel_interface_group_emit_group_flags_changed (obj, added, removed); if (mixin->priv->externals != NULL) { guint i; for (i = 0; i < mixin->priv->externals->len; i++) { tp_svc_channel_interface_group_emit_group_flags_changed ((GObject *) g_ptr_array_index (mixin->priv->externals, i), added, removed); } } } } static gchar * member_array_to_string (TpHandleRepoIface *repo, const GArray *array) { GString *str; guint i; str = g_string_new ("["); for (i = 0; i < array->len; i++) { TpHandle handle; const gchar *handle_str; handle = g_array_index (array, guint, i); handle_str = tp_handle_inspect (repo, handle); g_string_append_printf (str, "%s%u (%s)", /* indent to: " remote_pending: [" */ (i > 0) ? "\n " : "", handle, handle_str); } g_string_append (str, "]"); return g_string_free (str, FALSE); } static GArray *remove_handle_owners_if_exist (GObject *obj, GArray *array) G_GNUC_WARN_UNUSED_RESULT; typedef struct { TpGroupMixin *mixin; LocalPendingInfo *info; } _mixin_and_info; static void local_pending_added_foreach (guint i, gpointer userdata) { _mixin_and_info *data = userdata; TpGroupMixinPrivate *priv = data->mixin->priv; g_hash_table_insert (priv->local_pending_info, GUINT_TO_POINTER (i), local_pending_info_new (data->mixin->handle_repo, data->info->actor, data->info->reason, data->info->message)); } static void local_pending_added (TpGroupMixin *mixin, const TpIntset *added, TpHandle actor, guint reason, const gchar *message) { LocalPendingInfo info; _mixin_and_info data = { mixin, &info }; info.actor = actor; info.reason = reason; info.message = message; tp_intset_foreach (added, local_pending_added_foreach, &data); } static void local_pending_remove_foreach (guint i, gpointer userdata) { TpGroupMixin *mixin = (TpGroupMixin *) userdata; TpGroupMixinPrivate *priv = mixin->priv; g_hash_table_remove (priv->local_pending_info, GUINT_TO_POINTER(i)); } static void local_pending_remove (TpGroupMixin *mixin, TpIntset *removed) { tp_intset_foreach (removed, local_pending_remove_foreach, mixin); } static void add_members_in_array (GHashTable *contact_ids, TpHandleRepoIface *repo, const GArray *handles) { guint i; for (i = 0; i < handles->len; i++) { TpHandle handle = g_array_index (handles, TpHandle, i); const gchar *id = tp_handle_inspect (repo, handle); g_hash_table_insert (contact_ids, GUINT_TO_POINTER (handle), (gchar *) id); } } static gboolean maybe_add_contact_ids (TpGroupMixin *mixin, const GArray *add, const GArray *local_pending, const GArray *remote_pending, TpHandle actor, GHashTable *details) { GHashTable *contact_ids; /* If the library user had its own ideas about which members' IDs to include * in the change details, we'll leave that intact. */ if (tp_asv_lookup (details, "contact-ids") != NULL) return FALSE; /* The library user didn't include the new members' IDs in details; let's add * the IDs of the handles being added to the group (but not removed, as per * the spec) and of the actor. */ contact_ids = g_hash_table_new (NULL, NULL); add_members_in_array (contact_ids, mixin->handle_repo, add); add_members_in_array (contact_ids, mixin->handle_repo, local_pending); add_members_in_array (contact_ids, mixin->handle_repo, remote_pending); if (actor != 0) { const gchar *id = tp_handle_inspect (mixin->handle_repo, actor); g_hash_table_insert (contact_ids, GUINT_TO_POINTER (actor), (gchar *) id); } g_hash_table_insert (details, "contact-ids", tp_g_value_slice_new_take_boxed (TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP, contact_ids)); return TRUE; } static void remove_contact_ids (GHashTable *details) { GValue *contact_ids_v = g_hash_table_lookup (details, "contact-ids"); g_assert (contact_ids_v != NULL); g_hash_table_steal (details, "contact-ids"); tp_g_value_slice_free (contact_ids_v); } 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) { TpGroupMixin *mixin = TP_GROUP_MIXIN (channel); GHashTable *details_ = (GHashTable *) details; /* Cast the pain away! */ gboolean added_contact_ids; if (DEBUGGING) { gchar *add_str, *rem_str, *local_str, *remote_str; add_str = member_array_to_string (mixin->handle_repo, add); rem_str = member_array_to_string (mixin->handle_repo, del); local_str = member_array_to_string (mixin->handle_repo, local_pending); remote_str = member_array_to_string (mixin->handle_repo, remote_pending); DEBUG ("emitting members changed\n" " message : \"%s\"\n" " added : %s\n" " removed : %s\n" " local_pending : %s\n" " remote_pending: %s\n" " actor : %u\n" " reason : %u: %s\n", message, add_str, rem_str, local_str, remote_str, actor, reason, group_change_reason_str (reason)); g_free (add_str); g_free (rem_str); g_free (local_str); g_free (remote_str); } added_contact_ids = maybe_add_contact_ids (mixin, add, local_pending, remote_pending, actor, details_); tp_svc_channel_interface_group_emit_members_changed (channel, message, add, del, local_pending, remote_pending, actor, reason); tp_svc_channel_interface_group_emit_members_changed_detailed (channel, add, del, local_pending, remote_pending, details_); if (mixin->priv->externals != NULL) { guint i; for (i = 0; i < mixin->priv->externals->len; i++) { GObject *external = g_ptr_array_index (mixin->priv->externals, i); tp_svc_channel_interface_group_emit_members_changed (external, message, add, del, local_pending, remote_pending, actor, reason); tp_svc_channel_interface_group_emit_members_changed_detailed ( external, add, del, local_pending, remote_pending, details_); } } if (added_contact_ids) remove_contact_ids (details_); } 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) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); TpIntset *new_add, *new_remove, *new_local_pending, *new_remote_pending, *tmp, *tmp2, *empty; gboolean ret; empty = tp_intset_new (); if (message == NULL) message = ""; if (add == NULL) add = empty; if (del == NULL) del = empty; if (add_local_pending == NULL) add_local_pending = empty; if (add_remote_pending == NULL) add_remote_pending = empty; /* remember the actor handle before any handle unreffing happens */ if (actor) { tp_handle_set_add (mixin->priv->actors, actor); } /* members + add */ new_add = tp_handle_set_update (mixin->members, add); /* members - del */ new_remove = tp_handle_set_difference_update (mixin->members, del); /* members - add_local_pending */ tmp = tp_handle_set_difference_update (mixin->members, add_local_pending); tp_intset_destroy (tmp); /* members - add_remote_pending */ tmp = tp_handle_set_difference_update (mixin->members, add_remote_pending); tp_intset_destroy (tmp); /* local pending + add_local_pending */ new_local_pending = tp_handle_set_update (mixin->local_pending, add_local_pending); local_pending_added (mixin, add_local_pending, actor, reason, message); /* local pending - add */ tmp = tp_handle_set_difference_update (mixin->local_pending, add); local_pending_remove (mixin, tmp); tp_intset_destroy (tmp); /* local pending - del */ tmp = tp_handle_set_difference_update (mixin->local_pending, del); local_pending_remove (mixin, tmp); tmp2 = tp_intset_union (new_remove, tmp); tp_intset_destroy (new_remove); tp_intset_destroy (tmp); new_remove = tmp2; /* local pending - add_remote_pending */ tmp = tp_handle_set_difference_update (mixin->local_pending, add_remote_pending); local_pending_remove (mixin, tmp); tp_intset_destroy (tmp); /* remote pending + add_remote_pending */ new_remote_pending = tp_handle_set_update (mixin->remote_pending, add_remote_pending); /* remote pending - add */ tmp = tp_handle_set_difference_update (mixin->remote_pending, add); tp_intset_destroy (tmp); /* remote pending - del */ tmp = tp_handle_set_difference_update (mixin->remote_pending, del); tmp2 = tp_intset_union (new_remove, tmp); tp_intset_destroy (new_remove); tp_intset_destroy (tmp); new_remove = tmp2; /* remote pending - local_pending */ tmp = tp_handle_set_difference_update (mixin->remote_pending, add_local_pending); tp_intset_destroy (tmp); if (tp_intset_size (new_add) > 0 || tp_intset_size (new_remove) > 0 || tp_intset_size (new_local_pending) > 0 || tp_intset_size (new_remote_pending) > 0) { GArray *arr_add, *arr_remove, *arr_local, *arr_remote; GArray *arr_owners_removed; /* translate intsets to arrays */ arr_add = tp_intset_to_array (new_add); arr_remove = tp_intset_to_array (new_remove); arr_local = tp_intset_to_array (new_local_pending); arr_remote = tp_intset_to_array (new_remote_pending); /* remove any handle owner mappings */ arr_owners_removed = remove_handle_owners_if_exist (obj, arr_remove); /* emit signals */ emit_members_changed_signals (obj, message, arr_add, arr_remove, arr_local, arr_remote, actor, reason, details); if (arr_owners_removed->len > 0) { GHashTable *empty_hash_table = g_hash_table_new (NULL, NULL); tp_svc_channel_interface_group_emit_handle_owners_changed (obj, empty_hash_table, arr_owners_removed); tp_svc_channel_interface_group_emit_handle_owners_changed_detailed ( obj, empty_hash_table, arr_owners_removed, empty_hash_table); if (mixin->priv->externals != NULL) { guint i; for (i = 0; i < mixin->priv->externals->len; i++) { tp_svc_channel_interface_group_emit_handle_owners_changed ( g_ptr_array_index (mixin->priv->externals, i), empty_hash_table, arr_owners_removed); tp_svc_channel_interface_group_emit_handle_owners_changed_detailed ( g_ptr_array_index (mixin->priv->externals, i), empty_hash_table, arr_owners_removed, empty_hash_table); } } g_hash_table_unref (empty_hash_table); } /* free arrays */ g_array_unref (arr_add); g_array_unref (arr_remove); g_array_unref (arr_local); g_array_unref (arr_remote); g_array_unref (arr_owners_removed); ret = TRUE; } else { DEBUG ("not emitting signal, nothing changed"); ret = FALSE; } /* free intsets */ tp_intset_destroy (new_add); tp_intset_destroy (new_remove); tp_intset_destroy (new_local_pending); tp_intset_destroy (new_remote_pending); tp_intset_destroy (empty); return ret; } /** * tp_group_mixin_change_members: (skip) * @obj: An object implementing the group interface using this mixin * @message: A message to be sent to the affected contacts if possible; * %NULL is allowed, and is mapped to an empty string * @add: A set of contact handles to be added to the members (if not * already present) and removed from local pending and remote pending * (if present) * @del: A set of contact handles to be removed from members, * local pending or remote pending, wherever they are present * @add_local_pending: A set of contact handles to be added to local pending, * and removed from members and remote pending * @add_remote_pending: A set of contact handles to be added to remote pending, * and removed from members and local pending * @actor: The handle of the contact responsible for this change * @reason: The reason for this change * * Change the sets of members as given by the arguments, and emit the * MembersChanged and MembersChangedDetailed signals if the changes were not a * no-op. * * This function must be called in response to events on the underlying * IM protocol, and must not be called in direct response to user input; * it does not respect the permissions flags, but changes the group directly. * * If any two of add, del, add_local_pending and add_remote_pending have * a non-empty intersection, the result is undefined. Don't do that. * * Each of the TpIntset arguments may be %NULL, which is treated as * equivalent to an empty set. * * Returns: %TRUE if the group was changed and the MembersChanged(Detailed) * signals were emitted; %FALSE if nothing actually changed and the signals * were suppressed. */ 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) { GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); gboolean ret; if (actor != 0) { g_hash_table_insert (details, "actor", tp_g_value_slice_new_uint (actor)); } if (reason != TP_CHANNEL_GROUP_CHANGE_REASON_NONE) { g_hash_table_insert (details, "change-reason", tp_g_value_slice_new_uint (reason)); } if (message != NULL && message[0] != '\0') { g_hash_table_insert (details, "message", tp_g_value_slice_new_string (message)); } ret = change_members (obj, message, add, del, add_local_pending, add_remote_pending, actor, reason, details); g_hash_table_unref (details); return ret; } /** * tp_group_mixin_change_members_detailed: (skip) * @obj: An object implementing the group interface using this mixin * @add: A set of contact handles to be added to the members (if not * already present) and removed from local pending and remote pending * (if present) * @del: A set of contact handles to be removed from members, * local pending or remote pending, wherever they are present * @add_local_pending: A set of contact handles to be added to local pending, * and removed from members and remote pending * @add_remote_pending: A set of contact handles to be added to remote pending, * and removed from members and local pending * @details: a map from strings to GValues detailing the change * * Change the sets of members as given by the arguments, and emit the * MembersChanged and MembersChangedDetailed signals if the changes were not a * no-op. * * This function must be called in response to events on the underlying * IM protocol, and must not be called in direct response to user input; * it does not respect the permissions flags, but changes the group directly. * * If any two of add, del, add_local_pending and add_remote_pending have * a non-empty intersection, the result is undefined. Don't do that. * * Each of the TpIntset arguments may be %NULL, which is treated as * equivalent to an empty set. * * details may contain, among other entries, the well-known * keys (and corresponding type, wrapped in a GValue) defined by the * Group.MembersChangedDetailed signal's specification; these include "actor" * (a handle as G_TYPE_UINT), "change-reason" (an element of * #TpChannelGroupChangeReason as G_TYPE_UINT), "message" (G_TYPE_STRING), * "error" (G_TYPE_STRING), "debug-message" (G_TYPE_STRING). * * If all of the information in details could be passed to * tp_group_mixin_change_members() then calling this function instead provides * no benefit. Calling this function without setting * #TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED with * tp_group_mixin_change_members() first is not very useful, as clients will * not know to listen for MembersChangedDetailed and thus will miss the * details. * * Returns: %TRUE if the group was changed and the MembersChanged(Detailed) * signals were emitted; %FALSE if nothing actually changed and the signals * were suppressed. * * Since: 0.7.21 */ 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) { const gchar *message; TpHandle actor; TpChannelGroupChangeReason reason; gboolean valid; g_return_val_if_fail (details != NULL, FALSE); /* For each detail we're extracting for the benefit of old-school * MembersChanged, warn if it's present but badly typed. */ message = tp_asv_get_string (details, "message"); g_warn_if_fail (message != NULL || tp_asv_lookup (details, "message") == NULL); /* change_members will cry (via tp_handle_set_add) if actor is non-zero and * invalid. */ actor = tp_asv_get_uint32 (details, "actor", &valid); g_warn_if_fail (valid || tp_asv_lookup (details, "actor") == NULL); reason = tp_asv_get_uint32 (details, "change-reason", &valid); g_warn_if_fail (valid || tp_asv_lookup (details, "change-reason") == NULL); return change_members (obj, message, add, del, add_local_pending, add_remote_pending, actor, reason, details); } /** * tp_group_mixin_add_handle_owner: (skip) * @obj: A GObject implementing the group interface with this mixin * @local_handle: A contact handle valid within this group (may not be 0) * @owner_handle: A contact handle valid globally, or 0 if the owner of the * @local_handle is unknown * * Note that the given local handle is an alias within this group * for the given globally-valid handle. It will be returned from subsequent * GetHandleOwner queries where appropriate. * * Changed in 0.7.10: The @owner_handle may be 0. To comply with telepathy-spec * 0.17.6, before adding any channel-specific handle to the members, * local-pending members or remote-pending members, you must call either * this function or tp_group_mixin_add_handle_owners(). */ void tp_group_mixin_add_handle_owner (GObject *obj, TpHandle local_handle, TpHandle owner_handle) { GHashTable *tmp; g_return_if_fail (local_handle != 0); tmp = g_hash_table_new (g_direct_hash, g_direct_equal); g_hash_table_insert (tmp, GUINT_TO_POINTER (local_handle), GUINT_TO_POINTER (owner_handle)); tp_group_mixin_add_handle_owners (obj, tmp); g_hash_table_unref (tmp); } static void add_us_mapping_for_handleset (GHashTable *map, TpHandleRepoIface *repo, TpHandleSet *handles) { TpIntset *set; TpIntsetFastIter iter; TpHandle handle; set = tp_handle_set_peek (handles); tp_intset_fast_iter_init (&iter, set); while (tp_intset_fast_iter_next (&iter, &handle)) g_hash_table_insert (map, GUINT_TO_POINTER (handle), (gchar *) tp_handle_inspect (repo, handle)); } static void add_us_mapping_for_owners_map (GHashTable *map, TpHandleRepoIface *repo, GHashTable *owners) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, owners); while (g_hash_table_iter_next (&iter, &key, &value)) { TpHandle local_handle = GPOINTER_TO_UINT (key); TpHandle owner_handle = GPOINTER_TO_UINT (value); g_hash_table_insert (map, key, (gchar *) tp_handle_inspect (repo, local_handle)); if (owner_handle != 0) g_hash_table_insert (map, value, (gchar *) tp_handle_inspect (repo, owner_handle)); } } static void add_handle_owners_helper (gpointer key, gpointer value, gpointer user_data) { TpHandle local_handle = GPOINTER_TO_UINT (key); TpGroupMixin *mixin = user_data; g_return_if_fail (local_handle != 0); g_hash_table_insert (mixin->priv->handle_owners, key, value); } /** * tp_group_mixin_add_handle_owners: (skip) * @obj: A GObject implementing the group interface with this mixin * @local_to_owner_handle: A map from contact handles valid within this group * (which may not be 0) to either contact handles valid globally, or 0 if the * owner of the corresponding key is unknown; all handles are stored using * GUINT_TO_POINTER * * Note that the given local handles are aliases within this group * for the given globally-valid handles. * * To comply with telepathy-spec 0.17.6, before adding any channel-specific * handle to the members, local-pending members or remote-pending members, you * must call either this function or tp_group_mixin_add_handle_owner(). * * Since: 0.7.10 */ void tp_group_mixin_add_handle_owners (GObject *obj, GHashTable *local_to_owner_handle) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); GArray *empty_array; GHashTable *ids = g_hash_table_new (NULL, NULL); if (g_hash_table_size (local_to_owner_handle) == 0) return; empty_array = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0); g_hash_table_foreach (local_to_owner_handle, add_handle_owners_helper, mixin); tp_svc_channel_interface_group_emit_handle_owners_changed (obj, local_to_owner_handle, empty_array); add_us_mapping_for_owners_map (ids, mixin->handle_repo, local_to_owner_handle); tp_svc_channel_interface_group_emit_handle_owners_changed_detailed (obj, local_to_owner_handle, empty_array, ids); g_array_unref (empty_array); g_hash_table_unref (ids); } static GArray * remove_handle_owners_if_exist (GObject *obj, GArray *array) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); TpGroupMixinPrivate *priv = mixin->priv; guint i; GArray *ret; ret = g_array_sized_new (FALSE, FALSE, sizeof (guint), array->len); for (i = 0; i < array->len; i++) { TpHandle handle = g_array_index (array, guint, i); gpointer local_handle, owner_handle; g_assert (handle != 0); if (g_hash_table_lookup_extended (priv->handle_owners, GUINT_TO_POINTER (handle), &local_handle, &owner_handle)) { g_assert (GPOINTER_TO_UINT (local_handle) == handle); g_array_append_val (ret, handle); g_hash_table_remove (priv->handle_owners, GUINT_TO_POINTER (handle)); } } return ret; } static GHashTable * dup_member_identifiers (GObject *obj) { TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); GHashTable *ret = g_hash_table_new (NULL, NULL); g_hash_table_insert (ret, GUINT_TO_POINTER (mixin->self_handle), (gchar *) tp_handle_inspect (mixin->handle_repo, mixin->self_handle)); add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->priv->actors); add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->members); add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->local_pending); add_us_mapping_for_handleset (ret, mixin->handle_repo, mixin->remote_pending); add_us_mapping_for_owners_map (ret, mixin->handle_repo, mixin->priv->handle_owners); return ret; } /** * tp_group_mixin_iface_init: (skip) * @g_iface: A #TpSvcChannelInterfaceGroupClass * @iface_data: Unused * * Fill in the vtable entries needed to implement the group interface using * this mixin. This function should usually be called via * G_IMPLEMENT_INTERFACE. */ void tp_group_mixin_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfaceGroupClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_channel_interface_group_implement_##x (klass,\ tp_group_mixin_##x##_async) IMPLEMENT(add_members); IMPLEMENT(get_all_members); IMPLEMENT(get_group_flags); IMPLEMENT(get_handle_owners); IMPLEMENT(get_local_pending_members); IMPLEMENT(get_local_pending_members_with_info); IMPLEMENT(get_members); IMPLEMENT(get_remote_pending_members); IMPLEMENT(get_self_handle); IMPLEMENT(remove_members); IMPLEMENT(remove_members_with_reason); #undef IMPLEMENT } enum { MIXIN_DP_GROUP_FLAGS, MIXIN_DP_HANDLE_OWNERS, MIXIN_DP_LOCAL_PENDING_MEMBERS, MIXIN_DP_MEMBERS, MIXIN_DP_REMOTE_PENDING_MEMBERS, MIXIN_DP_SELF_HANDLE, MIXIN_DP_MEMBER_IDENTIFIERS, NUM_MIXIN_DBUS_PROPERTIES }; /** * tp_group_mixin_get_dbus_property: (skip) * @object: An object with this mixin * @interface: Must be %TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP * @name: A quark representing the D-Bus property name, either * "GroupFlags", "HandleOwners", "LocalPendingMembers", "Members", * "RemotePendingMembers" or "SelfHandle" * @value: A GValue pre-initialized to the right type, into which to put the * value * @unused: Ignored * * An implementation of #TpDBusPropertiesMixinGetter which assumes that the * @object has the group mixin. It can only be used for the Group interface. * * Since: 0.7.10 */ void tp_group_mixin_get_dbus_property (GObject *object, GQuark interface, GQuark name, GValue *value, gpointer unused G_GNUC_UNUSED) { TpGroupMixin *mixin; static GQuark q[NUM_MIXIN_DBUS_PROPERTIES] = { 0 }; if (G_UNLIKELY (q[0] == 0)) { q[MIXIN_DP_GROUP_FLAGS] = g_quark_from_static_string ("GroupFlags"); q[MIXIN_DP_HANDLE_OWNERS] = g_quark_from_static_string ("HandleOwners"); q[MIXIN_DP_LOCAL_PENDING_MEMBERS] = g_quark_from_static_string ( "LocalPendingMembers"); q[MIXIN_DP_MEMBERS] = g_quark_from_static_string ("Members"); q[MIXIN_DP_REMOTE_PENDING_MEMBERS] = g_quark_from_static_string ( "RemotePendingMembers"); q[MIXIN_DP_SELF_HANDLE] = g_quark_from_static_string ("SelfHandle"); q[MIXIN_DP_MEMBER_IDENTIFIERS] = g_quark_from_static_string ("MemberIdentifiers"); } g_return_if_fail (object != NULL); mixin = TP_GROUP_MIXIN (object); g_return_if_fail (mixin != NULL); g_return_if_fail (interface == TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP); g_return_if_fail (name != 0); g_return_if_fail (value != NULL); if (name == q[MIXIN_DP_GROUP_FLAGS]) { g_return_if_fail (G_VALUE_HOLDS_UINT (value)); g_value_set_uint (value, mixin->group_flags); } else if (name == q[MIXIN_DP_HANDLE_OWNERS]) { g_return_if_fail (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_OWNER_MAP)); g_value_set_boxed (value, mixin->priv->handle_owners); } else if (name == q[MIXIN_DP_LOCAL_PENDING_MEMBERS]) { GPtrArray *ret = NULL; gboolean success; g_return_if_fail (G_VALUE_HOLDS_BOXED (value)); success = tp_group_mixin_get_local_pending_members_with_info (object, &ret, NULL); g_assert (success); /* as of 0.7.8, cannot fail */ g_value_take_boxed (value, ret); } else if (name == q[MIXIN_DP_MEMBERS]) { GArray *ret = NULL; gboolean success; g_return_if_fail (G_VALUE_HOLDS_BOXED (value)); success = tp_group_mixin_get_members (object, &ret, NULL); g_assert (success); /* as of 0.7.8, cannot fail */ g_value_take_boxed (value, ret); } else if (name == q[MIXIN_DP_REMOTE_PENDING_MEMBERS]) { GArray *ret = NULL; gboolean success; g_return_if_fail (G_VALUE_HOLDS_BOXED (value)); success = tp_group_mixin_get_remote_pending_members (object, &ret, NULL); g_assert (success); /* as of 0.7.8, cannot fail */ g_value_take_boxed (value, ret); } else if (name == q[MIXIN_DP_SELF_HANDLE]) { g_return_if_fail (G_VALUE_HOLDS_UINT (value)); g_value_set_uint (value, mixin->self_handle); } else if (name == q[MIXIN_DP_MEMBER_IDENTIFIERS]) { g_return_if_fail (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP)); g_value_take_boxed (value, dup_member_identifiers (object)); } else { g_return_if_reached (); } } static TpDBusPropertiesMixinPropImpl known_group_props[] = { { "GroupFlags", NULL, NULL }, { "HandleOwners", NULL, NULL }, { "LocalPendingMembers", NULL, NULL }, { "Members", NULL, NULL }, { "RemotePendingMembers", NULL, NULL }, { "SelfHandle", NULL, NULL }, { "MemberIdentifiers", NULL, NULL }, { NULL } }; /** * tp_group_mixin_init_dbus_properties: (skip) * @cls: The class of an object with this mixin * * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of * the Group interface's properties. * * This uses tp_group_mixin_get_dbus_property() as the property getter and * sets up a list of the supported properties for it. Having called this, you * should add #TP_CHANNEL_GROUP_FLAG_PROPERTIES to any channels of this class * with tp_group_mixin_change_flags() to indicate that the DBus properties are * available. * * Since: 0.7.10 */ void tp_group_mixin_init_dbus_properties (GObjectClass *cls) { tp_dbus_properties_mixin_implement_interface (cls, TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP, tp_group_mixin_get_dbus_property, NULL, known_group_props); } #define TP_EXTERNAL_GROUP_MIXIN_OBJ(o) \ ((GObject *) g_object_get_qdata (o, \ _external_group_mixin_get_obj_quark ())) static GQuark _external_group_mixin_get_obj_quark (void) { static GQuark quark = 0; if (!quark) quark = g_quark_from_static_string ("TpExternalGroupMixinQuark"); return quark; } /** * tp_external_group_mixin_init: (skip) * @obj: An object implementing the groups interface using an external group * mixin * @obj_with_mixin: A GObject with the group mixin * * Fill in the qdata needed to implement the group interface using * the group mixin of another object. This function should usually be called * in the instance constructor. * * Since: 0.5.13 */ void tp_external_group_mixin_init (GObject *obj, GObject *obj_with_mixin) { g_object_ref (obj_with_mixin); g_object_set_qdata (obj, _external_group_mixin_get_obj_quark (), obj_with_mixin); tp_group_mixin_add_external (obj_with_mixin, obj); } /** * tp_external_group_mixin_finalize: (skip) * @obj: An object implementing the groups interface using an external group * mixin * * Remove the external group mixin. This function should usually be called * in the dispose or finalize function. * * Since: 0.5.13 */ void tp_external_group_mixin_finalize (GObject *obj) { GObject *obj_with_mixin = g_object_steal_qdata (obj, _external_group_mixin_get_obj_quark ()); tp_group_mixin_remove_external (obj_with_mixin, obj); g_object_unref (obj_with_mixin); } /** * tp_external_group_mixin_init_dbus_properties: (skip) * @cls: The class of an object with this mixin * * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of * the Group interface's properties. * * This uses tp_group_mixin_get_dbus_property() as the property getter and * sets up a list of the supported properties for it. Having called this, you * should add #TP_CHANNEL_GROUP_FLAG_PROPERTIES to channels containing the * mixin used by this class with tp_group_mixin_change_flags() to indicate that * the DBus properties are available. * * Since: 0.7.10 */ void tp_external_group_mixin_init_dbus_properties (GObjectClass *cls) { tp_dbus_properties_mixin_implement_interface (cls, TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP, tp_external_group_mixin_get_dbus_property, NULL, known_group_props); } /** * tp_external_group_mixin_get_dbus_property: (skip) * @object: An object with this mixin * @interface: Must be %TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP * @name: A quark representing the D-Bus property name, either * "GroupFlags", "HandleOwners", "LocalPendingMembers", "Members", * "RemotePendingMembers" or "SelfHandle" * @value: A GValue pre-initialized to the right type, into which to put the * value * @unused: Ignored * * An implementation of #TpDBusPropertiesMixinGetter which assumes that the * @object has the external group mixin. It can only be used for the Group * interface. * * Since: 0.7.10 */ void tp_external_group_mixin_get_dbus_property (GObject *object, GQuark interface, GQuark name, GValue *value, gpointer unused G_GNUC_UNUSED) { GObject *group = TP_EXTERNAL_GROUP_MIXIN_OBJ (object); if (group != NULL) { tp_group_mixin_get_dbus_property (group, interface, name, value, NULL); } else if (G_VALUE_HOLDS_BOXED (value)) { /* for certain boxed types we need to supply an empty value */ if (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_OWNER_MAP)) g_value_take_boxed (value, g_hash_table_new (NULL, NULL)); else if (G_VALUE_HOLDS (value, TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP)) g_value_take_boxed (value, g_hash_table_new (NULL, NULL)); else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_UINT_ARRAY)) g_value_take_boxed (value, g_array_sized_new (FALSE, FALSE, sizeof (guint), 0)); else if (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_LOCAL_PENDING_INFO_LIST)) g_value_take_boxed (value, g_ptr_array_sized_new (0)); } } #define EXTERNAL_OR_DIE(var) \ GObject *var = TP_EXTERNAL_GROUP_MIXIN_OBJ ((GObject *) obj); \ \ if (var == NULL) \ { \ GError na = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "I'm sure I " \ "had a group object around here somewhere?" };\ \ dbus_g_method_return_error (context, &na); \ return; \ } \ static void tp_external_group_mixin_add_members_async (TpSvcChannelInterfaceGroup *obj, const GArray *contacts, const gchar *message, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_add_members_async ((TpSvcChannelInterfaceGroup *) group, contacts, message, context); } static void tp_external_group_mixin_get_self_handle_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_self_handle_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_group_flags_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_group_flags_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_members_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_local_pending_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_local_pending_members_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_local_pending_members_with_info_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_local_pending_members_with_info_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_remote_pending_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_remote_pending_members_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_all_members_async (TpSvcChannelInterfaceGroup *obj, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_all_members_async ((TpSvcChannelInterfaceGroup *) group, context); } static void tp_external_group_mixin_get_handle_owners_async (TpSvcChannelInterfaceGroup *obj, const GArray *handles, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_get_handle_owners_async ((TpSvcChannelInterfaceGroup *) group, handles, context); } static void tp_external_group_mixin_remove_members_async (TpSvcChannelInterfaceGroup *obj, const GArray *contacts, const gchar *message, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_remove_members_with_reason_async ((TpSvcChannelInterfaceGroup *) group, contacts, message, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, context); } static void tp_external_group_mixin_remove_members_with_reason_async (TpSvcChannelInterfaceGroup *obj, const GArray *contacts, const gchar *message, guint reason, DBusGMethodInvocation *context) { EXTERNAL_OR_DIE (group) tp_group_mixin_remove_members_with_reason_async ((TpSvcChannelInterfaceGroup *) group, contacts, message, reason, context); } /** * tp_external_group_mixin_iface_init: (skip) * @g_iface: A #TpSvcChannelInterfaceGroupClass * @iface_data: Unused * * Fill in the vtable entries needed to implement the group interface using * the group mixin of another object. This function should usually be called * via G_IMPLEMENT_INTERFACE. * * Since: 0.5.13 */ void tp_external_group_mixin_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfaceGroupClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_channel_interface_group_implement_##x (klass,\ tp_external_group_mixin_##x##_async) IMPLEMENT(add_members); IMPLEMENT(get_all_members); IMPLEMENT(get_group_flags); IMPLEMENT(get_handle_owners); IMPLEMENT(get_local_pending_members); IMPLEMENT(get_local_pending_members_with_info); IMPLEMENT(get_members); IMPLEMENT(get_remote_pending_members); IMPLEMENT(get_self_handle); IMPLEMENT(remove_members); IMPLEMENT(remove_members_with_reason); #undef IMPLEMENT }