diff options
author | Will Thompson <will.thompson@collabora.co.uk> | 2012-04-02 16:46:25 +0100 |
---|---|---|
committer | Will Thompson <will.thompson@collabora.co.uk> | 2012-04-02 16:48:03 +0100 |
commit | dcab14f903605fc2f0c162e45b1c322966eb46c2 (patch) | |
tree | a241e53feceda2f927bf1b3061575e9a8002d7a5 | |
parent | 6275753ca716891e5865d5ea7ce5be94b096d34a (diff) | |
parent | 126261cf295622c659238bf51d85d8e39c8eaf2c (diff) | |
download | telepathy-glib-dcab14f903605fc2f0c162e45b1c322966eb46c2.tar.gz |
Merge branch '29271-some-dbus-tube-api'
https://bugs.freedesktop.org/show_bug.cgi?id=29271
Reviewed-by: Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | docs/reference/telepathy-glib-sections.txt | 6 | ||||
-rw-r--r-- | examples/client/Makefile.am | 2 | ||||
-rw-r--r-- | examples/client/dbus-tubes/Makefile.am | 23 | ||||
-rw-r--r-- | examples/client/dbus-tubes/accepter.c | 220 | ||||
-rw-r--r-- | examples/client/dbus-tubes/constants.h | 9 | ||||
-rw-r--r-- | examples/client/dbus-tubes/offerer.c | 238 | ||||
-rw-r--r-- | examples/client/stream-tubes/Makefile.am | 4 | ||||
-rw-r--r-- | telepathy-glib/automatic-client-factory.c | 170 | ||||
-rw-r--r-- | telepathy-glib/dbus-tube-channel.c | 478 | ||||
-rw-r--r-- | telepathy-glib/dbus-tube-channel.h | 25 | ||||
-rw-r--r-- | telepathy-glib/proxy.c | 10 | ||||
-rw-r--r-- | tests/dbus/dbus-tube.c | 268 | ||||
-rw-r--r-- | tests/lib/dbus-tube-chan.c | 135 | ||||
-rw-r--r-- | tests/lib/dbus-tube-chan.h | 10 |
16 files changed, 1531 insertions, 70 deletions
diff --git a/.gitignore b/.gitignore index 0d755a573..546bff798 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,8 @@ examples/client/telepathy-example-* examples/cm/*/telepathy-example-* examples/extensions/extensions.html /examples/future/*/telepathy-example-* +examples/client/dbus-tubes/accepter +examples/client/dbus-tubes/offerer examples/client/stream-tubes/accepter examples/client/stream-tubes/offerer /extensions/extensions.html diff --git a/configure.ac b/configure.ac index adaa4adc5..3d6ae4792 100644 --- a/configure.ac +++ b/configure.ac @@ -318,6 +318,7 @@ AC_OUTPUT( Makefile \ examples/client/js/Makefile \ examples/client/python/Makefile \ examples/client/stream-tubes/Makefile \ + examples/client/dbus-tubes/Makefile \ examples/cm/Makefile \ examples/cm/call/Makefile \ examples/cm/channelspecific/Makefile \ diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt index 2f665a9e1..0df19b1d3 100644 --- a/docs/reference/telepathy-glib-sections.txt +++ b/docs/reference/telepathy-glib-sections.txt @@ -6224,8 +6224,13 @@ TpStreamTubeConnectionPrivate <SUBSECTION> TpDBusTubeChannel TpDBusTubeChannelClass +TP_DBUS_TUBE_CHANNEL_FEATURE_CORE tp_dbus_tube_channel_get_parameters tp_dbus_tube_channel_get_service_name +tp_dbus_tube_channel_offer_async +tp_dbus_tube_channel_offer_finish +tp_dbus_tube_channel_accept_async +tp_dbus_tube_channel_accept_finish <SUBSECTION Standard> TP_IS_DBUS_TUBE_CHANNEL TP_IS_DBUS_TUBE_CHANNEL_CLASS @@ -6235,6 +6240,7 @@ TP_DBUS_TUBE_CHANNEL_GET_CLASS TP_TYPE_DBUS_TUBE_CHANNEL tp_dbus_tube_channel_get_type TpDBusTubeChannelPrivate +tp_dbus_tube_channel_feature_quark_core </SECTION> <SECTION> diff --git a/examples/client/Makefile.am b/examples/client/Makefile.am index 3481a0f4d..bd1c80bfe 100644 --- a/examples/client/Makefile.am +++ b/examples/client/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = stream-tubes python js +SUBDIRS = stream-tubes dbus-tubes python js EXAMPLES = diff --git a/examples/client/dbus-tubes/Makefile.am b/examples/client/dbus-tubes/Makefile.am new file mode 100644 index 000000000..90411ff32 --- /dev/null +++ b/examples/client/dbus-tubes/Makefile.am @@ -0,0 +1,23 @@ +noinst_PROGRAMS = \ + offerer \ + accepter \ + $(NULL) + +offerer_SOURCES = offerer.c constants.h +accepter_SOURCES = accepter.c constants.h + +# In an external project you'd use $(TP_GLIB_LIBS) (obtained from +# pkg-config via autoconf) instead of the .la path +LDADD = \ + $(top_builddir)/telepathy-glib/libtelepathy-glib.la \ + @DBUS_LIBS@ \ + @GLIB_LIBS@ + +AM_CFLAGS = \ + $(ERROR_CFLAGS) \ + @DBUS_CFLAGS@ \ + @GLIB_CFLAGS@ \ + @TP_GLIB_CFLAGS@ +AM_LDFLAGS = \ + $(ERROR_LDFLAGS) \ + $(NULL) diff --git a/examples/client/dbus-tubes/accepter.c b/examples/client/dbus-tubes/accepter.c new file mode 100644 index 000000000..638c536d7 --- /dev/null +++ b/examples/client/dbus-tubes/accepter.c @@ -0,0 +1,220 @@ +#include <telepathy-glib/telepathy-glib.h> +#include "constants.h" + +static GMainLoop *loop = NULL; + +static void +dbus_connection_closed_cb ( + GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + if (remote_peer_vanished) + g_debug ("remote peer disconnected: %s", error->message); + else if (error != NULL) + g_debug ("remote peer sent broken data: %s", error->message); + else + g_debug ("supposedly we closed the connection locally?!"); + + g_object_unref (connection); +} + +static void +lucky_number_cb ( + GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(u)"))) + { + guint32 x; + + g_variant_get (parameters, "(u)", &x); + g_debug ("My lucky number is: %u", x); + } + else + { + g_warning ("LuckyNumber's arguments were %s, not (u)", + g_variant_get_type_string (parameters)); + } +} + +static void +add_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *conn = G_DBUS_CONNECTION (source); + GVariant *ret; + GError *error = NULL; + + ret = g_dbus_connection_call_finish (conn, result, &error); + + if (ret != NULL) + { + gint32 value; + + g_variant_get (ret, "(i)", &value); + g_debug ("Adding my numbers together gave: %i", value); + g_variant_unref (ret); + } + else + { + g_warning ("Add() failed: %s", error->message); + g_clear_error (&error); + } +} + +static void +tube_accepted (GObject *tube, + GAsyncResult *res, + gpointer user_data) +{ + GDBusConnection *conn; + GError *error = NULL; + + conn = tp_dbus_tube_channel_accept_finish ( + TP_DBUS_TUBE_CHANNEL (tube), res, &error); + if (conn == NULL) + { + g_debug ("Failed to accept tube: %s", error->message); + g_error_free (error); + tp_channel_close_async (TP_CHANNEL (tube), NULL, NULL); + return; + } + + g_debug ("tube accepted"); + g_signal_connect (conn, "closed", + G_CALLBACK (dbus_connection_closed_cb), NULL); + + g_dbus_connection_signal_subscribe (conn, + /* since we only deal with 1-1 connections, no need to match sender */ + NULL, + EXAMPLE_INTERFACE, + "LuckyNumber", + EXAMPLE_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + lucky_number_cb, + NULL, NULL); + g_dbus_connection_call (conn, + NULL, + EXAMPLE_PATH, + EXAMPLE_INTERFACE, + "Add", + g_variant_new ("(ii)", 45, 54), + G_VARIANT_TYPE ("(i)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + add_cb, + NULL); +} + +static void +tube_invalidated_cb (TpStreamTubeChannel *tube, + guint domain, + gint code, + gchar *message, + gpointer user_data) +{ + g_debug ("Tube has been invalidated: %s", message); + g_main_loop_quit (loop); + g_object_unref (tube); +} + +static void +handle_channels (TpSimpleHandler *handler, + TpAccount *account, + TpConnection *conn, + GList *channels, + GList *requests, + gint64 action_time, + TpHandleChannelsContext *context, + gpointer user_data) +{ + TpDBusTubeChannel *tube; + GList *l; + GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "No channel to be handled" }; + + g_debug ("Handling channels"); + + for (l = channels; l != NULL; l = l->next) + { + TpDBusTubeChannel *channel = l->data; + + if (!TP_IS_DBUS_TUBE_CHANNEL (channel)) + continue; + + if (tp_strdiff (tp_dbus_tube_channel_get_service_name (channel), + EXAMPLE_SERVICE_NAME)) + continue; + + g_debug ("Accepting tube"); + + tube = g_object_ref (channel); + + g_signal_connect (tube, "invalidated", + G_CALLBACK (tube_invalidated_cb), NULL); + + tp_dbus_tube_channel_accept_async (tube, tube_accepted, context); + + tp_handle_channels_context_accept (context); + return; + } + + g_debug ("Rejecting channels"); + tp_handle_channels_context_fail (context, &error); +} + + +int +main (int argc, + const char **argv) +{ + TpAccountManager *manager; + TpBaseClient *handler; + GError *error = NULL; + + g_type_init (); + + manager = tp_account_manager_dup (); + handler = tp_simple_handler_new_with_am (manager, FALSE, FALSE, + "ExampleServiceHandler", FALSE, handle_channels, NULL, NULL); + + tp_base_client_take_handler_filter (handler, 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_TYPE_DBUS_TUBE_SERVICE_NAME, + G_TYPE_STRING, + EXAMPLE_SERVICE_NAME, + + NULL)); + + tp_base_client_register (handler, &error); + g_assert_no_error (error); + + g_debug ("Waiting for tube offer"); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_main_loop_unref (loop); + g_object_unref (handler); + g_object_unref (manager); + + return 0; +} diff --git a/examples/client/dbus-tubes/constants.h b/examples/client/dbus-tubes/constants.h new file mode 100644 index 000000000..77d9b1cec --- /dev/null +++ b/examples/client/dbus-tubes/constants.h @@ -0,0 +1,9 @@ +#ifndef DBUS_TUBE_EXAMPLE_CONSTANTS_H +#define DBUS_TUBE_EXAMPLE_CONSTANTS_H + +#define EXAMPLE_SERVICE_NAME "uk.co.example.calculator" + +#define EXAMPLE_INTERFACE "org.example.terriblecalculator" +#define EXAMPLE_PATH "/org/example/calculator" + +#endif diff --git a/examples/client/dbus-tubes/offerer.c b/examples/client/dbus-tubes/offerer.c new file mode 100644 index 000000000..87ffbb9e0 --- /dev/null +++ b/examples/client/dbus-tubes/offerer.c @@ -0,0 +1,238 @@ +#include <telepathy-glib/telepathy-glib.h> +#include "constants.h" + +static GMainLoop *loop = NULL; + +static void +connection_closed_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (source); + GError *error = NULL; + + if (!g_dbus_connection_close_finish (connection, result, &error)) + { + g_warning ("Couldn't close connection: %s", error->message); + g_clear_error (&error); + } + else + { + g_debug ("Connection closed."); + } + + tp_channel_close_async (TP_CHANNEL (user_data), NULL, NULL); + g_object_unref (connection); +} + +static void +handle_method_call ( + GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + if (tp_strdiff (method_name, "Add")) + { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method '%s' on interface " EXAMPLE_INTERFACE, + method_name); + } + else if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ii)"))) + { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "Add takes two int32 parameters, not %s", + g_variant_get_type_string (parameters)); + } + else /* hooray! */ + { + guint x, y; + gboolean ret; + + g_variant_get (parameters, "(ii)", &x, &y); + + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", x + y)); + + ret = g_dbus_connection_emit_signal (connection, + NULL, object_path, interface_name, "LuckyNumber", + g_variant_new ("(u)", g_random_int ()), + NULL); + /* "This can only fail if 'parameters' is not compatible with the D-Bus + * protocol." + */ + g_return_if_fail (ret); + + g_dbus_connection_flush_sync (connection, NULL, NULL); + g_dbus_connection_close (connection, NULL, connection_closed_cb, user_data); + } +} + +static void +register_object (GDBusConnection *connection, + TpDBusTubeChannel *channel) +{ + GDBusNodeInfo *introspection_data; + guint registration_id; + static const GDBusInterfaceVTable interface_vtable = + { + handle_method_call, + NULL, + NULL, + }; + static const gchar introspection_xml[] = + "<node>" + " <interface name='" EXAMPLE_INTERFACE "'>" + " <method name='Add'>" + " <arg type='i' name='x' direction='in'/>" + " <arg type='i' name='y' direction='in'/>" + " <arg type='i' name='result' direction='out'/>" + " </method>" + " <signal name='LuckyNumber'>" + " <arg type='u' name='number'/>" + " </signal>" + " </interface>" + "</node>"; + + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + registration_id = g_dbus_connection_register_object (connection, + EXAMPLE_PATH, introspection_data->interfaces[0], + &interface_vtable, g_object_ref (channel), g_object_unref, NULL); + g_assert (registration_id > 0); + + g_dbus_node_info_unref (introspection_data); +} + +static void +tube_offered (GObject *tube, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + GDBusConnection *conn; + + conn = tp_dbus_tube_channel_offer_finish (TP_DBUS_TUBE_CHANNEL (tube), res, + &error); + if (conn == NULL) + { + g_debug ("Failed to offer tube: %s", error->message); + g_error_free (error); + tp_channel_close_async (TP_CHANNEL (tube), NULL, NULL); + return; + } + + g_debug ("Tube opened"); + register_object (conn, TP_DBUS_TUBE_CHANNEL (tube)); +} + +static void +tube_invalidated_cb (TpStreamTubeChannel *tube, + guint domain, + gint code, + gchar *message, + gpointer user_data) +{ + g_debug ("Tube has been invalidated: %s", message); + g_main_loop_quit (loop); + g_object_unref (tube); +} + +static void +channel_created (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + TpChannel *channel; + GError *error = NULL; + TpDBusTubeChannel *tube; + + channel = tp_account_channel_request_create_and_handle_channel_finish ( + TP_ACCOUNT_CHANNEL_REQUEST (source), result, NULL, &error); + if (channel == NULL) + { + g_debug ("Failed to create channel: %s", error->message); + g_error_free (error); + g_main_loop_quit (loop); + return; + } + + g_debug ("Channel created: %s", tp_proxy_get_object_path (channel)); + + tube = TP_DBUS_TUBE_CHANNEL (channel); + + g_signal_connect (tube, "invalidated", + G_CALLBACK (tube_invalidated_cb), NULL); + + tp_dbus_tube_channel_offer_async (tube, NULL, tube_offered, NULL); +} + +int +main (int argc, + const char **argv) +{ + TpDBusDaemon *dbus; + TpAccount *account; + char *account_path; + GError *error = NULL; + TpAccountChannelRequest *req; + GHashTable *request; + + g_type_init (); + + if (argc != 3) + g_error ("Usage: offerer gabble/jabber/ladygaga t-pain@example.com"); + + dbus = tp_dbus_daemon_dup (&error); + g_assert_no_error (error); + + account_path = g_strconcat (TP_ACCOUNT_OBJECT_PATH_BASE, argv[1], NULL); + account = tp_account_new (dbus, account_path, &error); + g_assert_no_error (error); + g_free (account_path); + + request = 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, + argv[2], + + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME, + G_TYPE_STRING, + EXAMPLE_SERVICE_NAME, + + NULL); + + g_debug ("Offer channel to %s", argv[2]); + + req = tp_account_channel_request_new (account, request, + TP_USER_ACTION_TIME_CURRENT_TIME); + + tp_account_channel_request_create_and_handle_channel_async (req, NULL, + channel_created, NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_object_unref (account); + g_object_unref (req); + g_hash_table_unref (request); + g_main_loop_unref (loop); + + return 0; +} diff --git a/examples/client/stream-tubes/Makefile.am b/examples/client/stream-tubes/Makefile.am index 1f687f360..fb2259d6b 100644 --- a/examples/client/stream-tubes/Makefile.am +++ b/examples/client/stream-tubes/Makefile.am @@ -9,9 +9,9 @@ accepter_SOURCES = accepter.c # In an external project you'd use $(TP_GLIB_LIBS) (obtained from # pkg-config via autoconf) instead of the .la path LDADD = \ + $(top_builddir)/telepathy-glib/libtelepathy-glib.la \ @DBUS_LIBS@ \ - @GLIB_LIBS@ \ - $(top_builddir)/telepathy-glib/libtelepathy-glib.la + @GLIB_LIBS@ AM_CFLAGS = \ $(ERROR_CFLAGS) \ diff --git a/telepathy-glib/automatic-client-factory.c b/telepathy-glib/automatic-client-factory.c index ebab26529..b2cc10ab3 100644 --- a/telepathy-glib/automatic-client-factory.c +++ b/telepathy-glib/automatic-client-factory.c @@ -81,6 +81,10 @@ * <para>%TP_CALL_CHANNEL_FEATURE_CORE * for #TpCallChannel</para> * </listitem> + * <listitem> + * <para>%TP_DBUS_TUBE_CHANNEL_FEATURE_CORE + * for #TpDBusTubeChannel</para> + * </listitem> * </itemizedlist> * * Since: 0.15.5 @@ -120,6 +124,94 @@ G_DEFINE_TYPE (TpAutomaticClientFactory, tp_automatic_client_factory, #define chainup ((TpSimpleClientFactoryClass *) \ tp_automatic_client_factory_parent_class) +typedef gboolean (*CheckPropertiesFunc) ( + const gchar *object_path, + const GHashTable *properties); + +typedef TpChannel *(*NewFunc) ( + TpSimpleClientFactory *client, + TpConnection *conn, + const gchar *object_path, + const GHashTable *properties, + GError **error); + +typedef struct { + const gchar *channel_type; + GType gtype; + CheckPropertiesFunc check_properties; + NewFunc new_func; + /* 0-terminated. All of a sudden, 3 is not such a scary number. */ + GQuark features[3]; +} ChannelTypeMapping; + +static ChannelTypeMapping *channel_type_mapping = NULL; + +static gboolean +check_for_messages ( + const gchar *object_path, + const GHashTable *properties) +{ + /* Create a TpTextChannel only if the channel supports Messages */ + const gchar * const * interfaces; + + interfaces = tp_asv_get_strv (properties, TP_PROP_CHANNEL_INTERFACES); + + if (!tp_strv_contains (interfaces, TP_IFACE_CHANNEL_INTERFACE_MESSAGES)) + { + DEBUG ("channel %s doesn't implement Messages so we can't create " + "a TpTextChannel", object_path); + return FALSE; + } + + return TRUE; +} +static void +build_channel_type_mapping (void) +{ + ChannelTypeMapping i_hate_c[] = { + { TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, + TP_TYPE_STREAM_TUBE_CHANNEL, + NULL, + (NewFunc) _tp_stream_tube_channel_new_with_factory, + { 0 }, + }, + { TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, + TP_TYPE_DBUS_TUBE_CHANNEL, + NULL, + (NewFunc) _tp_dbus_tube_channel_new_with_factory, + { TP_DBUS_TUBE_CHANNEL_FEATURE_CORE, + 0 }, + }, + { TP_IFACE_CHANNEL_TYPE_TEXT, + TP_TYPE_TEXT_CHANNEL, + check_for_messages, + (NewFunc) _tp_text_channel_new_with_factory, + { TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES, + TP_TEXT_CHANNEL_FEATURE_SMS, + 0 }, + }, + { TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, + TP_TYPE_FILE_TRANSFER_CHANNEL, + NULL, + (NewFunc) _tp_file_transfer_channel_new_with_factory, + { TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE, + 0 }, + }, + { TP_IFACE_CHANNEL_TYPE_CALL, + TP_TYPE_CALL_CHANNEL, + NULL, + (NewFunc) _tp_call_channel_new_with_factory, + { TP_CALL_CHANNEL_FEATURE_CORE, + 0 }, + }, + { NULL } + }; + + g_return_if_fail (channel_type_mapping == NULL); + + channel_type_mapping = g_memdup (i_hate_c, sizeof i_hate_c); +} + static TpChannel * create_channel_impl (TpSimpleClientFactory *self, TpConnection *conn, @@ -128,44 +220,20 @@ create_channel_impl (TpSimpleClientFactory *self, GError **error) { const gchar *chan_type; + ChannelTypeMapping *m; chan_type = tp_asv_get_string (properties, TP_PROP_CHANNEL_CHANNEL_TYPE); - if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE)) - { - return (TpChannel *) _tp_stream_tube_channel_new_with_factory (self, conn, - object_path, properties, error); - } - else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE)) - { - return (TpChannel *) _tp_dbus_tube_channel_new_with_factory (self, conn, - object_path, properties, error); - } - else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT)) + for (m = channel_type_mapping; m->channel_type != NULL; m++) { - const gchar * const * interfaces; + if (tp_strdiff (chan_type, m->channel_type)) + continue; - interfaces = tp_asv_get_strv (properties, TP_PROP_CHANNEL_INTERFACES); + if (m->check_properties != NULL && + !m->check_properties (object_path, properties)) + break; - /* Create a TpTextChannel only if the channel supports Messages */ - if (tp_strv_contains (interfaces, TP_IFACE_CHANNEL_INTERFACE_MESSAGES)) - { - return (TpChannel *) _tp_text_channel_new_with_factory (self, conn, - object_path, properties, error); - } - - DEBUG ("channel %s doesn't implement Messages so we can't create " - "a TpTextChannel", object_path); - } - else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER)) - { - return (TpChannel *) _tp_file_transfer_channel_new_with_factory (self, - conn, object_path, properties, error); - } - else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_CALL)) - { - return (TpChannel *) _tp_call_channel_new_with_factory (self, - conn, object_path, properties, error); + return m->new_func (self, conn, object_path, properties, error); } /* Chainup on parent implementation as fallback */ @@ -177,34 +245,26 @@ dup_channel_features_impl (TpSimpleClientFactory *self, TpChannel *channel) { GArray *features; - GQuark feature; + GQuark standard_features[] = { + TP_CHANNEL_FEATURE_GROUP, + TP_CHANNEL_FEATURE_PASSWORD, + }; + ChannelTypeMapping *m; /* Chainup to get desired features for all channel types */ features = chainup->dup_channel_features (self, channel); - feature = TP_CHANNEL_FEATURE_GROUP; - g_array_append_val (features, feature); - - feature = TP_CHANNEL_FEATURE_PASSWORD; - g_array_append_val (features, feature); - - if (TP_IS_TEXT_CHANNEL (channel)) - { - feature = TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES; - g_array_append_val (features, feature); + g_array_append_vals (features, standard_features, G_N_ELEMENTS (standard_features)); - feature = TP_TEXT_CHANNEL_FEATURE_SMS; - g_array_append_val (features, feature); - } - else if (TP_IS_FILE_TRANSFER_CHANNEL (channel)) - { - feature = TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE; - g_array_append_val (features, feature); - } - else if (TP_IS_CALL_CHANNEL (channel)) + for (m = channel_type_mapping; m->channel_type != NULL; m++) { - feature = TP_CALL_CHANNEL_FEATURE_CORE; - g_array_append_val (features, feature); + if (G_TYPE_CHECK_INSTANCE_TYPE (channel, m->gtype)) + { + guint j; + for (j = 0; m->features[j] != 0; j++) + g_array_append_val (features, m->features[j]); + break; + } } return features; @@ -222,6 +282,8 @@ tp_automatic_client_factory_class_init (TpAutomaticClientFactoryClass *cls) simple_class->create_channel = create_channel_impl; simple_class->dup_channel_features = dup_channel_features_impl; + + build_channel_type_mapping (); } /** diff --git a/telepathy-glib/dbus-tube-channel.c b/telepathy-glib/dbus-tube-channel.c index 08748676f..998cb9957 100644 --- a/telepathy-glib/dbus-tube-channel.c +++ b/telepathy-glib/dbus-tube-channel.c @@ -21,10 +21,44 @@ /** * SECTION:dbus-tube-channel * @title: TpDBusTubeChannel - * @short_description: proxy object for a dbus tube channel + * @short_description: proxy object for D-Bus tube channels * - * #TpDBusTubeChannel is a sub-class of #TpChannel providing convenient API - * to offer and accept a dbus tube. + * #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 <ulink + * url="http://cgit.freedesktop.org/telepathy/telepathy-glib/tree/examples/client/dbus-tubes/">examples/client/dbus-tubes</ulink> + * directory. * * Since: 0.15.6 */ @@ -71,6 +105,10 @@ G_DEFINE_TYPE (TpDBusTubeChannel, tp_dbus_tube_channel, TP_TYPE_CHANNEL) struct _TpDBusTubeChannelPrivate { GHashTable *parameters; + TpTubeChannelState state; + + GSimpleAsyncResult *result; + gchar *address; }; enum @@ -85,6 +123,9 @@ 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); } @@ -115,6 +156,102 @@ tp_dbus_tube_channel_get_property (GObject *object, } 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; @@ -175,6 +312,85 @@ tp_dbus_tube_channel_constructed (GObject *obj) 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 @@ -182,11 +398,14 @@ 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: * @@ -290,3 +509,256 @@ tp_dbus_tube_channel_get_parameters (TpDBusTubeChannel *self) { return 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.UNRELEASED + */ +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_ERRORS, + 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"); + + /* 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.UNRELEASED + */ +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.UNRELEASED + */ +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_ERRORS, + 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.UNRELEASED + */ +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.UNRELEASED + */ +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) +} diff --git a/telepathy-glib/dbus-tube-channel.h b/telepathy-glib/dbus-tube-channel.h index 260bdf066..28b81c1fc 100644 --- a/telepathy-glib/dbus-tube-channel.h +++ b/telepathy-glib/dbus-tube-channel.h @@ -50,12 +50,37 @@ struct _TpDBusTubeChannelClass GCallback _padding[7]; }; +#define TP_DBUS_TUBE_CHANNEL_FEATURE_CORE \ + tp_dbus_tube_channel_feature_quark_core () +GQuark tp_dbus_tube_channel_feature_quark_core (void) G_GNUC_CONST; + GType tp_dbus_tube_channel_get_type (void); const gchar * tp_dbus_tube_channel_get_service_name (TpDBusTubeChannel *self); GHashTable * tp_dbus_tube_channel_get_parameters (TpDBusTubeChannel *self); +/* Outgoing tube methods */ + +void tp_dbus_tube_channel_offer_async (TpDBusTubeChannel *self, + GHashTable *params, + GAsyncReadyCallback callback, + gpointer user_data); + +GDBusConnection * tp_dbus_tube_channel_offer_finish (TpDBusTubeChannel *self, + GAsyncResult *result, + GError **error) G_GNUC_WARN_UNUSED_RESULT; + +/* Incoming tube methods */ + +void tp_dbus_tube_channel_accept_async (TpDBusTubeChannel *self, + GAsyncReadyCallback callback, + gpointer user_data); + +GDBusConnection * tp_dbus_tube_channel_accept_finish (TpDBusTubeChannel *self, + GAsyncResult *result, + GError **error) G_GNUC_WARN_UNUSED_RESULT; + G_END_DECLS #endif diff --git a/telepathy-glib/proxy.c b/telepathy-glib/proxy.c index 60a0b4946..4e8e839b0 100644 --- a/telepathy-glib/proxy.c +++ b/telepathy-glib/proxy.c @@ -567,7 +567,7 @@ static void tp_proxy_poll_features (TpProxy *self, const GError *error); static gboolean tp_proxy_emit_invalidated (gpointer p) { - TpProxy *self = p; + TpProxy *self = TP_PROXY (p); g_signal_emit (self, signals[SIGNAL_INVALIDATED], 0, self->invalidated->domain, self->invalidated->code, @@ -1417,7 +1417,7 @@ void _tp_proxy_ensure_factory (gpointer proxy, TpSimpleClientFactory *factory) { - TpProxy *self = proxy; + TpProxy *self = TP_PROXY (proxy); if (self->priv->factory != NULL) return; @@ -1823,7 +1823,7 @@ depends_prepare_cb (GObject *source, GAsyncResult *result, gpointer user_data) { - TpProxy *self = (TpProxy *) source; + TpProxy *self = TP_PROXY (source); tp_proxy_poll_features (self, NULL); } @@ -2013,7 +2013,7 @@ feature_prepared_cb (GObject *source, GAsyncResult *result, gpointer user_data) { - TpProxy *self = (TpProxy *) source; + TpProxy *self = TP_PROXY (source); TpProxyFeature *feature = user_data; GError *error = NULL; gboolean prepared = TRUE; @@ -2304,7 +2304,7 @@ prepare_before_signalling_connected_cb (GObject *source, GAsyncResult *result, gpointer user_data) { - TpProxy *self = user_data; + TpProxy *self = TP_PROXY (user_data); /* We don't care if the call succeeded or not as it was already prepared */ self->priv->pending_will_announce_calls--; diff --git a/tests/dbus/dbus-tube.c b/tests/dbus/dbus-tube.c index fe8216df4..71f47849a 100644 --- a/tests/dbus/dbus-tube.c +++ b/tests/dbus/dbus-tube.c @@ -34,6 +34,10 @@ typedef struct { TpConnection *connection; TpDBusTubeChannel *tube; + GDBusConnection *tube_conn; + GDBusConnection *cm_conn; + GVariant *call_result; + GError *error /* initialized where needed */; gint wait; } Test; @@ -68,6 +72,10 @@ teardown (Test *test, tp_tests_connection_assert_disconnect_succeeds (test->connection); g_object_unref (test->connection); g_object_unref (test->base_connection); + + g_clear_object (&test->tube_conn); + g_clear_object (&test->cm_conn); + tp_clear_pointer (&test->call_result, g_variant_unref); } static void @@ -206,6 +214,249 @@ test_properties (Test *test, g_hash_table_unref (parameters); } +static void +tube_offer_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + Test *test = user_data; + + g_clear_object (&test->tube_conn); + + test->tube_conn = tp_dbus_tube_channel_offer_finish ( + TP_DBUS_TUBE_CHANNEL (source), result, &test->error); + + test->wait--; + if (test->wait <= 0) + g_main_loop_quit (test->mainloop); +} + +static gboolean +new_connection_cb (TpTestsDBusTubeChannel *chan, + GDBusConnection *connection, + Test *test) +{ + g_clear_object (&test->cm_conn); + test->cm_conn = g_object_ref (connection); + + test->wait--; + if (test->wait <= 0) + g_main_loop_quit (test->mainloop); + + return TRUE; +} + +static void +handle_double_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + if (!tp_strdiff (method_name, "Double")) + { + guint value; + + g_variant_get (parameters, "(i)", &value); + + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", value * 2)); + } +} + +static void +register_object (GDBusConnection *connection) +{ + GDBusNodeInfo *introspection_data; + guint registration_id; + static const GDBusInterfaceVTable interface_vtable = + { + handle_double_call, + NULL, + NULL, + }; + static const gchar introspection_xml[] = + "<node>" + " <interface name='org.Example.TestInterface'>" + " <method name='Double'>" + " <arg type='i' name='value' direction='in'/>" + " <arg type='i' name='result' direction='out'/>" + " </method>" + " </interface>" + "</node>"; + + introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (introspection_data != NULL); + + registration_id = g_dbus_connection_register_object (connection, + "/org/Example/TestObject", introspection_data->interfaces[0], + &interface_vtable, NULL, NULL, NULL); + g_assert (registration_id > 0); + + g_dbus_node_info_unref (introspection_data); +} + +static void +double_call_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + Test *test = user_data; + + tp_clear_pointer (&test->call_result, g_variant_unref); + + test->call_result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), + result, &test->error); + + test->wait--; + if (test->wait <= 0) + g_main_loop_quit (test->mainloop); +} + +static void +use_tube (Test *test, + GDBusConnection *server_conn, + GDBusConnection *client_conn) +{ + gint result; + + /* Server publishes an object on the tube */ + register_object (server_conn); + + /* Client calls a remote method */ + g_dbus_connection_call (client_conn, NULL, "/org/Example/TestObject", + "org.Example.TestInterface", "Double", + g_variant_new ("(i)", 42), + G_VARIANT_TYPE ("(i)"), G_DBUS_CALL_FLAGS_NONE, -1, + NULL, double_call_cb, test); + + test->wait = 1; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); + + g_variant_get (test->call_result, "(i)", &result); + g_assert_cmpuint (result, ==, 42 * 2); +} + +static void +test_offer (Test *test, + gconstpointer data) +{ + const TpTestsDBusTubeChannelOpenMode open_mode = GPOINTER_TO_UINT (data); + GHashTable *params; + + /* Outgoing tube */ + create_tube_service (test, TRUE, TRUE); + tp_tests_dbus_tube_channel_set_open_mode (test->tube_chan_service, open_mode); + + params = tp_asv_new ("badger", G_TYPE_UINT, 42, NULL); + + g_signal_connect (test->tube_chan_service, "new-connection", + G_CALLBACK (new_connection_cb), test); + + tp_dbus_tube_channel_offer_async (test->tube, params, tube_offer_cb, test); + + test->wait = 2; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); + + check_parameters (tp_dbus_tube_channel_get_parameters (test->tube)); + + g_assert (G_IS_DBUS_CONNECTION (test->tube_conn)); + g_assert (G_IS_DBUS_CONNECTION (test->cm_conn)); + + use_tube (test, test->tube_conn, test->cm_conn); +} + +static void +test_offer_invalidated_before_open (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + /* Outgoing tube */ + create_tube_service (test, TRUE, TRUE); + tp_tests_dbus_tube_channel_set_open_mode (test->tube_chan_service, + TP_TESTS_DBUS_TUBE_CHANNEL_NEVER_OPEN); + + tp_dbus_tube_channel_offer_async (test->tube, NULL, tube_offer_cb, test); + + test->wait = 1; + g_main_loop_run (test->mainloop); + /* FIXME: this isn't a particularly good error… it's just what comes out when + * the channel gets closed from under us, and there isn't really API on + * DBusTube to give a better error. + * + * https://bugs.freedesktop.org/show_bug.cgi?id=48196 + */ + g_assert_error (test->error, TP_DBUS_ERRORS, TP_DBUS_ERROR_OBJECT_REMOVED); +} + +static void +tube_accept_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + Test *test = user_data; + + g_clear_object (&test->tube_conn); + + test->tube_conn = tp_dbus_tube_channel_accept_finish ( + TP_DBUS_TUBE_CHANNEL (source), result, &test->error); + + test->wait--; + if (test->wait <= 0) + g_main_loop_quit (test->mainloop); +} + +static void +test_accept (Test *test, + gconstpointer data) +{ + const TpTestsDBusTubeChannelOpenMode open_mode = GPOINTER_TO_UINT (data); + + /* Incoming tube */ + create_tube_service (test, FALSE, TRUE); + tp_tests_dbus_tube_channel_set_open_mode (test->tube_chan_service, open_mode); + + g_signal_connect (test->tube_chan_service, "new-connection", + G_CALLBACK (new_connection_cb), test); + + tp_dbus_tube_channel_accept_async (test->tube, tube_accept_cb, test); + + test->wait = 2; + g_main_loop_run (test->mainloop); + g_assert_no_error (test->error); + + g_assert (G_IS_DBUS_CONNECTION (test->tube_conn)); + g_assert (G_IS_DBUS_CONNECTION (test->cm_conn)); + + use_tube (test, test->cm_conn, test->tube_conn); +} + +static void +test_accept_invalidated_before_open (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + /* Incoming tube */ + create_tube_service (test, FALSE, TRUE); + tp_tests_dbus_tube_channel_set_open_mode (test->tube_chan_service, + TP_TESTS_DBUS_TUBE_CHANNEL_NEVER_OPEN); + + tp_dbus_tube_channel_accept_async (test->tube, tube_accept_cb, test); + + test->wait = 1; + g_main_loop_run (test->mainloop); + /* FIXME: this isn't a particularly good error… it's just what comes out when + * the channel gets closed from under us, and there isn't really API on + * DBusTube to give a better error. + * + * https://bugs.freedesktop.org/show_bug.cgi?id=48196 + */ + g_assert_error (test->error, TP_DBUS_ERRORS, TP_DBUS_ERROR_OBJECT_REMOVED); +} + int main (int argc, char **argv) @@ -217,6 +468,23 @@ main (int argc, teardown); g_test_add ("/dbus-tube/properties", Test, NULL, setup, test_properties, teardown); + /* Han shot first. */ + g_test_add ("/dbus-tube/offer-open-first", Test, + GUINT_TO_POINTER (TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_FIRST), + setup, test_offer, teardown); + g_test_add ("/dbus-tube/offer-open-second", Test, + GUINT_TO_POINTER (TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_SECOND), + setup, test_offer, teardown); + g_test_add ("/dbus-tube/offer-invalidated-before-open", Test, NULL, + setup, test_offer_invalidated_before_open, teardown); + g_test_add ("/dbus-tube/accept-open-first", Test, + GUINT_TO_POINTER (TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_FIRST), + setup, test_accept, teardown); + g_test_add ("/dbus-tube/accept-open-second", Test, + GUINT_TO_POINTER (TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_SECOND), + setup, test_accept, teardown); + g_test_add ("/dbus-tube/accept-invalidated-before-open", Test, NULL, + setup, test_accept_invalidated_before_open, teardown); return g_test_run (); } diff --git a/tests/lib/dbus-tube-chan.c b/tests/lib/dbus-tube-chan.c index 15217a9cd..7c615c86e 100644 --- a/tests/lib/dbus-tube-chan.c +++ b/tests/lib/dbus-tube-chan.c @@ -27,11 +27,25 @@ enum PROP_STATE, }; +enum +{ + SIG_NEW_CONNECTION, + LAST_SIGNAL +}; + +static guint _signals[LAST_SIGNAL] = { 0, }; + struct _TpTestsDBusTubeChannelPrivate { + /* Controls whether the channel should become open before returning from + * Open/Accept, after returning, or never. + */ + TpTestsDBusTubeChannelOpenMode open_mode; TpTubeChannelState state; /* TpHandle -> gchar * */ GHashTable *dbus_names; + + GDBusServer *dbus_server; }; static void @@ -108,6 +122,7 @@ tp_tests_dbus_tube_channel_init (TpTestsDBusTubeChannel *self) self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TESTS_TYPE_DBUS_TUBE_CHANNEL, TpTestsDBusTubeChannelPrivate); + self->priv->open_mode = TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_FIRST; self->priv->dbus_names = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); } @@ -139,6 +154,20 @@ dispose (GObject *object) tp_clear_pointer (&self->priv->dbus_names, g_hash_table_unref); + if (self->priv->dbus_server != NULL) + { + /* FIXME: this is pretty stupid but apparently unless you start and then + * stop the server before freeing it, it doesn't stop listening. Calling + * _start() twice is a no-op. + * + * https://bugzilla.gnome.org/show_bug.cgi?id=673372 + */ + g_dbus_server_start (self->priv->dbus_server); + + g_dbus_server_stop (self->priv->dbus_server); + g_clear_object (&self->priv->dbus_server); + } + ((GObjectClass *) tp_tests_dbus_tube_channel_parent_class)->dispose ( object); } @@ -242,6 +271,15 @@ tp_tests_dbus_tube_channel_class_init (TpTestsDBusTubeChannelClass *klass) g_object_class_install_property (object_class, PROP_STATE, param_spec); + _signals[SIG_NEW_CONNECTION] = g_signal_new ("new-connection", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, NULL, + NULL, + G_TYPE_BOOLEAN, + 1, G_TYPE_DBUS_CONNECTION); + tp_dbus_properties_mixin_implement_interface (object_class, TP_IFACE_QUARK_CHANNEL_TYPE_DBUS_TUBE, tp_dbus_properties_mixin_getter_gobject_properties, NULL, @@ -256,7 +294,6 @@ tp_tests_dbus_tube_channel_class_init (TpTestsDBusTubeChannelClass *klass) sizeof (TpTestsDBusTubeChannelPrivate)); } -#if 0 static void change_state (TpTestsDBusTubeChannel *self, TpTubeChannelState state) @@ -265,19 +302,107 @@ change_state (TpTestsDBusTubeChannel *self, tp_svc_channel_interface_tube_emit_tube_channel_state_changed (self, state); } -#endif + +static gboolean +dbus_new_connection_cb (GDBusServer *server, + GDBusConnection *connection, + gpointer user_data) +{ + TpTestsDBusTubeChannel *self = user_data; + gboolean ret = FALSE; + + g_signal_emit (self, _signals[SIG_NEW_CONNECTION], 0, connection, &ret); + return ret; +} + +static void +open_tube (TpTestsDBusTubeChannel *self) +{ + GError *error = NULL; + gchar *guid; + + guid = g_dbus_generate_guid (); + + self->priv->dbus_server = g_dbus_server_new_sync ( + "unix:abstract=dbus-tube-test", + G_DBUS_SERVER_FLAGS_NONE, guid, NULL, NULL, &error); + g_assert_no_error (error); + + g_free (guid); + + g_signal_connect (self->priv->dbus_server, "new-connection", + G_CALLBACK (dbus_new_connection_cb), self); +} + +static void +really_open_tube (TpTestsDBusTubeChannel *self) +{ + g_dbus_server_start (self->priv->dbus_server); + + change_state (self, TP_TUBE_CHANNEL_STATE_OPEN); +} + +static void +dbus_tube_offer (TpSvcChannelTypeDBusTube *chan, + GHashTable *parameters, + guint access_control, + DBusGMethodInvocation *context) +{ + TpTestsDBusTubeChannel *self = (TpTestsDBusTubeChannel *) chan; + + open_tube (self); + + if (self->priv->open_mode == TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_FIRST) + really_open_tube (self); + + tp_svc_channel_type_dbus_tube_return_from_offer (context, + g_dbus_server_get_client_address (self->priv->dbus_server)); + + if (self->priv->open_mode == TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_SECOND) + really_open_tube (self); + else if (self->priv->open_mode == TP_TESTS_DBUS_TUBE_CHANNEL_NEVER_OPEN) + tp_base_channel_close (TP_BASE_CHANNEL (self)); +} + +static void +dbus_tube_accept (TpSvcChannelTypeDBusTube *chan, + guint access_control, + DBusGMethodInvocation *context) +{ + TpTestsDBusTubeChannel *self = (TpTestsDBusTubeChannel *) chan; + + open_tube (self); + + if (self->priv->open_mode == TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_FIRST) + really_open_tube (self); + + tp_svc_channel_type_dbus_tube_return_from_accept (context, + g_dbus_server_get_client_address (self->priv->dbus_server)); + + if (self->priv->open_mode == TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_SECOND) + really_open_tube (self); + else if (self->priv->open_mode == TP_TESTS_DBUS_TUBE_CHANNEL_NEVER_OPEN) + tp_base_channel_close (TP_BASE_CHANNEL (self)); +} + +void +tp_tests_dbus_tube_channel_set_open_mode ( + TpTestsDBusTubeChannel *self, + TpTestsDBusTubeChannelOpenMode open_mode) +{ + self->priv->open_mode = open_mode; +} static void dbus_tube_iface_init (gpointer iface, gpointer data) { -#if 0 - /* TODO: implement methods */ TpSvcChannelTypeDBusTubeClass *klass = iface; #define IMPLEMENT(x) tp_svc_channel_type_dbus_tube_implement_##x (klass, dbus_tube_##x) + IMPLEMENT (offer); + IMPLEMENT (accept); #undef IMPLEMENT -#endif } /* Contact DBus Tube */ diff --git a/tests/lib/dbus-tube-chan.h b/tests/lib/dbus-tube-chan.h index 479f1d16d..30d496cba 100644 --- a/tests/lib/dbus-tube-chan.h +++ b/tests/lib/dbus-tube-chan.h @@ -54,6 +54,16 @@ struct _TpTestsDBusTubeChannel { TpTestsDBusTubeChannelPrivate *priv; }; +typedef enum { + TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_FIRST, + TP_TESTS_DBUS_TUBE_CHANNEL_OPEN_SECOND, + TP_TESTS_DBUS_TUBE_CHANNEL_NEVER_OPEN +} TpTestsDBusTubeChannelOpenMode; + +void tp_tests_dbus_tube_channel_set_open_mode ( + TpTestsDBusTubeChannel *self, + TpTestsDBusTubeChannelOpenMode open_mode); + /* Contact DBus Tube */ typedef struct _TpTestsContactDBusTubeChannel TpTestsContactDBusTubeChannel; |