/* * dbus-tube-channel.h - high level API for DBusTube channels * * Copyright (C) 2011 Collabora Ltd. * * 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:dbus-tube-channel * @title: TpDBusTubeChannel * @short_description: proxy object for D-Bus tube channels * * #TpDBusTubeChannel provides API for working with D-Bus tube channels, which * allow applications to open D-Bus connections to a contact or chat room. * * To create a new outgoing D-Bus tube channel, do something like: * * |[ * GHashTable *request_properties = tp_asv_new ( * TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, * TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT, * TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, tp_contact_get_identifier (contact), * TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME, G_TYPE_STRING, "com.example.walrus", * NULL); * TpAccountChannelRequest *req = tp_account_channel_request_new (account, * request_properties, TP_USER_ACTION_TIME_NOT_USER_ACTION); * tp_account_channel_request_create_and_handle_channel_async (req, NULL, callback, NULL); * * // ... * * static void * callback ( * GObject *source, * GAsyncResult *result, * gpointer user_data) * { * TpAccountChannelRequest *req = TP_ACCOUNT_CHANNEL_REQUEST (source); * TpChannel *channel; * GError *error = NULL; * * channel = tp_account_channel_request_create_and_handle_channel_finish (req, result, &error); * tp_dbus_tube_channel_offer_async (TP_DBUS_TUBE_CHANNEL (channel), NULL, offer_callback, NULL); * } * ]| * * You can find a fuller example in the examples/client/dbus-tubes * directory. * * Since: 0.18.0 */ /** * TpDBusTubeChannel: * * Data structure representing a #TpDBusTubeChannel. * * Since: 0.18.0 */ /** * TpDBusTubeChannelClass: * * The class of a #TpDBusTubeChannel. * * Since: 0.18.0 */ #include "config.h" #include "telepathy-glib/dbus-tube-channel.h" #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_CHANNEL #include "telepathy-glib/automatic-client-factory-internal.h" #include "telepathy-glib/channel-internal.h" #include "telepathy-glib/debug-internal.h" #include "telepathy-glib/variant-util-internal.h" #include #include G_DEFINE_TYPE (TpDBusTubeChannel, tp_dbus_tube_channel, TP_TYPE_CHANNEL) struct _TpDBusTubeChannelPrivate { GHashTable *parameters; TpTubeChannelState state; GSimpleAsyncResult *result; gchar *address; }; enum { PROP_SERVICE_NAME = 1, PROP_PARAMETERS, PROP_PARAMETERS_VARDICT }; static void tp_dbus_tube_channel_dispose (GObject *obj) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) obj; tp_clear_pointer (&self->priv->parameters, g_hash_table_unref); /* If priv->result isn't NULL, it owns a ref to self. */ g_warn_if_fail (self->priv->result == NULL); tp_clear_pointer (&self->priv->address, g_free); G_OBJECT_CLASS (tp_dbus_tube_channel_parent_class)->dispose (obj); } static void tp_dbus_tube_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) object; switch (property_id) { case PROP_SERVICE_NAME: g_value_set_string (value, tp_dbus_tube_channel_get_service_name (self)); break; case PROP_PARAMETERS: g_value_set_boxed (value, self->priv->parameters); break; case PROP_PARAMETERS_VARDICT: g_value_take_variant (value, tp_dbus_tube_channel_dup_parameters_vardict (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void complete_operation (TpDBusTubeChannel *self) { TpDBusTubeChannelPrivate *priv = self->priv; GSimpleAsyncResult *result = priv->result; /* This dance is to ensure that we don't accidentally manipulate priv->result * while calling out to user code. For instance, someone might call * tp_proxy_invalidate() on us, which winds up landing us in here via our * handler for that signal. */ g_assert (priv->result != NULL); result = priv->result; priv->result = NULL; g_simple_async_result_complete (result); g_object_unref (result); } static void dbus_connection_new_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpDBusTubeChannel *self = user_data; GDBusConnection *conn; GError *error = NULL; conn = g_dbus_connection_new_for_address_finish (result, &error); if (conn == NULL) { DEBUG ("Failed to create GDBusConnection: %s", error->message); g_simple_async_result_take_error (self->priv->result, error); } else { g_simple_async_result_set_op_res_gpointer (self->priv->result, conn, g_object_unref); } complete_operation (self); } static void check_tube_open (TpDBusTubeChannel *self) { if (self->priv->result == NULL) return; if (self->priv->address == NULL) return; if (self->priv->state != TP_TUBE_CHANNEL_STATE_OPEN) return; DEBUG ("Tube %s opened: %s", tp_proxy_get_object_path (self), self->priv->address); g_dbus_connection_new_for_address (self->priv->address, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL, NULL, dbus_connection_new_cb, self); } static void dbus_tube_invalidated_cb ( TpProxy *proxy, guint domain, gint code, gchar *message, gpointer user_data) { TpDBusTubeChannel *self = TP_DBUS_TUBE_CHANNEL (proxy); TpDBusTubeChannelPrivate *priv = self->priv; GError error = { domain, code, message }; if (priv->result != NULL) { DEBUG ("Tube invalidated: '%s'; failing pending offer/accept method call", message); g_simple_async_result_set_from_error (priv->result, &error); complete_operation (self); } } static void tube_state_changed_cb (TpChannel *channel, TpTubeChannelState state, gpointer user_data, GObject *weak_object) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) channel; self->priv->state = state; check_tube_open (self); } static void tp_dbus_tube_channel_constructed (GObject *obj) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) obj; void (*chain_up) (GObject *) = ((GObjectClass *) tp_dbus_tube_channel_parent_class)->constructed; TpChannel *chan = (TpChannel *) obj; GHashTable *props; if (chain_up != NULL) chain_up (obj); if (tp_channel_get_channel_type_id (chan) != TP_IFACE_QUARK_CHANNEL_TYPE_DBUS_TUBE) { GError error = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT, "Channel is not a D-Bus tube" }; DEBUG ("Channel is not a D-Bus tube: %s", tp_channel_get_channel_type ( chan)); tp_proxy_invalidate (TP_PROXY (self), &error); return; } props = _tp_channel_get_immutable_properties (TP_CHANNEL (self)); if (tp_asv_get_string (props, TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME) == NULL) { GError error = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT, "Tube doesn't have DBusTube.ServiceName property" }; DEBUG ("%s", error.message); tp_proxy_invalidate (TP_PROXY (self), &error); return; } /* Tube.Parameters is immutable for incoming tubes. For outgoing ones, * it's defined when offering the tube. */ if (!tp_channel_get_requested (TP_CHANNEL (self))) { GHashTable *params; params = tp_asv_get_boxed (props, TP_PROP_CHANNEL_INTERFACE_TUBE_PARAMETERS, TP_HASH_TYPE_STRING_VARIANT_MAP); if (params == NULL) { DEBUG ("Incoming tube doesn't have Tube.Parameters property"); self->priv->parameters = tp_asv_new (NULL, NULL); } else { self->priv->parameters = g_boxed_copy ( TP_HASH_TYPE_STRING_VARIANT_MAP, params); } } g_signal_connect (self, "invalidated", G_CALLBACK (dbus_tube_invalidated_cb), NULL); } static void get_state_cb (TpProxy *proxy, const GValue *value, const GError *error, gpointer user_data, GObject *weak_object) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) proxy; GSimpleAsyncResult *result = user_data; if (error != NULL) { DEBUG ("Failed to get Tube.State property: %s", error->message); g_simple_async_result_set_error (result, error->domain, error->code, "Failed to get Tube.State property: %s", error->message); } else { self->priv->state = g_value_get_uint (value); } g_simple_async_result_complete (result); } static void tp_dbus_tube_channel_prepare_core_feature_async (TpProxy *proxy, const TpProxyFeature *feature, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; GError *error = NULL; TpChannel *chan = (TpChannel *) proxy; result = g_simple_async_result_new ((GObject *) proxy, callback, user_data, tp_dbus_tube_channel_prepare_core_feature_async); if (tp_cli_channel_interface_tube_connect_to_tube_channel_state_changed (chan, tube_state_changed_cb, proxy, NULL, NULL, &error) == NULL) { WARNING ("Failed to connect to TubeChannelStateChanged on %s: %s", tp_proxy_get_object_path (proxy), error->message); g_error_free (error); } tp_cli_dbus_properties_call_get (proxy, -1, TP_IFACE_CHANNEL_INTERFACE_TUBE, "State", get_state_cb, result, g_object_unref, G_OBJECT (proxy)); } enum { FEAT_CORE, N_FEAT }; static const TpProxyFeature * tp_dbus_tube_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED) { static TpProxyFeature features[N_FEAT + 1] = { { 0 } }; if (G_LIKELY (features[0].name != 0)) return features; features[FEAT_CORE].name = TP_DBUS_TUBE_CHANNEL_FEATURE_CORE; features[FEAT_CORE].prepare_async = tp_dbus_tube_channel_prepare_core_feature_async; features[FEAT_CORE].core = TRUE; /* assert that the terminator at the end is there */ g_assert (features[N_FEAT].name == 0); return features; } static void tp_dbus_tube_channel_class_init (TpDBusTubeChannelClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GParamSpec *param_spec; TpProxyClass *proxy_class = (TpProxyClass *) klass; gobject_class->constructed = tp_dbus_tube_channel_constructed; gobject_class->get_property = tp_dbus_tube_channel_get_property; gobject_class->dispose = tp_dbus_tube_channel_dispose; proxy_class->list_features = tp_dbus_tube_channel_list_features; /** * TpDBusTubeChannel:service-name: * * A string representing the service name that will be used over the tube. * * Since: 0.18.0 */ param_spec = g_param_spec_string ("service-name", "Service Name", "The service name of the dbus tube", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_SERVICE_NAME, param_spec); /** * TpDBusTubeChannel:parameters: * * A string to #GValue #GHashTable representing the parameters of the tube. * * Will be %NULL for outgoing tubes until the tube has been offered. * * In high-level language bindings, use * tp_dbus_tube_channel_dup_parameters_vardict() to get the same information * in a more convenient format. * * Since: 0.18.0 */ param_spec = g_param_spec_boxed ("parameters", "Parameters", "The parameters of the dbus tube", TP_HASH_TYPE_STRING_VARIANT_MAP, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_PARAMETERS, param_spec); /** * TpDBusTubeChannel:parameters-vardict: * * A %G_VARIANT_TYPE_VARDICT representing the parameters of the tube. * * Will be %NULL for outgoing tubes until the tube has been offered. * * Since: 0.19.10 */ param_spec = g_param_spec_variant ("parameters-vardict", "Parameters", "The parameters of the D-Bus tube", G_VARIANT_TYPE_VARDICT, NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (gobject_class, PROP_PARAMETERS_VARDICT, param_spec); g_type_class_add_private (gobject_class, sizeof (TpDBusTubeChannelPrivate)); } static void tp_dbus_tube_channel_init (TpDBusTubeChannel *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_DBUS_TUBE_CHANNEL, TpDBusTubeChannelPrivate); } TpDBusTubeChannel * _tp_dbus_tube_channel_new_with_factory ( TpSimpleClientFactory *factory, TpConnection *conn, const gchar *object_path, const GHashTable *immutable_properties, GError **error) { TpProxy *conn_proxy = (TpProxy *) conn; g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL); g_return_val_if_fail (object_path != NULL, NULL); g_return_val_if_fail (immutable_properties != NULL, NULL); if (!tp_dbus_check_valid_object_path (object_path, error)) return NULL; return g_object_new (TP_TYPE_DBUS_TUBE_CHANNEL, "connection", conn, "dbus-daemon", conn_proxy->dbus_daemon, "bus-name", conn_proxy->bus_name, "object-path", object_path, "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE, "channel-properties", immutable_properties, "factory", factory, NULL); } /** * tp_dbus_tube_channel_get_service_name: * @self: a #TpDBusTubeChannel * * Return the #TpDBusTubeChannel:service-name property * * Returns: (transfer none): the value of #TpDBusTubeChannel:service-name * * Since: 0.18.0 */ const gchar * tp_dbus_tube_channel_get_service_name (TpDBusTubeChannel *self) { GHashTable *props; props = _tp_channel_get_immutable_properties (TP_CHANNEL (self)); return tp_asv_get_string (props, TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME); } /** * tp_dbus_tube_channel_get_parameters: (skip) * @self: a #TpDBusTubeChannel * * Return the #TpDBusTubeChannel:parameters property * * Returns: (transfer none) (element-type utf8 GObject.Value): * the value of #TpDBusTubeChannel:parameters * * Since: 0.18.0 */ GHashTable * tp_dbus_tube_channel_get_parameters (TpDBusTubeChannel *self) { return self->priv->parameters; } /** * tp_dbus_tube_channel_dup_parameters_vardict: * @self: a #TpDBusTubeChannel * * Return the parameters of the dbus-tube channel in a variant of * type %G_VARIANT_TYPE_VARDICT whose keys are strings representing * parameter names and values are variants representing corresponding * parameter values set by the offerer when offering this channel. * * The GVariant returned is %NULL if this is an outgoing tube that has not * yet been offered or the parameters property has not been set. * * Use g_variant_lookup(), g_variant_lookup_value(), or tp_vardict_get_uint32() * and similar functions for convenient access to the values. * * Returns: (transfer full): a new reference to a #GVariant * * Since: 0.19.10 */ GVariant * tp_dbus_tube_channel_dup_parameters_vardict (TpDBusTubeChannel *self) { g_return_val_if_fail (TP_IS_DBUS_TUBE_CHANNEL (self), NULL); if (self->priv->parameters == NULL) return NULL; return _tp_asv_to_vardict (self->priv->parameters); } /** * TP_DBUS_TUBE_CHANNEL_FEATURE_CORE: * * Expands to a call to a function that returns a quark representing the * core feature of a #TpDBusTubeChannel. * * One can ask for a feature to be prepared using the * tp_proxy_prepare_async() function, and waiting for it to callback. * * Since: 0.18.0 */ GQuark tp_dbus_tube_channel_feature_quark_core (void) { return g_quark_from_static_string ("tp-dbus-tube-channel-feature-core"); } static void dbus_tube_offer_cb (TpChannel *channel, const gchar *address, const GError *error, gpointer user_data, GObject *weak_object) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) channel; if (error != NULL) { DEBUG ("Offer() failed: %s", error->message); g_simple_async_result_set_from_error (self->priv->result, error); complete_operation (self); return; } self->priv->address = g_strdup (address); /* We have to wait that the tube is opened before being allowed to use it */ check_tube_open (self); } static void proxy_prepare_offer_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) source; GHashTable *params = user_data; GError *error = NULL; if (!tp_proxy_prepare_finish (source, result, &error)) { g_simple_async_result_take_error (self->priv->result, error); complete_operation (self); goto out; } if (self->priv->state != TP_TUBE_CHANNEL_STATE_NOT_OFFERED) { g_simple_async_result_set_error (self->priv->result, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Tube is not in the NotOffered state"); complete_operation (self); goto out; } g_assert (self->priv->parameters == NULL); if (params != NULL) self->priv->parameters = g_hash_table_ref (params); else self->priv->parameters = tp_asv_new (NULL, NULL); g_object_notify (G_OBJECT (self), "parameters"); g_object_notify (G_OBJECT (self), "parameters-vardict"); /* TODO: provide a way to use TP_SOCKET_ACCESS_CONTROL_LOCALHOST if you're in * an environment where you need to disable authentication. tp-glib can't * guess this for you. */ tp_cli_channel_type_dbus_tube_call_offer (TP_CHANNEL (self), -1, self->priv->parameters, TP_SOCKET_ACCESS_CONTROL_CREDENTIALS, dbus_tube_offer_cb, NULL, NULL, G_OBJECT (self)); out: tp_clear_pointer (¶ms, g_hash_table_unref); } /** * tp_dbus_tube_channel_offer_async: * @self: an outgoing #TpDBusTubeChannel * @params: (allow-none) (transfer none): parameters of the tube, or %NULL * @callback: a callback to call when the tube has been offered * @user_data: data to pass to @callback * * Offer an outgoing D-Bus tube. When the tube has been offered and accepted * @callback will be called. You can then call * tp_dbus_tube_channel_offer_finish() to get the #GDBusConnection that will * be used to communicate through the tube. * * Since: 0.18.0 */ void tp_dbus_tube_channel_offer_async (TpDBusTubeChannel *self, GHashTable *params, GAsyncReadyCallback callback, gpointer user_data) { GQuark features[] = { TP_DBUS_TUBE_CHANNEL_FEATURE_CORE, 0 }; g_return_if_fail (TP_IS_DBUS_TUBE_CHANNEL (self)); g_return_if_fail (self->priv->result == NULL); g_return_if_fail (tp_channel_get_requested (TP_CHANNEL (self))); g_return_if_fail (self->priv->parameters == NULL); self->priv->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_dbus_tube_channel_offer_async); /* We need CORE to be prepared as we rely on State changes */ tp_proxy_prepare_async (self, features, proxy_prepare_offer_cb, params != NULL ? g_hash_table_ref (params) : params); } /** * tp_dbus_tube_channel_offer_finish: * @self: a #TpDBusTubeChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes offering an outgoing D-Bus tube. The returned #GDBusConnection * is ready to be used to exchange data through the tube. * * Returns: (transfer full): a reference on a #GDBusConnection if the tube * has been successfully offered and opened; %NULL otherwise. * * Since: 0.18.0 */ GDBusConnection * tp_dbus_tube_channel_offer_finish (TpDBusTubeChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_return_copy_pointer (self, tp_dbus_tube_channel_offer_async, g_object_ref) } static void dbus_tube_accept_cb (TpChannel *channel, const gchar *address, const GError *error, gpointer user_data, GObject *weak_object) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) channel; if (error != NULL) { DEBUG ("Accept() failed: %s", error->message); g_simple_async_result_set_from_error (self->priv->result, error); complete_operation (self); return; } self->priv->address = g_strdup (address); /* We have to wait that the tube is opened before being allowed to use it */ check_tube_open (self); } static void proxy_prepare_accept_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpDBusTubeChannel *self = (TpDBusTubeChannel *) source; GError *error = NULL; if (!tp_proxy_prepare_finish (source, result, &error)) { g_simple_async_result_take_error (self->priv->result, error); complete_operation (self); return; } if (self->priv->state != TP_TUBE_CHANNEL_STATE_LOCAL_PENDING) { g_simple_async_result_set_error (self->priv->result, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Tube is not in the LocalPending state"); complete_operation (self); return; } /* TODO: provide a way to use TP_SOCKET_ACCESS_CONTROL_LOCALHOST if you're in * an environment where you need to disable authentication. tp-glib can't * guess this for you. */ tp_cli_channel_type_dbus_tube_call_accept (TP_CHANNEL (self), -1, TP_SOCKET_ACCESS_CONTROL_CREDENTIALS, dbus_tube_accept_cb, NULL, NULL, G_OBJECT (self)); } /** * tp_dbus_tube_channel_accept_async: * @self: an incoming #TpDBusTubeChannel * @callback: a callback to call when the tube has been offered * @user_data: data to pass to @callback * * Accept an incoming D-Bus tube. When the tube has been accepted * @callback will be called. You can then call * tp_dbus_tube_channel_accept_finish() to get the #GDBusConnection that will * be used to communicate through the tube. * * Since: 0.18.0 */ void tp_dbus_tube_channel_accept_async (TpDBusTubeChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GQuark features[] = { TP_DBUS_TUBE_CHANNEL_FEATURE_CORE, 0 }; g_return_if_fail (TP_IS_DBUS_TUBE_CHANNEL (self)); g_return_if_fail (self->priv->result == NULL); g_return_if_fail (!tp_channel_get_requested (TP_CHANNEL (self))); self->priv->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, tp_dbus_tube_channel_accept_async); /* We need CORE to be prepared as we rely on State changes */ tp_proxy_prepare_async (self, features, proxy_prepare_accept_cb, NULL); } /** * tp_dbus_tube_channel_accept_finish: * @self: a #TpDBusTubeChannel * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes to accept an incoming D-Bus tube. The returned #GDBusConnection * is ready to be used to exchange data through the tube. * * Returns: (transfer full): a reference on a #GDBusConnection if the tube * has been successfully accepted and opened; %NULL otherwise. * * Since: 0.18.0 */ GDBusConnection * tp_dbus_tube_channel_accept_finish (TpDBusTubeChannel *self, GAsyncResult *result, GError **error) { _tp_implement_finish_return_copy_pointer (self, tp_dbus_tube_channel_accept_async, g_object_ref) }