diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-09-28 18:58:10 +0100 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-09-28 18:58:10 +0100 |
commit | a64cd72472d48ad8e88bfebe5e6046f02d4b5c93 (patch) | |
tree | 3a6c29cc5719c0b20f86fc15732d11b3e39556f1 | |
parent | 5f1ad2c76f43842856fb01a2dc51648a42f24284 (diff) | |
parent | 9c86f446c6a00142c373aae1fa357f5c00f0f2c6 (diff) |
Merge branch 'moar-caps'
36 files changed, 718 insertions, 118 deletions
diff --git a/src/capabilities.h b/gabble/capabilities.h index 8e7d1a03..2e317552 100644 --- a/src/capabilities.h +++ b/gabble/capabilities.h @@ -23,7 +23,7 @@ #include <glib-object.h> -#include "gabble/capabilities-set.h" +#include <gabble/capabilities-set.h> /* Pseudo-capabilities for buggy or strange implementations, represented as * strings starting with a character not allowed in XML (the ASCII beep :-) */ diff --git a/src/caps-channel-manager.h b/gabble/caps-channel-manager.h index 05947b75..9442476a 100644 --- a/src/caps-channel-manager.h +++ b/gabble/caps-channel-manager.h @@ -70,7 +70,8 @@ typedef void (*GabbleCapsChannelManagerRepresentClientFunc) ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set); + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms); void gabble_caps_channel_manager_reset_capabilities ( GabbleCapsChannelManager *caps_manager); @@ -86,7 +87,8 @@ void gabble_caps_channel_manager_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set); + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms); struct _GabbleCapsChannelManagerInterface { GTypeInterface parent; diff --git a/gabble/connection.h b/gabble/connection.h index 575412bb..0c946cf1 100644 --- a/gabble/connection.h +++ b/gabble/connection.h @@ -22,10 +22,13 @@ #define GABBLE_PLUGINS_CONNECTION_H #include <telepathy-glib/base-connection.h> +#include <telepathy-glib/base-contact-list.h> #include <gabble/capabilities-set.h> #include <gabble/types.h> +#include <wocky/wocky-xep-0115-capabilities.h> + G_BEGIN_DECLS #define GABBLE_TYPE_CONNECTION (gabble_connection_get_type ()) @@ -55,6 +58,26 @@ gchar *gabble_connection_add_sidecar_own_caps ( const GabbleCapabilitySet *cap_set, const GPtrArray *identities) G_GNUC_WARN_UNUSED_RESULT; +WockySession *gabble_connection_get_session ( + GabbleConnection *connection); + +gchar *gabble_connection_get_full_jid (GabbleConnection *conn); + +const gchar * gabble_connection_get_jid_for_caps (GabbleConnection *conn, + WockyXep0115Capabilities *caps); + +const gchar * gabble_connection_pick_best_resource_for_caps ( + GabbleConnection *connection, + const gchar *jid, + GabbleCapabilitySetPredicate predicate, + gconstpointer user_data); + +TpBaseContactList * gabble_connection_get_contact_list ( + GabbleConnection *connection); + +WockyXep0115Capabilities * gabble_connection_get_caps ( + GabbleConnection *connection, TpHandle handle); + G_END_DECLS #endif diff --git a/gabble/gabble.h b/gabble/gabble.h index fe1a5027..8393ae8c 100644 --- a/gabble/gabble.h +++ b/gabble/gabble.h @@ -24,7 +24,9 @@ #include <glib-object.h> +#include <gabble/capabilities.h> #include <gabble/capabilities-set.h> +#include <gabble/caps-channel-manager.h> #include <gabble/caps-hash.h> #include <gabble/connection.h> #include <gabble/error.h> diff --git a/plugins/test.c b/plugins/test.c index add4ba5f..026091db 100644 --- a/plugins/test.c +++ b/plugins/test.c @@ -5,10 +5,12 @@ #include <telepathy-glib/telepathy-glib.h> #include <wocky/wocky-disco-identity.h> +#include <wocky/wocky-data-form.h> #include "extensions/extensions.h" #include <gabble/plugin.h> +#include <gabble/caps-channel-manager.h> #define DEBUG(msg, ...) \ g_debug ("%s: " msg, G_STRFUNC, ##__VA_ARGS__) @@ -484,11 +486,15 @@ async_initable_iface_init ( * TestChannelManager implementation * ***********************************/ static void channel_manager_iface_init (gpointer, gpointer); +static void caps_channel_manager_iface_init (gpointer g_iface, + gpointer iface_data); G_DEFINE_TYPE_WITH_CODE (TestChannelManager, test_channel_manager, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, - channel_manager_iface_init)); + channel_manager_iface_init) + G_IMPLEMENT_INTERFACE (GABBLE_TYPE_CAPS_CHANNEL_MANAGER, + caps_channel_manager_iface_init)); static void test_channel_manager_init (TestChannelManager *self) @@ -593,8 +599,65 @@ test_channel_manager_type_foreach_channel_class (GType type, } static void +test_channel_manager_represent_client ( + GabbleCapsChannelManager *manager, + const gchar *client_name, + const GPtrArray *filters, + const gchar * const *cap_tokens, + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) +{ + WockyStanza *stanza; + WockyDataForm *form; + + if (tp_strdiff (client_name, "dataformtest")) + return; + + stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, + WOCKY_STANZA_SUB_TYPE_NONE, NULL, "badger", + '(', "x", + ':', "jabber:x:data", + '@', "type", "result", + '(', "field", + '@', "var", "FORM_TYPE", + '@', "type", "hidden", + '(', "value", '$', "gabble:test:channel:manager:data:form", ')', + ')', + '(', "field", + '@', "var", "animal", + '(', "value", '$', "badger", ')', + '(', "value", '$', "snake", ')', + '(', "value", '$', "weasel", ')', + ')', + '(', "field", + '@', "var", "cheese", + '(', "value", '$', "omgnothorriblecheese", ')', + ')', + '(', "field", + '@', "var", "favourite_crane", + '(', "value", '$', "a tall one", ')', + '(', "value", '$', "a short one", ')', + ')', + '(', "field", + '@', "var", "running_out_of", + '(', "value", '$', "ideas", ')', + '(', "value", '$', "cake", ')', + ')', + ')', + NULL); + + form = wocky_data_form_new_from_node ( + wocky_node_get_first_child (wocky_stanza_get_top_node (stanza)), + NULL); + + g_ptr_array_add (data_forms, form); + + g_object_unref (stanza); +} + +static void channel_manager_iface_init (gpointer g_iface, - gpointer iface_data) + gpointer iface_data) { TpChannelManagerIface *iface = g_iface; @@ -606,3 +669,12 @@ channel_manager_iface_init (gpointer g_iface, iface->request_channel = NULL; iface->foreach_channel_class = NULL; } + +static void +caps_channel_manager_iface_init (gpointer g_iface, + gpointer iface_data) +{ + GabbleCapsChannelManagerInterface *iface = g_iface; + + iface->represent_client = test_channel_manager_represent_client; +} diff --git a/src/Makefile.am b/src/Makefile.am index c94cfaa0..0d5355ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -44,12 +44,12 @@ libgabble_convenience_la_SOURCES = \ call-stream.h \ call-stream.c \ $(top_srcdir)/gabble/capabilities-set.h \ - capabilities.h \ + $(top_srcdir)/gabble/capabilities.h \ capabilities.c \ $(top_srcdir)/gabble/caps-hash.h \ caps-hash.h \ caps-hash.c \ - caps-channel-manager.h \ + $(top_srcdir)/gabble/caps-channel-manager.h \ caps-channel-manager.c \ conn-aliasing.h \ conn-aliasing.c \ diff --git a/src/auth-manager.c b/src/auth-manager.c index a9424854..d9638eed 100644 --- a/src/auth-manager.c +++ b/src/auth-manager.c @@ -28,7 +28,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_AUTH -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "server-sasl-channel.h" #include "connection.h" #include "debug.h" diff --git a/src/capabilities.c b/src/capabilities.c index b3ef11e8..d0b36e78 100644 --- a/src/capabilities.c +++ b/src/capabilities.c @@ -19,7 +19,7 @@ */ #include "config.h" -#include "capabilities.h" +#include "gabble/capabilities.h" #include <stdlib.h> #include <string.h> diff --git a/src/caps-channel-manager.c b/src/caps-channel-manager.c index 73b3e222..dea13a65 100644 --- a/src/caps-channel-manager.c +++ b/src/caps-channel-manager.c @@ -21,7 +21,7 @@ */ #include "config.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include <telepathy-glib/dbus.h> #include <telepathy-glib/channel-manager.h> @@ -82,6 +82,7 @@ gabble_caps_channel_manager_get_contact_capabilities ( * GHashTable with string keys and GValue values * @cap_tokens: the handler capability tokens supported by the client * @cap_set: a set into which to merge additional XMPP capabilities + * @data_forms: a #GPtrArray of #WockyDataForm objects * * Convert the capabilities of a Telepathy client into XMPP capabilities to be * advertised. @@ -95,7 +96,8 @@ gabble_caps_channel_manager_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) { GabbleCapsChannelManagerInterface *iface = GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE (caps_manager); @@ -103,6 +105,6 @@ gabble_caps_channel_manager_represent_client ( if (method != NULL) { - method (caps_manager, client_name, filters, cap_tokens, cap_set); + method (caps_manager, client_name, filters, cap_tokens, cap_set, data_forms); } } diff --git a/src/caps-hash.c b/src/caps-hash.c index bf8a2a08..1518748e 100644 --- a/src/caps-hash.c +++ b/src/caps-hash.c @@ -35,7 +35,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_PRESENCE #include "base64.h" -#include "capabilities.h" +#include "gabble/capabilities.h" #include "debug.h" #include "namespaces.h" #include "presence-cache.h" @@ -61,6 +61,7 @@ caps_hash_compute_from_self_presence (GabbleConnection *self) const GabbleCapabilitySet *cap_set; GPtrArray *features = g_ptr_array_new (); GPtrArray *identities = wocky_disco_identity_array_new (); + GPtrArray *data_forms; gchar *str; /* XEP-0030 requires at least 1 identity. We don't need more. */ @@ -71,7 +72,9 @@ caps_hash_compute_from_self_presence (GabbleConnection *self) cap_set = gabble_presence_peek_caps (presence); gabble_capability_set_foreach (cap_set, ptr_array_add_str, features); - str = wocky_caps_hash_compute_from_lists (features, identities, NULL); + data_forms = gabble_presence_peek_data_forms (presence); + + str = wocky_caps_hash_compute_from_lists (features, identities, data_forms); g_ptr_array_free (features, TRUE); wocky_disco_identity_array_free (identities); diff --git a/src/connection.c b/src/connection.c index b06cbc10..1ea7d655 100644 --- a/src/connection.c +++ b/src/connection.c @@ -36,6 +36,7 @@ #include <wocky/wocky-tls-handler.h> #include <wocky/wocky-ping.h> #include <wocky/wocky-xmpp-error.h> +#include <wocky/wocky-data-form.h> #include <telepathy-glib/channel-manager.h> #include <telepathy-glib/dbus.h> #include <telepathy-glib/enums.h> @@ -50,8 +51,8 @@ #define DEBUG_FLAG GABBLE_DEBUG_CONNECTION #include "bytestream-factory.h" -#include "capabilities.h" -#include "caps-channel-manager.h" +#include "gabble/capabilities.h" +#include "gabble/caps-channel-manager.h" #include "caps-hash.h" #include "auth-manager.h" #include "conn-aliasing.h" @@ -258,6 +259,10 @@ struct _GabbleConnectionPrivate /* the union of the above */ GabbleCapabilitySet *all_caps; + /* data forms provided via UpdateCapabilities() + * gchar * (client name) => GPtrArray<owned WockyDataForm> */ + GHashTable *client_data_forms; + /* auth manager */ GabbleAuthManager *auth_manager; @@ -444,6 +449,9 @@ gabble_connection_constructor (GType type, priv->client_caps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gabble_capability_set_free); + priv->client_data_forms = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_ptr_array_unref); + /* Historically, the optional Jingle transports were in our initial * presence, but could be removed by AdvertiseCapabilities(). Emulate * that here for now. */ @@ -1222,6 +1230,8 @@ gabble_connection_dispose (GObject *object) gabble_capability_set_free (priv->sidecar_caps); gabble_capability_set_free (priv->bonus_caps); + g_hash_table_destroy (priv->client_data_forms); + if (priv->disconnect_timer != 0) { g_source_remove (priv->disconnect_timer); @@ -1362,6 +1372,14 @@ WockyPorter *gabble_connection_dup_porter (GabbleConnection *conn) return NULL; } +WockySession * +gabble_connection_get_session (GabbleConnection *connection) +{ + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + + return connection->session; +} + /** * _gabble_connection_send * @@ -2303,7 +2321,8 @@ gabble_connection_fill_in_caps (GabbleConnection *self, /* Ensure this set of capabilities is in the cache. */ gabble_presence_cache_add_own_caps (self->presence_cache, caps_hash, - gabble_presence_peek_caps (presence), NULL); + gabble_presence_peek_caps (presence), NULL, + gabble_presence_peek_data_forms (presence)); /* XEP-0115 deprecates 'ext' feature bundles. But we still need * BUNDLE_VOICE_V1 it for backward-compatibility with Gabble 0.2 */ @@ -2401,10 +2420,13 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, GHashTableIter iter; gpointer k, v; GabbleCapabilitySet *save_set; + GPtrArray *data_forms; save_set = self->priv->all_caps; self->priv->all_caps = gabble_capability_set_new (); + data_forms = g_ptr_array_new (); + gabble_capability_set_update (self->priv->all_caps, gabble_capabilities_get_fixed_caps ()); gabble_capability_set_update (self->priv->all_caps, self->priv->notify_caps); @@ -2412,6 +2434,7 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, gabble_capability_set_update (self->priv->all_caps, self->priv->sidecar_caps); gabble_capability_set_update (self->priv->all_caps, self->priv->bonus_caps); + /* first, normal caps */ g_hash_table_iter_init (&iter, self->priv->client_caps); while (g_hash_table_iter_next (&iter, &k, &v)) @@ -2427,13 +2450,22 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, gabble_capability_set_update (self->priv->all_caps, v); } + /* now data forms */ + g_hash_table_iter_init (&iter, self->priv->client_data_forms); + + /* just borrow the ref, data_forms doesn't have a free func */ + while (g_hash_table_iter_next (&iter, &k, &v)) + tp_g_ptr_array_extend (data_forms, v); + if (self->self_presence != NULL) gabble_presence_set_capabilities (self->self_presence, - self->priv->resource, self->priv->all_caps, self->priv->caps_serial++); + self->priv->resource, self->priv->all_caps, data_forms, + self->priv->caps_serial++); if (gabble_capability_set_equals (self->priv->all_caps, save_set)) { gabble_capability_set_free (save_set); + g_ptr_array_unref (data_forms); DEBUG ("nothing to do"); return FALSE; } @@ -2442,6 +2474,7 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, if (base->status != TP_CONNECTION_STATUS_CONNECTED) { gabble_capability_set_free (save_set); + g_ptr_array_unref (data_forms); DEBUG ("not emitting self-presence stanza: not connected yet"); return FALSE; } @@ -2449,6 +2482,7 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, if (!conn_presence_signal_own_presence (self, NULL, &error)) { gabble_capability_set_free (save_set); + g_ptr_array_unref (data_forms); DEBUG ("error sending presence: %s", error->message); g_error_free (error); return FALSE; @@ -2459,6 +2493,8 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, else *old_out = save_set; + g_ptr_array_unref (data_forms); + return TRUE; } @@ -2570,6 +2606,7 @@ iq_disco_cb (WockyPorter *porter, const GabbleCapabilityInfo *info = NULL; const GabbleCapabilitySet *features = NULL; const GPtrArray *identities = NULL; + const GPtrArray *data_forms = NULL; /* query's existence is checked by WockyPorter before this function is called */ query = wocky_node_get_child (wocky_stanza_get_top_node (stanza), "query"); @@ -2599,19 +2636,25 @@ iq_disco_cb (WockyPorter *porter, wocky_node_set_attribute (result_query, "node", node); if (node == NULL) - features = gabble_presence_peek_caps (self->self_presence); - /* If node is not NULL, it can be either a caps bundle as defined in the - * legacy XEP-0115 version 1.3 or an hash as defined in XEP-0115 version - * 1.5. Let's see if it's a verification string we've told the cache about. - */ + { + features = gabble_presence_peek_caps (self->self_presence); + data_forms = gabble_presence_peek_data_forms (self->self_presence); + } else - info = gabble_presence_cache_peek_own_caps (self->presence_cache, - suffix); + { + /* If node is not NULL, it can be either a caps bundle as defined in the + * legacy XEP-0115 version 1.3 or an hash as defined in XEP-0115 version + * 1.5. Let's see if it's a verification string we've told the cache about. + */ + info = gabble_presence_cache_peek_own_caps (self->presence_cache, + suffix); + } if (info) { features = info->cap_set; identities = info->identities; + data_forms = info->data_forms; } if (identities && identities->len != 0) @@ -2650,6 +2693,18 @@ iq_disco_cb (WockyPorter *porter, features = gabble_capabilities_get_bundle_video_v1 (); } + if (data_forms != NULL) + { + guint i; + + for (i = 0; i < data_forms->len; i++) + { + WockyDataForm *form = g_ptr_array_index (data_forms, i); + + wocky_data_form_add_to_node (form, result_query); + } + } + if (features == NULL && tp_strdiff (suffix, BUNDLE_PMUC_V1)) { _gabble_connection_send_iq_error (self, stanza, @@ -3193,6 +3248,111 @@ gabble_connection_advertise_capabilities (TpSvcConnectionInterfaceCapabilities * g_ptr_array_free (ret, TRUE); } +static const gchar * +get_form_type (WockyDataForm *form) +{ + WockyDataFormField *field; + + field = g_hash_table_lookup (form->fields, + "FORM_TYPE"); + g_assert (field != NULL); + + return field->raw_value_contents[0]; +} + +static const gchar * +check_form_is_unique (GabbleConnection *self, + const gchar *form_type) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, self->priv->client_data_forms); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const gchar *manager = key; + GPtrArray *data_forms = value; + guint i; + + if (data_forms == NULL || data_forms->len == 0) + continue; + + for (i = 0; i < data_forms->len; i++) + { + WockyDataForm *form = g_ptr_array_index (data_forms, i); + + if (!tp_strdiff (get_form_type (form), form_type)) + return manager; + } + } + + return NULL; +} + +static gboolean +check_data_form_in_list (GPtrArray *array, + const gchar *form_type) +{ + guint i; + + for (i = 0; i < array->len; i++) + { + WockyDataForm *form = g_ptr_array_index (array, i); + + if (!tp_strdiff (get_form_type (form), form_type)) + return TRUE; + } + + return FALSE; +} + +static gboolean +check_data_form_is_valid (GabbleConnection *self, + WockyDataForm *form, + GPtrArray *existing_forms) +{ + WockyDataFormField *field; + const gchar *form_type, *other_client; + + /* We want rid of forms with no FORM_TYPE quickly. */ + field = g_hash_table_lookup (form->fields, "FORM_TYPE"); + + if (field == NULL || tp_str_empty (field->raw_value_contents[0])) + { + WARNING ("data form with no FORM_TYPE field; ignoring"); + return FALSE; + } + + form_type = field->raw_value_contents[0]; + + /* We'll get warnings (potentially bad) if two clients cause a + * channel manager to create two data forms with the same FORM_TYPE, + * or if multiple channel managers create two data forms with the + * same FORM_TYPE, for the same client. This is probably not a + * problem in practice given hardly anyone uses data forms in entity + * capabilities anyway. */ + + /* We don't want the same data form from another caps channel + * manager for this client either */ + if (check_data_form_in_list (existing_forms, form_type)) + { + WARNING ("duplicate data form '%s' from another channel " + "manager; ignoring", form_type); + return FALSE; + } + + /* And lastly we don't want a form we're already advertising. */ + other_client = check_form_is_unique (self, form_type); + if (other_client != NULL) + { + WARNING ("Data form '%s' already provided by client " + "%s; ignoring", form_type, other_client); + return FALSE; + } + + return TRUE; +} + /** * gabble_connection_update_capabilities * @@ -3239,10 +3399,12 @@ gabble_connection_update_capabilities ( const GPtrArray *filters = g_value_get_boxed (va->values + 1); const gchar * const * cap_tokens = g_value_get_boxed (va->values + 2); GabbleCapabilitySet *cap_set; + GPtrArray *data_forms; g_hash_table_remove (self->priv->client_caps, client_name); + g_hash_table_remove (self->priv->client_data_forms, client_name); - if ((cap_tokens == NULL || cap_tokens[0] != NULL) && + if ((cap_tokens == NULL || cap_tokens[0] == NULL) && filters->len == 0) { /* no capabilities */ @@ -3251,6 +3413,8 @@ gabble_connection_update_capabilities ( } cap_set = gabble_capability_set_new (); + data_forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); tp_base_connection_channel_manager_iter_init (&iter, base); @@ -3258,18 +3422,30 @@ gabble_connection_update_capabilities ( { if (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager)) { + GPtrArray *forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); + guint j; + + /* First, represent the client... */ gabble_caps_channel_manager_represent_client ( GABBLE_CAPS_CHANNEL_MANAGER (manager), client_name, filters, - cap_tokens, cap_set); + cap_tokens, cap_set, forms); + + /* Now check the forms... */ + for (j = 0; j < forms->len; j++) + { + WockyDataForm *form = g_ptr_array_index (forms, j); + + if (check_data_form_is_valid (self, form, data_forms)) + g_ptr_array_add (data_forms, g_object_ref (form)); + } + + g_ptr_array_unref (forms); } } - if (gabble_capability_set_size (cap_set) == 0) - { - DEBUG ("client %s has no interesting capabilities", client_name); - gabble_capability_set_free (cap_set); - } - else + /* first deal with normal caps */ + if (gabble_capability_set_size (cap_set) > 0) { if (DEBUGGING) { @@ -3282,6 +3458,33 @@ gabble_connection_update_capabilities ( g_hash_table_insert (self->priv->client_caps, g_strdup (client_name), cap_set); } + else + { + DEBUG ("client %s has no interesting capabilities", client_name); + gabble_capability_set_free (cap_set); + } + + /* now data forms */ + if (data_forms->len > 0) + { + guint j; + + /* now print out what forms we have here */ + DEBUG ("client %s contributes %u data form%s:", client_name, + data_forms->len, + data_forms->len > 1 ? "s" : ""); + + for (j = 0; j < data_forms->len; j++) + DEBUG (" - %s", get_form_type (g_ptr_array_index (data_forms, i))); + + g_hash_table_insert (self->priv->client_data_forms, + g_strdup (client_name), data_forms); + } + else + { + DEBUG ("client %s has no interesting data forms", client_name); + g_ptr_array_unref (data_forms); + } } if (gabble_connection_refresh_capabilities (self, &old_caps)) @@ -3711,9 +3914,95 @@ gabble_connection_add_sidecar_own_caps (GabbleConnection *self, ver = gabble_caps_hash_compute (cap_set, identities_copy); gabble_presence_cache_add_own_caps (self->presence_cache, ver, - cap_set, identities_copy); + cap_set, identities_copy, NULL); wocky_disco_identity_array_free (identities_copy); return ver; } + +const gchar * +gabble_connection_get_jid_for_caps (GabbleConnection *conn, + WockyXep0115Capabilities *caps) +{ + TpHandle handle; + TpBaseConnection *base; + TpHandleRepoIface *contact_handles; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL); + g_return_val_if_fail (GABBLE_IS_PRESENCE (caps), NULL); + + base = (TpBaseConnection *) conn; + + if ((GabblePresence *) caps == conn->self_presence) + { + handle = tp_base_connection_get_self_handle (base); + } + else + { + handle = gabble_presence_cache_get_handle (conn->presence_cache, + (GabblePresence *) caps); + } + + contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + + return tp_handle_inspect (contact_handles, handle); +} + +const gchar * +gabble_connection_pick_best_resource_for_caps (GabbleConnection *connection, + const gchar *jid, + GabbleCapabilitySetPredicate predicate, + gconstpointer user_data) +{ + TpBaseConnection *base; + TpHandleRepoIface *contact_handles; + TpHandle handle; + GabblePresence *presence; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!tp_str_empty (jid), NULL); + + base = (TpBaseConnection *) connection; + contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + + handle = tp_handle_ensure (contact_handles, jid, + NULL, NULL); + + if (handle == 0) + return NULL; + + presence = gabble_presence_cache_get (connection->presence_cache, + handle); + + if (presence == NULL) + return NULL; + + return gabble_presence_pick_resource_by_caps (presence, 0, + predicate, user_data); +} + +TpBaseContactList * +gabble_connection_get_contact_list (GabbleConnection *connection) +{ + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + + return (TpBaseContactList *) connection->roster; +} + +WockyXep0115Capabilities * +gabble_connection_get_caps (GabbleConnection *connection, + TpHandle handle) +{ + GabblePresence *presence; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (handle > 0, NULL); + + presence = gabble_presence_cache_get (connection->presence_cache, + handle); + + return (WockyXep0115Capabilities *) presence; +} diff --git a/src/connection.h b/src/connection.h index 586b648c..d1dc73a5 100644 --- a/src/connection.h +++ b/src/connection.h @@ -34,7 +34,7 @@ #include <wocky/wocky-pep-service.h> #include "gabble/connection.h" -#include "capabilities.h" +#include "gabble/capabilities.h" #include "error.h" #include "ft-manager.h" #include "jingle-factory.h" @@ -204,8 +204,6 @@ typedef enum { GABBLE_CONNECTION_ALIAS_FROM_ROSTER } GabbleConnectionAliasSource; -gchar *gabble_connection_get_full_jid (GabbleConnection *conn); - WockyPorter *gabble_connection_dup_porter (GabbleConnection *conn); gboolean _gabble_connection_set_properties_from_account ( diff --git a/src/ft-manager.c b/src/ft-manager.c index 9c3128df..b79881ac 100644 --- a/src/ft-manager.c +++ b/src/ft-manager.c @@ -30,7 +30,7 @@ #include "jingle-session.h" #include "jingle-share.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "ft-manager.h" #include "error.h" @@ -824,7 +824,8 @@ gabble_ft_manager_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens G_GNUC_UNUSED, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms G_GNUC_UNUSED) { guint i; diff --git a/src/im-factory.c b/src/im-factory.c index 08f19f6c..be518b27 100644 --- a/src/im-factory.c +++ b/src/im-factory.c @@ -36,7 +36,7 @@ #include "extensions/extensions.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "disco.h" diff --git a/src/jingle-session.c b/src/jingle-session.c index f20c2d63..2981e3e8 100644 --- a/src/jingle-session.c +++ b/src/jingle-session.c @@ -30,7 +30,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MEDIA -#include "capabilities.h" +#include "gabble/capabilities.h" #include "connection.h" #include "conn-presence.h" #include "debug.h" diff --git a/src/legacy-caps.h b/src/legacy-caps.h index 080ac3b0..e2de46c3 100644 --- a/src/legacy-caps.h +++ b/src/legacy-caps.h @@ -23,7 +23,7 @@ #include <glib-object.h> -#include "capabilities.h" +#include "gabble/capabilities.h" typedef void (*TypeFlagsToCapsFunc) (guint typeflags, GabbleCapabilitySet *caps); typedef guint (*CapsToTypeFlagsFunc) (const GabbleCapabilitySet *caps); diff --git a/src/media-factory.c b/src/media-factory.c index 4a2c7c70..e8ec9908 100644 --- a/src/media-factory.c +++ b/src/media-factory.c @@ -36,7 +36,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MEDIA -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "jingle-factory.h" @@ -1208,7 +1208,8 @@ gabble_media_factory_represent_client (GabbleCapsChannelManager *manager, const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) { static GQuark q_gtalk_p2p = 0, q_ice_udp = 0, q_h264 = 0; static GQuark qc_gtalk_p2p = 0, qc_ice_udp = 0, qc_h264 = 0; diff --git a/src/muc-factory.c b/src/muc-factory.c index 1feb21a1..554b1504 100644 --- a/src/muc-factory.c +++ b/src/muc-factory.c @@ -36,7 +36,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MUC -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "conn-olpc.h" #include "debug.h" diff --git a/src/presence-cache.c b/src/presence-cache.c index 05556f8b..89a6534e 100644 --- a/src/presence-cache.c +++ b/src/presence-cache.c @@ -41,11 +41,13 @@ #include <wocky/wocky-caps-hash.h> #include <wocky/wocky-disco-identity.h> #include <wocky/wocky-utils.h> +#include <wocky/wocky-namespaces.h> +#include <wocky/wocky-data-form.h> #define DEBUG_FLAG GABBLE_DEBUG_PRESENCE -#include "capabilities.h" -#include "caps-channel-manager.h" +#include "gabble/capabilities.h" +#include "gabble/caps-channel-manager.h" #include "conn-presence.h" #include "debug.h" #include "disco.h" @@ -251,18 +253,36 @@ capability_info_free (GabbleCapabilityInfo *info) wocky_disco_identity_array_free (info->identities); info->identities = NULL; + if (info->data_forms != NULL) + g_ptr_array_unref (info->data_forms); + info->data_forms = NULL; + tp_intset_destroy (info->guys); g_slice_free (GabbleCapabilityInfo, info); } +static void +replace_data_forms (GabbleCapabilityInfo *info, + GPtrArray *data_forms) +{ + if (data_forms == info->data_forms) + return; + + tp_clear_pointer (&info->data_forms, g_ptr_array_unref); + + if (data_forms != NULL) + info->data_forms = g_ptr_array_ref (data_forms); +} + static guint capability_info_recvd (GabblePresenceCache *cache, const gchar *node, TpHandle handle, GabbleCapabilitySet *cap_set, guint trust_inc, - guint client_types) + guint client_types, + GPtrArray *data_forms) { GabbleCapabilityInfo *info = capability_info_get (cache, node); @@ -293,6 +313,8 @@ capability_info_recvd (GabblePresenceCache *cache, info->client_types = client_types; + replace_data_forms (info, data_forms); + return info->trust; } @@ -1012,7 +1034,7 @@ _parse_node (GabblePresence *presence, gabble_capability_set_add (cap_set, QUIRK_GOOGLE_WEBMAIL_CLIENT); gabble_capability_set_add (cap_set, QUIRK_OMITS_CONTENT_CREATORS); - gabble_presence_set_capabilities (presence, resource, cap_set, serial); + gabble_presence_set_capabilities (presence, resource, cap_set, NULL, serial); gabble_capability_set_free (cap_set); } } @@ -1142,6 +1164,7 @@ set_caps_for (DiscoWaiter *waiter, GabblePresenceCache *cache, GabbleCapabilitySet *cap_set, guint client_types, + GPtrArray *data_forms, TpHandle responder_handle, const gchar *responder_jid) { @@ -1158,7 +1181,7 @@ set_caps_for (DiscoWaiter *waiter, waiter->handle, responder_handle, responder_jid); gabble_presence_set_capabilities (presence, waiter->resource, cap_set, - waiter->serial); + data_forms, waiter->serial); new_cap_set = gabble_presence_peek_caps (presence); emit_capabilities_update (cache, waiter->handle, old_cap_set, new_cap_set); gabble_capability_set_free (old_cap_set); @@ -1222,6 +1245,29 @@ client_types_from_message (TpHandle handle, return client_types; } +static GPtrArray * +data_forms_from_message (WockyNode *node) +{ + GPtrArray *out = g_ptr_array_new_with_free_func (g_object_unref); + + WockyNodeIter iter; + WockyNode *x_node = NULL; + + wocky_node_iter_init (&iter, node, "x", WOCKY_XMPP_NS_DATA); + while (wocky_node_iter_next (&iter, &x_node)) + { + WockyDataForm *form = wocky_data_form_new_from_node (x_node, NULL); + + /* we've already parsed the reply to check the hash matches, so + * we can already guarantee these data forms will be parsed + * fine */ + if (G_LIKELY (form != NULL)) + g_ptr_array_add (out, form); + } + + return out; +} + static void _signal_presences_updated (GabblePresenceCache *cache, TpHandle handle) @@ -1257,6 +1303,7 @@ _caps_disco_cb (GabbleDisco *disco, gboolean jid_is_valid; gpointer key; guint client_types = 0; + GPtrArray *data_forms = NULL; cache = GABBLE_PRESENCE_CACHE (user_data); priv = cache->priv; @@ -1305,6 +1352,7 @@ _caps_disco_cb (GabbleDisco *disco, cap_set = gabble_capability_set_new_from_stanza (query_result); client_types = client_types_from_message (handle, query_result, waiter_self->resource); + data_forms = data_forms_from_message (query_result); /* Only 'sha-1' is mandatory to implement by XEP-0115. If the remote contact * uses another hash algorithm, don't check the hash and fallback to the old @@ -1326,7 +1374,7 @@ _caps_disco_cb (GabbleDisco *disco, else if (g_str_equal (waiter_self->ver, computed_hash)) { trust = capability_info_recvd (cache, node, handle, cap_set, - CAPABILITY_BUNDLE_ENOUGH_TRUST, client_types); + CAPABILITY_BUNDLE_ENOUGH_TRUST, client_types, data_forms); } else { @@ -1341,7 +1389,8 @@ _caps_disco_cb (GabbleDisco *disco, } else { - trust = capability_info_recvd (cache, node, handle, cap_set, 1, client_types); + trust = capability_info_recvd (cache, node, handle, cap_set, 1, + client_types, data_forms); } /* Remove the node from the hash table without freeing the key or list of @@ -1381,7 +1430,8 @@ _caps_disco_cb (GabbleDisco *disco, { DiscoWaiter *waiter = (DiscoWaiter *) i->data; - set_caps_for (waiter, cache, cap_set, client_types, handle, jid); + set_caps_for (waiter, cache, cap_set, client_types, + data_forms, handle, jid); emit_capabilities_discovered (cache, waiter->handle); } @@ -1409,7 +1459,8 @@ _caps_disco_cb (GabbleDisco *disco, g_free (tmp); } - set_caps_for (waiter_self, cache, cap_set, client_types, handle, jid); + set_caps_for (waiter_self, cache, cap_set, client_types, + data_forms, handle, jid); } waiters = g_slist_remove (waiters, waiter_self); @@ -1435,6 +1486,7 @@ _caps_disco_cb (GabbleDisco *disco, } gabble_capability_set_free (cap_set); + g_ptr_array_unref (data_forms); OUT: if (handle) @@ -1501,7 +1553,7 @@ _process_caps_uri (GabblePresenceCache *cache, guint types; gabble_presence_set_capabilities ( - presence, resource, cap_set, serial); + presence, resource, cap_set, info->data_forms, serial); /* We can only get this information from actual disco replies, * so we depend on having this information from the caps cache. */ @@ -2100,7 +2152,8 @@ gabble_presence_cache_add_own_caps ( GabblePresenceCache *cache, const gchar *ver, const GabbleCapabilitySet *cap_set, - const GPtrArray *identities) + const GPtrArray *identities, + GPtrArray *data_forms) { gchar *uri = g_strdup_printf ("%s#%s", NS_GABBLE_CAPS, ver); GabbleCapabilityInfo *info = capability_info_get (cache, uri); @@ -2135,6 +2188,8 @@ gabble_presence_cache_add_own_caps ( info->trust = CAPABILITY_BUNDLE_ENOUGH_TRUST; tp_intset_add (info->guys, cache->priv->conn->parent.self_handle); + replace_data_forms (info, data_forms); + /* FIXME: we should satisfy any waiters for this node now. fd.o bug #24619. */ out: @@ -2497,3 +2552,20 @@ gabble_presence_cache_disco_in_progress (GabblePresenceCache *cache, return in_progress; } + +TpHandle +gabble_presence_cache_get_handle (GabblePresenceCache *cache, + GabblePresence *presence) +{ + GHashTableIter iter; + gpointer key, val; + + g_hash_table_iter_init (&iter, cache->priv->presence); + while (g_hash_table_iter_next (&iter, &key, &val)) + { + if (presence == val) + return GPOINTER_TO_UINT (key); + } + + return 0; +} diff --git a/src/presence-cache.h b/src/presence-cache.h index 44af23fb..0e528396 100644 --- a/src/presence-cache.h +++ b/src/presence-cache.h @@ -65,6 +65,9 @@ struct _GabbleCapabilityInfo /* array of GabbleDiscoIdentity or NULL */ GPtrArray *identities; + /* array of WockyDataForm or NULL */ + GPtrArray *data_forms; + TpIntSet *guys; guint trust; @@ -107,7 +110,8 @@ void gabble_presence_cache_maybe_remove (GabblePresenceCache *cache, void gabble_presence_cache_add_own_caps (GabblePresenceCache *cache, const gchar *ver, const GabbleCapabilitySet *cap_set, - const GPtrArray *identities); + const GPtrArray *identities, + GPtrArray *data_forms); const GabbleCapabilityInfo *gabble_presence_cache_peek_own_caps ( GabblePresenceCache *cache, const gchar *ver); @@ -140,6 +144,9 @@ GHashTable* gabble_presence_cache_get_location (GabblePresenceCache *cache, gboolean gabble_presence_cache_disco_in_progress (GabblePresenceCache *cache, TpHandle handle, const gchar *resource); +TpHandle gabble_presence_cache_get_handle (GabblePresenceCache *cache, + GabblePresence *presence); + G_END_DECLS #endif /* __GABBLE_PRESENCE_CACHE_H__ */ diff --git a/src/presence.c b/src/presence.c index a707822a..1ddaedf6 100644 --- a/src/presence.c +++ b/src/presence.c @@ -24,8 +24,9 @@ #include <string.h> #include <telepathy-glib/channel-manager.h> #include <wocky/wocky-utils.h> +#include <wocky/wocky-xep-0115-capabilities.h> -#include "capabilities.h" +#include "gabble/capabilities.h" #include "conn-presence.h" #include "presence-cache.h" #include "namespaces.h" @@ -36,7 +37,12 @@ #include "debug.h" -G_DEFINE_TYPE (GabblePresence, gabble_presence, G_TYPE_OBJECT); +static void xep_0115_capabilities_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (GabblePresence, gabble_presence, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (WOCKY_TYPE_XEP_0115_CAPABILITIES, + xep_0115_capabilities_iface_init); +) typedef struct _Resource Resource; @@ -44,6 +50,7 @@ struct _Resource { gchar *name; guint client_type; GabbleCapabilitySet *cap_set; + GPtrArray *data_forms; guint caps_serial; GabblePresenceId status; gchar *status_message; @@ -58,6 +65,9 @@ struct _GabblePresencePrivate { /* The aggregated caps of all the contacts' resources. */ GabbleCapabilitySet *cap_set; + /* The aggregated data forms of all the contacts' resources */ + GPtrArray *data_forms; + gchar *no_resource_status_message; GSList *resources; guint olpc_views; @@ -72,6 +82,8 @@ _resource_new (gchar *name) new->name = name; new->client_type = 0; new->cap_set = gabble_capability_set_new (); + new->data_forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); new->status = GABBLE_PRESENCE_OFFLINE; new->status_message = NULL; new->priority = 0; @@ -87,6 +99,7 @@ _resource_free (Resource *resource) g_free (resource->name); g_free (resource->status_message); gabble_capability_set_free (resource->cap_set); + g_ptr_array_unref (resource->data_forms); g_slice_free (Resource, resource); } @@ -103,6 +116,7 @@ gabble_presence_finalize (GObject *object) g_slist_free (priv->resources); gabble_capability_set_free (priv->cap_set); + g_ptr_array_unref (priv->data_forms); g_free (presence->nickname); g_free (presence->avatar_sha1); @@ -128,6 +142,8 @@ gabble_presence_init (GabblePresence *self) priv = self->priv; priv->cap_set = gabble_capability_set_new (); + priv->data_forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); priv->resources = NULL; self->status = GABBLE_PRESENCE_UNKNOWN; @@ -198,6 +214,13 @@ gabble_presence_peek_caps (GabblePresence *presence) return presence->priv->cap_set; } +GPtrArray * +gabble_presence_peek_data_forms (GabblePresence *presence) +{ + g_return_val_if_fail (presence != NULL, NULL); + return presence->priv->data_forms; +} + gboolean gabble_presence_has_resources (GabblePresence *self) { @@ -266,10 +289,22 @@ gabble_presence_resource_has_caps (GabblePresence *presence, return FALSE; } +static void +extend_and_dup (GPtrArray *target, + GPtrArray *source) +{ + if (source == NULL) + return; + + g_ptr_array_foreach (source, (GFunc) g_object_ref, NULL); + tp_g_ptr_array_extend (target, source); +} + void gabble_presence_set_capabilities (GabblePresence *presence, const gchar *resource, const GabbleCapabilitySet *cap_set, + const GPtrArray *data_forms, guint serial) { GabblePresencePrivate *priv = presence->priv; @@ -288,11 +323,13 @@ gabble_presence_set_capabilities (GabblePresence *presence, } gabble_capability_set_clear (priv->cap_set); + g_ptr_array_set_size (priv->data_forms, 0); if (resource == NULL) { DEBUG ("Setting capabilities for bare JID"); gabble_capability_set_update (priv->cap_set, cap_set); + extend_and_dup (priv->data_forms, (GPtrArray *) data_forms); return; } @@ -315,6 +352,7 @@ gabble_presence_set_capabilities (GabblePresence *presence, tmp->caps_serial); tmp->caps_serial = serial; gabble_capability_set_clear (tmp->cap_set); + g_ptr_array_set_size (tmp->data_forms, 0); } if (serial >= tmp->caps_serial) @@ -322,11 +360,19 @@ gabble_presence_set_capabilities (GabblePresence *presence, DEBUG ("updating caps for resource %s", resource); gabble_capability_set_update (tmp->cap_set, cap_set); + + /* TODO: deal with duplicates */ + extend_and_dup (tmp->data_forms, (GPtrArray *) data_forms); } } gabble_capability_set_update (priv->cap_set, tmp->cap_set); + + /* TODO: deal with duplicates */ + extend_and_dup (priv->data_forms, tmp->data_forms); } + + g_signal_emit_by_name (presence, "capabilities-changed"); } static Resource * @@ -855,3 +901,20 @@ gabble_presence_get_client_types_array (GabblePresence *presence, return (gchar **) g_ptr_array_free (array, FALSE); } + +static const GPtrArray * +gabble_presence_get_data_forms (WockyXep0115Capabilities *caps) +{ + GabblePresence *presence = GABBLE_PRESENCE (caps); + + return presence->priv->data_forms; +} + +static void +xep_0115_capabilities_iface_init (gpointer g_iface, + gpointer iface_data) +{ + WockyXep0115CapabilitiesInterface *iface = g_iface; + + iface->get_data_forms = gabble_presence_get_data_forms; +} diff --git a/src/presence.h b/src/presence.h index 4f9126d0..50d6945a 100644 --- a/src/presence.h +++ b/src/presence.h @@ -24,7 +24,7 @@ #include <glib-object.h> -#include "capabilities.h" +#include "gabble/capabilities.h" #include "connection.h" #include "types.h" @@ -96,11 +96,13 @@ gboolean gabble_presence_update (GabblePresence *presence, void gabble_presence_set_capabilities (GabblePresence *presence, const gchar *resource, const GabbleCapabilitySet *cap_set, + const GPtrArray *data_forms, guint serial); gboolean gabble_presence_has_cap (GabblePresence *presence, const gchar *ns); GabbleCapabilitySet *gabble_presence_dup_caps (GabblePresence *presence); const GabbleCapabilitySet *gabble_presence_peek_caps (GabblePresence *presence); +GPtrArray *gabble_presence_peek_data_forms (GabblePresence *presence); gboolean gabble_presence_has_resources (GabblePresence *self); diff --git a/src/private-tubes-factory.c b/src/private-tubes-factory.c index 4ec36fd1..52c10a84 100644 --- a/src/private-tubes-factory.c +++ b/src/private-tubes-factory.c @@ -36,8 +36,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_TUBES -#include "caps-channel-manager.h" -#include "capabilities.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "muc-channel.h" @@ -612,7 +611,8 @@ gabble_private_tubes_factory_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) { guint i; diff --git a/src/roomlist-manager.c b/src/roomlist-manager.c index 76abee68..5f38a102 100644 --- a/src/roomlist-manager.c +++ b/src/roomlist-manager.c @@ -33,7 +33,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MUC -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "namespaces.h" diff --git a/src/roster.c b/src/roster.c index 798b83c6..8d8c3b7b 100644 --- a/src/roster.c +++ b/src/roster.c @@ -37,7 +37,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_ROSTER -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "conn-aliasing.h" #include "conn-presence.h" #include "conn-util.h" diff --git a/src/search-manager.c b/src/search-manager.c index d3c1fa57..fec4b74d 100644 --- a/src/search-manager.c +++ b/src/search-manager.c @@ -29,7 +29,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_SEARCH -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "disco.h" diff --git a/src/server-tls-manager.c b/src/server-tls-manager.c index 62ab8865..48364512 100644 --- a/src/server-tls-manager.c +++ b/src/server-tls-manager.c @@ -23,7 +23,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_TLS #include "debug.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "server-tls-channel.h" #include "util.h" diff --git a/tests/test-presence.c b/tests/test-presence.c index 7b9e423a..8319cb2d 100644 --- a/tests/test-presence.c +++ b/tests/test-presence.c @@ -20,6 +20,7 @@ big_test_of_doom (void) GabblePresence *presence; GabbleCapabilitySet *cap_set; time_t now = time (NULL); + GPtrArray *data_forms = g_ptr_array_new (); /* When we create a new presence, we know nothing about the contact in * question's presence. @@ -138,7 +139,7 @@ big_test_of_doom (void) GABBLE_PRESENCE_AVAILABLE, "dingoes", -1, NULL, now)); cap_set = gabble_capability_set_new (); gabble_capability_set_add (cap_set, NS_GOOGLE_FEAT_VOICE); - gabble_presence_set_capabilities (presence, "bar", cap_set, 0); + gabble_presence_set_capabilities (presence, "bar", cap_set, data_forms, 0); gabble_capability_set_free (cap_set); /* no resource with non-negative priority has the Google voice cap */ @@ -149,7 +150,7 @@ big_test_of_doom (void) /* give voice cap to first resource */ cap_set = gabble_capability_set_new (); gabble_capability_set_add (cap_set, NS_GOOGLE_FEAT_VOICE); - gabble_presence_set_capabilities (presence, "foo", cap_set, 0); + gabble_presence_set_capabilities (presence, "foo", cap_set, data_forms, 0); gabble_capability_set_free (cap_set); /* resource has voice cap */ @@ -169,6 +170,7 @@ big_test_of_doom (void) gabble_capability_set_predicate_has, NS_GOOGLE_FEAT_VOICE); g_assert (NULL == resource); + g_ptr_array_unref (data_forms); g_object_unref (presence); } diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index 68f5d2cc..b8b92cbb 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -98,6 +98,7 @@ TWISTED_TESTS = \ text/test-text.py \ tls/server-tls-channel.py \ version.py \ + dataforms.py \ $(NULL) TWISTED_TUBE_TESTS = \ diff --git a/tests/twisted/caps/advertise-contact-caps.py b/tests/twisted/caps/advertise-contact-caps.py index 55630d3e..8affe93b 100644 --- a/tests/twisted/caps/advertise-contact-caps.py +++ b/tests/twisted/caps/advertise-contact-caps.py @@ -46,7 +46,7 @@ def run_test(q, bus, conn, stream, args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]), EventPattern('stream-presence'), ) - (disco_response, namespaces) = disco_caps(q, stream, initial_presence) + (disco_response, namespaces, _) = disco_caps(q, stream, initial_presence) check_caps(namespaces, [ns.TUBES + '/stream#x-abiword']) conn.ContactCapabilities.UpdateCapabilities([ @@ -70,7 +70,7 @@ def run_test(q, bus, conn, stream, media_interface + '/video/h264', ]), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, JINGLE_CAPS + [ns.TUBES + '/stream#x-abiword']) @@ -87,7 +87,7 @@ def run_test(q, bus, conn, stream, media_interface + '/ice-udp', ]), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, JINGLE_CAPS_EXCEPT_GVIDEO + [ns.TUBES + '/stream#x-abiword']) @@ -96,7 +96,7 @@ def run_test(q, bus, conn, stream, conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.AbiWord', [], []), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, JINGLE_CAPS_EXCEPT_GVIDEO) @@ -104,7 +104,7 @@ def run_test(q, bus, conn, stream, conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.KCall', [], []), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, []) @@ -124,7 +124,7 @@ def run_test(q, bus, conn, stream, media_interface + '/video/h264', ]), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.TUBES + '/stream#x-abiword']) @@ -132,7 +132,7 @@ def run_test(q, bus, conn, stream, conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.AbiWord', [], []), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, []) @@ -148,7 +148,7 @@ def run_test(q, bus, conn, stream, initial_audio: True}, ], [media_interface + '/gtalk-p2p']), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.GOOGLE_P2P, ns.JINGLE_TRANSPORT_RAWUDP, ns.JINGLE, @@ -164,7 +164,7 @@ def run_test(q, bus, conn, stream, initial_audio: True}, ], []) ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.JINGLE_TRANSPORT_RAWUDP, ns.JINGLE, @@ -183,7 +183,7 @@ def run_test(q, bus, conn, stream, media_interface + '/video/theora', ]), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.JINGLE_TRANSPORT_ICEUDP, ns.JINGLE_TRANSPORT_RAWUDP, ns.JINGLE, @@ -194,7 +194,7 @@ def run_test(q, bus, conn, stream, conn.ContactCapabilities.UpdateCapabilities([ (cs.CLIENT + '.KCall', [], []), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, []) @@ -205,7 +205,7 @@ def run_test(q, bus, conn, stream, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, }], []), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.FILE_TRANSFER]) @@ -235,7 +235,7 @@ def run_mixed_test (q, bus, conn, stream): ]), ]) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, JINGLE_CAPS) diff --git a/tests/twisted/caps/advertise-legacy.py b/tests/twisted/caps/advertise-legacy.py index b16c7c07..8c386321 100644 --- a/tests/twisted/caps/advertise-legacy.py +++ b/tests/twisted/caps/advertise-legacy.py @@ -27,7 +27,7 @@ def run_test(q, bus, conn, stream): (cs.CHANNEL_TYPE_STREAM_TUBE, 2L**32-1)] remove = [] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, JINGLE_CAPS) @@ -36,7 +36,7 @@ def run_test(q, bus, conn, stream): remove = [cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.CHANNEL_TYPE_STREAM_TUBE] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, []) @@ -50,7 +50,7 @@ def run_test(q, bus, conn, stream): cs.MEDIA_CAP_AUDIO | cs.MEDIA_CAP_GTALKP2P)] remove = [] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.GOOGLE_P2P, ns.JINGLE_TRANSPORT_RAWUDP, ns.JINGLE, @@ -62,7 +62,7 @@ def run_test(q, bus, conn, stream): remove = [cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.CHANNEL_TYPE_STREAM_TUBE] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, []) @@ -71,7 +71,7 @@ def run_test(q, bus, conn, stream): add = [(cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.MEDIA_CAP_AUDIO)] remove = [] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.JINGLE_TRANSPORT_RAWUDP, ns.JINGLE, @@ -83,7 +83,7 @@ def run_test(q, bus, conn, stream): remove = [cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.CHANNEL_TYPE_STREAM_TUBE] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, []) @@ -93,7 +93,7 @@ def run_test(q, bus, conn, stream): cs.MEDIA_CAP_VIDEO | cs.MEDIA_CAP_ICEUDP)] remove = [] caps = conn.Capabilities.AdvertiseCapabilities(add, remove) - (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + (disco_response, namespaces, _, _) = receive_presence_and_ask_caps(q, stream, False) check_caps(namespaces, [ns.JINGLE_TRANSPORT_ICEUDP, ns.JINGLE_TRANSPORT_RAWUDP, ns.JINGLE, diff --git a/tests/twisted/caps/initial-caps.py b/tests/twisted/caps/initial-caps.py index 8d50f0ee..6aef228b 100644 --- a/tests/twisted/caps/initial-caps.py +++ b/tests/twisted/caps/initial-caps.py @@ -15,7 +15,7 @@ import ns def run_test(q, bus, conn, stream): initial_presence = q.expect('stream-presence') - _, namespaces = disco_caps(q, stream, initial_presence) + _, namespaces, _ = disco_caps(q, stream, initial_presence) # For some reason, until we advertise any capabilities, these caps turn # up in our presence diff --git a/tests/twisted/caps/tube-caps.py b/tests/twisted/caps/tube-caps.py index a70f286d..3d3f698d 100644 --- a/tests/twisted/caps/tube-caps.py +++ b/tests/twisted/caps/tube-caps.py @@ -232,7 +232,7 @@ def advertise_caps(q, conn, stream, filters, expected_features, unexpected_featu [(cs.CLIENT + '.Foo', filters, [])]) # Expect Gabble to reply with the correct caps - event, namespaces, signaled_caps = receive_presence_and_ask_caps(q, stream) + event, namespaces, _, signaled_caps = receive_presence_and_ask_caps(q, stream) assertSameElements(expected_caps, signaled_caps) diff --git a/tests/twisted/caps_helper.py b/tests/twisted/caps_helper.py index 7f6f97b9..72c15421 100644 --- a/tests/twisted/caps_helper.py +++ b/tests/twisted/caps_helper.py @@ -205,6 +205,41 @@ def receive_presence_and_ask_caps(q, stream, expect_dbus=True): return disco_caps(q, stream, presence) + (signaled_caps,) +def extract_disco_parts(stanza): + identity_nodes = xpath.queryForNodes('/iq/query/identity', stanza) + assertLength(1, identity_nodes) + identity_node = identity_nodes[0] + + assertEquals('client', identity_node['category']) + assertEquals(config.CLIENT_TYPE, identity_node['type']) + assertEquals(config.PACKAGE_STRING, identity_node['name']) + assertDoesNotContain('xml:lang', identity_node.attributes) + + identity = 'client/%s//%s' % (config.CLIENT_TYPE, config.PACKAGE_STRING) + + features = [] + for feature in xpath.queryForNodes('/iq/query/feature', stanza): + features.append(feature['var']) + + # a quick and ugly data form extractor + x_nodes = xpath.queryForNodes('/iq/query/x', stanza) or [] + dataforms = {} + for form in x_nodes: + name = None + fields = {} + for field in xpath.queryForNodes('/x/field', form): + if field['var'] == 'FORM_TYPE': + name = str(field.firstChildElement()) + else: + values = [str(x) for x in xpath.queryForNodes('/field/value', field)] + + fields[field['var']] = values + + if name is not None: + dataforms[name] = fields + + return ([identity], features, dataforms) + def disco_caps(q, stream, presence): c_nodes = xpath.queryForNodes('/presence/c', presence.stanza) assert c_nodes is not None @@ -225,25 +260,12 @@ def disco_caps(q, stream, presence): event = q.expect('stream-iq', query_ns=ns.DISCO_INFO, iq_id=request['id']) # Check that Gabble's announcing the identity we think it should be. - identity_nodes = xpath.queryForNodes('/iq/query/identity', event.stanza) - assertLength(1, identity_nodes) - identity_node = identity_nodes[0] - - assertEquals('client', identity_node['category']) - assertEquals(config.CLIENT_TYPE, identity_node['type']) - assertEquals(config.PACKAGE_STRING, identity_node['name']) - assertDoesNotContain('xml:lang', identity_node.attributes) - - identity = 'client/%s//%s' % (config.CLIENT_TYPE, config.PACKAGE_STRING) - - features = [] - for feature in xpath.queryForNodes('/iq/query/feature', event.stanza): - features.append(feature['var']) + (identities, features, dataforms) = extract_disco_parts(event.stanza) # Check if the hash matches the announced capabilities - assertEquals(compute_caps_hash([identity], features, {}), ver) + assertEquals(compute_caps_hash(identities, features, dataforms), ver) - return (event, features) + return (event, features, dataforms) def caps_contain(event, cap): node = xpath.queryForNodes('/iq/query/feature[@var="%s"]' diff --git a/tests/twisted/dataforms.py b/tests/twisted/dataforms.py new file mode 100644 index 00000000..e211bd9c --- /dev/null +++ b/tests/twisted/dataforms.py @@ -0,0 +1,32 @@ +""" +Test dataforms +""" + +from servicetest import sync_dbus, assertEquals +from gabbletest import exec_test, sync_stream +import ns +import constants as cs +from caps_helper import receive_presence_and_ask_caps + +def test(q, bus, conn, stream): + q.expect('stream-presence') + + # gabble won't try to represent the client if doesn't have any + # RCCs or HCTs, so let's add some + conn.ContactCapabilities.UpdateCapabilities( + [ + ('dataformtest', [], ['banan', 'hi']) + ]) + + _, _, forms, _ = receive_presence_and_ask_caps(q, stream) + + assertEquals({'gabble:test:channel:manager:data:form': + {'cheese': ['omgnothorriblecheese'], + 'running_out_of': ['ideas', 'cake'], + 'favourite_crane': ['a tall one', 'a short one'], + 'animal': ['badger', 'snake', 'weasel'] + } + }, forms) + +if __name__ == '__main__': + exec_test(test) diff --git a/tests/twisted/jingle-share/file_transfer_helper.py b/tests/twisted/jingle-share/file_transfer_helper.py index 0fed8952..d555b008 100644 --- a/tests/twisted/jingle-share/file_transfer_helper.py +++ b/tests/twisted/jingle-share/file_transfer_helper.py @@ -11,7 +11,8 @@ import ns from caps_helper import text_fixed_properties, text_allowed_properties, \ stream_tube_fixed_properties, stream_tube_allowed_properties, \ dbus_tube_fixed_properties, dbus_tube_allowed_properties, \ - ft_fixed_properties, ft_allowed_properties, compute_caps_hash + ft_fixed_properties, ft_allowed_properties, compute_caps_hash, \ + extract_disco_parts from twisted.words.xish import domish, xpath @@ -59,6 +60,7 @@ generic_caps = [(text_fixed_properties, text_allowed_properties), class FileTransferTest(object): caps_identities = None caps_features = None + caps_dataforms = None caps_ft = None def __init__(self, file, address_type, access_control, access_control_param): @@ -141,12 +143,13 @@ class FileTransferTest(object): c = nodes[0] if 'share-v1' in c.getAttribute('ext'): assert FileTransferTest.caps_identities is not None and \ - FileTransferTest.caps_features is not None + FileTransferTest.caps_features is not None and \ + FileTransferTest.caps_dataforms is not None new_hash = compute_caps_hash(FileTransferTest.caps_identities, FileTransferTest.caps_features + \ [ns.GOOGLE_FEAT_SHARE], - {}) + FileTransferTest.caps_dataforms) # Replace ver hash from one with file-transfer ns to one without FileTransferTest.caps_ft = c.attributes['ver'] c.attributes['ver'] = new_hash @@ -173,7 +176,9 @@ class FileTransferTest(object): if iq.getAttribute('type') == 'result': if FileTransferTest.caps_identities is None or \ - FileTransferTest.caps_features is None: + FileTransferTest.caps_features is None or \ + FileTransferTest.caps_dataforms is None: + # create our own identity identity_nodes = xpath.queryForNodes('/iq/query/identity', iq) assertLength(1, identity_nodes) identity_node = identity_nodes[0] @@ -183,22 +188,23 @@ class FileTransferTest(object): identity_name = identity_node['name'] identity = '%s/%s//%s' % (identity_category, identity_type, identity_name) - FileTransferTest.caps_identities = [identity] - FileTransferTest.caps_features = [] - for feature in xpath.queryForNodes('/iq/query/feature', iq): - FileTransferTest.caps_features.append(feature['var']) + _, features, dataforms = extract_disco_parts(iq) + + FileTransferTest.caps_identities = [identity] + FileTransferTest.caps_features = features + FileTransferTest.caps_dataforms = dataforms # Check if the hash matches the announced capabilities assertEquals(compute_caps_hash(FileTransferTest.caps_identities, FileTransferTest.caps_features, - {}), ver) + FileTransferTest.caps_dataforms), ver) if ver == FileTransferTest.caps_ft: caps_share = compute_caps_hash(FileTransferTest.caps_identities, FileTransferTest.caps_features + \ [ns.GOOGLE_FEAT_SHARE], - {}) + FileTransferTest.caps_dataforms) n = query.attributes['node'].replace(ver, caps_share) query.attributes['node'] = n @@ -214,7 +220,7 @@ class FileTransferTest(object): caps_share = compute_caps_hash(FileTransferTest.caps_identities, FileTransferTest.caps_features + \ [ns.GOOGLE_FEAT_SHARE], - {}) + FileTransferTest.caps_dataforms) if ver == caps_share: n = query.attributes['node'].replace(ver, |