/* * channel.c - proxy for a Telepathy channel (Group interface) * * Copyright (C) 2007-2008 Collabora Ltd. * Copyright (C) 2007-2008 Nokia Corporation * * 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 */ #include "config.h" #include "telepathy-glib/channel-internal.h" #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_GROUPS #include "telepathy-glib/debug-internal.h" #include "telepathy-glib/proxy-internal.h" /* channel-group.c is ~all deprecated APIs, modern APIs are in * channel-contacts.c. So we allow this module to use deprecated functions. */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS /** * TP_ERRORS_REMOVED_FROM_GROUP: * * #GError domain representing the local user being removed from a channel * with the Group interface. The @code in a #GError with this domain must * be a member of #TpChannelGroupChangeReason. * * This error may be raised on non-Group channels with certain reason codes * if there's no better error code to use (mainly * %TP_CHANNEL_GROUP_CHANGE_REASON_NONE). * * This macro expands to a function call returning a #GQuark. * * Since: 0.7.1 */ GQuark tp_errors_removed_from_group_quark (void) { static GQuark q = 0; if (q == 0) q = g_quark_from_static_string ("tp_errors_removed_from_group_quark"); return q; } static void local_pending_info_free (LocalPendingInfo *info) { g_free (info->message); g_clear_object (&info->actor_contact); g_slice_free (LocalPendingInfo, info); } /** * tp_channel_group_get_self_handle: * @self: a channel * * Return the #TpChannel:group-self-handle property (see the description * of that property for notes on validity). * * Returns: the handle representing the user, or 0 * Since: 0.7.12 * Deprecated: New code should use tp_channel_group_get_self_contact() instead. */ TpHandle tp_channel_group_get_self_handle (TpChannel *self) { g_return_val_if_fail (TP_IS_CHANNEL (self), 0); return self->priv->group_self_handle; } /** * tp_channel_group_get_flags: * @self: a channel * * Return the #TpChannel:group-flags property (see the description * of that property for notes on validity). * * Returns: the group flags, or 0 * Since: 0.7.12 */ TpChannelGroupFlags tp_channel_group_get_flags (TpChannel *self) { g_return_val_if_fail (TP_IS_CHANNEL (self), 0); return self->priv->group_flags; } /** * tp_channel_group_get_members: * @self: a channel * * If @self is a group and the %TP_CHANNEL_FEATURE_GROUP feature has been * prepared, return a #TpIntset containing its members. * * If @self is a group but %TP_CHANNEL_FEATURE_GROUP has not been prepared, * the result may either be a set of members, or %NULL. * * If @self is not a group, return %NULL. * * Returns: (transfer none): the members, or %NULL * Since: 0.7.12 * Deprecated: New code should use tp_channel_group_dup_members_contacts() * instead. */ const TpIntset * tp_channel_group_get_members (TpChannel *self) { g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); return self->priv->group_members; } /** * tp_channel_group_get_local_pending: * @self: a channel * * If @self is a group and the %TP_CHANNEL_FEATURE_GROUP feature has been * prepared, return a #TpIntset containing its local-pending members. * * If @self is a group but %TP_CHANNEL_FEATURE_GROUP has not been prepared, * the result may either be a set of local-pending members, or %NULL. * * If @self is not a group, return %NULL. * * Returns: (transfer none): the local-pending members, or %NULL * Since: 0.7.12 * Deprecated: New code should use tp_channel_group_dup_local_pending_contacts() * instead. */ const TpIntset * tp_channel_group_get_local_pending (TpChannel *self) { g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); return self->priv->group_local_pending; } /** * tp_channel_group_get_remote_pending: * @self: a channel * * If @self is a group and the %TP_CHANNEL_FEATURE_GROUP feature has been * prepared, return a #TpIntset containing its remote-pending members. * * If @self is a group but %TP_CHANNEL_FEATURE_GROUP has not been prepared, * the result may either be a set of remote-pending members, or %NULL. * * If @self is not a group, return %NULL. * * Returns: (transfer none): the remote-pending members, or %NULL * Since: 0.7.12 * Deprecated: New code should use * tp_channel_group_dup_remote_pending_contacts() instead. */ const TpIntset * tp_channel_group_get_remote_pending (TpChannel *self) { g_return_val_if_fail (TP_IS_CHANNEL (self), NULL); return self->priv->group_remote_pending; } /** * tp_channel_group_get_local_pending_info: * @self: a channel * @local_pending: the handle of a local-pending contact about whom more * information is needed * @actor: (out) (allow-none): either %NULL or a location to return the contact * who requested the change * @reason: (out) (allow-none): either %NULL or a location to return the reason * for the change * @message: (out) (transfer none) (allow-none): either %NULL or a location to * return the user-supplied message * * If @local_pending is actually the handle of a local-pending contact, * write additional information into @actor, @reason and @message and return * %TRUE. The handle and message are not referenced or copied, and can only be * assumed to remain valid until the main loop is re-entered. * * If @local_pending is not the handle of a local-pending contact, * write 0 into @actor, %TP_CHANNEL_GROUP_CHANGE_REASON_NONE into @reason * and "" into @message, and return %FALSE. * * Returns: %TRUE if the contact is in fact local-pending * Since: 0.7.12 * Deprecated: New code should use * tp_channel_group_get_local_pending_contact_info() instead. */ gboolean tp_channel_group_get_local_pending_info (TpChannel *self, TpHandle local_pending, TpHandle *actor, TpChannelGroupChangeReason *reason, const gchar **message) { gboolean ret = FALSE; TpHandle a = 0; TpChannelGroupChangeReason r = TP_CHANNEL_GROUP_CHANGE_REASON_NONE; const gchar *m = ""; g_return_val_if_fail (TP_IS_CHANNEL (self), FALSE); if (self->priv->group_local_pending != NULL) { /* it could conceivably be someone who is local-pending */ ret = tp_intset_is_member (self->priv->group_local_pending, local_pending); if (ret && self->priv->group_local_pending_info != NULL) { /* we might even have information about them */ LocalPendingInfo *info = g_hash_table_lookup ( self->priv->group_local_pending_info, GUINT_TO_POINTER (local_pending)); if (info != NULL) { a = info->actor; r = info->reason; if (info->message != NULL) m = info->message; } /* else we have no info, which means (0, NONE, NULL) */ } } if (actor != NULL) *actor = a; if (message != NULL) *message = m; if (reason != NULL) *reason = r; return ret; } /** * tp_channel_group_get_handle_owner: * @self: a channel * @handle: a handle which is a member of this channel * * Synopsis (see below for further explanation): * * - if @self is not a group or @handle is not a member of this channel, * result is undefined; * - if %TP_CHANNEL_FEATURE_GROUP has not yet been prepared, result is * undefined; * - if @self does not have flags that include * %TP_CHANNEL_GROUP_FLAG_PROPERTIES, * result is undefined; * - if @handle is channel-specific and its globally valid "owner" is known, * return that owner; * - if @handle is channel-specific and its globally valid "owner" is unknown, * return zero; * - if @handle is globally valid, return @handle itself * * Some channels (those with flags that include * %TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) have a concept of * "channel-specific handles". These are handles that only have meaning within * the context of the channel - for instance, in XMPP Multi-User Chat, * participants in a chatroom are identified by an in-room JID consisting * of the JID of the chatroom plus a local nickname. * * Depending on the protocol and configuration, it might be possible to find * out what globally valid handle (i.e. an identifier that you could add to * your contact list) "owns" a channel-specific handle. For instance, in * most XMPP MUC chatrooms, normal users cannot see what global JID * corresponds to an in-room JID, but moderators can. * * This is further complicated by the fact that channels with channel-specific * handles can sometimes have members with globally valid handles (for * instance, if you invite someone to an XMPP MUC using their globally valid * JID, you would expect to see the handle representing that JID in the * Group's remote-pending set). * * This function's result is undefined unless the channel is ready * and its flags include %TP_CHANNEL_GROUP_FLAG_PROPERTIES (an implementation * without extra D-Bus round trips is not possible using the older API). * * Returns: the global handle that owns the given handle, or 0 * Since: 0.7.12 * Deprecated: New code should use tp_channel_group_get_contact_owner() instead. */ TpHandle tp_channel_group_get_handle_owner (TpChannel *self, TpHandle handle) { gpointer key, value; g_return_val_if_fail (TP_IS_CHANNEL (self), 0); if (self->priv->group_handle_owners == NULL) { /* undefined result - pretending it's global is probably as good as * any other behaviour, since we can't know either way */ return handle; } if (g_hash_table_lookup_extended (self->priv->group_handle_owners, GUINT_TO_POINTER (handle), &key, &value)) { /* channel-specific, value is either owner or 0 if unknown */ return GPOINTER_TO_UINT (value); } else { /* either already globally valid, or not a member */ return handle; } } /* This must be called before the local group members lists are created. Until * this is called, the proxy is listening to both MembersChanged and * MembersChangedDetailed, but they are ignored until priv->group_members * exists. If that list is created before one signal is disconnected, the * proxy will react to state changes twice and madness will ensue. */ static void _got_initial_group_flags (TpChannel *self, TpChannelGroupFlags flags) { TpChannelPrivate *priv = self->priv; g_assert (priv->group_flags == 0); g_assert (self->priv->group_members == NULL); priv->group_flags = flags; DEBUG ("Initial GroupFlags: %u", flags); priv->have_group_flags = TRUE; if (flags != 0) g_object_notify ((GObject *) self, "group-flags"); if (tp_proxy_get_invalidated (self) != NULL) { /* Because the proxy has been invalidated, it is not safe to call * tp_proxy_signal_connection_disconnect (below), so just return early */ return; } /* If the channel claims to support MembersChangedDetailed, disconnect from * MembersChanged. Otherwise, disconnect from MembersChangedDetailed in case * it secretly emits it anyway, so we're only listening to one change * notification. */ if (flags & TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED) tp_proxy_signal_connection_disconnect (priv->members_changed_sig); else tp_proxy_signal_connection_disconnect (priv->members_changed_detailed_sig); priv->members_changed_sig = NULL; priv->members_changed_detailed_sig = NULL; } static void tp_channel_got_group_flags_0_16_cb (TpChannel *self, guint flags, const GError *error, gpointer user_data G_GNUC_UNUSED, GObject *weak_object G_GNUC_UNUSED) { g_assert (self->priv->group_flags == 0); if (error != NULL) { /* GetGroupFlags() has existed with its current signature since November * 2005. I think it's reasonable to say that if it doesn't work, the * channel is broken. */ _tp_channel_abort_introspection (self, "GetGroupFlags() failed", error); return; } /* If we reach this point, GetAll has already failed... */ if (flags & TP_CHANNEL_GROUP_FLAG_PROPERTIES) { DEBUG ("Treason uncloaked! The channel claims to support Group " "properties, but GetAll didn't work"); flags &= ~TP_CHANNEL_GROUP_FLAG_PROPERTIES; } _got_initial_group_flags (self, flags); _tp_channel_continue_introspection (self); } static void tp_channel_group_self_handle_changed_cb (TpChannel *self, guint self_handle, gpointer unused G_GNUC_UNUSED, GObject *unused_object G_GNUC_UNUSED) { if (self_handle == self->priv->group_self_handle) return; DEBUG ("%p SelfHandle changed to %u", self, self_handle); self->priv->group_self_handle = self_handle; g_object_notify ((GObject *) self, "group-self-handle"); } static void tp_channel_group_self_contact_changed_cb (TpChannel *self, guint self_handle, const gchar *identifier, gpointer user_data, GObject *weak_object) { tp_channel_group_self_handle_changed_cb (self, self_handle, user_data, weak_object); _tp_channel_contacts_self_contact_changed (self, self_handle, identifier); } static void tp_channel_got_self_handle_0_16_cb (TpChannel *self, guint self_handle, const GError *error, gpointer user_data G_GNUC_UNUSED, GObject *weak_object G_GNUC_UNUSED) { if (error != NULL) { DEBUG ("%p Group.GetSelfHandle() failed, assuming 0: %s", self, error->message); tp_channel_group_self_handle_changed_cb (self, 0, NULL, NULL); } else { DEBUG ("Initial Group.SelfHandle: %u", self_handle); tp_channel_group_self_handle_changed_cb (self, self_handle, NULL, NULL); } _tp_channel_continue_introspection (self); } static void _tp_channel_get_self_handle_0_16 (TpChannel *self) { tp_cli_channel_interface_group_call_get_self_handle (self, -1, tp_channel_got_self_handle_0_16_cb, NULL, NULL, NULL); } static void _tp_channel_get_group_flags_0_16 (TpChannel *self) { tp_cli_channel_interface_group_call_get_group_flags (self, -1, tp_channel_got_group_flags_0_16_cb, NULL, NULL, NULL); } static void _tp_channel_group_set_one_lp (TpChannel *self, TpHandle handle, TpHandle actor, TpChannelGroupChangeReason reason, const gchar *message) { LocalPendingInfo *info = NULL; g_assert (self->priv->group_local_pending != NULL); tp_intset_add (self->priv->group_local_pending, handle); tp_intset_remove (self->priv->group_members, handle); tp_intset_remove (self->priv->group_remote_pending, handle); if (actor == 0 && reason == TP_CHANNEL_GROUP_CHANGE_REASON_NONE && tp_str_empty (message)) { /* we just don't bother storing informationless local-pending */ if (self->priv->group_local_pending_info != NULL) { g_hash_table_remove (self->priv->group_local_pending_info, GUINT_TO_POINTER (handle)); } return; } if (self->priv->group_local_pending_info == NULL) { self->priv->group_local_pending_info = g_hash_table_new_full ( g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) local_pending_info_free); } else { info = g_hash_table_lookup (self->priv->group_local_pending_info, GUINT_TO_POINTER (handle)); } if (info == NULL) { info = g_slice_new0 (LocalPendingInfo); } else { g_hash_table_steal (self->priv->group_local_pending_info, GUINT_TO_POINTER (handle)); } info->actor = actor; info->reason = reason; g_free (info->message); if (tp_str_empty (message)) info->message = NULL; else info->message = g_strdup (message); g_hash_table_insert (self->priv->group_local_pending_info, GUINT_TO_POINTER (handle), info); } static void _tp_channel_group_set_lp (TpChannel *self, const GPtrArray *info) { guint i; /* should only be called during initialization */ g_assert (self->priv->group_local_pending != NULL); g_assert (self->priv->group_local_pending_info == NULL); tp_intset_clear (self->priv->group_local_pending); /* NULL-safe for ease of use with tp_asv_get_boxed */ if (info == NULL) { return; } for (i = 0; i < info->len; i++) { GValueArray *item = g_ptr_array_index (info, i); TpHandle handle = g_value_get_uint (item->values + 0); TpHandle actor = g_value_get_uint (item->values + 1); TpChannelGroupChangeReason reason = g_value_get_uint ( item->values + 2); const gchar *message = g_value_get_string (item->values + 3); if (handle == 0) { DEBUG ("Ignoring handle 0, claimed to be in local-pending"); continue; } DEBUG ("+L %u, actor=%u, reason=%u, message=%s", handle, actor, reason, message); _tp_channel_group_set_one_lp (self, handle, actor, reason, message); } } static void tp_channel_got_all_members_0_16_cb (TpChannel *self, const GArray *members, const GArray *local_pending, const GArray *remote_pending, const GError *error, gpointer user_data G_GNUC_UNUSED, GObject *weak_object G_GNUC_UNUSED) { g_assert (self->priv->group_local_pending == NULL); g_assert (self->priv->group_local_pending_info == NULL); g_assert (self->priv->group_members == NULL); g_assert (self->priv->group_remote_pending == NULL); if (error == NULL) { DEBUG ("%p GetAllMembers returned %u members + %u LP + %u RP", self, members->len, local_pending->len, remote_pending->len); self->priv->group_local_pending = tp_intset_from_array (local_pending); self->priv->group_members = tp_intset_from_array (members); self->priv->group_remote_pending = tp_intset_from_array (remote_pending); if (tp_intset_remove (self->priv->group_members, 0)) { DEBUG ("Ignoring handle 0, claimed to be in group"); } if (tp_intset_remove (self->priv->group_local_pending, 0)) { DEBUG ("Ignoring handle 0, claimed to be in local-pending"); } if (tp_intset_remove (self->priv->group_remote_pending, 0)) { DEBUG ("Ignoring handle 0, claimed to be in remote-pending"); } /* the local-pending info will be filled in with the result of * GetLocalPendingMembersWithInfo, if it succeeds */ } else { DEBUG ("%p GetAllMembers failed, assuming empty: %s", self, error->message); self->priv->group_local_pending = tp_intset_new (); self->priv->group_members = tp_intset_new (); self->priv->group_remote_pending = tp_intset_new (); } g_assert (self->priv->group_local_pending != NULL); g_assert (self->priv->group_members != NULL); g_assert (self->priv->group_remote_pending != NULL); _tp_channel_continue_introspection (self); } static void _tp_channel_get_all_members_0_16 (TpChannel *self) { tp_cli_channel_interface_group_call_get_all_members (self, -1, tp_channel_got_all_members_0_16_cb, NULL, NULL, NULL); } static void tp_channel_glpmwi_0_16_cb (TpChannel *self, const GPtrArray *info, const GError *error, gpointer user_data G_GNUC_UNUSED, GObject *object G_GNUC_UNUSED) { /* this should always run after tp_channel_got_all_members_0_16 */ g_assert (self->priv->group_local_pending != NULL); g_assert (self->priv->group_local_pending_info == NULL); if (error == NULL) { DEBUG ("%p GetLocalPendingMembersWithInfo returned %u records", self, info->len); _tp_channel_group_set_lp (self, info); } else { DEBUG ("%p GetLocalPendingMembersWithInfo failed, keeping result of " "GetAllMembers instead: %s", self, error->message); } _tp_channel_continue_introspection (self); } static void _tp_channel_glpmwi_0_16 (TpChannel *self) { tp_cli_channel_interface_group_call_get_local_pending_members_with_info ( self, -1, tp_channel_glpmwi_0_16_cb, NULL, NULL, NULL); } static void _tp_channel_emit_initial_sets (TpChannel *self) { GArray *added, *remote_pending; GArray empty_array = { NULL, 0 }; TpIntsetFastIter iter; TpHandle handle; tp_intset_fast_iter_init (&iter, self->priv->group_local_pending); added = tp_intset_to_array (self->priv->group_members); remote_pending = tp_intset_to_array (self->priv->group_remote_pending); g_signal_emit_by_name (self, "group-members-changed", "", added, &empty_array, &empty_array, remote_pending, 0, 0); while (tp_intset_fast_iter_next (&iter, &handle)) { GArray local_pending = { (gchar *) &handle, 1 }; TpHandle actor; TpChannelGroupChangeReason reason; const gchar *message; tp_channel_group_get_local_pending_info (self, handle, &actor, &reason, &message); g_signal_emit_by_name (self, "group-members-changed", message, &empty_array, &empty_array, &local_pending, &empty_array, actor, reason); } g_array_unref (added); g_array_unref (remote_pending); _tp_channel_continue_introspection (self); } static void tp_channel_got_group_properties_cb (TpProxy *proxy, GHashTable *asv, const GError *error, gpointer unused G_GNUC_UNUSED, GObject *unused_object G_GNUC_UNUSED) { TpChannel *self = TP_CHANNEL (proxy); static GType au_type = 0; if (G_UNLIKELY (au_type == 0)) { au_type = dbus_g_type_get_collection ("GArray", G_TYPE_UINT); } if (error != NULL) { DEBUG ("Error getting group properties, falling back to 0.16 API: %s", error->message); } else if ((tp_asv_get_uint32 (asv, "GroupFlags", NULL) & TP_CHANNEL_GROUP_FLAG_PROPERTIES) == 0) { DEBUG ("Got group properties, but no Properties flag: assuming a " "broken implementation and falling back to 0.16 API"); } else { GHashTable *table; GArray *arr; DEBUG ("Received %u group properties", g_hash_table_size (asv)); _got_initial_group_flags (self, tp_asv_get_uint32 (asv, "GroupFlags", NULL)); tp_channel_group_self_handle_changed_cb (self, tp_asv_get_uint32 (asv, "SelfHandle", NULL), NULL, NULL); g_assert (self->priv->group_members == NULL); g_assert (self->priv->group_remote_pending == NULL); arr = tp_asv_get_boxed (asv, "Members", au_type); if (arr == NULL) self->priv->group_members = tp_intset_new (); else self->priv->group_members = tp_intset_from_array (arr); if (tp_intset_remove (self->priv->group_members, 0)) { DEBUG ("Ignoring handle 0, claimed to be in group"); } arr = tp_asv_get_boxed (asv, "RemotePendingMembers", au_type); if (arr == NULL) self->priv->group_remote_pending = tp_intset_new (); else self->priv->group_remote_pending = tp_intset_from_array (arr); if (tp_intset_remove (self->priv->group_remote_pending, 0)) { DEBUG ("Ignoring handle 0, claimed to be in remote-pending"); } g_assert (self->priv->group_local_pending == NULL); g_assert (self->priv->group_local_pending_info == NULL); self->priv->group_local_pending = tp_intset_new (); /* this is NULL-safe with respect to the array */ _tp_channel_group_set_lp (self, tp_asv_get_boxed (asv, "LocalPendingMembers", TP_ARRAY_TYPE_LOCAL_PENDING_INFO_LIST)); table = tp_asv_get_boxed (asv, "HandleOwners", TP_HASH_TYPE_HANDLE_OWNER_MAP); self->priv->group_handle_owners = g_hash_table_new (g_direct_hash, g_direct_equal); if (table != NULL) tp_g_hash_table_update (self->priv->group_handle_owners, table, NULL, NULL); table = tp_asv_get_boxed (asv, "MemberIdentifiers", TP_HASH_TYPE_HANDLE_IDENTIFIER_MAP); /* If CM implements MemberIdentifiers property, assume it also emits * SelfContactChanged and HandleOwnersChangedDetailed */ if (table != NULL) { tp_proxy_signal_connection_disconnect ( self->priv->self_handle_changed_sig); tp_proxy_signal_connection_disconnect ( self->priv->handle_owners_changed_sig); } else { tp_proxy_signal_connection_disconnect ( self->priv->self_contact_changed_sig); tp_proxy_signal_connection_disconnect ( self->priv->handle_owners_changed_detailed_sig); } self->priv->self_handle_changed_sig = NULL; self->priv->self_contact_changed_sig = NULL; self->priv->handle_owners_changed_sig = NULL; self->priv->handle_owners_changed_detailed_sig = NULL; _tp_channel_contacts_group_init (self, table); goto OUT; } /* Failure case: fall back. This is quite annoying, as we need to combine: * * - GetGroupFlags * - GetAllMembers * - GetLocalPendingMembersWithInfo * * Channel-specific handles can't really have a sane client API (without * lots of silly round-trips) unless the CM implements the HandleOwners * property, so I intend to ignore this in the fallback case. */ g_queue_push_tail (self->priv->introspect_needed, _tp_channel_get_group_flags_0_16); g_queue_push_tail (self->priv->introspect_needed, _tp_channel_get_self_handle_0_16); g_queue_push_tail (self->priv->introspect_needed, _tp_channel_get_all_members_0_16); g_queue_push_tail (self->priv->introspect_needed, _tp_channel_glpmwi_0_16); self->priv->cm_too_old_for_contacts = TRUE; OUT: g_queue_push_tail (self->priv->introspect_needed, _tp_channel_emit_initial_sets); _tp_channel_continue_introspection (self); } /* * If the @group_remove_error is derived from a TpChannelGroupChangeReason, * attempt to rewrite it into a TpError. */ static void _tp_channel_group_improve_remove_error (TpChannel *self, TpHandle actor) { GError *error = self->priv->group_remove_error; if (error == NULL || error->domain != TP_ERRORS_REMOVED_FROM_GROUP) return; switch (error->code) { case TP_CHANNEL_GROUP_CHANGE_REASON_NONE: if (actor == self->priv->group_self_handle || actor == tp_connection_get_self_handle (self->priv->connection)) { error->code = TP_ERROR_CANCELLED; } else { error->code = TP_ERROR_TERMINATED; } break; case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE: error->code = TP_ERROR_OFFLINE; break; case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED: error->code = TP_ERROR_CHANNEL_KICKED; break; case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY: error->code = TP_ERROR_BUSY; break; case TP_CHANNEL_GROUP_CHANGE_REASON_INVITED: DEBUG ("%s: Channel_Group_Change_Reason_Invited makes no sense as a " "removal reason!", tp_proxy_get_object_path (self)); error->domain = TP_DBUS_ERRORS; error->code = TP_DBUS_ERROR_INCONSISTENT; return; case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED: error->code = TP_ERROR_CHANNEL_BANNED; break; case TP_CHANNEL_GROUP_CHANGE_REASON_ERROR: /* hopefully all CMs that use this will also give us an error detail, * but if they didn't, or gave us one we didn't understand... */ error->code = TP_ERROR_NOT_AVAILABLE; break; case TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT: error->code = TP_ERROR_DOES_NOT_EXIST; break; case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: error->code = TP_ERROR_NO_ANSWER; break; /* TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED shouldn't be the last error * seen in the channel - we'll get removed again with a real reason, * later, so there's no point in doing anything special with this one */ case TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED: error->code = TP_ERROR_PERMISSION_DENIED; break; case TP_CHANNEL_GROUP_CHANGE_REASON_SEPARATED: DEBUG ("%s: Channel_Group_Change_Reason_Separated makes no sense as a " "removal reason!", tp_proxy_get_object_path (self)); error->domain = TP_DBUS_ERRORS; error->code = TP_DBUS_ERROR_INCONSISTENT; return; /* all values up to and including Separated have been checked */ default: /* We don't understand this reason code, so keeping the domain and code * the same (i.e. using TP_ERRORS_REMOVED_FROM_GROUP) is no worse than * anything else we could do. */ return; } /* If we changed the code we also need to change the domain; if not, we did * an early return, so we'll never reach this */ error->domain = TP_ERROR; } static void handle_members_changed (TpChannel *self, const gchar *message, const GArray *added, const GArray *removed, const GArray *local_pending, const GArray *remote_pending, guint actor, guint reason, GHashTable *details) { guint i; if (self->priv->group_members == NULL) return; g_assert (self->priv->group_local_pending != NULL); g_assert (self->priv->group_remote_pending != NULL); for (i = 0; i < added->len; i++) { TpHandle handle = g_array_index (added, guint, i); DEBUG ("+++ contact#%u", handle); if (handle == 0) { DEBUG ("handle 0 shouldn't be in MembersChanged, ignoring"); continue; } tp_intset_add (self->priv->group_members, handle); tp_intset_remove (self->priv->group_local_pending, handle); tp_intset_remove (self->priv->group_remote_pending, handle); } for (i = 0; i < local_pending->len; i++) { TpHandle handle = g_array_index (local_pending, guint, i); DEBUG ("+LP contact#%u", handle); if (handle == 0) { DEBUG ("handle 0 shouldn't be in MembersChanged, ignoring"); continue; } /* Special-case renaming a local-pending contact, if the * signal is spec-compliant. Keep the old actor/reason/message in * this case */ if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED && added->len == 0 && local_pending->len == 1 && remote_pending->len == 0 && removed->len == 1 && self->priv->group_local_pending_info != NULL) { TpHandle old = g_array_index (removed, guint, 0); LocalPendingInfo *info = g_hash_table_lookup ( self->priv->group_local_pending_info, GUINT_TO_POINTER (old)); if (info != NULL) { _tp_channel_group_set_one_lp (self, handle, info->actor, info->reason, info->message); continue; } } /* not reached if the Renamed special case occurred */ _tp_channel_group_set_one_lp (self, handle, actor, reason, message); } for (i = 0; i < remote_pending->len; i++) { TpHandle handle = g_array_index (remote_pending, guint, i); DEBUG ("+RP contact#%u", handle); if (handle == 0) { DEBUG ("handle 0 shouldn't be in MembersChanged, ignoring"); continue; } tp_intset_add (self->priv->group_remote_pending, handle); tp_intset_remove (self->priv->group_members, handle); tp_intset_remove (self->priv->group_local_pending, handle); } for (i = 0; i < removed->len; i++) { TpHandle handle = g_array_index (removed, guint, i); DEBUG ("--- contact#%u", handle); if (handle == 0) { DEBUG ("handle 0 shouldn't be in MembersChanged, ignoring"); continue; } if (self->priv->group_local_pending_info != NULL) g_hash_table_remove (self->priv->group_local_pending_info, GUINT_TO_POINTER (handle)); tp_intset_remove (self->priv->group_members, handle); tp_intset_remove (self->priv->group_local_pending, handle); tp_intset_remove (self->priv->group_remote_pending, handle); if (handle == self->priv->group_self_handle || handle == tp_connection_get_self_handle (self->priv->connection)) { const gchar *error_detail = tp_asv_get_string (details, "error"); const gchar *debug_message = tp_asv_get_string (details, "debug-message"); if (debug_message == NULL && !tp_str_empty (message)) debug_message = message; if (debug_message == NULL && error_detail != NULL) debug_message = error_detail; if (debug_message == NULL) debug_message = "(no message provided)"; if (self->priv->group_remove_error != NULL) g_clear_error (&self->priv->group_remove_error); if (error_detail != NULL) { /* CM specified a D-Bus error name */ tp_proxy_dbus_error_to_gerror (self, error_detail, debug_message == NULL || debug_message[0] == '\0' ? error_detail : debug_message, &self->priv->group_remove_error); /* ... but if we don't know anything about that D-Bus error * name, we can still do better by using RemovedFromGroup */ if (g_error_matches (self->priv->group_remove_error, TP_DBUS_ERRORS, TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR)) { self->priv->group_remove_error->domain = TP_ERRORS_REMOVED_FROM_GROUP; self->priv->group_remove_error->code = reason; _tp_channel_group_improve_remove_error (self, actor); } } else { /* Use our separate error domain */ g_set_error_literal (&self->priv->group_remove_error, TP_ERRORS_REMOVED_FROM_GROUP, reason, debug_message); _tp_channel_group_improve_remove_error (self, actor); } } } g_signal_emit_by_name (self, "group-members-changed", message, added, removed, local_pending, remote_pending, actor, reason); g_signal_emit_by_name (self, "group-members-changed-detailed", added, removed, local_pending, remote_pending, details); _tp_channel_contacts_members_changed (self, added, removed, local_pending, remote_pending, actor, details); } static void tp_channel_group_members_changed_cb (TpChannel *self, const gchar *message, const GArray *added, const GArray *removed, const GArray *local_pending, const GArray *remote_pending, guint actor, guint reason, gpointer unused G_GNUC_UNUSED, GObject *unused_object G_GNUC_UNUSED) { GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); DEBUG ("%p MembersChanged: added %u, removed %u, " "moved %u to LP and %u to RP, actor %u, reason %u, message %s", self, added->len, removed->len, local_pending->len, remote_pending->len, actor, reason, message); 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 != '\0') { g_hash_table_insert (details, "message", tp_g_value_slice_new_string (message)); } handle_members_changed (self, message, added, removed, local_pending, remote_pending, actor, reason, details); g_hash_table_unref (details); } static void tp_channel_group_members_changed_detailed_cb (TpChannel *self, const GArray *added, const GArray *removed, const GArray *local_pending, const GArray *remote_pending, GHashTable *details, gpointer unused G_GNUC_UNUSED, GObject *weak_obj G_GNUC_UNUSED) { const gchar *message; guint actor; guint reason; DEBUG ("%p MembersChangedDetailed: added %u, removed %u, " "moved %u to LP and %u to RP", self, added->len, removed->len, local_pending->len, remote_pending->len); actor = tp_asv_get_uint32 (details, "actor", NULL); reason = tp_asv_get_uint32 (details, "change-reason", NULL); message = tp_asv_get_string (details, "message"); if (message == NULL) message = ""; handle_members_changed (self, message, added, removed, local_pending, remote_pending, actor, reason, details); } static void tp_channel_handle_owners_changed_cb (TpChannel *self, GHashTable *added, const GArray *removed, gpointer unused G_GNUC_UNUSED, GObject *unused_object G_GNUC_UNUSED) { guint i; /* ignore the signal if we don't have the initial set yet */ if (self->priv->group_handle_owners == NULL) return; tp_g_hash_table_update (self->priv->group_handle_owners, added, NULL, NULL); for (i = 0; i < removed->len; i++) { g_hash_table_remove (self->priv->group_handle_owners, GUINT_TO_POINTER (g_array_index (removed, guint, i))); } } static void tp_channel_handle_owners_changed_detailed_cb (TpChannel *self, GHashTable *added, const GArray *removed, GHashTable *identifiers, gpointer user_data, GObject *weak_object) { tp_channel_handle_owners_changed_cb (self, added, removed, user_data, weak_object); _tp_channel_contacts_handle_owners_changed (self, added, removed, identifiers); } #define IMMUTABLE_FLAGS \ (TP_CHANNEL_GROUP_FLAG_PROPERTIES | \ TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED) static void tp_channel_group_flags_changed_cb (TpChannel *self, guint added, guint removed, gpointer unused G_GNUC_UNUSED, GObject *unused_object G_GNUC_UNUSED) { if (self->priv->have_group_flags) { DEBUG ("%p GroupFlagsChanged: +%u -%u", self, added, removed); added &= ~(self->priv->group_flags); removed &= self->priv->group_flags; DEBUG ("%p GroupFlagsChanged (after filtering): +%u -%u", self, added, removed); if ((added & IMMUTABLE_FLAGS) || (removed & IMMUTABLE_FLAGS)) { GError *e = g_error_new (TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT, "CM is broken: it changed the Properties/" "Members_Changed_Detailed flags on an existing group channel " "(offending changes: added=%u, removed=%u)", added & IMMUTABLE_FLAGS, removed & IMMUTABLE_FLAGS); tp_proxy_invalidate ((TpProxy *) self, e); g_error_free (e); return; } self->priv->group_flags |= added; self->priv->group_flags &= ~removed; if (added != 0 || removed != 0) { g_object_notify ((GObject *) self, "group-flags"); g_signal_emit_by_name (self, "group-flags-changed", added, removed); } } } #undef IMMUTABLE_FLAGS void _tp_channel_get_group_properties (TpChannel *self) { TpChannelPrivate *priv = self->priv; TpProxySignalConnection *sc; GError *error = NULL; if (!tp_proxy_has_interface_by_id (self, TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) { _tp_proxy_set_feature_prepared ((TpProxy *) self, TP_CHANNEL_FEATURE_GROUP, FALSE); DEBUG ("%p: not a Group, continuing", self); _tp_channel_continue_introspection (self); return; } DEBUG ("%p", self); /* If this callback has been called, 'self' has not been invalidated. And we * just checked above that the proxy has the Group interface. So, connecting * to these signals must succeed. */ #define DIE(sig) \ { \ CRITICAL ("couldn't connect to " sig ": %s", error->message); \ g_assert_not_reached (); \ g_error_free (error); \ return; \ } priv->members_changed_sig = tp_cli_channel_interface_group_connect_to_members_changed (self, tp_channel_group_members_changed_cb, NULL, NULL, NULL, &error); if (priv->members_changed_sig == NULL) DIE ("MembersChanged"); priv->members_changed_detailed_sig = tp_cli_channel_interface_group_connect_to_members_changed_detailed (self, tp_channel_group_members_changed_detailed_cb, NULL, NULL, NULL, &error); if (priv->members_changed_detailed_sig == NULL) DIE ("MembersChangedDetailed"); sc = tp_cli_channel_interface_group_connect_to_group_flags_changed (self, tp_channel_group_flags_changed_cb, NULL, NULL, NULL, &error); if (sc == NULL) DIE ("GroupFlagsChanged"); priv->self_handle_changed_sig = tp_cli_channel_interface_group_connect_to_self_handle_changed (self, tp_channel_group_self_handle_changed_cb, NULL, NULL, NULL, &error); if (priv->self_handle_changed_sig == NULL) DIE ("SelfHandleChanged"); priv->self_contact_changed_sig = tp_cli_channel_interface_group_connect_to_self_contact_changed (self, tp_channel_group_self_contact_changed_cb, NULL, NULL, NULL, &error); if (priv->self_contact_changed_sig == NULL) DIE ("SelfContactChanged"); priv->handle_owners_changed_sig = tp_cli_channel_interface_group_connect_to_handle_owners_changed (self, tp_channel_handle_owners_changed_cb, NULL, NULL, NULL, &error); if (priv->handle_owners_changed_sig == NULL) DIE ("HandleOwnersChanged"); priv->handle_owners_changed_detailed_sig = tp_cli_channel_interface_group_connect_to_handle_owners_changed_detailed ( self, tp_channel_handle_owners_changed_detailed_cb, NULL, NULL, NULL, &error); if (priv->handle_owners_changed_detailed_sig == NULL) DIE ("HandleOwnersChangedDetailed"); /* First try the 0.17 API (properties). If this fails we'll fall back */ tp_cli_dbus_properties_call_get_all (self, -1, TP_IFACE_CHANNEL_INTERFACE_GROUP, tp_channel_got_group_properties_cb, NULL, NULL, NULL); } G_GNUC_END_IGNORE_DEPRECATIONS