/* * gabble-roster-channel.c - Source for GabbleRosterChannel * Copyright (C) 2005, 2006 Collabora Ltd. * Copyright (C) 2005 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 "roster-channel.h" #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG GABBLE_DEBUG_ROSTER #include "connection.h" #include "debug.h" #include "roster.h" #include "util.h" static void channel_iface_init (gpointer, gpointer); G_DEFINE_TYPE_WITH_CODE (GabbleRosterChannel, gabble_roster_channel, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, tp_group_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, tp_dbus_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL); G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL)); static const gchar *gabble_roster_channel_interfaces[] = { TP_IFACE_CHANNEL_INTERFACE_GROUP, NULL }; /* properties */ enum { PROP_OBJECT_PATH = 1, PROP_CHANNEL_TYPE, PROP_HANDLE_TYPE, PROP_HANDLE, PROP_TARGET_ID, PROP_REQUESTED, PROP_INITIATOR_HANDLE, PROP_INITIATOR_ID, PROP_CONNECTION, PROP_INTERFACES, PROP_CHANNEL_DESTROYED, PROP_CHANNEL_PROPERTIES, LAST_PROPERTY }; /* private structure */ struct _GabbleRosterChannelPrivate { GabbleConnection *conn; char *object_path; TpHandle handle; guint handle_type; gboolean dispose_has_run; gboolean closed; }; static void gabble_roster_channel_init (GabbleRosterChannel *self) { GabbleRosterChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_ROSTER_CHANNEL, GabbleRosterChannelPrivate); self->priv = priv; /* allocate any data required by the object here */ } static GObject * gabble_roster_channel_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *obj; GabbleRosterChannelPrivate *priv; DBusGConnection *bus; TpBaseConnection *conn; TpHandle self_handle; guint handle_type; TpHandleRepoIface *handle_repo, *contact_repo; obj = G_OBJECT_CLASS (gabble_roster_channel_parent_class)-> constructor (type, n_props, props); priv = GABBLE_ROSTER_CHANNEL (obj)->priv; conn = (TpBaseConnection *) priv->conn; handle_type = priv->handle_type; handle_repo = tp_base_connection_get_handles (conn, handle_type); contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); self_handle = conn->self_handle; /* register object on the bus */ bus = tp_get_bus (); dbus_g_connection_register_g_object (bus, priv->object_path, obj); g_assert (handle_type == TP_HANDLE_TYPE_GROUP || handle_type == TP_HANDLE_TYPE_LIST); /* ref our list handle */ tp_handle_ref (handle_repo, priv->handle); /* initialize group mixin */ tp_group_mixin_init (obj, G_STRUCT_OFFSET (GabbleRosterChannel, group), contact_repo, self_handle); if (handle_type == TP_HANDLE_TYPE_GROUP) { tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); } else if (handle_type != TP_HANDLE_TYPE_LIST) { g_assert_not_reached (); } /* magic contact lists from here down... */ else if (GABBLE_LIST_HANDLE_PUBLISH == priv->handle) { tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT | TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); } else if (GABBLE_LIST_HANDLE_SUBSCRIBE == priv->handle) { tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND | TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD | TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); } else if (GABBLE_LIST_HANDLE_STORED == priv->handle) { tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); } else if (GABBLE_LIST_HANDLE_DENY == priv->handle) { tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); } else { g_assert_not_reached (); } return obj; } static void gabble_roster_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GabbleRosterChannel *chan = GABBLE_ROSTER_CHANNEL (object); GabbleRosterChannelPrivate *priv = chan->priv; switch (property_id) { case PROP_OBJECT_PATH: g_value_set_string (value, priv->object_path); break; case PROP_CHANNEL_TYPE: g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); break; case PROP_HANDLE_TYPE: g_value_set_uint (value, priv->handle_type); break; case PROP_HANDLE: g_value_set_uint (value, priv->handle); break; case PROP_TARGET_ID: { TpHandleRepoIface *repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, priv->handle_type); g_value_set_string (value, tp_handle_inspect (repo, priv->handle)); } break; case PROP_CONNECTION: g_value_set_object (value, priv->conn); break; case PROP_INTERFACES: g_value_set_boxed (value, gabble_roster_channel_interfaces); break; case PROP_INITIATOR_HANDLE: g_value_set_uint (value, 0); break; case PROP_INITIATOR_ID: g_value_set_static_string (value, ""); break; case PROP_REQUESTED: g_value_set_boolean (value, FALSE); break; case PROP_CHANNEL_DESTROYED: g_value_set_boolean (value, priv->closed); break; case PROP_CHANNEL_PROPERTIES: g_value_take_boxed (value, tp_dbus_properties_mixin_make_properties_hash (object, TP_IFACE_CHANNEL, "TargetHandle", TP_IFACE_CHANNEL, "TargetHandleType", TP_IFACE_CHANNEL, "ChannelType", TP_IFACE_CHANNEL, "TargetID", TP_IFACE_CHANNEL, "InitiatorHandle", TP_IFACE_CHANNEL, "InitiatorID", TP_IFACE_CHANNEL, "Requested", TP_IFACE_CHANNEL, "Interfaces", NULL)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_roster_channel_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleRosterChannel *chan = GABBLE_ROSTER_CHANNEL (object); GabbleRosterChannelPrivate *priv = chan->priv; switch (property_id) { case PROP_OBJECT_PATH: g_free (priv->object_path); priv->object_path = g_value_dup_string (value); case PROP_CHANNEL_TYPE: /* in telepathy-glib > 0.7.0 this property is writable in the * interface, but not actually meaningfully changeable on this channel, * so we do nothing */ break; case PROP_HANDLE_TYPE: priv->handle_type = g_value_get_uint (value); break; case PROP_HANDLE: priv->handle = g_value_get_uint (value); break; case PROP_CONNECTION: priv->conn = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_roster_channel_dispose (GObject *object); static void gabble_roster_channel_finalize (GObject *object); static gboolean _gabble_roster_channel_add_member_cb (GObject *obj, TpHandle handle, const gchar *message, GError **error); static gboolean _gabble_roster_channel_remove_member_cb (GObject *obj, TpHandle handle, const gchar *message, GError **error); static void gabble_roster_channel_class_init (GabbleRosterChannelClass *gabble_roster_channel_class) { static TpDBusPropertiesMixinPropImpl channel_props[] = { { "TargetHandleType", "handle-type", NULL }, { "TargetHandle", "handle", NULL }, { "TargetID", "target-id", NULL }, { "ChannelType", "channel-type", NULL }, { "Interfaces", "interfaces", NULL }, { "Requested", "requested", NULL }, { "InitiatorHandle", "initiator-handle", NULL }, { "InitiatorID", "initiator-id", NULL }, { NULL } }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { { TP_IFACE_CHANNEL, tp_dbus_properties_mixin_getter_gobject_properties, NULL, channel_props, }, { NULL } }; GObjectClass *object_class = G_OBJECT_CLASS (gabble_roster_channel_class); GParamSpec *param_spec; g_type_class_add_private (gabble_roster_channel_class, sizeof (GabbleRosterChannelPrivate)); object_class->constructor = gabble_roster_channel_constructor; object_class->get_property = gabble_roster_channel_get_property; object_class->set_property = gabble_roster_channel_set_property; object_class->dispose = gabble_roster_channel_dispose; object_class->finalize = gabble_roster_channel_finalize; param_spec = g_param_spec_object ("connection", "GabbleConnection object", "Gabble connection object that owns this Roster channel object.", GABBLE_TYPE_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces", "Additional Channel.Interface.* interfaces", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); param_spec = g_param_spec_string ("target-id", "Target JID", "The string obtained by inspecting this channel's handle", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec); param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle", "The contact who initiated the channel", 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE, param_spec); param_spec = g_param_spec_string ("initiator-id", "Initiator's bare JID", "The string obtained by inspecting the initiator-handle", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_INITIATOR_ID, param_spec); param_spec = g_param_spec_boolean ("requested", "Requested?", "True if this channel was requested by the local user", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_REQUESTED, param_spec); g_object_class_override_property (object_class, PROP_OBJECT_PATH, "object-path"); g_object_class_override_property (object_class, PROP_CHANNEL_TYPE, "channel-type"); g_object_class_override_property (object_class, PROP_HANDLE_TYPE, "handle-type"); g_object_class_override_property (object_class, PROP_HANDLE, "handle"); g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED, "channel-destroyed"); g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES, "channel-properties"); gabble_roster_channel_class->properties_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (GabbleRosterChannelClass, properties_class)); tp_group_mixin_class_init (object_class, G_STRUCT_OFFSET (GabbleRosterChannelClass, group_class), _gabble_roster_channel_add_member_cb, _gabble_roster_channel_remove_member_cb); tp_group_mixin_init_dbus_properties (object_class); } void gabble_roster_channel_dispose (GObject *object) { GabbleRosterChannel *self = GABBLE_ROSTER_CHANNEL (object); GabbleRosterChannelPrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (!priv->closed) tp_svc_channel_emit_closed ((TpSvcChannel *) object); /* release any references held by the object here */ if (G_OBJECT_CLASS (gabble_roster_channel_parent_class)->dispose) G_OBJECT_CLASS (gabble_roster_channel_parent_class)->dispose (object); } void gabble_roster_channel_finalize (GObject *object) { GabbleRosterChannel *self = GABBLE_ROSTER_CHANNEL (object); GabbleRosterChannelPrivate *priv = self->priv; TpBaseConnection *conn = (TpBaseConnection *) priv->conn; TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (conn, priv->handle_type); /* free any data held directly by the object here */ g_free (priv->object_path); tp_handle_unref (handle_repo, priv->handle); tp_group_mixin_finalize (object); G_OBJECT_CLASS (gabble_roster_channel_parent_class)->finalize (object); } /** * _gabble_roster_channel_add_member_cb * * Called by the group mixin to add one member. */ static gboolean _gabble_roster_channel_add_member_cb (GObject *obj, TpHandle handle, const gchar *message, GError **error) { GabbleRosterChannelPrivate *priv; gboolean ret = FALSE; #ifdef ENABLE_DEBUG TpHandleRepoIface *handle_repo; #endif TpHandleRepoIface *contact_repo; const gchar *contact_id; priv = GABBLE_ROSTER_CHANNEL (obj)->priv; #ifdef ENABLE_DEBUG handle_repo = tp_base_connection_get_handles ((TpBaseConnection *) priv->conn, priv->handle_type); #endif contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); contact_id = tp_handle_inspect (contact_repo, handle); DEBUG ("called on %s with handle %u (%s) \"%s\"", tp_handle_inspect (handle_repo, priv->handle), handle, contact_id, message); if (TP_HANDLE_TYPE_GROUP == priv->handle_type) { ret = gabble_roster_handle_add_to_group (priv->conn->roster, handle, priv->handle, error); } else if (TP_HANDLE_TYPE_LIST != priv->handle_type) { g_assert_not_reached (); return FALSE; } /* "magic" contact lists, from here down... */ /* publish list */ else if (GABBLE_LIST_HANDLE_PUBLISH == priv->handle) { /* send */ ret = gabble_connection_send_presence (priv->conn, LM_MESSAGE_SUB_TYPE_SUBSCRIBED, contact_id, message, error); } /* subscribe list */ else if (GABBLE_LIST_HANDLE_SUBSCRIBE == priv->handle) { /* add item to the roster (GTalk depends on this, clearing the H flag) */ gabble_roster_handle_add (priv->conn->roster, handle, NULL); /* send */ ret = gabble_connection_send_presence (priv->conn, LM_MESSAGE_SUB_TYPE_SUBSCRIBE, contact_id, message, error); } /* deny list */ else if (GABBLE_LIST_HANDLE_DENY == priv->handle) { /* block contact */ ret = gabble_roster_handle_set_blocked (priv->conn->roster, handle, TRUE, error); } else { g_assert_not_reached (); } return ret; } /** * _gabble_roster_channel_remove_member_cb * * Called by the group mixin to remove one member. */ static gboolean _gabble_roster_channel_remove_member_cb (GObject *obj, TpHandle handle, const gchar *message, GError **error) { GabbleRosterChannelPrivate *priv; TpBaseConnection *conn; TpHandleRepoIface *contact_repo; #ifdef ENABLE_DEBUG TpHandleRepoIface *handle_repo; #endif gboolean ret = FALSE; const gchar *contact_id; priv = GABBLE_ROSTER_CHANNEL (obj)->priv; conn = (TpBaseConnection *) priv->conn; #ifdef ENABLE_DEBUG handle_repo = tp_base_connection_get_handles (conn, priv->handle_type); #endif contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); contact_id = tp_handle_inspect (contact_repo, handle); DEBUG ("called on %s with handle %u (%s) \"%s\"", tp_handle_inspect (handle_repo, priv->handle), handle, contact_id, message); if (TP_HANDLE_TYPE_GROUP == priv->handle_type) { ret = gabble_roster_handle_remove_from_group (priv->conn->roster, handle, priv->handle, error); } else if (TP_HANDLE_TYPE_LIST != priv->handle_type) { g_assert_not_reached (); return FALSE; } /* "magic" contact lists, from here down... */ /* publish list */ else if (GABBLE_LIST_HANDLE_PUBLISH == priv->handle) { /* send */ ret = gabble_connection_send_presence (priv->conn, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED, contact_id, message, error); /* remove it from local_pending here, because roster callback doesn't know if it can (subscription='none' is used both during request and when it's rejected) */ if (tp_handle_set_is_member (GABBLE_ROSTER_CHANNEL (obj)->group.local_pending, handle)) { TpIntSet *rem = tp_intset_new (); tp_intset_add (rem, handle); tp_group_mixin_change_members (obj, "", NULL, rem, NULL, NULL, 0, 0); tp_intset_destroy (rem); } } /* subscribe list */ else if (GABBLE_LIST_HANDLE_SUBSCRIBE == priv->handle) { /* send */ ret = gabble_connection_send_presence (priv->conn, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE, contact_id, message, error); } /* stored list */ else if (GABBLE_LIST_HANDLE_STORED == priv->handle) { /* send roster subscription=remove IQ */ ret = gabble_roster_handle_remove (priv->conn->roster, handle, error); } /* deny list */ else if (GABBLE_LIST_HANDLE_DENY == priv->handle) { /* unblock contact */ ret = gabble_roster_handle_set_blocked (priv->conn->roster, handle, FALSE, error); } else { g_assert_not_reached (); } return ret; } /** * gabble_roster_channel_close * * Implements D-Bus method Close * on interface org.freedesktop.Telepathy.Channel */ static void gabble_roster_channel_close (TpSvcChannel *iface, DBusGMethodInvocation *context) { GabbleRosterChannel *self = GABBLE_ROSTER_CHANNEL (iface); GabbleRosterChannelPrivate *priv; g_assert (GABBLE_IS_ROSTER_CHANNEL (self)); priv = self->priv; if (priv->handle_type == TP_HANDLE_TYPE_LIST) { GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "you may not close contact list channels" }; dbus_g_method_return_error (context, &e); } else /* TP_HANDLE_TYPE_GROUP */ { if (tp_handle_set_size (self->group.members) == 0) { /* deleting groups isn't a concept that exists on XMPP, * so just close the channel */ priv->closed = TRUE; tp_svc_channel_emit_closed ((TpSvcChannel *) self); tp_svc_channel_return_from_close (context); return; } else { GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "you may not close this group, because it's not empty" }; dbus_g_method_return_error (context, &e); } } } /** * gabble_roster_channel_get_channel_type * * Implements D-Bus method GetChannelType * on interface org.freedesktop.Telepathy.Channel */ static void gabble_roster_channel_get_channel_type (TpSvcChannel *iface, DBusGMethodInvocation *context) { tp_svc_channel_return_from_get_channel_type (context, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); } /** * gabble_roster_channel_get_handle * * Implements D-Bus method GetHandle * on interface org.freedesktop.Telepathy.Channel */ static void gabble_roster_channel_get_handle (TpSvcChannel *iface, DBusGMethodInvocation *context) { GabbleRosterChannel *self = GABBLE_ROSTER_CHANNEL (iface); GabbleRosterChannelPrivate *priv; g_assert (GABBLE_IS_ROSTER_CHANNEL (self)); priv = self->priv; tp_svc_channel_return_from_get_handle (context, priv->handle_type, priv->handle); } /** * gabble_roster_channel_get_interfaces * * Implements D-Bus method GetInterfaces * on interface org.freedesktop.Telepathy.Channel */ static void gabble_roster_channel_get_interfaces (TpSvcChannel *self, DBusGMethodInvocation *context) { tp_svc_channel_return_from_get_interfaces (context, gabble_roster_channel_interfaces); } static void channel_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface; #define IMPLEMENT(x) tp_svc_channel_implement_##x (\ klass, gabble_roster_channel_##x) IMPLEMENT(close); IMPLEMENT(get_channel_type); IMPLEMENT(get_handle); IMPLEMENT(get_interfaces); #undef IMPLEMENT }