From 57fea06e435ecd677875b92694cb5c4f3c08f4b0 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 28 Oct 2013 13:27:03 +0000 Subject: [PATCH 07/15] TpProtocol: add high-level API for the Addressing interface --- docs/reference/telepathy-glib-sections.txt | 7 + examples/cm/echo-message-parts/protocol.c | 68 ++++++++ telepathy-glib/protocol.c | 246 +++++++++++++++++++++++++++-- telepathy-glib/protocol.h | 35 ++++ tests/dbus/protocol-objects.c | 60 +++++++ 5 files changed, 403 insertions(+), 13 deletions(-) diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt index df10421..36845e6 100644 --- a/docs/reference/telepathy-glib-sections.txt +++ b/docs/reference/telepathy-glib-sections.txt @@ -6180,6 +6180,13 @@ tp_protocol_normalize_contact_finish tp_protocol_get_avatar_requirements +tp_protocol_get_addressable_uri_schemes +tp_protocol_get_addressable_vcard_fields +tp_protocol_normalize_contact_uri_async +tp_protocol_normalize_contact_uri_finish +tp_protocol_normalize_vcard_address_async +tp_protocol_normalize_vcard_address_finish + tp_cli_protocol_call_identify_account tp_cli_protocol_call_normalize_contact tp_cli_protocol_callback_for_identify_account diff --git a/examples/cm/echo-message-parts/protocol.c b/examples/cm/echo-message-parts/protocol.c index ae8894a..7917ba0 100644 --- a/examples/cm/echo-message-parts/protocol.c +++ b/examples/cm/echo-message-parts/protocol.c @@ -226,6 +226,72 @@ dup_supported_vcard_fields (TpBaseProtocol *self) return g_strdupv ((GStrv) addressing_vcard_fields); } +static gchar * +normalize_vcard_address (TpBaseProtocol *self, + const gchar *vcard_field, + const gchar *vcard_address, + GError **error) +{ + if (g_ascii_strcasecmp (vcard_field, "x-jabber") == 0) + { + /* This is not really how you normalize a JID but it's good enough + * for an example. In real life you'd do syntax-checking beyond + * "is it empty?", stringprep, and so on. Here, we just assume + * non-empty means valid, and lower-case means normalized. */ + + if (tp_str_empty (vcard_address)) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "The empty string is not a valid JID"); + return NULL; + } + + return g_utf8_strdown (vcard_address, -1); + } + else + { + g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, + "Don't know how to normalize vCard field: %s", vcard_field); + return NULL; + } +} + +static gchar * +normalize_contact_uri (TpBaseProtocol *self, + const gchar *uri, + GError **error) +{ + gchar *scheme = g_uri_parse_scheme (uri); + + if (g_ascii_strcasecmp (scheme, "xmpp") == 0) + { + gchar *ret = NULL; + gchar *id; + + id = normalize_vcard_address (self, "x-jabber", uri + 5, error); + + if (id != NULL) + ret = g_strdup_printf ("%s:%s", scheme, id); + + g_free (scheme); + g_free (id); + return ret; + } + else if (scheme == NULL) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Not a valid URI: %s", uri); + return NULL; + } + else + { + g_set_error (error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, + "Don't know how to normalize URIs of that scheme: %s", scheme); + g_free (scheme); + return NULL; + } +} + static void example_echo_2_protocol_class_init ( ExampleEcho2ProtocolClass *klass) @@ -248,4 +314,6 @@ addressing_iface_init (TpProtocolAddressingInterface *iface) { iface->dup_supported_vcard_fields = dup_supported_vcard_fields; iface->dup_supported_uri_schemes = dup_supported_uri_schemes; + iface->normalize_vcard_address = normalize_vcard_address; + iface->normalize_contact_uri = normalize_contact_uri; } diff --git a/telepathy-glib/protocol.c b/telepathy-glib/protocol.c index 7296db8..9b23aab 100644 --- a/telepathy-glib/protocol.c +++ b/telepathy-glib/protocol.c @@ -146,6 +146,8 @@ struct _TpProtocolPrivate TpCapabilities *capabilities; TpAvatarRequirements *avatar_req; gchar *cm_name; + GStrv addressable_vcard_fields; + GStrv addressable_uri_schemes; }; enum @@ -160,6 +162,8 @@ enum PROP_AUTHENTICATION_TYPES, PROP_AVATAR_REQUIREMENTS, PROP_CM_NAME, + PROP_ADDRESSABLE_VCARD_FIELDS, + PROP_ADDRESSABLE_URI_SCHEMES, N_PROPS }; @@ -294,6 +298,15 @@ tp_protocol_get_property (GObject *object, g_value_set_string (value, tp_protocol_get_cm_name (self)); break; + case PROP_ADDRESSABLE_VCARD_FIELDS: + g_value_set_boxed (value, tp_protocol_get_addressable_vcard_fields ( + self)); + break; + + case PROP_ADDRESSABLE_URI_SCHEMES: + g_value_set_boxed (value, tp_protocol_get_addressable_uri_schemes (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -395,6 +408,8 @@ tp_protocol_finalize (GObject *object) g_free (self->priv->english_name); g_free (self->priv->icon_name); g_free (self->priv->cm_name); + g_strfreev (self->priv->addressable_vcard_fields); + g_strfreev (self->priv->addressable_uri_schemes); if (self->priv->protocol_properties != NULL) g_hash_table_unref (self->priv->protocol_properties); @@ -455,6 +470,19 @@ title_case (const gchar *s) return g_strdup_printf ("%s%s", buf, g_utf8_next_char (s)); } +static GStrv +asv_strdupv_or_empty (const GHashTable *asv, + const gchar *key) +{ + const gchar * const *strings = tp_asv_get_boxed (asv, key, G_TYPE_STRV); + static const gchar * const no_strings[] = { NULL }; + + if (strings != NULL) + return g_strdupv ((GStrv) strings); + else + return g_strdupv ((GStrv) no_strings); +} + static void tp_protocol_constructed (GObject *object) { @@ -465,7 +493,6 @@ tp_protocol_constructed (GObject *object) const gchar *s; const GPtrArray *rccs; gboolean had_immutables = TRUE; - const gchar * const *auth_types = NULL; const gchar * const *interfaces; if (chain_up != NULL) @@ -527,19 +554,9 @@ tp_protocol_constructed (GObject *object) if (rccs != NULL) self->priv->capabilities = _tp_capabilities_new (rccs, FALSE); - auth_types = tp_asv_get_boxed ( + self->priv->authentication_types = asv_strdupv_or_empty ( self->priv->protocol_properties, - TP_PROP_PROTOCOL_AUTHENTICATION_TYPES, G_TYPE_STRV); - - if (auth_types != NULL) - { - self->priv->authentication_types = g_strdupv ((GStrv) auth_types); - } - else - { - gchar *tmp[] = { NULL }; - self->priv->authentication_types = g_strdupv (tmp); - } + TP_PROP_PROTOCOL_AUTHENTICATION_TYPES); interfaces = tp_asv_get_strv (self->priv->protocol_properties, TP_PROP_PROTOCOL_INTERFACES); @@ -568,6 +585,17 @@ tp_protocol_constructed (GObject *object) TP_PROP_PROTOCOL_INTERFACE_AVATARS_MAXIMUM_AVATAR_BYTES, NULL)); } + if (tp_proxy_has_interface_by_id (self, + TP_IFACE_QUARK_PROTOCOL_INTERFACE_ADDRESSING)) + { + self->priv->addressable_vcard_fields = asv_strdupv_or_empty ( + self->priv->protocol_properties, + TP_PROP_PROTOCOL_INTERFACE_ADDRESSING_ADDRESSABLE_VCARD_FIELDS); + self->priv->addressable_uri_schemes = asv_strdupv_or_empty ( + self->priv->protocol_properties, + TP_PROP_PROTOCOL_INTERFACE_ADDRESSING_ADDRESSABLE_URI_SCHEMES); + } + /* become ready immediately */ _tp_proxy_set_feature_prepared (proxy, TP_PROTOCOL_FEATURE_PARAMETERS, had_immutables); @@ -780,6 +808,45 @@ tp_protocol_class_init (TpProtocolClass *klass) NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * TpProtocol:addressable-vcard-fields: + * + * A non-%NULL #GStrv of vCard fields supported by this protocol. + * If this protocol does not support addressing contacts by a vCard field, + * the list is empty. + * + * For instance, a SIP connection manager that supports calling contacts + * by SIP URI (vCard field SIP) or telephone number (vCard field TEL) + * might have { "sip", "tel", NULL }. + * + * Since: 0.UNRELEASED + */ + g_object_class_install_property (object_class, PROP_ADDRESSABLE_VCARD_FIELDS, + g_param_spec_boxed ("addressable-vcard-fields", + "AddressableVCardFields", + "A list of vCard fields", + G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * TpProtocol:addressable-uri-schemes: + * + * A non-%NULL #GStrv of URI schemes supported by this protocol. + * If this protocol does not support addressing contacts by URI, + * the list is empty. + * + * For instance, a SIP connection manager that supports calling contacts + * by SIP URI (sip:alice@example.com, sips:bob@example.com) + * or telephone number (tel:+1-555-0123) might have + * { "sip", "sips", "tel", NULL }. + * + * Since: 0.UNRELEASED + */ + g_object_class_install_property (object_class, PROP_ADDRESSABLE_URI_SCHEMES, + g_param_spec_boxed ("addressable-uri-schemes", + "AddressableURISchemes", + "A list of URI schemes", + G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + proxy_class->list_features = tp_protocol_list_features; proxy_class->must_have_unique_name = FALSE; proxy_class->interface = TP_IFACE_QUARK_PROTOCOL; @@ -1932,3 +1999,156 @@ tp_protocol_identify_account_finish (TpProtocol *self, return g_task_propagate_pointer (G_TASK (result), error); } + +/** + * tp_protocol_normalize_contact_uri_async: + * @self: a protocol + * @uri: a contact URI, possibly invalid + * @cancellable: (allow-none): may be used to cancel the async request + * @callback: (scope async): a callback to call when the request is satisfied + * @user_data: (closure) (allow-none): data to pass to @callback + * + * Perform best-effort offline contact normalization, for a contact in + * the form of a URI. This method will fail if the URI is not in a + * scheme supported by this protocol or connection manager. + * + * Since: 0.UNRELEASED + */ +void +tp_protocol_normalize_contact_uri_async (TpProtocol *self, + const gchar *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_return_if_fail (TP_IS_PROTOCOL (self)); + g_return_if_fail (uri != NULL); + /* this makes no sense to call for its side-effects */ + g_return_if_fail (callback != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, tp_protocol_normalize_contact_uri_async); + + tp_cli_protocol_interface_addressing_call_normalize_contact_uri (self, -1, + uri, tp_protocol_async_string_cb, task, g_object_unref, NULL); +} + +/** + * tp_protocol_normalize_contact_uri_finish: + * @self: a protocol + * @result: a #GAsyncResult + * @error: a #GError to fill + * + * Interpret the result of tp_protocol_normalize_contact_uri_async(). + * + * Returns: (transfer full): the normalized form of @uri, + * or %NULL on error + * Since: 0.UNRELEASED + */ +gchar * +tp_protocol_normalize_contact_uri_finish (TpProtocol *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, + tp_protocol_normalize_contact_uri_async), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * tp_protocol_normalize_vcard_address_async: + * @self: a protocol + * @field: a vCard field + * @value: an address that is a value of @field + * @cancellable: (allow-none): may be used to cancel the async request + * @callback: (scope async): a callback to call when the request is satisfied + * @user_data: (closure) (allow-none): data to pass to @callback + * + * Perform best-effort offline contact normalization, for a contact in + * the form of a vCard field. This method will fail if the vCard field + * is not supported by this protocol or connection manager. + * + * Since: 0.UNRELEASED + */ +void +tp_protocol_normalize_vcard_address_async (TpProtocol *self, + const gchar *field, + const gchar *value, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_return_if_fail (TP_IS_PROTOCOL (self)); + g_return_if_fail (!tp_str_empty (field)); + g_return_if_fail (value != NULL); + /* this makes no sense to call for its side-effects */ + g_return_if_fail (callback != NULL); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, tp_protocol_normalize_vcard_address_async); + + tp_cli_protocol_interface_addressing_call_normalize_vcard_address (self, -1, + field, value, tp_protocol_async_string_cb, task, g_object_unref, NULL); +} + +/** + * tp_protocol_normalize_vcard_address_finish: + * @self: a protocol + * @result: a #GAsyncResult + * @error: a #GError to fill + * + * Interpret the result of tp_protocol_normalize_vcard_address_async(). + * + * Returns: (transfer full): the normalized form of @value, + * or %NULL on error + * Since: 0.UNRELEASED + */ +gchar * +tp_protocol_normalize_vcard_address_finish (TpProtocol *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, + tp_protocol_normalize_vcard_address_async), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * tp_protocol_get_addressable_vcard_fields: + * @self: a protocol object + * + * + * + * Returns: (transfer none): the value of #TpProtocol:addressable-vcard-fields + * Since: 0.UNRELEASED + */ +const gchar * const * +tp_protocol_get_addressable_vcard_fields (TpProtocol *self) +{ + g_return_val_if_fail (TP_IS_PROTOCOL (self), NULL); + return (const gchar * const *) self->priv->addressable_vcard_fields; +} + +/** + * tp_protocol_get_addressable_uri_schemes: + * @self: a protocol object + * + * + * + * Returns: (transfer none): the value of #TpProtocol:addressable-uri-schemes + * Since: 0.UNRELEASED + */ +const gchar * const * +tp_protocol_get_addressable_uri_schemes (TpProtocol *self) +{ + g_return_val_if_fail (TP_IS_PROTOCOL (self), NULL); + return (const gchar * const *) self->priv->addressable_uri_schemes; +} diff --git a/telepathy-glib/protocol.h b/telepathy-glib/protocol.h index 05f7781..8555144 100644 --- a/telepathy-glib/protocol.h +++ b/telepathy-glib/protocol.h @@ -113,6 +113,16 @@ const gchar * const * /* gtk-doc sucks */ tp_protocol_get_authentication_types (TpProtocol *self); +_TP_AVAILABLE_IN_UNRELEASED +const gchar * const * +/* ... */ +tp_protocol_get_addressable_vcard_fields (TpProtocol *self); + +_TP_AVAILABLE_IN_UNRELEASED +const gchar * const * +/* ... */ +tp_protocol_get_addressable_uri_schemes (TpProtocol *self); + #define TP_PROTOCOL_FEATURE_CORE \ (tp_protocol_get_feature_quark_core ()) GQuark tp_protocol_get_feature_quark_core (void) G_GNUC_CONST; @@ -149,6 +159,31 @@ gchar *tp_protocol_identify_account_finish (TpProtocol *self, GAsyncResult *result, GError **error); +_TP_AVAILABLE_IN_UNRELEASED +void tp_protocol_normalize_contact_uri_async (TpProtocol *self, + const gchar *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +_TP_AVAILABLE_IN_UNRELEASED +gchar *tp_protocol_normalize_contact_uri_finish (TpProtocol *self, + GAsyncResult *result, + GError **error); + +_TP_AVAILABLE_IN_UNRELEASED +void tp_protocol_normalize_vcard_address_async (TpProtocol *self, + const gchar *field, + const gchar *value, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +_TP_AVAILABLE_IN_UNRELEASED +gchar *tp_protocol_normalize_vcard_address_finish (TpProtocol *self, + GAsyncResult *result, + GError **error); + G_END_DECLS #include diff --git a/tests/dbus/protocol-objects.c b/tests/dbus/protocol-objects.c index fee4afe..b1c0fda 100644 --- a/tests/dbus/protocol-objects.c +++ b/tests/dbus/protocol-objects.c @@ -565,6 +565,66 @@ test_normalize (Test *test, g_assert_cmpstr (s, ==, NULL); g_clear_object (&result); g_clear_error (&test->error); + + tp_protocol_normalize_contact_uri_async (test->protocol, + "xmpp:MiXeDcAsE", NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + s = tp_protocol_normalize_contact_uri_finish (test->protocol, result, + &test->error); + g_assert_no_error (test->error); + g_assert_cmpstr (s, ==, "xmpp:mixedcase"); + g_clear_object (&result); + g_free (s); + + tp_protocol_normalize_contact_uri_async (test->protocol, + "xmpp:", NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + s = tp_protocol_normalize_contact_uri_finish (test->protocol, result, + &test->error); + g_assert_cmpstr (s, ==, NULL); + g_assert_error (test->error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT); + g_clear_object (&result); + g_clear_error (&test->error); + + tp_protocol_normalize_contact_uri_async (test->protocol, + "http://example.com", NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + s = tp_protocol_normalize_contact_uri_finish (test->protocol, result, + &test->error); + g_assert_cmpstr (s, ==, NULL); + g_assert_error (test->error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED); + g_clear_object (&result); + g_clear_error (&test->error); + + tp_protocol_normalize_vcard_address_async (test->protocol, + "x-jabber", "MiXeDcAsE", NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + s = tp_protocol_normalize_vcard_address_finish (test->protocol, result, + &test->error); + g_assert_no_error (test->error); + g_assert_cmpstr (s, ==, "mixedcase"); + g_clear_object (&result); + g_free (s); + + tp_protocol_normalize_vcard_address_async (test->protocol, + "x-jabber", "", NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + s = tp_protocol_normalize_vcard_address_finish (test->protocol, result, + &test->error); + g_assert_error (test->error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT); + g_assert_cmpstr (s, ==, NULL); + g_clear_object (&result); + g_clear_error (&test->error); + + tp_protocol_normalize_vcard_address_async (test->protocol, + "x-skype", "", NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + s = tp_protocol_normalize_vcard_address_finish (test->protocol, result, + &test->error); + g_assert_error (test->error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED); + g_assert_cmpstr (s, ==, NULL); + g_clear_object (&result); + g_clear_error (&test->error); } static void -- 1.8.4.rc3