diff options
author | Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> | 2010-05-25 12:36:19 +0200 |
---|---|---|
committer | Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> | 2010-05-25 12:36:19 +0200 |
commit | 632d29892b3ef7acd7ff9b303a9cce9804378b22 (patch) | |
tree | b247b5fa7baf31a8cb74daf3005a1645196d0fb7 | |
parent | 3f0783b15eb48c50b3760bec4caceea780262468 (diff) | |
parent | 7f232db322e5128944d9d8a21df3c5ec6ec8a971 (diff) |
Merge branch 'base-handler-27872'
-rw-r--r-- | docs/reference/Makefile.am | 1 | ||||
-rw-r--r-- | docs/reference/telepathy-glib-docs.sgml | 1 | ||||
-rw-r--r-- | docs/reference/telepathy-glib-sections.txt | 32 | ||||
-rw-r--r-- | telepathy-glib/Makefile.am | 3 | ||||
-rw-r--r-- | telepathy-glib/add-dispatch-operation-context.c | 2 | ||||
-rw-r--r-- | telepathy-glib/base-client.c | 954 | ||||
-rw-r--r-- | telepathy-glib/base-client.h | 43 | ||||
-rw-r--r-- | telepathy-glib/handle-channels-context-internal.h | 78 | ||||
-rw-r--r-- | telepathy-glib/handle-channels-context.c | 695 | ||||
-rw-r--r-- | telepathy-glib/handle-channels-context.h | 65 | ||||
-rw-r--r-- | telepathy-glib/introspection.am | 1 | ||||
-rw-r--r-- | telepathy-glib/signals-marshal.list | 3 | ||||
-rw-r--r-- | tests/dbus/base-client.c | 414 | ||||
-rw-r--r-- | tests/dbus/channel-dispatch-operation.c | 10 | ||||
-rw-r--r-- | tests/lib/simple-client.c | 54 | ||||
-rw-r--r-- | tests/lib/simple-client.h | 1 | ||||
-rw-r--r-- | tests/lib/textchan-null.c | 25 | ||||
-rw-r--r-- | tests/lib/textchan-null.h | 2 |
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__ */ |