summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Desmottes <guillaume.desmottes@collabora.co.uk>2010-05-25 12:36:19 +0200
committerGuillaume Desmottes <guillaume.desmottes@collabora.co.uk>2010-05-25 12:36:19 +0200
commit632d29892b3ef7acd7ff9b303a9cce9804378b22 (patch)
treeb247b5fa7baf31a8cb74daf3005a1645196d0fb7
parent3f0783b15eb48c50b3760bec4caceea780262468 (diff)
parent7f232db322e5128944d9d8a21df3c5ec6ec8a971 (diff)
Merge branch 'base-handler-27872'
-rw-r--r--docs/reference/Makefile.am1
-rw-r--r--docs/reference/telepathy-glib-docs.sgml1
-rw-r--r--docs/reference/telepathy-glib-sections.txt32
-rw-r--r--telepathy-glib/Makefile.am3
-rw-r--r--telepathy-glib/add-dispatch-operation-context.c2
-rw-r--r--telepathy-glib/base-client.c954
-rw-r--r--telepathy-glib/base-client.h43
-rw-r--r--telepathy-glib/handle-channels-context-internal.h78
-rw-r--r--telepathy-glib/handle-channels-context.c695
-rw-r--r--telepathy-glib/handle-channels-context.h65
-rw-r--r--telepathy-glib/introspection.am1
-rw-r--r--telepathy-glib/signals-marshal.list3
-rw-r--r--tests/dbus/base-client.c414
-rw-r--r--tests/dbus/channel-dispatch-operation.c10
-rw-r--r--tests/lib/simple-client.c54
-rw-r--r--tests/lib/simple-client.h1
-rw-r--r--tests/lib/textchan-null.c25
-rw-r--r--tests/lib/textchan-null.h2
18 files changed, 2362 insertions, 22 deletions
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index a0601b8ff..ba1cd2666 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -68,6 +68,7 @@ IGNORE_HFILES=\
channel-internal.h \
connection-internal.h \
debug-internal.h \
+ handle-channels-context-internal.h \
handle-repo-internal.h \
observe-channels-context-internal.h \
proxy-internal.h \
diff --git a/docs/reference/telepathy-glib-docs.sgml b/docs/reference/telepathy-glib-docs.sgml
index 46002b189..32a4a2355 100644
--- a/docs/reference/telepathy-glib-docs.sgml
+++ b/docs/reference/telepathy-glib-docs.sgml
@@ -100,6 +100,7 @@
<xi:include href="xml/base-client.xml"/>
<xi:include href="xml/observe-channels-context.xml"/>
<xi:include href="xml/add-dispatch-operation-context.xml"/>
+ <xi:include href="xml/handle-channels-context.xml"/>
<xi:include href="xml/simple-observer.xml"/>
<xi:include href="xml/simple-approver.xml"/>
</chapter>
diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt
index bd02621a0..6dce81021 100644
--- a/docs/reference/telepathy-glib-sections.txt
+++ b/docs/reference/telepathy-glib-sections.txt
@@ -3774,7 +3774,20 @@ tp_base_client_add_approver_filter
tp_base_client_take_approver_filter
TpBaseClientClassAddDispatchOperationImpl
tp_base_client_implement_add_dispatch_operation
+tp_base_client_add_handler_capabilities
+tp_base_client_add_handler_capabilities_varargs
+tp_base_client_add_handler_capability
+tp_base_client_add_handler_filter
+tp_base_client_be_a_handler
+TpBaseClientClassHandleChannelsImpl
+tp_base_client_implement_handle_channels
+tp_base_client_get_handled_channels
+tp_base_client_get_pending_requests
+tp_base_client_set_handler_bypass_approval
+tp_base_client_set_handler_request_notification
+tp_base_client_take_handler_filter
tp_base_client_register
+tp_base_client_unregister
tp_base_client_get_bus_name
tp_base_client_get_object_path
<SUBSECTION Standard>
@@ -3869,3 +3882,22 @@ TpSimpleApproverClass
TpSimpleApproverPrivate
</SECTION>
+<SECTION>
+<INCLUDE>telepathy-glib/handle-channels-context.h</INCLUDE>
+<FILE>handle-channels-context</FILE>
+<TITLE>handle-channels-context</TITLE>
+TpHandleChannelsContext
+tp_handle_channels_context_accept
+tp_handle_channels_context_delay
+tp_handle_channels_context_fail
+<SUBSECTION Standard>
+tp_handle_channels_context_get_type
+TP_HANDLE_CHANNELS_CONTEXT
+TP_HANDLE_CHANNELS_CONTEXT_CLASS
+TP_HANDLE_CHANNELS_CONTEXT_GET_CLASS
+TP_IS_HANDLE_CHANNELS_CONTEXT
+TP_IS_HANDLE_CHANNELS_CONTEXT_CLASS
+TpHandleChannelsContextClass
+TpHandleChannelsContextPrivate
+TP_TYPE_HANDLE_CHANNELS_CONTEXT
+</SECTION>
diff --git a/telepathy-glib/Makefile.am b/telepathy-glib/Makefile.am
index b0252b9e0..d1cf82e1d 100644
--- a/telepathy-glib/Makefile.am
+++ b/telepathy-glib/Makefile.am
@@ -56,6 +56,7 @@ our_headers = \
group-mixin.h \
gtypes.h \
handle.h \
+ handle-channels-context.h \
handle-repo.h \
handle-repo-static.h \
handle-repo-dynamic.h \
@@ -150,6 +151,8 @@ libtelepathy_glib_internal_la_SOURCES = \
group-mixin.c \
gtypes.c \
handle.c \
+ handle-channels-context-internal.h \
+ handle-channels-context.c \
handle-repo.c \
handle-repo-dynamic.c \
handle-repo-internal.h \
diff --git a/telepathy-glib/add-dispatch-operation-context.c b/telepathy-glib/add-dispatch-operation-context.c
index f10e04dcf..4461d3673 100644
--- a/telepathy-glib/add-dispatch-operation-context.c
+++ b/telepathy-glib/add-dispatch-operation-context.c
@@ -106,7 +106,7 @@ tp_add_dispatch_operation_context_dispose (GObject *object)
GError error = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
"Disposing the TpAddDispatchOperationContext" };
- g_warning ("Disposing a context in the %s state",
+ WARNING ("Disposing a context in the %s state",
self->priv->state == TP_ADD_DISPATCH_OPERATION_CONTEXT_STATE_NONE ?
"none": "delayed");
diff --git a/telepathy-glib/base-client.c b/telepathy-glib/base-client.c
index 5f05ae863..51af2cc6f 100644
--- a/telepathy-glib/base-client.c
+++ b/telepathy-glib/base-client.c
@@ -99,14 +99,43 @@
* Since: 0.11.5
*/
+/**
+ * TpBaseClientClassHandleChannelsImpl:
+ * @client: a #TpBaseClient instance
+ * @account: a #TpAccount having %TP_ACCOUNT_FEATURE_CORE prepared if possible
+ * @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared
+ * if possible
+ * @channels: (element-type TelepathyGLib.Channel): a #GList of #TpChannel,
+ * all having %TP_CHANNEL_FEATURE_CORE prepared if possible
+ * @requests_satisfied: (element-type TelepathyGLib.ChannelRequest): a #GList of
+ * #TpChannelRequest having their object-path defined but are not guaranteed
+ * to be prepared.
+ * @user_action_time: the time at which user action occurred, or 0 if this
+ * channel is to be handled for some reason not involving user action.
+ * @context: a #TpHandleChannelsContext representing the context of this
+ * D-Bus call
+ *
+ * Signature of the implementation of the HandleChannels method.
+ *
+ * This function must call either tp_handle_channels_context_accept(),
+ * tp_handle_channels_context_delay() or tp_handle_channels_context_fail()
+ * on @context before it returns.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+
#include "telepathy-glib/base-client.h"
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
#include <telepathy-glib/account-manager.h>
#include <telepathy-glib/add-dispatch-operation-context-internal.h>
#include <telepathy-glib/channel-dispatch-operation-internal.h>
#include <telepathy-glib/channel-request.h>
#include <telepathy-glib/channel.h>
#include <telepathy-glib/dbus-internal.h>
+#include <telepathy-glib/handle-channels-context-internal.h>
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/observe-channels-context-internal.h>
#include <telepathy-glib/svc-client.h>
@@ -115,15 +144,19 @@
#define DEBUG_FLAG TP_DEBUG_CLIENT
#include "telepathy-glib/debug-internal.h"
+#include "telepathy-glib/_gen/signals-marshal.h"
struct _TpBaseClientClassPrivate {
/*<private>*/
TpBaseClientClassObserveChannelsImpl observe_channels_impl;
TpBaseClientClassAddDispatchOperationImpl add_dispatch_operation_impl;
+ TpBaseClientClassHandleChannelsImpl handle_channels_impl;
};
static void observer_iface_init (gpointer, gpointer);
static void approver_iface_init (gpointer, gpointer);
+static void handler_iface_init (gpointer, gpointer);
+static void requests_iface_init (gpointer, gpointer);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE(TpBaseClient, tp_base_client, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
@@ -131,6 +164,9 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE(TpBaseClient, tp_base_client, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CLIENT, NULL);
G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CLIENT_OBSERVER, observer_iface_init);
G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CLIENT_APPROVER, approver_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CLIENT_HANDLER, handler_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CLIENT_INTERFACE_REQUESTS,
+ requests_iface_init);
g_type_add_class_private (g_define_type_id, sizeof (
TpBaseClientClassPrivate)))
@@ -141,6 +177,16 @@ enum {
N_PROPS
};
+enum {
+ SIGNAL_REQUEST_ADDED,
+ SIGNAL_REQUEST_REMOVED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+static dbus_int32_t clients_slot = -1;
+
typedef enum {
CLIENT_IS_OBSERVER = 1 << 0,
CLIENT_IS_APPROVER = 1 << 1,
@@ -155,6 +201,8 @@ struct _TpBaseClientPrivate
TpDBusDaemon *dbus;
gchar *name;
gboolean uniquify_name;
+ /* reffed */
+ DBusConnection *libdbus;
gboolean registered;
ClientFlags flags;
@@ -167,6 +215,11 @@ struct _TpBaseClientPrivate
/* array of g_strdup(token), plus NULL included in length */
GPtrArray *handler_caps;
+ GList *pending_requests;
+ /* Channels actually handled by THIS observer.
+ * borrowed path (gchar *) => reffed TpChannel */
+ GHashTable *my_chans;
+
gchar *bus_name;
gchar *object_path;
@@ -345,6 +398,265 @@ tp_base_client_take_approver_filter (TpBaseClient *self,
}
/**
+ * tp_base_client_be_a_handler:
+ * @self: a #TpBaseClient
+ *
+ * Register @self as a ChannelHandler with an empty list of filter.
+ * This is useful if you want to create a client that only handle channels
+ * for which it's the PreferredHandler.
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_be_a_handler (TpBaseClient *self)
+{
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+ g_return_if_fail (cls->priv->handle_channels_impl != NULL);
+
+ self->priv->flags |= CLIENT_IS_HANDLER;
+}
+
+/**
+ * tp_base_client_add_handler_filter:
+ * @self: a #TpBaseClient
+ * @filter: (transfer none) (element-type utf8 GObject.Value):
+ * a %TP_HASH_TYPE_CHANNEL_CLASS
+ *
+ * Register a new channel class as Handler.HandlerChannelFilter.
+ * The @handle_channels virtual method set up using
+ * tp_base_client_implement_handle_channels() will be called whenever
+ * a new channel's properties match the ones in @filter.
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_add_handler_filter (TpBaseClient *self,
+ GHashTable *filter)
+{
+ g_return_if_fail (filter != NULL);
+
+ tp_base_client_take_handler_filter (self,
+ _tp_base_client_copy_filter (filter));
+}
+
+/**
+ * tp_base_client_take_handler_filter: (skip)
+ * @self: a #TpBaseClient
+ * @filter: (transfer full) (element-type utf8 GObject.Value):
+ * a %TP_HASH_TYPE_CHANNEL_CLASS, ownership of which is taken by @self
+ *
+ * The same as tp_base_client_add_handler_filter(), but ownership of @filter
+ * is taken by @self. This makes it convenient to call using tp_asv_new():
+ *
+ * |[
+ * tp_base_client_take_handler_filter (client,
+ * tp_asv_new (
+ * TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ * TP_IFACE_CHANNEL_TYPE_TEXT,
+ * TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ * TP_HANDLE_TYPE_CONTACT,
+ * ...));
+ * ]|
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_take_handler_filter (TpBaseClient *self,
+ GHashTable *filter)
+{
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+ g_return_if_fail (cls->priv->handle_channels_impl != NULL);
+
+ self->priv->flags |= CLIENT_IS_HANDLER;
+ g_ptr_array_add (self->priv->handler_filters, filter);
+}
+
+/**
+ * tp_base_client_set_handler_bypass_approval:
+ * @self: a #TpBaseClient
+ * @bypass_approval: the value of the Handler.BypassApproval property
+ *
+ * Set whether the channels destined for this handler are automatically
+ * handled, without invoking approvers.
+ * (This is implemented by setting the value of its BypassApproval
+ * D-Bus property.)
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_set_handler_bypass_approval (TpBaseClient *self,
+ gboolean bypass_approval)
+{
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+ g_return_if_fail (cls->priv->handle_channels_impl != NULL);
+
+ self->priv->flags |= (CLIENT_IS_HANDLER | CLIENT_HANDLER_BYPASSES_APPROVAL);
+}
+
+/**
+ * tp_base_client_set_handler_request_notification:
+ * @self: a #TpBaseClient
+ *
+ * Indicate that @self is a Handler willing to be notified about requests for
+ * channels that it is likely to be asked to handle.
+ * That means the TpBaseClient::request-added and TpBaseClient::request-removed:
+ * signals will be fired and tp_base_client_get_pending_requests() will
+ * return the list of pending requests.
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_set_handler_request_notification (TpBaseClient *self)
+{
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+ g_return_if_fail (cls->priv->handle_channels_impl != NULL);
+
+ self->priv->flags |= (CLIENT_IS_HANDLER | CLIENT_HANDLER_WANTS_REQUESTS);
+}
+
+static void
+_tp_base_client_add_handler_capability (TpBaseClient *self,
+ const gchar *token)
+{
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+
+ g_return_if_fail (cls->priv->handle_channels_impl != NULL);
+
+ self->priv->flags |= CLIENT_IS_HANDLER;
+
+ g_assert (g_ptr_array_index (self->priv->handler_caps,
+ self->priv->handler_caps->len - 1) == NULL);
+ g_ptr_array_index (self->priv->handler_caps,
+ self->priv->handler_caps->len - 1) = g_strdup (token);
+ g_ptr_array_add (self->priv->handler_caps, NULL);
+}
+
+/**
+ * tp_base_client_add_handler_capability:
+ * @self: a client, which must not have been registered with
+ * tp_base_client_register() yet
+ * @token: a capability token as defined by the Telepathy D-Bus API
+ * Specification
+ *
+ * Add one capability token to this client, as if via
+ * tp_base_client_add_handler_capabilities().
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_add_handler_capability (TpBaseClient *self,
+ const gchar *token)
+{
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+ g_return_if_fail (cls->priv->handle_channels_impl != NULL);
+
+ _tp_base_client_add_handler_capability (self, token);
+}
+
+/**
+ * tp_base_client_add_handler_capabilities:
+ * @self: a client, which must not have been registered with
+ * tp_base_client_register() yet
+ * @tokens: (array zero-terminated=1) (element-type utf8): capability
+ * tokens as defined by the Telepathy D-Bus API Specification
+ *
+ * Add several capability tokens to this client. These are used to signal
+ * that Telepathy connection managers should advertise certain capabilities
+ * to other contacts, such as the ability to receive audio/video calls using
+ * particular streaming protocols and codecs.
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_add_handler_capabilities (TpBaseClient *self,
+ const gchar * const *tokens)
+{
+ const gchar * const *iter;
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+
+ if (tokens == NULL)
+ return;
+
+ for (iter = tokens; *iter != NULL; iter++)
+ _tp_base_client_add_handler_capability (self, *iter);
+}
+
+/**
+ * tp_base_client_add_handler_capabilities_varargs: (skip)
+ * @self: a client, which must not have been registered with
+ * tp_base_client_register() yet
+ * @first_token: a capability token from the Telepathy D-Bus API Specification
+ * @...: more tokens, ending with %NULL
+ *
+ * Convenience C API equivalent to calling
+ * tp_base_client_add_handler_capability() for each capability token.
+ *
+ * This method may only be called before tp_base_client_register() is
+ * called, and may only be called on objects whose class has called
+ * tp_base_client_implement_handle_channels().
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_add_handler_capabilities_varargs (TpBaseClient *self,
+ const gchar *first_token, ...)
+{
+ va_list ap;
+ const gchar *token = first_token;
+
+ g_return_if_fail (TP_IS_BASE_CLIENT (self));
+ g_return_if_fail (!self->priv->registered);
+
+ va_start (ap, first_token);
+
+ for (token = first_token; token != NULL; token = va_arg (ap, gchar *))
+ _tp_base_client_add_handler_capability (self, token);
+
+ va_end (ap);
+}
+
+/**
* tp_base_client_register:
* @self: a #TpBaseClient, which must not have been registered with
* tp_base_client_register() yet
@@ -365,6 +677,8 @@ gboolean
tp_base_client_register (TpBaseClient *self,
GError **error)
{
+ GHashTable *clients;
+
g_return_val_if_fail (TP_IS_BASE_CLIENT (self), FALSE);
g_return_val_if_fail (!self->priv->registered, FALSE);
/* Client should at least be an Observer, Approver or Handler */
@@ -384,9 +698,102 @@ tp_base_client_register (TpBaseClient *self,
self->priv->registered = TRUE;
+ if (!(self->priv->flags & CLIENT_IS_HANDLER))
+ return TRUE;
+
+ /* Client is an handler */
+ self->priv->libdbus = dbus_connection_ref (
+ dbus_g_connection_get_connection (
+ tp_proxy_get_dbus_connection (self->priv->dbus)));
+
+ /* one ref per TpBaseClient, released in tp_base_client_unregister() */
+ if (!dbus_connection_allocate_data_slot (&clients_slot))
+ ERROR ("Out of memory");
+
+ clients = dbus_connection_get_data (self->priv->libdbus, clients_slot);
+
+ if (clients == NULL)
+ {
+ /* Map DBusConnection to the self->priv->my_chans hash table owned by
+ * the client using this DBusConnection.
+
+ * borrowed client path => borrowed (GHashTable *) */
+ clients = g_hash_table_new (g_str_hash, g_str_equal);
+
+ dbus_connection_set_data (self->priv->libdbus, clients_slot, clients,
+ (DBusFreeFunction) g_hash_table_unref);
+ }
+
+ g_hash_table_insert (clients, self->priv->object_path, self->priv->my_chans);
+
return TRUE;
}
+/**
+ * tp_base_client_get_pending_requests:
+ * @self: a #TpBaseClient
+ *
+ * Only works if tp_base_client_set_handler_request_notification() has been
+ * called.
+ * Returns the list of requests @self is likely be asked to handle.
+ *
+ * Returns: (transfer container) (element-type Tp.ChannelRequest): a #GList
+ * of #TpChannelRequest
+ *
+ * Since: 0.11.UNRELEASED
+ */
+GList *
+tp_base_client_get_pending_requests (TpBaseClient *self)
+{
+ g_return_val_if_fail (self->priv->flags & CLIENT_IS_HANDLER, NULL);
+
+ return g_list_copy (self->priv->pending_requests);
+}
+
+/**
+ * tp_base_client_get_handled_channels:
+ * @self: a #TpBaseClient
+ *
+ * Returns the set of channels currently handled by this base client or by any
+ * other #TpBaseClient with which it shares a unique name.
+ *
+ * Returns: (transfer container) (element-type Tp.Channel): the handled
+ * channels
+ *
+ * Since: 0.11.UNRELEASED
+ */
+GList *
+tp_base_client_get_handled_channels (TpBaseClient *self)
+{
+ GList *result = NULL;
+ GHashTable *clients;
+ GHashTableIter iter;
+ gpointer value;
+ GHashTable *set;
+
+ g_return_val_if_fail (self->priv->flags & CLIENT_IS_HANDLER, NULL);
+
+ if (clients_slot == -1)
+ return NULL;
+
+ set = g_hash_table_new (g_str_hash, g_str_equal);
+
+ clients = dbus_connection_get_data (self->priv->libdbus, clients_slot);
+
+ g_hash_table_iter_init (&iter, clients);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ GHashTable *chans = value;
+
+ tp_g_hash_table_update (set, chans, NULL, NULL);
+ }
+
+ result = g_hash_table_get_values (set);
+ g_hash_table_unref (set);
+
+ return result;
+}
+
static void
tp_base_client_init (TpBaseClient *self)
{
@@ -402,6 +809,9 @@ tp_base_client_init (TpBaseClient *self)
(GDestroyNotify) g_hash_table_unref);
self->priv->handler_caps = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (self->priv->handler_caps, NULL);
+
+ self->priv->my_chans = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, NULL);
}
static void
@@ -411,6 +821,8 @@ tp_base_client_dispose (GObject *object)
void (*dispose) (GObject *) =
G_OBJECT_CLASS (tp_base_client_parent_class)->dispose;
+ tp_base_client_unregister (self);
+
if (self->priv->dbus != NULL)
{
g_object_unref (self->priv->dbus);
@@ -423,6 +835,16 @@ tp_base_client_dispose (GObject *object)
self->priv->account_mgr = NULL;
}
+ g_list_foreach (self->priv->pending_requests, (GFunc) g_object_unref, NULL);
+ g_list_free (self->priv->pending_requests);
+ self->priv->pending_requests = NULL;
+
+ if (self->priv->my_chans != NULL)
+ {
+ g_hash_table_unref (self->priv->my_chans);
+ self->priv->my_chans = NULL;
+ }
+
if (dispose != NULL)
dispose (object);
}
@@ -607,6 +1029,35 @@ tp_base_client_get_dbus_properties (GObject *object,
g_value_set_boxed (value, self->priv->approver_filters);
break;
+ case DP_HANDLER_CHANNEL_FILTER:
+ g_value_set_boxed (value, self->priv->handler_filters);
+ break;
+
+ case DP_BYPASS_APPROVAL:
+ g_value_set_boolean (value,
+ (self->priv->flags & CLIENT_HANDLER_BYPASSES_APPROVAL) != 0);
+ break;
+
+ case DP_CAPABILITIES:
+ /* this is already NULL-terminated */
+ g_value_set_boxed (value, (GStrv) self->priv->handler_caps->pdata);
+ break;
+
+ case DP_HANDLED_CHANNELS:
+ {
+ GList *channels = tp_base_client_get_handled_channels (self);
+ GList *iter;
+ GPtrArray *arr = g_ptr_array_sized_new (g_list_length (channels));
+
+ for (iter = channels; iter != NULL; iter = iter->next)
+ g_ptr_array_add (arr,
+ g_strdup (tp_proxy_get_object_path (iter->data)));
+
+ g_value_take_boxed (value, arr);
+ g_list_free (channels);
+ }
+ break;
+
case DP_OBSERVER_RECOVER:
g_value_set_boolean (value,
(self->priv->flags & CLIENT_OBSERVER_RECOVER) != 0);
@@ -625,6 +1076,17 @@ tp_base_client_class_init (TpBaseClientClass *cls)
{ "Interfaces", GINT_TO_POINTER (DP_INTERFACES) },
{ NULL }
};
+ static TpDBusPropertiesMixinPropImpl handler_properties[] = {
+ { "HandlerChannelFilter",
+ GINT_TO_POINTER (DP_HANDLER_CHANNEL_FILTER) },
+ { "BypassApproval",
+ GINT_TO_POINTER (DP_BYPASS_APPROVAL) },
+ { "Capabilities",
+ GINT_TO_POINTER (DP_CAPABILITIES) },
+ { "HandledChannels",
+ GINT_TO_POINTER (DP_HANDLED_CHANNELS) },
+ { NULL }
+ };
static TpDBusPropertiesMixinPropImpl approver_properties[] = {
{ "ApproverChannelFilter",
GINT_TO_POINTER (DP_APPROVER_CHANNEL_FILTER) },
@@ -644,6 +1106,8 @@ tp_base_client_class_init (TpBaseClientClass *cls)
observer_properties },
{ TP_IFACE_CLIENT_APPROVER, tp_base_client_get_dbus_properties, NULL,
approver_properties },
+ { TP_IFACE_CLIENT_HANDLER, tp_base_client_get_dbus_properties, NULL,
+ handler_properties },
{ NULL }
};
GObjectClass *object_class = G_OBJECT_CLASS (cls);
@@ -703,6 +1167,56 @@ tp_base_client_class_init (TpBaseClientClass *cls)
g_object_class_install_property (object_class, PROP_UNIQUIFY_NAME,
param_spec);
+ /**
+ * TpBaseClient::request-added:
+ * @self: a #TpBaseClient
+ * @account: the #TpAccount on which the request was made
+ * having %TP_ACCOUNT_FEATURE_CORE prepared if possible
+ * @request: a #TpChannelRequest having its object-path defined but
+ * is not guaranteed to be prepared.
+ *
+ * Emitted when a channels have been requested, and that if the
+ * request is successful, they will probably be handled by this Handler.
+ *
+ * This signal is only fired if
+ * tp_base_client_set_handler_request_notification() has been called
+ * on @self previously.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ signals[SIGNAL_REQUEST_ADDED] = g_signal_new (
+ "request-added", G_OBJECT_CLASS_TYPE (cls),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _tp_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2,
+ TP_TYPE_ACCOUNT, TP_TYPE_CHANNEL_REQUEST);
+
+ /**
+ * TpBaseClient::request-removed:
+ * @self: a #TpBaseClient
+ * @request: the #TpChannelRequest being removed
+ * @error: the name of the D-Bus error with which the request failed.
+ * @message: any message supplied with the D-Bus error.
+ *
+ * Emitted when a request has failed and should be disregarded.
+ *
+ * This signal is only fired if
+ * tp_base_client_set_handler_request_notification() has been called
+ * on @self previously.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ signals[SIGNAL_REQUEST_REMOVED] = g_signal_new (
+ "request-removed", G_OBJECT_CLASS_TYPE (cls),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _tp_marshal_VOID__OBJECT_STRING_STRING,
+ G_TYPE_NONE, 3,
+ TP_TYPE_CHANNEL_REQUEST, G_TYPE_STRING, G_TYPE_STRING);
+
cls->dbus_properties_class.interfaces = prop_ifaces;
tp_dbus_properties_mixin_class_init (object_class,
G_STRUCT_OFFSET (TpBaseClientClass, dbus_properties_class));
@@ -1093,6 +1607,376 @@ approver_iface_init (gpointer g_iface,
#undef IMPLEMENT
}
+static void
+chan_invalidated_cb (TpChannel *channel,
+ guint domain,
+ gint code,
+ gchar *message,
+ TpBaseClient *self)
+{
+ DEBUG ("Channel %s has been invalidated", tp_proxy_get_object_path (channel));
+
+ g_hash_table_remove (self->priv->my_chans, tp_proxy_get_object_path (
+ channel));
+}
+
+static void
+ctx_done_cb (TpHandleChannelsContext *context,
+ TpBaseClient *self)
+{
+ guint i;
+
+ for (i = 0; i < context->channels->len; i++)
+ {
+ TpChannel *channel = g_ptr_array_index (context->channels, i);
+
+ if (tp_proxy_get_invalidated (channel) == NULL)
+ {
+ g_hash_table_insert (self->priv->my_chans,
+ (gchar *) tp_proxy_get_object_path (channel),
+ g_object_ref (channel));
+
+ tp_g_signal_connect_object (channel, "invalidated",
+ G_CALLBACK (chan_invalidated_cb), self, 0);
+ }
+ }
+}
+
+static void
+handle_channels_context_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TpBaseClient *self = user_data;
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+ TpHandleChannelsContext *ctx = TP_HANDLE_CHANNELS_CONTEXT (source);
+ GError *error = NULL;
+ GList *channels_list, *requests_list;
+
+ if (!_tp_handle_channels_context_prepare_finish (ctx, result, &error))
+ {
+ DEBUG ("Failed to prepare TpHandleChannelsContext: %s", error->message);
+ tp_handle_channels_context_fail (ctx, error);
+ g_error_free (error);
+ return;
+ }
+
+ channels_list = ptr_array_to_list (ctx->channels);
+ requests_list = ptr_array_to_list (ctx->requests_satisfied);
+
+ tp_g_signal_connect_object (ctx, "done", G_CALLBACK (ctx_done_cb),
+ self, 0);
+
+ cls->priv->handle_channels_impl (self, ctx->account, ctx->connection,
+ channels_list, requests_list, ctx->user_action_time, ctx);
+
+ g_list_free (channels_list);
+ g_list_free (requests_list);
+
+ if (_tp_handle_channels_context_get_state (ctx) ==
+ TP_OBSERVE_CHANNELS_CONTEXT_STATE_NONE)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Implementation of HandledChannels in %s didn't call "
+ "tp_handle_channels_context_{accept,fail,delay}",
+ G_OBJECT_TYPE_NAME (self));
+
+ CRITICAL ("%s", error->message);
+
+ tp_handle_channels_context_fail (ctx, error);
+ g_error_free (error);
+ }
+}
+
+static TpChannelRequest *
+find_request_by_path (TpBaseClient *self,
+ const gchar *path)
+{
+ GList *l;
+
+ for (l = self->priv->pending_requests; l != NULL; l = g_list_next (l))
+ {
+ TpChannelRequest *request = l->data;
+
+ if (!tp_strdiff (tp_proxy_get_object_path (request), path))
+ return request;
+ }
+
+ return NULL;
+}
+
+static void
+_tp_base_client_handle_channels (TpSvcClientHandler *iface,
+ const gchar *account_path,
+ const gchar *connection_path,
+ const GPtrArray *channels_arr,
+ const GPtrArray *requests_arr,
+ guint64 user_action_time,
+ GHashTable *handler_info,
+ DBusGMethodInvocation *context)
+{
+ TpBaseClient *self = TP_BASE_CLIENT (iface);
+ TpHandleChannelsContext *ctx;
+ TpBaseClientClass *cls = TP_BASE_CLIENT_GET_CLASS (self);
+ GError *error = NULL;
+ TpAccount *account = NULL;
+ TpConnection *connection = NULL;
+ GPtrArray *channels = NULL, *requests = NULL;
+ guint i;
+
+ if (!(self->priv->flags & CLIENT_IS_HANDLER))
+ {
+ /* Pretend that the method is not implemented if we are not supposed to
+ * be an Handler. */
+ tp_dbus_g_method_return_not_implemented (context);
+ return;
+ }
+
+ if (cls->priv->handle_channels_impl == NULL)
+ {
+ DEBUG ("class %s does not implement HandleChannels",
+ G_OBJECT_TYPE_NAME (self));
+
+ tp_dbus_g_method_return_not_implemented (context);
+ return;
+ }
+
+ if (channels_arr->len == 0)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Channels should contain at least one channel");
+ DEBUG ("%s", error->message);
+ goto out;
+ }
+
+ account = tp_account_manager_ensure_account (self->priv->account_mgr,
+ account_path);
+
+ connection = tp_account_ensure_connection (account, connection_path);
+ if (connection == NULL)
+ {
+ DEBUG ("Failed to create TpConnection");
+ goto out;
+ }
+
+ channels = g_ptr_array_sized_new (channels_arr->len);
+ g_ptr_array_set_free_func (channels, g_object_unref);
+ for (i = 0; i < channels_arr->len; i++)
+ {
+ const gchar *chan_path;
+ GHashTable *chan_props;
+ TpChannel *channel;
+
+ tp_value_array_unpack (g_ptr_array_index (channels_arr, i), 2,
+ &chan_path, &chan_props);
+
+ channel = tp_channel_new_from_properties (connection,
+ chan_path, chan_props, &error);
+ if (channel == NULL)
+ {
+ DEBUG ("Failed to create TpChannel: %s", error->message);
+ goto out;
+ }
+
+ g_ptr_array_add (channels, channel);
+ }
+
+ requests = g_ptr_array_sized_new (requests_arr->len);
+ g_ptr_array_set_free_func (requests, g_object_unref);
+ for (i = 0; i < requests_arr->len; i++)
+ {
+ const gchar *req_path = g_ptr_array_index (requests_arr, i);
+ TpChannelRequest *request;
+
+ request = find_request_by_path (self, req_path);
+ if (request != NULL)
+ {
+ g_object_ref (request);
+ }
+ else
+ {
+ request = tp_channel_request_new (self->priv->dbus, req_path, NULL,
+ &error);
+ if (request == NULL)
+ {
+ DEBUG ("Failed to create TpChannelRequest: %s", error->message);
+ goto out;
+ }
+ }
+
+ g_ptr_array_add (requests, request);
+ }
+
+ ctx = _tp_handle_channels_context_new (account, connection, channels,
+ requests, user_action_time, handler_info, context);
+
+ _tp_handle_channels_context_prepare_async (ctx,
+ handle_channels_context_prepare_cb, self);
+
+ g_object_unref (ctx);
+
+out:
+ if (channels != NULL)
+ g_ptr_array_unref (channels);
+
+ if (requests != NULL)
+ g_ptr_array_unref (requests);
+
+ if (error == NULL)
+ return;
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+handler_iface_init (gpointer g_iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) tp_svc_client_handler_implement_##x (\
+ g_iface, _tp_base_client_##x)
+ IMPLEMENT (handle_channels);
+#undef IMPLEMENT
+}
+
+typedef struct
+{
+ TpBaseClient *self;
+ TpChannelRequest *request;
+} channel_request_prepare_account_ctx;
+
+static channel_request_prepare_account_ctx *
+channel_request_prepare_account_ctx_new (TpBaseClient *self,
+ TpChannelRequest *request)
+{
+ channel_request_prepare_account_ctx *ctx = g_slice_new (
+ channel_request_prepare_account_ctx);
+
+ ctx->self = g_object_ref (self);
+ ctx->request = g_object_ref (request);
+ return ctx;
+}
+
+static void
+channel_request_prepare_account_ctx_free (
+ channel_request_prepare_account_ctx *ctx)
+{
+ g_object_unref (ctx->self);
+ g_object_unref (ctx->request);
+
+ g_slice_free (channel_request_prepare_account_ctx, ctx);
+}
+
+static void
+channel_request_account_prepare_cb (GObject *account,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ channel_request_prepare_account_ctx *ctx = user_data;
+ GError *error = NULL;
+
+ if (!tp_proxy_prepare_finish (account, result, &error))
+ {
+ DEBUG ("Failed to prepare account: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_signal_emit (ctx->self, signals[SIGNAL_REQUEST_ADDED], 0, account,
+ ctx->request);
+
+ channel_request_prepare_account_ctx_free (ctx);
+}
+
+static void
+_tp_base_client_add_request (TpSvcClientInterfaceRequests *iface,
+ const gchar *path,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ TpBaseClient *self = TP_BASE_CLIENT (iface);
+ TpChannelRequest *request;
+ TpAccount *account;
+ GError *error = NULL;
+ GQuark account_features[] = { TP_ACCOUNT_FEATURE_CORE, 0 };
+ channel_request_prepare_account_ctx *ctx;
+
+ request = tp_channel_request_new (self->priv->dbus, path, properties, &error);
+ if (request == NULL)
+ {
+ DEBUG ("Failed to create TpChannelRequest: %s", error->message);
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ path = tp_asv_get_object_path (properties, TP_PROP_CHANNEL_REQUEST_ACCOUNT);
+ if (path == NULL)
+ {
+ error = g_error_new_literal (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Mandatory 'Account' property is missing");
+
+ DEBUG ("%s", error->message);
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ account = tp_account_manager_ensure_account (self->priv->account_mgr,
+ path);
+
+ self->priv->pending_requests = g_list_append (self->priv->pending_requests,
+ request);
+
+ ctx = channel_request_prepare_account_ctx_new (self, request);
+
+ tp_proxy_prepare_async (account, account_features,
+ channel_request_account_prepare_cb, ctx);
+
+ tp_svc_client_interface_requests_return_from_add_request (context);
+}
+
+static void
+_tp_base_client_remove_request (TpSvcClientInterfaceRequests *iface,
+ const gchar *path,
+ const gchar *error,
+ const gchar *reason,
+ DBusGMethodInvocation *context)
+{
+ TpBaseClient *self = TP_BASE_CLIENT (iface);
+ TpChannelRequest *request;
+
+ request = find_request_by_path (self, path);
+ if (request == NULL)
+ {
+ GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Uknown ChannelRequest" };
+
+ dbus_g_method_return_error (context, &err);
+ return;
+ }
+
+ self->priv->pending_requests = g_list_remove (self->priv->pending_requests,
+ request);
+
+ g_signal_emit (self, signals[SIGNAL_REQUEST_REMOVED], 0, request,
+ error, reason);
+
+ tp_svc_client_interface_requests_return_from_remove_request (context);
+}
+
+static void
+requests_iface_init (gpointer g_iface,
+ gpointer unused G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) tp_svc_client_interface_requests_implement_##x (\
+ g_iface, _tp_base_client_##x)
+ IMPLEMENT (add_request);
+ IMPLEMENT (remove_request);
+#undef IMPLEMENT
+}
+
/**
* tp_base_client_implement_observe_channels:
* @klass: the #TpBaseClientClass of the object
@@ -1164,3 +2048,73 @@ tp_base_client_implement_add_dispatch_operation (TpBaseClientClass *cls,
{
cls->priv->add_dispatch_operation_impl = impl;
}
+
+/**
+ * tp_base_client_implement_handle_channels:
+ * @klass: the #TpBaseClientClass of the object
+ * @impl: the #TpBaseClientClassHandleChannelsImpl function implementing
+ * HandleCHannels()
+ *
+ * Called by subclasses to define the actual implementation of the
+ * HandleChannels() D-Bus method.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_implement_handle_channels (TpBaseClientClass *cls,
+ TpBaseClientClassHandleChannelsImpl impl)
+{
+ cls->priv->handle_channels_impl = impl;
+}
+
+/**
+ * tp_base_client_unregister:
+ * @self: a client, which may already have been registered with
+ * tp_base_client_register(), or not
+ *
+ * Remove this client object from D-Bus, if tp_base_client_register()
+ * has already been called.
+ *
+ * If the object is not registered, this method may be called, but has
+ * no effect.
+ *
+ * Releasing the last reference to the object also has the same effect
+ * as calling this method, but this method should be preferred, as it
+ * has more deterministic behaviour.
+ *
+ * If the object still exists, tp_base_client_register() may be used to
+ * attempt to register it again.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_base_client_unregister (TpBaseClient *self)
+{
+ GError *error = NULL;
+ GHashTable *clients;
+
+ if (!self->priv->registered)
+ return;
+
+ if (!tp_dbus_daemon_release_name (self->priv->dbus, self->priv->bus_name,
+ &error))
+ {
+ WARNING ("Failed to release bus name (%s): %s", self->priv->bus_name,
+ error->message);
+
+ g_error_free (error);
+ }
+
+ tp_dbus_daemon_unregister_object (self->priv->dbus, self);
+
+ clients = dbus_connection_get_data (self->priv->libdbus, clients_slot);
+ if (clients != NULL)
+ g_hash_table_remove (clients, self->priv->object_path);
+
+ dbus_connection_unref (self->priv->libdbus);
+ self->priv->libdbus = NULL;
+
+ dbus_connection_free_data_slot (&clients_slot);
+
+ self->priv->registered = FALSE;
+}
diff --git a/telepathy-glib/base-client.h b/telepathy-glib/base-client.h
index 29d8ce2f6..05026e496 100644
--- a/telepathy-glib/base-client.h
+++ b/telepathy-glib/base-client.h
@@ -26,6 +26,7 @@
#include <telepathy-glib/account.h>
#include <telepathy-glib/add-dispatch-operation-context.h>
+#include <telepathy-glib/handle-channels-context.h>
#include <telepathy-glib/observe-channels-context.h>
#include <telepathy-glib/channel-dispatch-operation.h>
#include <telepathy-glib/connection.h>
@@ -81,6 +82,18 @@ typedef void (*TpBaseClientClassAddDispatchOperationImpl) (
void tp_base_client_implement_add_dispatch_operation (TpBaseClientClass *klass,
TpBaseClientClassAddDispatchOperationImpl impl);
+typedef void (*TpBaseClientClassHandleChannelsImpl) (
+ TpBaseClient *client,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ GList *requests_satisfied,
+ gint64 user_action_time,
+ TpHandleChannelsContext *context);
+
+void tp_base_client_implement_handle_channels (TpBaseClientClass *klass,
+ TpBaseClientClassHandleChannelsImpl impl);
+
/* setup functions which can only be called before register() */
void tp_base_client_add_observer_filter (TpBaseClient *self,
@@ -97,6 +110,29 @@ void tp_base_client_add_approver_filter (TpBaseClient *self,
void tp_base_client_take_approver_filter (TpBaseClient *self,
GHashTable *filter);
+void tp_base_client_be_a_handler (TpBaseClient *self);
+
+void tp_base_client_add_handler_filter (TpBaseClient *self,
+ GHashTable *filter);
+void tp_base_client_take_handler_filter (TpBaseClient *self,
+ GHashTable *filter);
+void tp_base_client_set_handler_bypass_approval (TpBaseClient *self,
+ gboolean bypass_approval);
+
+void tp_base_client_set_handler_request_notification (TpBaseClient *self);
+
+void tp_base_client_add_handler_capability (TpBaseClient *self,
+ const gchar *token);
+void tp_base_client_add_handler_capabilities (TpBaseClient *self,
+ const gchar * const *tokens);
+void tp_base_client_add_handler_capabilities_varargs (TpBaseClient *self,
+ const gchar *first_token, ...) G_GNUC_NULL_TERMINATED;
+
+/* future, potentially (currently in spec as a draft):
+void tp_base_client_set_handler_related_conferences_bypass_approval (
+ TpBaseClient *self, gboolean bypass_approval);
+ */
+
gboolean tp_base_client_register (TpBaseClient *self,
GError **error);
@@ -104,6 +140,13 @@ const gchar *tp_base_client_get_bus_name (TpBaseClient *self);
const gchar *tp_base_client_get_object_path (TpBaseClient *self);
+/* Normal methods, can be called at any time */
+
+GList *tp_base_client_get_pending_requests (TpBaseClient *self);
+GList *tp_base_client_get_handled_channels (TpBaseClient *self);
+
+void tp_base_client_unregister (TpBaseClient *self);
+
#define TP_TYPE_BASE_CLIENT \
(tp_base_client_get_type ())
#define TP_BASE_CLIENT(obj) \
diff --git a/telepathy-glib/handle-channels-context-internal.h b/telepathy-glib/handle-channels-context-internal.h
new file mode 100644
index 000000000..85bc54627
--- /dev/null
+++ b/telepathy-glib/handle-channels-context-internal.h
@@ -0,0 +1,78 @@
+/*
+ * object for HandleChannels calls context (internal)
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TP_HANDLE_CHANNELS_CONTEXT_INTERNAL_H__
+#define __TP_HANDLE_CHANNELS_CONTEXT_INTERNAL_H__
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/account.h>
+#include <telepathy-glib/handle-channels-context.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE,
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_DONE,
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_FAILED,
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_DELAYED,
+} TpHandleChannelsContextState;
+
+struct _TpHandleChannelsContext {
+ /*<private>*/
+ GObject parent;
+ TpHandleChannelsContextPrivate *priv;
+
+ TpAccount *account;
+ TpConnection *connection;
+ /* array of reffed TpChannel */
+ GPtrArray *channels;
+ /* array of reffed TpChannelRequest */
+ GPtrArray *requests_satisfied;
+ guint64 user_action_time;
+ GHashTable *handler_info;
+};
+
+TpHandleChannelsContext * _tp_handle_channels_context_new (
+ TpAccount *account,
+ TpConnection *connection,
+ GPtrArray *channels,
+ GPtrArray *requests_satisfied,
+ guint64 user_action_time,
+ GHashTable *handler_info,
+ DBusGMethodInvocation *dbus_context);
+
+TpHandleChannelsContextState _tp_handle_channels_context_get_state
+ (TpHandleChannelsContext *self);
+
+void _tp_handle_channels_context_prepare_async (
+ TpHandleChannelsContext *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean _tp_handle_channels_context_prepare_finish (
+ TpHandleChannelsContext *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/telepathy-glib/handle-channels-context.c b/telepathy-glib/handle-channels-context.c
new file mode 100644
index 000000000..cebc216ea
--- /dev/null
+++ b/telepathy-glib/handle-channels-context.c
@@ -0,0 +1,695 @@
+/*
+ * object for HandleChannels calls context
+ *
+ * Copyright © 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION: handle-channels-context
+ * @title: TpHandleChannelsContext
+ * @short_description: context of a Handler.HandleChannels() call
+ *
+ * Object used to represent the context of a Handler.HandleChannels()
+ * D-Bus call on a #TpBaseClient.
+ */
+
+/**
+ * TpHandleChannelsContext:
+ *
+ * Data structure representing the context of a Handler.HandleChannels()
+ * call.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+
+/**
+ * TpHandleChannelsContextClass:
+ *
+ * The class of a #TpHandleChannelsContext.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+
+#include "telepathy-glib/handle-channels-context.h"
+#include "telepathy-glib/handle-channels-context-internal.h"
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+
+#define DEBUG_FLAG TP_DEBUG_CLIENT
+#include "telepathy-glib/debug-internal.h"
+#include "telepathy-glib/_gen/signals-marshal.h"
+
+struct _TpHandleChannelsContextClass {
+ /*<private>*/
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE(TpHandleChannelsContext,
+ tp_handle_channels_context, G_TYPE_OBJECT)
+
+enum {
+ PROP_ACCOUNT = 1,
+ PROP_CONNECTION,
+ PROP_CHANNELS,
+ PROP_REQUESTS_SATISFIED,
+ PROP_USER_ACTION_TIME,
+ PROP_HANDLER_INFO,
+ PROP_DBUS_CONTEXT,
+ N_PROPS
+};
+
+enum {
+ SIGNAL_DONE,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _TpHandleChannelsContextPrivate
+{
+ TpHandleChannelsContextState state;
+ GSimpleAsyncResult *result;
+ DBusGMethodInvocation *dbus_context;
+
+ /* Number of calls we are waiting they return. Once they have all returned
+ * the context is considered as prepared */
+ guint num_pending;
+};
+
+static void
+tp_handle_channels_context_init (TpHandleChannelsContext *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TYPE_HANDLE_CHANNELS_CONTEXT,
+ TpHandleChannelsContextPrivate);
+
+ self->priv->state = TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE;
+}
+
+static void
+tp_handle_channels_context_dispose (GObject *object)
+{
+ TpHandleChannelsContext *self = TP_HANDLE_CHANNELS_CONTEXT (
+ object);
+ void (*dispose) (GObject *) =
+ G_OBJECT_CLASS (tp_handle_channels_context_parent_class)->dispose;
+
+ if (self->priv->state == TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE ||
+ self->priv->state == TP_HANDLE_CHANNELS_CONTEXT_STATE_DELAYED)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Disposing the TpHandleChannelsContext" };
+
+ WARNING ("Disposing a context in the %s state",
+ self->priv->state == TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE ?
+ "none": "delayed");
+
+ tp_handle_channels_context_fail (self, &error);
+ }
+
+ if (self->account != NULL)
+ {
+ g_object_unref (self->account);
+ self->account = NULL;
+ }
+
+ if (self->connection != NULL)
+ {
+ g_object_unref (self->connection);
+ self->connection = NULL;
+ }
+
+ if (self->channels != NULL)
+ {
+ g_ptr_array_foreach (self->channels, (GFunc) g_object_unref, NULL);
+ g_ptr_array_unref (self->channels);
+ self->channels = NULL;
+ }
+
+ if (self->requests_satisfied != NULL)
+ {
+ g_ptr_array_foreach (self->requests_satisfied, (GFunc) g_object_unref,
+ NULL);
+ g_ptr_array_unref (self->requests_satisfied);
+ self->requests_satisfied = NULL;
+ }
+
+ if (self->handler_info != NULL)
+ {
+ g_hash_table_unref (self->handler_info);
+ self->handler_info = NULL;
+ }
+
+ if (self->priv->result != NULL)
+ {
+ g_object_unref (self->priv->result);
+ self->priv->result = NULL;
+ }
+
+ if (dispose != NULL)
+ dispose (object);
+}
+
+static void
+tp_handle_channels_context_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpHandleChannelsContext *self = TP_HANDLE_CHANNELS_CONTEXT (
+ object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_object (value, self->account);
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+
+ case PROP_CHANNELS:
+ g_value_set_boxed (value, self->channels);
+ break;
+
+ case PROP_REQUESTS_SATISFIED:
+ g_value_set_boxed (value, self->requests_satisfied);
+ break;
+
+ case PROP_USER_ACTION_TIME:
+ g_value_set_int64 (value, self->user_action_time);
+ break;
+
+ case PROP_HANDLER_INFO:
+ g_value_set_boxed (value, self->handler_info);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tp_handle_channels_context_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpHandleChannelsContext *self = TP_HANDLE_CHANNELS_CONTEXT (
+ object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ self->account = g_value_dup_object (value);
+ break;
+
+ case PROP_CONNECTION:
+ self->connection = g_value_dup_object (value);
+ break;
+
+ case PROP_CHANNELS:
+ self->channels = g_value_dup_boxed (value);
+ g_ptr_array_foreach (self->channels, (GFunc) g_object_ref, NULL);
+ break;
+
+ case PROP_REQUESTS_SATISFIED:
+ self->requests_satisfied = g_value_dup_boxed (value);
+ g_ptr_array_foreach (self->requests_satisfied, (GFunc) g_object_ref,
+ NULL);
+ break;
+
+ case PROP_USER_ACTION_TIME:
+ self->user_action_time = g_value_get_int64 (value);
+ break;
+
+ case PROP_HANDLER_INFO:
+ self->handler_info = g_value_dup_boxed (value);
+ break;
+
+ case PROP_DBUS_CONTEXT:
+ self->priv->dbus_context = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tp_handle_channels_context_constructed (GObject *object)
+{
+ TpHandleChannelsContext *self = TP_HANDLE_CHANNELS_CONTEXT (
+ object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *)
+ tp_handle_channels_context_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (self->account != NULL);
+ g_assert (self->connection != NULL);
+ g_assert (self->channels != NULL);
+ g_assert (self->requests_satisfied != NULL);
+ g_assert (self->handler_info != NULL);
+ g_assert (self->priv->dbus_context != NULL);
+}
+
+static void
+tp_handle_channels_context_class_init (
+ TpHandleChannelsContextClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (cls, sizeof (TpHandleChannelsContextPrivate));
+
+ object_class->get_property = tp_handle_channels_context_get_property;
+ object_class->set_property = tp_handle_channels_context_set_property;
+ object_class->constructed = tp_handle_channels_context_constructed;
+ object_class->dispose = tp_handle_channels_context_dispose;
+
+ /**
+ * TpHandleChannelsContext:account:
+ *
+ * A #TpAccount object representing the Account of the DispatchOperation
+ * that has been passed to HandleChannels.
+ * Read-only except during construction.
+ *
+ * This property can't be %NULL.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_object ("account", "TpAccount",
+ "The TpAccount of the context",
+ TP_TYPE_ACCOUNT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext:connection:
+ *
+ * A #TpConnection object representing the Connection of the DispatchOperation
+ * that has been passed to HandleChannels.
+ * Read-only except during construction.
+ *
+ * This property can't be %NULL.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_object ("connection", "TpConnection",
+ "The TpConnection of the context",
+ TP_TYPE_CONNECTION,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext:channels:
+ *
+ * A #GPtrArray containing #TpChannel objects representing the channels
+ * that have been passed to HandleChannels.
+ * Read-only except during construction.
+ *
+ * This property can't be %NULL.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_boxed ("channels", "GPtrArray of TpChannel",
+ "The TpChannels that have been passed to HandleChannels",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CHANNELS,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext:requests-satisfied:
+ *
+ * A #GPtrArray containing #TpChannelRequest objects representing the
+ * requests that have been passed to HandleChannels.
+ * Read-only except during construction.
+ *
+ * This property can't be %NULL.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_boxed ("requests-satisfied",
+ "GPtrArray of TpChannelRequest",
+ "The TpChannelRequest that has been passed to "
+ "HandleChannels",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTS_SATISFIED,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext:user-action-time:
+ *
+ * The User_Action_Time that have been passed to HandleChannels.
+ * Read-only except during construction.
+ *
+ * If 0, the action doesn't involve any user action. Clients
+ * SHOULD avoid stealing focus when presenting the channel.
+ *
+ * If #G_MAXINT64: clients SHOULD behave as though the user
+ * action happened at the current time, e.g. a client MAY
+ * request that its window gains focus.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_int64 ("user-action-time",
+ "User action time",
+ "The User_Action_Time that has been passed to HandleChannels",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_USER_ACTION_TIME,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext:handler-info:
+ *
+ * A #GHashTable where the keys are string and values are GValue instances.
+ * It represents the Handler_info hash table that has been passed to
+ * HandleChannels.
+ *
+ * This property can't be %NULL.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_boxed ("handler-info", "Handler info",
+ "The Handler that has been passed to ObserveChannels",
+ TP_HASH_TYPE_STRING_VARIANT_MAP,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HANDLER_INFO,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext:dbus-context: (skip)
+ *
+ * The #DBusGMethodInvocation representing the D-Bus context of the
+ * HandleChannels call.
+ * Can only be written during construction.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ param_spec = g_param_spec_pointer ("dbus-context", "D-Bus context",
+ "The DBusGMethodInvocation associated with the HandleChannels call",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_CONTEXT,
+ param_spec);
+
+ /**
+ * TpHandleChannelsContext::done:
+ * @self: a #TpHandleChannelsContext
+ *
+ * Emitted when tp_handle_channels_context_accept has been called on @self.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+ signals[SIGNAL_DONE] = g_signal_new (
+ "done", G_OBJECT_CLASS_TYPE (cls),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _tp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+}
+
+TpHandleChannelsContext * _tp_handle_channels_context_new (
+ TpAccount *account,
+ TpConnection *connection,
+ GPtrArray *channels,
+ GPtrArray *requests_satisfied,
+ guint64 user_action_time,
+ GHashTable *handler_info,
+ DBusGMethodInvocation *dbus_context)
+{
+ return g_object_new (TP_TYPE_HANDLE_CHANNELS_CONTEXT,
+ "account", account,
+ "connection", connection,
+ "channels", channels,
+ "requests-satisfied", requests_satisfied,
+ "user-action-time", user_action_time,
+ "handler-info", handler_info,
+ "dbus-context", dbus_context,
+ NULL);
+}
+
+/**
+ * tp_handle_channels_context_accept:
+ * @self: a #TpHandleChannelsContext
+ *
+ * Called by #TpBaseClientClassAddDispatchOperationImpl when it's done so
+ * the D-Bus method can return.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_handle_channels_context_accept (TpHandleChannelsContext *self)
+{
+ g_return_if_fail (self->priv->state ==
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE
+ || self->priv->state == TP_HANDLE_CHANNELS_CONTEXT_STATE_DELAYED);
+ g_return_if_fail (self->priv->dbus_context != NULL);
+
+ self->priv->state = TP_HANDLE_CHANNELS_CONTEXT_STATE_DONE;
+ dbus_g_method_return (self->priv->dbus_context);
+
+ self->priv->dbus_context = NULL;
+
+ g_signal_emit (self, signals[SIGNAL_DONE], 0);
+}
+
+/**
+ * tp_handle_channels_context_fail:
+ * @self: a #TpHandleChannelsContext
+ * @error: the error to return from the method
+ *
+ * Called by #TpBaseClientClassAddDispatchOperationImpl to raise a D-Bus error.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_handle_channels_context_fail (TpHandleChannelsContext *self,
+ const GError *error)
+{
+ g_return_if_fail (self->priv->state ==
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE
+ || self->priv->state == TP_HANDLE_CHANNELS_CONTEXT_STATE_DELAYED);
+ g_return_if_fail (self->priv->dbus_context != NULL);
+
+ self->priv->state = TP_HANDLE_CHANNELS_CONTEXT_STATE_FAILED;
+ dbus_g_method_return_error (self->priv->dbus_context, error);
+
+ self->priv->dbus_context = NULL;
+}
+
+/**
+ * tp_handle_channels_context_delay:
+ * @self: a #TpHandleChannelsContext
+ *
+ * Called by #TpBaseClientClassAddDispatchOperationImpl to indicate that it
+ * implements the method in an async way. The caller must take a reference
+ * to the #TpHandleChannelsContext before calling this function, and
+ * is responsible for calling either
+ * tp_handle_channels_context_accept() or
+ * tp_handle_channels_context_fail() later.
+ *
+ * Since: 0.11.UNRELEASED
+ */
+void
+tp_handle_channels_context_delay (TpHandleChannelsContext *self)
+{
+ g_return_if_fail (self->priv->state ==
+ TP_HANDLE_CHANNELS_CONTEXT_STATE_NONE);
+
+ self->priv->state = TP_HANDLE_CHANNELS_CONTEXT_STATE_DELAYED;
+}
+
+TpHandleChannelsContextState
+_tp_handle_channels_context_get_state (
+ TpHandleChannelsContext *self)
+{
+ return self->priv->state;
+}
+
+static gboolean
+context_is_prepared (TpHandleChannelsContext *self)
+{
+ return self->priv->num_pending == 0;
+}
+
+static void
+context_check_prepare (TpHandleChannelsContext *self)
+{
+ if (!context_is_prepared (self))
+ return;
+
+ /* is prepared */
+ g_simple_async_result_complete (self->priv->result);
+
+ g_object_unref (self->priv->result);
+ self->priv->result = NULL;
+}
+
+static void
+account_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TpHandleChannelsContext *self = user_data;
+ GError *error = NULL;
+
+ if (self->priv->result == NULL)
+ goto out;
+
+ if (!tp_proxy_prepare_finish (source, result, &error))
+ {
+ DEBUG ("Failed to prepare account: %s", error->message);
+ g_error_free (error);
+ }
+
+ self->priv->num_pending--;
+ context_check_prepare (self);
+
+out:
+ g_object_unref (self);
+}
+
+static void
+conn_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TpHandleChannelsContext *self = user_data;
+ GError *error = NULL;
+
+ if (self->priv->result == NULL)
+ goto out;
+
+ if (!tp_proxy_prepare_finish (source, result, &error))
+ {
+ DEBUG ("Failed to prepare connection: %s", error->message);
+ g_error_free (error);
+ }
+
+ self->priv->num_pending--;
+ context_check_prepare (self);
+
+out:
+ g_object_unref (self);
+}
+
+static void
+channel_prepare_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TpHandleChannelsContext *self = user_data;
+ GError *error = NULL;
+
+ if (self->priv->result == NULL)
+ goto out;
+
+ if (!tp_proxy_prepare_finish (source, result, &error))
+ {
+ DEBUG ("Failed to prepare channel: %s", error->message);
+
+ g_error_free (error);
+ }
+
+ self->priv->num_pending--;
+ context_check_prepare (self);
+
+out:
+ g_object_unref (self);
+}
+
+static void
+context_prepare (TpHandleChannelsContext *self)
+{
+ GQuark account_features[] = { TP_ACCOUNT_FEATURE_CORE, 0 };
+ GQuark conn_features[] = { TP_CONNECTION_FEATURE_CORE, 0 };
+ GQuark channel_features[] = { TP_CHANNEL_FEATURE_CORE, 0 };
+ guint i;
+
+ self->priv->num_pending = 2;
+
+ tp_proxy_prepare_async (self->account, account_features,
+ account_prepare_cb, g_object_ref (self));
+
+ tp_proxy_prepare_async (self->connection, conn_features,
+ conn_prepare_cb, g_object_ref (self));
+
+ for (i = 0; i < self->channels->len; i++)
+ {
+ TpChannel *channel = g_ptr_array_index (self->channels, i);
+
+ self->priv->num_pending++;
+
+ tp_proxy_prepare_async (channel, channel_features,
+ channel_prepare_cb, g_object_ref (self));
+ }
+}
+
+void
+_tp_handle_channels_context_prepare_async (
+ TpHandleChannelsContext *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (TP_IS_HANDLE_CHANNELS_CONTEXT (self));
+ /* This is only used once, by TpBaseClient, so for simplicity, we only
+ * allow one asynchronous preparation */
+ g_return_if_fail (self->priv->result == NULL);
+
+ self->priv->result = g_simple_async_result_new (G_OBJECT (self),
+ callback, user_data, _tp_handle_channels_context_prepare_async);
+
+ context_prepare (self);
+}
+
+gboolean
+_tp_handle_channels_context_prepare_finish (
+ TpHandleChannelsContext *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (TP_IS_HANDLE_CHANNELS_CONTEXT (self), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), _tp_handle_channels_context_prepare_async),
+ FALSE);
+
+ return TRUE;
+}
diff --git a/telepathy-glib/handle-channels-context.h b/telepathy-glib/handle-channels-context.h
new file mode 100644
index 000000000..92dbf52bb
--- /dev/null
+++ b/telepathy-glib/handle-channels-context.h
@@ -0,0 +1,65 @@
+/*
+ * object for HandleChannels calls context
+ *
+ * Copyright © 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TP_HANDLE_CHANNELS_CONTEXT_H__
+#define __TP_HANDLE_CHANNELS_CONTEXT_H__
+
+#include <gio/gio.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpHandleChannelsContext TpHandleChannelsContext;
+typedef struct _TpHandleChannelsContextClass \
+ TpHandleChannelsContextClass;
+typedef struct _TpHandleChannelsContextPrivate \
+ TpHandleChannelsContextPrivate;
+
+GType tp_handle_channels_context_get_type (void);
+
+#define TP_TYPE_HANDLE_CHANNELS_CONTEXT \
+ (tp_handle_channels_context_get_type ())
+#define TP_HANDLE_CHANNELS_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TYPE_HANDLE_CHANNELS_CONTEXT, \
+ TpHandleChannelsContext))
+#define TP_HANDLE_CHANNELS_CONTEXT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TYPE_HANDLE_CHANNELS_CONTEXT, \
+ TpHandleChannelsContextClass))
+#define TP_IS_HANDLE_CHANNELS_CONTEXT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TYPE_HANDLE_CHANNELS_CONTEXT))
+#define TP_IS_HANDLE_CHANNELS_CONTEXT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TYPE_HANDLE_CHANNELS_CONTEXT))
+#define TP_HANDLE_CHANNELS_CONTEXT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TYPE_HANDLE_CHANNELS_CONTEXT, \
+ TpHandleChannelsContextClass))
+
+void tp_handle_channels_context_accept (
+ TpHandleChannelsContext *self);
+
+void tp_handle_channels_context_fail (
+ TpHandleChannelsContext *self,
+ const GError *error);
+
+void tp_handle_channels_context_delay (
+ TpHandleChannelsContext *self);
+
+G_END_DECLS
+
+#endif
diff --git a/telepathy-glib/introspection.am b/telepathy-glib/introspection.am
index 6abf32207..ebe863b87 100644
--- a/telepathy-glib/introspection.am
+++ b/telepathy-glib/introspection.am
@@ -13,6 +13,7 @@ INTROSPECTION_FILES = \
$(srcdir)/connection-manager.c $(srcdir)/connection-manager.h \
$(srcdir)/channel.c $(srcdir)/channel.h \
$(srcdir)/handle.c $(srcdir)/handle.h \
+ $(srcdir)/handle-channels-context.c $(srcdir)/handle-channels-context.h \
$(srcdir)/dbus-daemon.c $(srcdir)/dbus-daemon.h \
$(srcdir)/interfaces.c $(srcdir)/interfaces.h \
$(srcdir)/intset.c $(srcdir)/intset.h \
diff --git a/telepathy-glib/signals-marshal.list b/telepathy-glib/signals-marshal.list
index e9661bdcd..77034435c 100644
--- a/telepathy-glib/signals-marshal.list
+++ b/telepathy-glib/signals-marshal.list
@@ -10,3 +10,6 @@ VOID:UINT,STRING,STRING
VOID:UINT,UINT,UINT,STRING,POINTER
VOID:OBJECT,BOOLEAN
VOID:OBJECT,UINT,INT,STRING
+VOID:OBJECT,OBJECT
+VOID:OBJECT,STRING,STRING
+VOID:VOID
diff --git a/tests/dbus/base-client.c b/tests/dbus/base-client.c
index 742f90799..1c76c6c62 100644
--- a/tests/dbus/base-client.c
+++ b/tests/dbus/base-client.c
@@ -7,12 +7,15 @@
* notice and this notice are preserved.
*/
+/* We include -internal headers of context to be able to easily access to
+ * their semi-private attributes (connection, account, channels, etc). */
#include <telepathy-glib/account-manager.h>
#include <telepathy-glib/add-dispatch-operation-context-internal.h>
#include <telepathy-glib/base-client.h>
#include <telepathy-glib/client.h>
#include <telepathy-glib/debug.h>
#include <telepathy-glib/defs.h>
+#include <telepathy-glib/handle-channels-context-internal.h>
#include <telepathy-glib/observe-channels-context-internal.h>
#include <telepathy-glib/proxy-subclass.h>
@@ -46,6 +49,7 @@ typedef struct {
GError *error /* initialized where needed */;
GStrv interfaces;
+ gint wait;
} Test;
#define ACCOUNT_PATH TP_ACCOUNT_OBJECT_PATH_BASE "what/ev/er"
@@ -265,6 +269,7 @@ get_client_prop_cb (TpProxy *proxy,
g_assert_cmpint (g_hash_table_size (properties), == , 1);
+ g_strfreev (test->interfaces);
test->interfaces = g_strdupv ((GStrv) tp_asv_get_strv (
properties, "Interfaces"));
@@ -272,6 +277,57 @@ out:
g_main_loop_quit (test->mainloop);
}
+static void
+test_register (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ tp_base_client_be_a_handler (test->base_client);
+
+ /* no-op as the client is not registered yet */
+ tp_base_client_unregister (test->base_client);
+
+ /* Client is not registered yet */
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT, get_client_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ g_assert_error (test->error, DBUS_GERROR, DBUS_GERROR_SERVICE_UNKNOWN);
+ g_error_free (test->error);
+ test->error = NULL;
+
+ /* register the client */
+ tp_base_client_register (test->base_client, &test->error);
+ g_assert_no_error (test->error);
+
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT, get_client_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ g_assert_no_error (test->error);
+
+ /* unregister the client */
+ tp_base_client_unregister (test->base_client);
+ test_proxy_run_until_dbus_queue_processed (test->client);
+
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT, get_client_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ g_assert_error (test->error, DBUS_GERROR, DBUS_GERROR_SERVICE_UNKNOWN);
+ g_error_free (test->error);
+ test->error = NULL;
+
+ /* re-register the client */
+ tp_base_client_register (test->base_client, &test->error);
+ g_assert_no_error (test->error);
+
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT, get_client_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ g_assert_no_error (test->error);
+}
+
/* Test Observer */
static void
check_filters (GPtrArray *filters)
@@ -343,7 +399,9 @@ no_return_cb (TpClient *proxy,
}
out:
- g_main_loop_quit (test->mainloop);
+ test->wait--;
+ if (test->wait <= 0)
+ g_main_loop_quit (test->mainloop);
}
static void
@@ -465,7 +523,7 @@ test_observer (Test *test,
channels, "/", requests_satisified, info,
no_return_cb, test, NULL, NULL);
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service);
+ test_text_channel_null_close (test->text_chan_service);
g_main_loop_run (test->mainloop);
g_assert_no_error (test->error);
@@ -607,7 +665,7 @@ test_approver (Test *test,
channels, CDO_PATH, properties,
no_return_cb, test, NULL, NULL);
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service_2);
+ test_text_channel_null_close (test->text_chan_service_2);
g_object_unref (test->text_chan_service_2);
test->text_chan_service_2 = NULL;
@@ -633,8 +691,7 @@ test_approver (Test *test,
channels, CDO_PATH, properties,
no_return_cb, test, NULL, NULL);
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service);
-
+ test_text_channel_null_close (test->text_chan_service);
g_object_unref (test->text_chan_service);
test->text_chan_service = NULL;
@@ -649,6 +706,347 @@ test_approver (Test *test,
g_hash_table_unref (properties);
}
+/* Test Handler */
+static void
+get_handler_prop_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ Test *test = user_data;
+ GPtrArray *filters;
+ gboolean bypass;
+ gboolean valid;
+ const gchar * const * capabilities;
+ GPtrArray *handled;
+
+ if (error != NULL)
+ {
+ test->error = g_error_copy (error);
+ goto out;
+ }
+
+ g_assert_cmpint (g_hash_table_size (properties), == , 4);
+
+ filters = tp_asv_get_boxed (properties, "HandlerChannelFilter",
+ TP_ARRAY_TYPE_CHANNEL_CLASS_LIST);
+ check_filters (filters);
+
+ bypass = tp_asv_get_boolean (properties, "BypassApproval", &valid);
+ g_assert (valid);
+ g_assert (bypass);
+
+ capabilities = tp_asv_get_strv (properties, "Capabilities");
+ g_assert_cmpint (g_strv_length ((GStrv) capabilities), ==, 5);
+ g_assert (tp_strv_contains (capabilities, "badger"));
+ g_assert (tp_strv_contains (capabilities, "mushroom"));
+ g_assert (tp_strv_contains (capabilities, "snake"));
+ g_assert (tp_strv_contains (capabilities, "goat"));
+ g_assert (tp_strv_contains (capabilities, "pony"));
+
+ handled = tp_asv_get_boxed (properties, "HandledChannels",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+ g_assert (handled != NULL);
+ g_assert_cmpint (handled->len, ==, 0);
+
+out:
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+channel_invalidated_cb (TpChannel *channel,
+ guint domain,
+ gint code,
+ gchar *message,
+ Test *test)
+{
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+test_handler (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ GHashTable *filter;
+ const gchar *caps[] = { "mushroom", "snake", NULL };
+ GPtrArray *channels;
+ GPtrArray *requests_satisified;
+ GHashTable *info;
+ GList *chans;
+ SimpleClient *client_2;
+
+ filter = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+ NULL);
+
+ tp_base_client_add_handler_filter (test->base_client, filter);
+ g_hash_table_unref (filter);
+
+ tp_base_client_take_handler_filter (test->base_client, tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ NULL));
+
+ tp_base_client_set_handler_bypass_approval (test->base_client, TRUE);
+
+ tp_base_client_add_handler_capability (test->base_client, "badger");
+ tp_base_client_add_handler_capabilities (test->base_client, caps);
+ tp_base_client_add_handler_capabilities_varargs (test->base_client,
+ "goat", "pony", NULL);
+
+ tp_base_client_register (test->base_client, &test->error);
+ g_assert_no_error (test->error);
+
+ /* Check Client properties */
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT, get_client_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ g_assert_no_error (test->error);
+ g_assert_cmpint (g_strv_length (test->interfaces), ==, 1);
+ g_assert (tp_strv_contains ((const gchar * const *) test->interfaces,
+ TP_IFACE_CLIENT_HANDLER));
+
+ /* Check Handler properties */
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT_HANDLER, get_handler_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ g_assert_no_error (test->error);
+
+ /* Call HandleChannels */
+ channels = g_ptr_array_sized_new (2);
+ add_channel_to_ptr_array (channels, test->text_chan);
+ add_channel_to_ptr_array (channels, test->text_chan_2);
+
+ requests_satisified = g_ptr_array_sized_new (0);
+ info = g_hash_table_new (NULL, NULL);
+
+ tp_proxy_add_interface_by_id (TP_PROXY (test->client),
+ TP_IFACE_QUARK_CLIENT_HANDLER);
+
+ tp_cli_client_handler_call_handle_channels (test->client, -1,
+ tp_proxy_get_object_path (test->account),
+ tp_proxy_get_object_path (test->connection),
+ channels, requests_satisified, 0, info,
+ no_return_cb, test, NULL, NULL);
+
+ g_main_loop_run (test->mainloop);
+ g_assert_no_error (test->error);
+
+ g_assert (test->simple_client->handle_channels_ctx != NULL);
+ g_assert (test->simple_client->handle_channels_ctx->account == test->account);
+
+ chans = tp_base_client_get_handled_channels (test->base_client);
+ g_assert_cmpuint (g_list_length (chans), ==, 2);
+ g_list_free (chans);
+
+ /* One of the channel is closed */
+ g_signal_connect (test->text_chan, "invalidated",
+ G_CALLBACK (channel_invalidated_cb), test);
+ test_text_channel_null_close (test->text_chan_service);
+ g_main_loop_run (test->mainloop);
+
+ chans = tp_base_client_get_handled_channels (test->base_client);
+ g_assert_cmpuint (g_list_length (chans), ==, 1);
+ g_list_free (chans);
+
+ /* Create another client sharing the same unique name */
+ client_2 = simple_client_new (test->dbus, "Test", TRUE);
+ tp_base_client_be_a_handler (TP_BASE_CLIENT (client_2));
+ tp_base_client_register (TP_BASE_CLIENT (client_2), &test->error);
+ g_assert_no_error (test->error);
+
+ chans = tp_base_client_get_handled_channels (TP_BASE_CLIENT (client_2));
+ g_assert_cmpuint (g_list_length (chans), ==, 1);
+ g_list_free (chans);
+
+ g_object_unref (client_2);
+
+ g_ptr_array_foreach (channels, free_channel_details, NULL);
+ g_ptr_array_free (channels, TRUE);
+ g_ptr_array_free (requests_satisified, TRUE);
+ g_hash_table_unref (info);
+}
+
+/* Test Requests interface on Handler */
+static void
+get_requests_prop_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ Test *test = user_data;
+
+ if (error != NULL)
+ {
+ test->error = g_error_copy (error);
+ goto out;
+ }
+
+ g_assert_cmpint (g_hash_table_size (properties), == , 0);
+
+out:
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+request_added_cb (TpBaseClient *client,
+ TpAccount *account,
+ TpChannelRequest *request,
+ Test *test)
+{
+ GList *requests;
+
+ g_assert (TP_IS_CHANNEL_REQUEST (request));
+ g_assert (TP_IS_ACCOUNT (account));
+ g_assert (tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ requests = tp_base_client_get_pending_requests (test->base_client);
+ g_assert_cmpuint (g_list_length ((GList *) requests), ==, 1);
+ g_assert (requests->data == request);
+ g_list_free (requests);
+
+ test->wait--;
+ if (test->wait == 0)
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+request_removed_cb (TpBaseClient *client,
+ TpChannelRequest *request,
+ const gchar *error,
+ const gchar *reason,
+ Test *test)
+{
+ g_assert (TP_IS_CHANNEL_REQUEST (request));
+
+ test->wait--;
+ if (test->wait == 0)
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+test_handler_requests (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ GHashTable *properties;
+ GPtrArray *channels;
+ GPtrArray *requests_satisified;
+ GHashTable *info;
+ TpChannelRequest *request;
+ GList *requests;
+
+ tp_base_client_take_handler_filter (test->base_client, tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ NULL));
+
+ tp_base_client_set_handler_request_notification (test->base_client);
+
+ tp_base_client_register (test->base_client, &test->error);
+ g_assert_no_error (test->error);
+
+ /* Check Client properties */
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT, get_client_prop_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ g_assert_no_error (test->error);
+ g_assert_cmpint (g_strv_length (test->interfaces), ==, 2);
+ g_assert (tp_strv_contains ((const gchar * const *) test->interfaces,
+ TP_IFACE_CLIENT_HANDLER));
+ g_assert (tp_strv_contains ((const gchar * const *) test->interfaces,
+ TP_IFACE_CLIENT_INTERFACE_REQUESTS));
+
+ /* Check Requests properties */
+ tp_cli_dbus_properties_call_get_all (test->client, -1,
+ TP_IFACE_CLIENT_INTERFACE_REQUESTS, get_requests_prop_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ g_assert_no_error (test->error);
+
+ g_assert (tp_base_client_get_pending_requests (test->base_client) == NULL);
+
+ /* Call AddRequest */
+ properties = tp_asv_new (
+ TP_PROP_CHANNEL_REQUEST_ACCOUNT, DBUS_TYPE_G_OBJECT_PATH, ACCOUNT_PATH,
+ NULL);
+
+ tp_proxy_add_interface_by_id (TP_PROXY (test->client),
+ TP_IFACE_QUARK_CLIENT_INTERFACE_REQUESTS);
+
+ g_signal_connect (test->base_client, "request-added",
+ G_CALLBACK (request_added_cb), test);
+
+ tp_cli_client_interface_requests_call_add_request (test->client, -1,
+ "/Request", properties,
+ no_return_cb, test, NULL, NULL);
+
+ test->wait = 2;
+ g_main_loop_run (test->mainloop);
+ g_assert_no_error (test->error);
+
+ requests = tp_base_client_get_pending_requests (test->base_client);
+ g_assert (requests != NULL);
+ g_list_free (requests);
+
+ /* Call HandleChannels */
+ channels = g_ptr_array_sized_new (2);
+ add_channel_to_ptr_array (channels, test->text_chan);
+
+ requests_satisified = g_ptr_array_sized_new (1);
+ g_ptr_array_add (requests_satisified, "/Request");
+
+ info = g_hash_table_new (NULL, NULL);
+
+ tp_proxy_add_interface_by_id (TP_PROXY (test->client),
+ TP_IFACE_QUARK_CLIENT_HANDLER);
+
+ tp_cli_client_handler_call_handle_channels (test->client, -1,
+ tp_proxy_get_object_path (test->account),
+ tp_proxy_get_object_path (test->connection),
+ channels, requests_satisified, 0, info,
+ no_return_cb, test, NULL, NULL);
+
+ g_main_loop_run (test->mainloop);
+ g_assert_no_error (test->error);
+
+ g_assert (test->simple_client->handle_channels_ctx != NULL);
+ g_assert_cmpint (
+ test->simple_client->handle_channels_ctx->requests_satisfied->len, ==, 1);
+ request = g_ptr_array_index (
+ test->simple_client->handle_channels_ctx->requests_satisfied, 0);
+ requests = tp_base_client_get_pending_requests (test->base_client);
+ g_assert (requests->data == request);
+ g_list_free (requests);
+
+ /* Call RemoveRequest */
+ g_signal_connect (test->base_client, "request-removed",
+ G_CALLBACK (request_removed_cb), test);
+
+ tp_cli_client_interface_requests_call_remove_request (test->client, -1,
+ "/Request", "Badger", "snake",
+ no_return_cb, test, NULL, NULL);
+
+ test->wait = 2;
+ g_main_loop_run (test->mainloop);
+ g_assert_no_error (test->error);
+
+ g_assert (tp_base_client_get_pending_requests (test->base_client) == NULL);
+
+ g_hash_table_unref (properties);
+ g_ptr_array_foreach (channels, free_channel_details, NULL);
+ g_ptr_array_free (channels, TRUE);
+ g_ptr_array_free (requests_satisified, TRUE);
+ g_hash_table_unref (info);
+}
+
int
main (int argc,
char **argv)
@@ -660,10 +1058,16 @@ main (int argc,
g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
g_test_add ("/base-client/basics", Test, NULL, setup, test_basics, teardown);
+ g_test_add ("/base-client/register", Test, NULL, setup, test_register,
+ teardown);
g_test_add ("/base-client/observer", Test, NULL, setup, test_observer,
teardown);
g_test_add ("/base-client/approver", Test, NULL, setup, test_approver,
teardown);
+ g_test_add ("/base-client/handler", Test, NULL, setup, test_handler,
+ teardown);
+ g_test_add ("/base-client/handler-requests", Test, NULL, setup,
+ test_handler_requests, teardown);
return g_test_run ();
}
diff --git a/tests/dbus/channel-dispatch-operation.c b/tests/dbus/channel-dispatch-operation.c
index 903c5decf..2743dc2bd 100644
--- a/tests/dbus/channel-dispatch-operation.c
+++ b/tests/dbus/channel-dispatch-operation.c
@@ -560,7 +560,7 @@ test_channel_lost (Test *test,
test);
/* First channel disappears and so is lost */
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service);
+ test_text_channel_null_close (test->text_chan_service);
g_object_unref (test->text_chan_service);
test->text_chan_service = NULL;
@@ -583,7 +583,7 @@ test_channel_lost (Test *test,
g_signal_connect (test->cdo, "invalidated", G_CALLBACK (invalidated_cb),
test);
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service_2);
+ test_text_channel_null_close (test->text_chan_service_2);
g_object_unref (test->text_chan_service_2);
test->text_chan_service_2 = NULL;
@@ -672,7 +672,7 @@ test_channel_lost_preparing (Test *test,
tp_proxy_prepare_async (test->cdo, features, features_prepared_cb, test);
/* First channel disappears while preparing */
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service);
+ test_text_channel_null_close (test->text_chan_service);
g_object_unref (test->text_chan_service);
test->text_chan_service = NULL;
@@ -723,7 +723,7 @@ test_finished_preparing (Test *test,
tp_proxy_prepare_async (test->cdo, features, features_not_prepared_cb, test);
/* The 2 channels are lost while preparing */
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service);
+ test_text_channel_null_close (test->text_chan_service);
g_object_unref (test->text_chan_service);
test->text_chan_service = NULL;
@@ -731,7 +731,7 @@ test_finished_preparing (Test *test,
simple_channel_dispatch_operation_lost_channel (test->cdo_service,
test->text_chan);
- tp_dbus_daemon_unregister_object (test->dbus, test->text_chan_service_2);
+ test_text_channel_null_close (test->text_chan_service_2);
g_object_unref (test->text_chan_service_2);
test->text_chan_service_2 = NULL;
diff --git a/tests/lib/simple-client.c b/tests/lib/simple-client.c
index 707fab4d4..815b024a1 100644
--- a/tests/lib/simple-client.c
+++ b/tests/lib/simple-client.c
@@ -134,6 +134,51 @@ simple_add_dispatch_operation (
}
static void
+simple_handle_channels (TpBaseClient *client,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ GList *requests_satisfied,
+ gint64 user_action_time,
+ TpHandleChannelsContext *context)
+{
+ SimpleClient *self = SIMPLE_CLIENT (client);
+ GList *l;
+
+ if (self->handle_channels_ctx != NULL)
+ {
+ g_object_unref (self->handle_channels_ctx);
+ self->handle_channels_ctx = NULL;
+ }
+
+ g_assert (TP_IS_ACCOUNT (account));
+ g_assert (tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ g_assert (TP_IS_CONNECTION (connection));
+ g_assert (tp_proxy_is_prepared (connection, TP_CONNECTION_FEATURE_CORE));
+
+ g_assert_cmpuint (g_list_length (channels), >, 0);
+ for (l = channels; l != NULL; l = g_list_next (l))
+ {
+ TpChannel *channel = l->data;
+
+ g_assert (TP_IS_CHANNEL (channel));
+ g_assert (tp_proxy_is_prepared (channel, TP_CHANNEL_FEATURE_CORE) ||
+ tp_proxy_get_invalidated (channel) != NULL);
+ }
+
+ for (l = requests_satisfied; l != NULL; l = g_list_next (l))
+ {
+ TpChannelRequest *request = l->data;
+
+ g_assert (TP_IS_CHANNEL_REQUEST (request));
+ }
+
+ self->handle_channels_ctx = g_object_ref (context);
+ tp_handle_channels_context_accept (context);
+}
+
+static void
simple_client_init (SimpleClient *self)
{
}
@@ -157,6 +202,12 @@ simple_client_dispose (GObject *object)
self->add_dispatch_ctx = NULL;
}
+ if (self->handle_channels_ctx != NULL)
+ {
+ g_object_unref (self->handle_channels_ctx);
+ self->handle_channels_ctx = NULL;
+ }
+
if (dispose != NULL)
dispose (object);
}
@@ -174,6 +225,9 @@ simple_client_class_init (SimpleClientClass *klass)
tp_base_client_implement_add_dispatch_operation (base_class,
simple_add_dispatch_operation);
+
+ tp_base_client_implement_handle_channels (base_class,
+ simple_handle_channels);
}
SimpleClient *
diff --git a/tests/lib/simple-client.h b/tests/lib/simple-client.h
index 3746f02f2..6830bd6d3 100644
--- a/tests/lib/simple-client.h
+++ b/tests/lib/simple-client.h
@@ -28,6 +28,7 @@ struct _SimpleClient {
TpObserveChannelsContext *observe_ctx;
TpAddDispatchOperationContext *add_dispatch_ctx;
+ TpHandleChannelsContext *handle_channels_ctx;
};
GType simple_client_get_type (void);
diff --git a/tests/lib/textchan-null.c b/tests/lib/textchan-null.c
index de34d7008..a458a1a00 100644
--- a/tests/lib/textchan-null.c
+++ b/tests/lib/textchan-null.c
@@ -205,6 +205,18 @@ set_property (GObject *object,
}
}
+void
+test_text_channel_null_close (TestTextChannelNull *self)
+{
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ tp_dbus_daemon_unregister_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn), self);
+ }
+}
+
static void
dispose (GObject *object)
{
@@ -214,11 +226,7 @@ dispose (GObject *object)
return;
self->priv->disposed = TRUE;
-
- if (!self->priv->closed)
- {
- tp_svc_channel_emit_closed (self);
- }
+ test_text_channel_null_close (self);
((GObjectClass *) test_text_channel_null_parent_class)->dispose (object);
}
@@ -444,12 +452,7 @@ channel_close (TpSvcChannel *iface,
{
TestTextChannelNull *self = TEST_TEXT_CHANNEL_NULL (iface);
- if (!self->priv->closed)
- {
- self->priv->closed = TRUE;
- tp_svc_channel_emit_closed (self);
- }
-
+ test_text_channel_null_close (self);
tp_svc_channel_return_from_close (context);
}
diff --git a/tests/lib/textchan-null.h b/tests/lib/textchan-null.h
index 58bf908ff..2da4ef3b5 100644
--- a/tests/lib/textchan-null.h
+++ b/tests/lib/textchan-null.h
@@ -128,6 +128,8 @@ GType test_props_group_text_channel_get_type (void);
(G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
TestPropsGroupTextChannelClass))
+void test_text_channel_null_close (TestTextChannelNull *self);
+
G_END_DECLS
#endif /* #ifndef __TEST_TEXT_CHANNEL_NULL_H__ */