/* * gabble-call-stream.c - Source for TpyBaseMediaCallStream * Copyright (C) 2009-2011 Collabora Ltd. * @author Sjoerd Simons * @author Jonny Lamb * @author David Laban * * 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 "base-media-call-stream.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG TPY_DEBUG_CALL #include "debug.h" static void call_stream_media_iface_init (gpointer, gpointer); G_DEFINE_TYPE_WITH_CODE(TpyBaseMediaCallStream, tpy_base_media_call_stream, TPY_TYPE_BASE_CALL_STREAM, G_IMPLEMENT_INTERFACE (TPY_TYPE_SVC_CALL_STREAM_INTERFACE_MEDIA, call_stream_media_iface_init); ); /* properties */ enum { PROP_LOCAL_CANDIDATES = 1, PROP_LOCAL_CREDENTIALS, PROP_ENDPOINTS, PROP_TRANSPORT, PROP_STUN_SERVERS, PROP_RELAY_INFO, PROP_HAS_SERVER_INFO, }; /* private structure */ struct _TpyBaseMediaCallStreamPrivate { gboolean dispose_has_run; GList *endpoints; GPtrArray *local_candidates; GPtrArray *relay_info; GPtrArray *stun_servers; TpyStreamTransportType transport; gchar *username; gchar *password; gboolean got_relay_info; }; static void tpy_base_media_call_stream_init (TpyBaseMediaCallStream *self) { TpyBaseMediaCallStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TPY_TYPE_BASE_MEDIA_CALL_STREAM, TpyBaseMediaCallStreamPrivate); self->priv = priv; priv->local_candidates = g_ptr_array_new (); priv->relay_info = g_ptr_array_new (); priv->stun_servers = g_ptr_array_new (); priv->username = g_strdup (""); priv->password = g_strdup (""); } static void tpy_base_media_call_stream_dispose (GObject *object); static void tpy_base_media_call_stream_finalize (GObject *object); static gboolean has_server_info (TpyBaseMediaCallStream *self) { /* extend this function when HasServerInfo gains more info to * retrieve than just relay info */ return self->priv->got_relay_info; } static void tpy_base_media_call_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpyBaseMediaCallStream *stream = TPY_BASE_MEDIA_CALL_STREAM (object); TpyBaseMediaCallStreamPrivate *priv = stream->priv; switch (property_id) { case PROP_LOCAL_CANDIDATES: { g_value_set_boxed (value, stream->priv->local_candidates); break; } case PROP_LOCAL_CREDENTIALS: { g_value_take_boxed (value, tp_value_array_build (2, G_TYPE_STRING, stream->priv->username, G_TYPE_STRING, stream->priv->password, G_TYPE_INVALID)); break; } case PROP_ENDPOINTS: { GPtrArray *arr = g_ptr_array_sized_new (1); GList *l; for (l = priv->endpoints; l != NULL; l = g_list_next (l)) { TpyCallStreamEndpoint *e = TPY_CALL_STREAM_ENDPOINT (l->data); g_ptr_array_add (arr, g_strdup (tpy_call_stream_endpoint_get_object_path (e))); } g_value_take_boxed (value, arr); break; } case PROP_TRANSPORT: { g_value_set_uint (value, priv->transport); break; } case PROP_STUN_SERVERS: { g_value_set_boxed (value, priv->stun_servers); break; } case PROP_RELAY_INFO: { g_value_set_boxed (value, priv->relay_info); break; } case PROP_HAS_SERVER_INFO: { g_value_set_boolean (value, has_server_info (stream)); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tpy_base_media_call_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { TpyBaseMediaCallStream *stream = TPY_BASE_MEDIA_CALL_STREAM (object); TpyBaseMediaCallStreamPrivate *priv = stream->priv; switch (property_id) { case PROP_TRANSPORT: priv->transport = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void maybe_emit_server_info_retrieved (TpyBaseMediaCallStream *self) { if (has_server_info (self)) tpy_svc_call_stream_interface_media_emit_server_info_retrieved (self); } void tpy_base_media_call_stream_set_relay_info ( TpyBaseMediaCallStream *self, const GPtrArray *relays) { TpyBaseMediaCallStreamPrivate *priv = self->priv; if (relays != NULL) { g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, priv->relay_info); priv->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, relays); } tpy_svc_call_stream_interface_media_emit_relay_info_changed ( self, priv->relay_info); if (!priv->got_relay_info) { priv->got_relay_info = TRUE; maybe_emit_server_info_retrieved (self); } } void tpy_base_media_call_stream_set_transport ( TpyBaseMediaCallStream *self, TpyStreamTransportType transport) { self->priv->transport = transport; } void tpy_base_media_call_stream_take_endpoint ( TpyBaseMediaCallStream *self, TpyCallStreamEndpoint *endpoint) { self->priv->endpoints = g_list_append (self->priv->endpoints, endpoint); } GList * tpy_base_media_call_stream_get_endpoints ( TpyBaseMediaCallStream *self) { return self->priv->endpoints; } void tpy_base_media_call_stream_set_stun_servers (TpyBaseMediaCallStream *self, const GPtrArray *stun_servers) { TpyBaseMediaCallStreamPrivate *priv = self->priv; g_return_if_fail (stun_servers != NULL); g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, priv->stun_servers); priv->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, stun_servers); tpy_svc_call_stream_interface_media_emit_stun_servers_changed ( self, stun_servers); } const gchar *tpy_base_media_call_stream_get_username ( TpyBaseMediaCallStream *self) { return self->priv->username; } const gchar *tpy_base_media_call_stream_get_password ( TpyBaseMediaCallStream *self) { return self->priv->password; } static void tpy_base_media_call_stream_constructed (GObject *obj) { TpyBaseMediaCallStreamClass *klass = TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS (obj); GObjectClass *g_klass = G_OBJECT_CLASS ( tpy_base_media_call_stream_parent_class); if (g_klass->constructed != NULL) g_klass->constructed (obj); g_return_if_fail (klass->add_local_candidates != NULL); } static void tpy_base_media_call_stream_class_init ( TpyBaseMediaCallStreamClass *tpy_base_media_call_stream_class) { GObjectClass *object_class = G_OBJECT_CLASS (tpy_base_media_call_stream_class); GParamSpec *param_spec; TpyBaseCallStreamClass *bcs_class = TPY_BASE_CALL_STREAM_CLASS (tpy_base_media_call_stream_class); static TpDBusPropertiesMixinPropImpl stream_media_props[] = { { "Transport", "transport", NULL }, { "LocalCandidates", "local-candidates", NULL }, { "LocalCredentials", "local-credentials", NULL }, { "STUNServers", "stun-servers", NULL }, { "RelayInfo", "relay-info", NULL }, { "HasServerInfo", "has-server-info", NULL }, { "Endpoints", "endpoints", NULL }, { NULL } }; static const gchar *interfaces[] = { TPY_IFACE_CALL_STREAM_INTERFACE_MEDIA, NULL }; g_type_class_add_private (tpy_base_media_call_stream_class, sizeof (TpyBaseMediaCallStreamPrivate)); object_class->set_property = tpy_base_media_call_stream_set_property; object_class->get_property = tpy_base_media_call_stream_get_property; object_class->dispose = tpy_base_media_call_stream_dispose; object_class->finalize = tpy_base_media_call_stream_finalize; object_class->constructed = tpy_base_media_call_stream_constructed; param_spec = g_param_spec_boxed ("local-candidates", "LocalCandidates", "List of local candidates", TPY_ARRAY_TYPE_CANDIDATE_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_LOCAL_CANDIDATES, param_spec); param_spec = g_param_spec_boxed ("local-credentials", "LocalCredentials", "ufrag and pwd as defined by ICE", TPY_STRUCT_TYPE_STREAM_CREDENTIALS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_LOCAL_CREDENTIALS, param_spec); param_spec = g_param_spec_boxed ("endpoints", "Endpoints", "The endpoints of this content", TP_ARRAY_TYPE_OBJECT_PATH_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_ENDPOINTS, param_spec); param_spec = g_param_spec_uint ("transport", "Transport", "The transport type of this stream", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_TRANSPORT, param_spec); param_spec = g_param_spec_boxed ("stun-servers", "STUNServers", "List of STUN servers", TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_STUN_SERVERS, param_spec); param_spec = g_param_spec_boxed ("relay-info", "RelayInfo", "List of relay information", TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_RELAY_INFO, param_spec); param_spec = g_param_spec_boolean ("has-server-info", "HasServerInfo", "True if the server information about STUN and " "relay servers has been retrieved", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_HAS_SERVER_INFO, param_spec); tp_dbus_properties_mixin_implement_interface (object_class, TPY_IFACE_QUARK_CALL_STREAM_INTERFACE_MEDIA, tp_dbus_properties_mixin_getter_gobject_properties, NULL, stream_media_props); bcs_class->extra_interfaces = interfaces; } static void tpy_base_media_call_stream_dispose (GObject *object) { TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (object); TpyBaseMediaCallStreamPrivate *priv = self->priv; GList *l; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; for (l = priv->endpoints; l != NULL; l = g_list_next (l)) { g_object_unref (l->data); } tp_clear_pointer (&priv->endpoints, g_list_free); if (G_OBJECT_CLASS (tpy_base_media_call_stream_parent_class)->dispose) G_OBJECT_CLASS (tpy_base_media_call_stream_parent_class)->dispose (object); } static void tpy_base_media_call_stream_finalize (GObject *object) { TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (object); TpyBaseMediaCallStreamPrivate *priv = self->priv; g_boxed_free (TPY_ARRAY_TYPE_CANDIDATE_LIST, priv->local_candidates); g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, priv->relay_info); g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, priv->stun_servers); G_OBJECT_CLASS (tpy_base_media_call_stream_parent_class)->finalize (object); } static void tpy_base_media_call_stream_add_candidates ( TpySvcCallStreamInterfaceMedia *iface, const GPtrArray *candidates, DBusGMethodInvocation *context) { TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (iface); TpyBaseMediaCallStreamClass *klass = TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS (self); GPtrArray *accepted_candidates = NULL; guint i; GError *error = NULL; if (klass->add_local_candidates != NULL) accepted_candidates = klass->add_local_candidates (self, candidates, &error); else g_set_error_literal (&error, TP_ERRORS, TP_ERROR_CONFUSED, "CM failed to implement the compulsory function add_local_candidates"); if (accepted_candidates == NULL) goto except; for (i = 0; i < accepted_candidates->len; i++) g_ptr_array_add (self->priv->local_candidates, g_ptr_array_index (accepted_candidates, i)); tpy_svc_call_stream_interface_media_emit_local_candidates_added (self, accepted_candidates); tpy_svc_call_stream_interface_media_return_from_add_candidates (context); goto finally; except: dbus_g_method_return_error (context, error); g_clear_error (&error); finally: /* Note that we only do a shallow free because we've copied the contents into * local_candidates. */ if (accepted_candidates != NULL) g_ptr_array_unref (accepted_candidates); } static void tpy_base_media_call_stream_candidates_prepared ( TpySvcCallStreamInterfaceMedia *iface, DBusGMethodInvocation *context) { TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (iface); TpyBaseMediaCallStreamClass *klass = TPY_BASE_MEDIA_CALL_STREAM_GET_CLASS (self); if (klass->local_candidates_prepared != NULL) klass->local_candidates_prepared (self); tpy_svc_call_stream_interface_media_return_from_candidates_prepared ( context); } static void tpy_base_media_call_stream_set_credentials ( TpySvcCallStreamInterfaceMedia *iface, const gchar *username, const gchar *password, DBusGMethodInvocation *context) { TpyBaseMediaCallStream *self = TPY_BASE_MEDIA_CALL_STREAM (iface); g_free (self->priv->username); self->priv->username = g_strdup (username); g_free (self->priv->password); self->priv->password = g_strdup (password); g_boxed_free (TPY_ARRAY_TYPE_CANDIDATE_LIST, self->priv->local_candidates); self->priv->local_candidates = g_ptr_array_new (); g_object_notify (G_OBJECT (self), "local-candidates"); g_object_notify (G_OBJECT (self), "local-credentials"); tpy_svc_call_stream_interface_media_emit_local_credentials_changed (self, username, password); tpy_svc_call_stream_interface_media_return_from_set_credentials (context); } static void call_stream_media_iface_init (gpointer g_iface, gpointer iface_data) { TpySvcCallStreamInterfaceMediaClass *klass = (TpySvcCallStreamInterfaceMediaClass *) g_iface; #define IMPLEMENT(x) tpy_svc_call_stream_interface_media_implement_##x (\ klass, tpy_base_media_call_stream_##x) IMPLEMENT(add_candidates); IMPLEMENT(candidates_prepared); IMPLEMENT(set_credentials); #undef IMPLEMENT }