summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2012-04-02 16:46:25 +0100
committerWill Thompson <will.thompson@collabora.co.uk>2012-04-02 16:48:03 +0100
commitdcab14f903605fc2f0c162e45b1c322966eb46c2 (patch)
treea241e53feceda2f927bf1b3061575e9a8002d7a5
parent6275753ca716891e5865d5ea7ce5be94b096d34a (diff)
parent126261cf295622c659238bf51d85d8e39c8eaf2c (diff)
downloadtelepathy-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--.gitignore2
-rw-r--r--configure.ac1
-rw-r--r--docs/reference/telepathy-glib-sections.txt6
-rw-r--r--examples/client/Makefile.am2
-rw-r--r--examples/client/dbus-tubes/Makefile.am23
-rw-r--r--examples/client/dbus-tubes/accepter.c220
-rw-r--r--examples/client/dbus-tubes/constants.h9
-rw-r--r--examples/client/dbus-tubes/offerer.c238
-rw-r--r--examples/client/stream-tubes/Makefile.am4
-rw-r--r--telepathy-glib/automatic-client-factory.c170
-rw-r--r--telepathy-glib/dbus-tube-channel.c478
-rw-r--r--telepathy-glib/dbus-tube-channel.h25
-rw-r--r--telepathy-glib/proxy.c10
-rw-r--r--tests/dbus/dbus-tube.c268
-rw-r--r--tests/lib/dbus-tube-chan.c135
-rw-r--r--tests/lib/dbus-tube-chan.h10
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 (&params, 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;