summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2013-11-11 19:45:13 +0000
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2013-11-11 19:45:13 +0000
commitfc228b814e01a9cd231d4b9d8e4709effafd42fe (patch)
treebc1538923274ef61ea8ec303e06782d0c65091f7
parent7155875747057e6a5c4bac068a59d433720a42cb (diff)
parent1aa78d2cf4d9b44c15c5b627eef210943646e3fb (diff)
Merge branch 'master' into next
Conflicts: docs/reference/telepathy-glib-sections.txt
-rw-r--r--configure.ac4
-rw-r--r--docs/reference/telepathy-glib/telepathy-glib-sections.txt23
-rw-r--r--examples/client/python/inspect-cm.py23
-rw-r--r--examples/cm/echo-message-parts/protocol.c70
-rw-r--r--telepathy-glib/presence-mixin.c219
-rw-r--r--telepathy-glib/presence-mixin.h35
-rw-r--r--telepathy-glib/protocol.c638
-rw-r--r--telepathy-glib/protocol.h63
-rw-r--r--tests/dbus/protocol-objects.c152
9 files changed, 1166 insertions, 61 deletions
diff --git a/configure.ac b/configure.ac
index ec4379e2b..d695f11d9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -238,10 +238,10 @@ AC_SUBST(tpglibtestsdir)
dnl Check for Glib
PKG_CHECK_MODULES(GLIB,
- [glib-2.0 >= 2.34.0, gobject-2.0 >= 2.34.0, gio-2.0 >= 2.34.0])
+ [glib-2.0 >= 2.36, gobject-2.0 >= 2.36, gio-2.0 >= 2.36])
AC_DEFINE([GLIB_VERSION_MIN_REQUIRED], [GLIB_VERSION_2_34], [Ignore post 2.34 deprecations])
-AC_DEFINE([GLIB_VERSION_MAX_ALLOWED], [GLIB_VERSION_2_34], [Prevent post 2.34 APIs])
+AC_DEFINE([GLIB_VERSION_MAX_ALLOWED], [GLIB_VERSION_2_36], [Prevent post 2.36 APIs])
dnl Check for GIO-Unix
PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0],
diff --git a/docs/reference/telepathy-glib/telepathy-glib-sections.txt b/docs/reference/telepathy-glib/telepathy-glib-sections.txt
index e7c96f7f7..a5a5d0ff4 100644
--- a/docs/reference/telepathy-glib/telepathy-glib-sections.txt
+++ b/docs/reference/telepathy-glib/telepathy-glib-sections.txt
@@ -1701,6 +1701,13 @@ TP_CONTACTS_MIXIN
<FILE>presence-mixin</FILE>
TpPresenceStatusOptionalArgumentSpec
TpPresenceStatusSpec
+tp_presence_status_spec_can_set_on_self
+tp_presence_status_spec_get_name
+tp_presence_status_spec_get_presence_type
+tp_presence_status_spec_has_message
+tp_presence_status_spec_new
+tp_presence_status_spec_copy
+tp_presence_status_spec_free
TpPresenceMixinStatusAvailableFunc
TpPresenceMixinGetContactStatusesFunc
TpPresenceMixinSetOwnStatusFunc
@@ -1731,6 +1738,8 @@ TpPresenceMixinClassPrivate
<SUBSECTION Standard>
TP_PRESENCE_MIXIN_CLASS
TP_PRESENCE_MIXIN
+TpPresenceStatusSpecPrivate
+tp_presence_status_spec_get_type
</SECTION>
<SECTION>
@@ -4904,7 +4913,21 @@ tp_protocol_get_english_name
tp_protocol_get_icon_name
tp_protocol_get_vcard_field
tp_protocol_get_authentication_types
+tp_protocol_identify_account_async
+tp_protocol_identify_account_finish
+tp_protocol_normalize_contact_async
+tp_protocol_normalize_contact_finish
+<SUBSECTION>
tp_protocol_get_avatar_requirements
+<SUBSECTION>
+tp_protocol_dup_presence_statuses
+<SUBSECTION>
+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
<SUBSECTION Standard>
tp_protocol_get_type
TP_PROTOCOL
diff --git a/examples/client/python/inspect-cm.py b/examples/client/python/inspect-cm.py
index c653cf716..0564078a9 100644
--- a/examples/client/python/inspect-cm.py
+++ b/examples/client/python/inspect-cm.py
@@ -28,9 +28,11 @@ def describe(cm):
print("\t\tNo default")
def manager_prepared_cb(cm, result, loop):
- cm.prepare_finish(result)
- describe(cm)
- loop.quit()
+ try:
+ cm.prepare_finish(result)
+ describe(cm)
+ finally:
+ loop.quit()
def inspect(name):
cm = Tp.ConnectionManager(
@@ -41,13 +43,14 @@ def inspect(name):
cm.prepare_async(None, cm, loop)
def cms_cb(source, result, loop):
- cms = Tp.list_connection_managers_finish(result)
-
- for cm in cms:
- describe(cm)
- print("")
-
- loop.quit()
+ try:
+ cms = Tp.list_connection_managers_finish(result)
+
+ for cm in cms:
+ describe(cm)
+ print("")
+ finally:
+ loop.quit()
if __name__ == '__main__':
loop = GObject.MainLoop()
diff --git a/examples/cm/echo-message-parts/protocol.c b/examples/cm/echo-message-parts/protocol.c
index ef3135063..16cef0920 100644
--- a/examples/cm/echo-message-parts/protocol.c
+++ b/examples/cm/echo-message-parts/protocol.c
@@ -116,7 +116,7 @@ identify_account (TpBaseProtocol *self G_GNUC_UNUSED,
const gchar *account = tp_asv_get_string (asv, "account");
if (account != NULL)
- return g_strdup (account);
+ return g_utf8_strdown (account, -1);
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"'account' parameter not given");
@@ -227,6 +227,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)
@@ -249,4 +315,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/presence-mixin.c b/telepathy-glib/presence-mixin.c
index 44e57ebae..f25663181 100644
--- a/telepathy-glib/presence-mixin.c
+++ b/telepathy-glib/presence-mixin.c
@@ -96,7 +96,10 @@
* @optional_arguments: An array of #TpPresenceStatusOptionalArgumentSpec
* structures representing the optional arguments for this status, terminated
* by a NULL name. If there are no optional arguments for a status, this can
- * be NULL.
+ * be NULL. In modern Telepathy connection managers, the only optional
+ * argument should be a string (type "s") named "message" on statuses
+ * that have an optional human-readable message. All other optional arguments
+ * are deprecated.
*
* Structure specifying a supported presence status.
*
@@ -118,9 +121,13 @@
* In addition to the fields documented here, there are two gpointer fields
* which must currently be %NULL. A meaning may be defined for these in a
* future version of telepathy-glib.
+ *
+ * In modern Telepathy connection managers, the only optional
+ * argument should be a %G_TYPE_STRING named "message", on statuses
+ * that have an optional human-readable message. All other optional arguments
+ * are deprecated.
*/
-
/**
* TpPresenceMixinStatusAvailableFunc:
* @obj: An instance of a #TpBaseConnection subclass implementing the presence
@@ -298,6 +305,11 @@ deep_copy_hashtable (GHashTable *hash_table)
* Construct a presence status structure. You should free the returned
* structure with #tp_presence_status_free.
*
+ * In modern Telepathy connection managers, the only optional
+ * argument should be a %G_TYPE_STRING named "message", on statuses
+ * that have an optional human-readable message. All other optional arguments
+ * are deprecated.
+ *
* Returns: A pointer to the newly allocated presence status structure.
*/
TpPresenceStatus *
@@ -669,27 +681,17 @@ tp_presence_mixin_get_dbus_property (GObject *object,
for (i=0; mixin_cls->statuses[i].name != NULL; i++)
{
- const TpPresenceStatusOptionalArgumentSpec *specs;
- int j;
- gboolean message = FALSE;
+ gboolean message;
/* we include statuses here even if they're not available
* to set on yourself */
if (!check_status_available (object, mixin_cls, i, NULL, FALSE))
continue;
- specs = mixin_cls->statuses[i].optional_arguments;
-
- for (j = 0; specs != NULL && specs[j].name != NULL; j++)
- {
- if (!tp_strdiff (specs[j].name, "message"))
- {
- message = TRUE;
- break;
- }
- }
+ message = tp_presence_status_spec_has_message (
+ &mixin_cls->statuses[i]);
- status = tp_value_array_build (3,
+ status = tp_value_array_build (3,
G_TYPE_UINT, (guint) mixin_cls->statuses[i].presence_type,
G_TYPE_BOOLEAN, mixin_cls->statuses[i].self,
G_TYPE_BOOLEAN, message,
@@ -933,3 +935,188 @@ tp_presence_mixin_register_with_contacts_mixin (GObject *obj)
tp_presence_mixin_fill_contact_attributes);
}
+/* For now, self->priv is just self if heap-allocated, NULL if not. */
+static gboolean
+_tp_presence_status_spec_is_heap_allocated (const TpPresenceStatusSpec *self)
+{
+ return (self->priv == (TpPresenceStatusSpecPrivate *) self);
+}
+
+/**
+ * tp_presence_status_spec_get_presence_type:
+ * @self: a presence status specification
+ *
+ * Return the category into which this presence type falls. For instance,
+ * for XMPP's "" (do not disturb) status, this would return
+ * %TP_CONNECTION_PRESENCE_TYPE_BUSY.
+ *
+ * Returns: a #TpConnectionPresenceType
+ * Since: 0.UNRELEASED
+ */
+TpConnectionPresenceType
+tp_presence_status_spec_get_presence_type (const TpPresenceStatusSpec *self)
+{
+ g_return_val_if_fail (self != NULL, TP_CONNECTION_PRESENCE_TYPE_UNSET);
+
+ return self->presence_type;
+}
+
+/**
+ * tp_presence_status_spec_get_name:
+ * @self: a presence status specification
+ *
+ * <!-- -->
+ *
+ * Returns: (transfer none): the name of this presence status,
+ * such as "available" or "out-to-lunch".
+ * Since: 0.UNRELEASED
+ */
+const gchar *
+tp_presence_status_spec_get_name (const TpPresenceStatusSpec *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ return self->name;
+}
+
+/**
+ * tp_presence_status_spec_can_set_on_self:
+ * @self: a presence status specification
+ *
+ * <!-- -->
+ *
+ * Returns: %TRUE if the user can set this presence status on themselves (most
+ * statuses), or %FALSE if they cannot directly set it on
+ * themselves (typically used for %TP_CONNECTION_PRESENCE_TYPE_OFFLINE
+ * and %TP_CONNECTION_PRESENCE_TYPE_ERROR)
+ * Since: 0.UNRELEASED
+ */
+gboolean
+tp_presence_status_spec_can_set_on_self (const TpPresenceStatusSpec *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+
+ return self->self;
+}
+
+/**
+ * tp_presence_status_spec_has_message:
+ * @self: a presence status specification
+ *
+ * <!-- -->
+ *
+ * Returns: %TRUE if this presence status is accompanied by an optional
+ * human-readable message
+ * Since: 0.UNRELEASED
+ */
+gboolean
+tp_presence_status_spec_has_message (const TpPresenceStatusSpec *self)
+{
+ const TpPresenceStatusOptionalArgumentSpec *arg;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+
+ if (self->optional_arguments == NULL)
+ return FALSE;
+
+ for (arg = self->optional_arguments; arg->name != NULL; arg++)
+ {
+ if (!tp_strdiff (arg->name, "message") && !tp_strdiff (arg->dtype, "s"))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * tp_presence_status_spec_new:
+ * @name: the name of the new presence status
+ * @type: the category into which this presence status falls
+ * @can_set_on_self: %TRUE if the user can set this presence status
+ * on themselves
+ * @has_message: %TRUE if this presence status is accompanied by an
+ * optional human-readable message
+ *
+ * <!-- -->
+ *
+ * Returns: (transfer full): a new #TpPresenceStatusSpec
+ * Since: 0.UNRELEASED
+ */
+TpPresenceStatusSpec *
+tp_presence_status_spec_new (const gchar *name,
+ TpConnectionPresenceType type,
+ gboolean can_set_on_self,
+ gboolean has_message)
+{
+ TpPresenceStatusSpec *ret;
+ static const TpPresenceStatusOptionalArgumentSpec yes_it_has_a_message[] = {
+ { "message", "s" },
+ { NULL }
+ };
+
+ g_return_val_if_fail (!tp_str_empty (name), NULL);
+ g_return_val_if_fail (type >= 0 && type < TP_NUM_CONNECTION_PRESENCE_TYPES,
+ NULL);
+
+ ret = g_slice_new0 (TpPresenceStatusSpec);
+
+ ret->name = g_strdup (name);
+ ret->presence_type = type;
+ ret->self = can_set_on_self;
+
+ if (has_message)
+ ret->optional_arguments = yes_it_has_a_message;
+ else
+ ret->optional_arguments = NULL;
+
+ /* dummy marker for "this is on the heap" rather than a real struct */
+ ret->priv = (TpPresenceStatusSpecPrivate *) ret;
+
+ return ret;
+}
+
+/**
+ * tp_presence_status_spec_copy:
+ * @self: a presence status specification
+ *
+ * Copy a presence status specification.
+ *
+ * If @self has optional arguments other than a string named "message",
+ * they are not copied. Optional arguments with other names or types
+ * are deprecated.
+ *
+ * Returns: (transfer full): a new #TpPresenceStatusSpec resembling @self
+ * Since: 0.UNRELEASED
+ */
+TpPresenceStatusSpec *
+tp_presence_status_spec_copy (const TpPresenceStatusSpec *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ return tp_presence_status_spec_new (self->name, self->presence_type,
+ self->self, tp_presence_status_spec_has_message (self));
+}
+
+/**
+ * tp_presence_status_spec_free:
+ * @self: (transfer full): a presence status specification
+ *
+ * Free a presence status specification produced by
+ * tp_presence_status_spec_new() or tp_presence_status_spec_copy().
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_presence_status_spec_free (TpPresenceStatusSpec *self)
+{
+ g_return_if_fail (_tp_presence_status_spec_is_heap_allocated (self));
+
+ /* This struct was designed to always be on the stack, so freeing this
+ * needs a non-const-correct cast */
+ g_free ((gchar *) self->name);
+
+ g_slice_free (TpPresenceStatusSpec, self);
+}
+
+G_DEFINE_BOXED_TYPE (TpPresenceStatusSpec, tp_presence_status_spec,
+ tp_presence_status_spec_copy, tp_presence_status_spec_free)
diff --git a/telepathy-glib/presence-mixin.h b/telepathy-glib/presence-mixin.h
index f94bf6579..6e7b98432 100644
--- a/telepathy-glib/presence-mixin.h
+++ b/telepathy-glib/presence-mixin.h
@@ -34,6 +34,7 @@ G_BEGIN_DECLS
typedef struct _TpPresenceStatusOptionalArgumentSpec
TpPresenceStatusOptionalArgumentSpec;
typedef struct _TpPresenceStatusSpec TpPresenceStatusSpec;
+typedef struct _TpPresenceStatusSpecPrivate TpPresenceStatusSpecPrivate;
struct _TpPresenceStatusOptionalArgumentSpec {
const gchar *name;
@@ -52,9 +53,41 @@ struct _TpPresenceStatusSpec {
/*<private>*/
gpointer _future1;
- gpointer _future2;
+ TpPresenceStatusSpecPrivate *priv;
};
+_TP_AVAILABLE_IN_UNRELEASED
+TpConnectionPresenceType tp_presence_status_spec_get_presence_type (
+ const TpPresenceStatusSpec *self);
+
+_TP_AVAILABLE_IN_UNRELEASED
+const gchar *tp_presence_status_spec_get_name (
+ const TpPresenceStatusSpec *self);
+
+_TP_AVAILABLE_IN_UNRELEASED
+gboolean tp_presence_status_spec_can_set_on_self (
+ const TpPresenceStatusSpec *self);
+
+_TP_AVAILABLE_IN_UNRELEASED
+gboolean tp_presence_status_spec_has_message (
+ const TpPresenceStatusSpec *self);
+
+_TP_AVAILABLE_IN_UNRELEASED
+GType tp_presence_status_spec_get_type (void);
+
+_TP_AVAILABLE_IN_UNRELEASED
+TpPresenceStatusSpec *tp_presence_status_spec_new (const gchar *name,
+ TpConnectionPresenceType type,
+ gboolean can_set_on_self,
+ gboolean has_message);
+
+_TP_AVAILABLE_IN_UNRELEASED
+TpPresenceStatusSpec *tp_presence_status_spec_copy (
+ const TpPresenceStatusSpec *self);
+
+_TP_AVAILABLE_IN_UNRELEASED
+void tp_presence_status_spec_free (TpPresenceStatusSpec *self);
+
typedef struct _TpPresenceStatus TpPresenceStatus;
struct _TpPresenceStatus {
diff --git a/telepathy-glib/protocol.c b/telepathy-glib/protocol.c
index 3d98b3830..1f508a418 100644
--- a/telepathy-glib/protocol.c
+++ b/telepathy-glib/protocol.c
@@ -47,6 +47,8 @@
#include "telepathy-glib/cli-misc.h"
#include "telepathy-glib/debug-internal.h"
#include "telepathy-glib/proxy-internal.h"
+#include "telepathy-glib/util-internal.h"
+#include "telepathy-glib/variant-util-internal.h"
#include <string.h>
@@ -146,6 +148,10 @@ struct _TpProtocolPrivate
TpCapabilities *capabilities;
TpAvatarRequirements *avatar_req;
gchar *cm_name;
+ GStrv addressable_vcard_fields;
+ GStrv addressable_uri_schemes;
+ /* (transfer container) (element-type utf8 Simple_Status_Spec) */
+ GHashTable *presence_statuses;
};
enum
@@ -160,6 +166,8 @@ enum
PROP_AUTHENTICATION_TYPES,
PROP_AVATAR_REQUIREMENTS,
PROP_CM_NAME,
+ PROP_ADDRESSABLE_VCARD_FIELDS,
+ PROP_ADDRESSABLE_URI_SCHEMES,
N_PROPS
};
@@ -279,6 +287,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;
@@ -354,6 +371,11 @@ 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->presence_statuses != NULL)
+ g_hash_table_unref (self->priv->presence_statuses);
if (self->priv->protocol_properties != NULL)
g_hash_table_unref (self->priv->protocol_properties);
@@ -414,6 +436,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)
{
@@ -424,7 +459,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)
@@ -444,7 +478,21 @@ tp_protocol_constructed (GObject *object)
}
else
{
+ GHashTableIter iter;
+ gpointer k, v;
+
DEBUG ("immutable properties already supplied");
+
+ g_hash_table_iter_init (&iter, self->priv->protocol_properties);
+
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ gchar *printed;
+
+ printed = g_strdup_value_contents (v);
+ DEBUG ("%s = %s", (const gchar *) k, printed);
+ g_free (printed);
+ }
}
self->priv->params = tp_protocol_params_from_param_specs (
@@ -486,19 +534,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);
@@ -508,6 +546,9 @@ tp_protocol_constructed (GObject *object)
if (tp_proxy_has_interface_by_id (self,
TP_IFACE_QUARK_PROTOCOL_INTERFACE_AVATARS1))
{
+ DEBUG ("%s/%s implements Avatars", self->priv->cm_name,
+ self->priv->name);
+
self->priv->avatar_req = tp_avatar_requirements_new (
(GStrv) tp_asv_get_strv (self->priv->protocol_properties,
TP_PROP_PROTOCOL_INTERFACE_AVATARS1_SUPPORTED_AVATAR_MIME_TYPES),
@@ -527,6 +568,58 @@ tp_protocol_constructed (GObject *object)
TP_PROP_PROTOCOL_INTERFACE_AVATARS1_MAXIMUM_AVATAR_BYTES, NULL));
}
+ if (tp_proxy_has_interface_by_id (self,
+ TP_IFACE_QUARK_PROTOCOL_INTERFACE_ADDRESSING1))
+ {
+ DEBUG ("%s/%s implements Addressing", self->priv->cm_name,
+ self->priv->name);
+
+ self->priv->addressable_vcard_fields = asv_strdupv_or_empty (
+ self->priv->protocol_properties,
+ TP_PROP_PROTOCOL_INTERFACE_ADDRESSING1_ADDRESSABLE_VCARD_FIELDS);
+ self->priv->addressable_uri_schemes = asv_strdupv_or_empty (
+ self->priv->protocol_properties,
+ TP_PROP_PROTOCOL_INTERFACE_ADDRESSING1_ADDRESSABLE_URI_SCHEMES);
+ }
+
+ if (tp_proxy_has_interface_by_id (self,
+ TP_IFACE_QUARK_PROTOCOL_INTERFACE_PRESENCE1))
+ {
+ DEBUG ("%s/%s implements Presence", self->priv->cm_name,
+ self->priv->name);
+
+ self->priv->presence_statuses = tp_asv_get_boxed (
+ self->priv->protocol_properties,
+ TP_PROP_PROTOCOL_INTERFACE_PRESENCE1_STATUSES,
+ TP_HASH_TYPE_STATUS_SPEC_MAP);
+
+ if (self->priv->presence_statuses != NULL)
+ {
+ GHashTableIter iter;
+ gpointer k, v;
+
+ g_hash_table_ref (self->priv->presence_statuses);
+
+ DEBUG ("%s/%s presence statuses:", self->priv->cm_name,
+ self->priv->name);
+ g_hash_table_iter_init (&iter, self->priv->presence_statuses);
+
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ guint type;
+ gboolean on_self, message;
+
+ tp_value_array_unpack (v, 3,
+ &type,
+ &on_self,
+ &message);
+ DEBUG ("\tstatus '%s': type %u%s%s",
+ (const gchar *) k, type, on_self ? ", can set on self" : "",
+ message ? ", has message" : "");
+ }
+ }
+ }
+
/* become ready immediately */
_tp_proxy_set_feature_prepared (proxy, TP_PROTOCOL_FEATURE_PARAMETERS,
had_immutables);
@@ -739,6 +832,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&commat;example.com, sips:bob&commat;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;
@@ -1194,7 +1326,7 @@ init_gvalue_from_dbus_sig (const gchar *sig,
static gboolean
parse_default_value (GValue *value,
const gchar *sig,
- gchar *string,
+ gchar *raw_value,
GKeyFile *file,
const gchar *group,
const gchar *key)
@@ -1217,31 +1349,28 @@ parse_default_value (GValue *value,
* So, on error, let's fall back to more lenient parsing that explicitly
* allows everything we historically allowed. */
g_error_free (error);
- s = g_key_file_get_value (file, group, key, NULL);
- if (s == NULL)
+ if (raw_value == NULL)
return FALSE;
- for (p = s; *p != '\0'; p++)
+ for (p = raw_value; *p != '\0'; p++)
{
*p = g_ascii_tolower (*p);
}
- if (!tp_strdiff (s, "1") || !tp_strdiff (s, "true"))
+ if (!tp_strdiff (raw_value, "1") || !tp_strdiff (raw_value, "true"))
{
g_value_set_boolean (value, TRUE);
}
- else if (!tp_strdiff (s, "0") || !tp_strdiff (s, "false"))
+ else if (!tp_strdiff (raw_value, "0") || !tp_strdiff (raw_value, "false"))
{
g_value_set_boolean (value, TRUE);
}
else
{
- g_free (s);
return FALSE;
}
- g_free (s);
return TRUE;
case 's':
@@ -1290,7 +1419,7 @@ parse_default_value (GValue *value,
case 'n':
case 'i':
case 'x':
- if (string[0] == '\0')
+ if (raw_value[0] == '\0')
{
return FALSE;
}
@@ -1437,20 +1566,34 @@ _tp_protocol_parse_channel_class (GKeyFile *file,
const gchar *dbus_type;
GValue *v = g_slice_new0 (GValue);
- value = g_key_file_get_string (file, group, *key, NULL);
+ value = g_key_file_get_value (file, group, *key, NULL);
/* keys without a space are reserved */
if (space == NULL)
- goto cleanup;
+ {
+ DEBUG ("\t'%s' isn't a fixed property", *key);
+ goto cleanup;
+ }
property = g_strndup (*key, space - *key);
dbus_type = space + 1;
if (!init_gvalue_from_dbus_sig (dbus_type, v))
- goto cleanup;
+ {
+ DEBUG ("\tunable to parse D-Bus type '%s' for '%s' in a "
+ ".manager file", dbus_type, property);
+ goto cleanup;
+ }
if (!parse_default_value (v, dbus_type, value, file, group, *key))
- goto cleanup;
+ {
+ DEBUG ("\tunable to parse '%s' as a value of type '%s' for '%s'",
+ value, dbus_type, property);
+ goto cleanup;
+ }
+
+ DEBUG ("\tfixed: '%s' of type '%s' = '%s'",
+ property, dbus_type, value);
/* transfer ownership to @ret */
g_hash_table_insert (ret, property, v);
@@ -1476,16 +1619,27 @@ cleanup:
}
static GValueArray *
-_tp_protocol_parse_rcc (GKeyFile *file,
+_tp_protocol_parse_rcc (const gchar *cm_debug_name,
+ const gchar *protocol_debug_name,
+ GKeyFile *file,
const gchar *group)
{
GHashTable *fixed;
GStrv allowed;
GValueArray *ret;
+ guint i;
+
+ DEBUG ("%s/%s: parsing requestable channel class '%s'", cm_debug_name,
+ protocol_debug_name, group);
fixed = _tp_protocol_parse_channel_class (file, group);
allowed = g_key_file_get_string_list (file, group, "allowed", NULL, NULL);
+ for (i = 0; allowed != NULL && allowed[i] != NULL; i++)
+ {
+ DEBUG ("\tallowed: '%s'", allowed[i]);
+ }
+
ret = tp_value_array_build (2,
TP_HASH_TYPE_CHANNEL_CLASS, fixed,
G_TYPE_STRV, allowed,
@@ -1504,6 +1658,7 @@ _tp_protocol_parse_manager_file (GKeyFile *file,
gchar **protocol_name)
{
GHashTable *immutables;
+ GHashTable *status_specs;
GPtrArray *param_specs, *rccs;
const gchar *name;
gchar **rcc_groups, **rcc_group;
@@ -1580,7 +1735,7 @@ _tp_protocol_parse_manager_file (GKeyFile *file,
}
def = g_strdup_printf ("default-%s", param.name);
- value = g_key_file_get_string (file, group, def, NULL);
+ value = g_key_file_get_value (file, group, def, NULL);
init_gvalue_from_dbus_sig (param.dbus_signature,
&param.default_value);
@@ -1622,8 +1777,6 @@ _tp_protocol_parse_manager_file (GKeyFile *file,
}
}
- g_strfreev (keys);
-
immutables = tp_asv_new (
TP_PROP_PROTOCOL_PARAMETERS, TP_ARRAY_TYPE_PARAM_SPEC_LIST, param_specs,
NULL);
@@ -1683,11 +1836,89 @@ _tp_protocol_parse_manager_file (GKeyFile *file,
if (rcc_groups != NULL)
{
for (rcc_group = rcc_groups; *rcc_group != NULL; rcc_group++)
- g_ptr_array_add (rccs, _tp_protocol_parse_rcc (file, *rcc_group));
+ g_ptr_array_add (rccs,
+ _tp_protocol_parse_rcc (cm_debug_name, name, file, *rcc_group));
}
g_strfreev (rcc_groups);
+ /* Statuses */
+ status_specs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) tp_value_array_free);
+
+ for (key = keys; key != NULL && *key != NULL; key++)
+ {
+ if (g_str_has_prefix (*key, "status-"))
+ {
+ GValueArray *ubb;
+ gint64 type;
+ gboolean on_self = FALSE, has_message = FALSE;
+ gchar *value, *endptr;
+ gchar **strv, **iter;
+
+ if (!tp_strdiff (*key, "status-"))
+ {
+ DEBUG ("'status-' is not a valid status");
+ continue;
+ }
+
+ value = g_key_file_get_value (file, group, *key, NULL);
+ strv = g_strsplit (value, " ", 0);
+ g_free (value);
+
+ type = g_ascii_strtoll (strv[0], &endptr, 10);
+
+ if (endptr <= strv[0] || *endptr != '\0')
+ {
+ DEBUG ("invalid (non-numeric?) status type %s", strv[0]);
+ goto next_status;
+ }
+
+ if (type == TP_CONNECTION_PRESENCE_TYPE_UNSET ||
+ type < 0 || type >= TP_NUM_CONNECTION_PRESENCE_TYPES)
+ {
+ DEBUG ("presence type out of range: %" G_GINT64_FORMAT,
+ type);
+ goto next_status;
+ }
+
+ for (iter = strv + 1; *iter != NULL; iter++)
+ {
+ if (!tp_strdiff (*iter, "settable"))
+ on_self = TRUE;
+ else if (!tp_strdiff (*iter, "message"))
+ has_message = TRUE;
+ else
+ DEBUG ("unknown status modifier '%s'", *iter);
+ }
+
+ ubb = tp_value_array_build (3,
+ G_TYPE_UINT, (guint) type,
+ G_TYPE_BOOLEAN, on_self,
+ G_TYPE_BOOLEAN, has_message,
+ G_TYPE_INVALID);
+
+ /* strlen ("status-") == 7 */
+ g_hash_table_insert (status_specs, g_strdup (*key + 7),
+ ubb);
+ DEBUG ("Status '%s': type %u%s%s", *key + 7, (guint) type,
+ on_self ? ", can set on self" : "",
+ has_message ? ", has message" : "");
+
+next_status:
+ g_strfreev (strv);
+ }
+ }
+
+ if (g_hash_table_size (status_specs) > 0)
+ tp_asv_take_boxed (immutables,
+ TP_PROP_PROTOCOL_INTERFACE_PRESENCE1_STATUSES,
+ TP_HASH_TYPE_STATUS_SPEC_MAP, status_specs);
+ else
+ g_hash_table_unref (status_specs);
+
+ g_strfreev (keys);
+
tp_asv_take_boxed (immutables, TP_PROP_PROTOCOL_REQUESTABLE_CHANNEL_CLASSES,
TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs);
@@ -1732,3 +1963,348 @@ tp_protocol_get_cm_name (TpProtocol *self)
return self->priv->cm_name;
}
+
+/*
+ * Handle the result from a tp_cli_protocol_* function that
+ * returns one string. user_data is a #GTask.
+ */
+static void
+tp_protocol_async_string_cb (TpProxy *proxy,
+ const gchar *normalized,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ if (error == NULL)
+ g_task_return_pointer (user_data, g_strdup (normalized), g_free);
+ else
+ g_task_return_error (user_data, g_error_copy (error));
+}
+
+/**
+ * tp_protocol_normalize_contact_async:
+ * @self: a protocol
+ * @contact: a contact identifier, 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. This does syntactic
+ * normalization (e.g. transforming case-insensitive text to lower-case),
+ * but does not query servers or anything similar.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_protocol_normalize_contact_async (TpProtocol *self,
+ const gchar *contact,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ g_return_if_fail (TP_IS_PROTOCOL (self));
+ g_return_if_fail (contact != 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_async);
+
+ tp_cli_protocol_call_normalize_contact (self, -1, contact,
+ tp_protocol_async_string_cb, task, g_object_unref, NULL);
+}
+
+/**
+ * tp_protocol_normalize_contact_finish:
+ * @self: a protocol
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Interpret the result of tp_protocol_normalize_contact_async().
+ *
+ * Returns: (transfer full): the normalized form of @contact,
+ * or %NULL on error
+ * Since: 0.UNRELEASED
+ */
+gchar *
+tp_protocol_normalize_contact_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_async), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/**
+ * tp_protocol_identify_account_async:
+ * @self: a protocol
+ * @vardict: the account parameters as a #GVariant of
+ * type %G_VARIANT_TYPE_VARDICT. If it is floating, ownership will
+ * be taken, as if via g_variant_ref_sink().
+ * @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
+ *
+ * Return a string that could identify the account with the given
+ * parameters. In most protocols that string is a normalized 'account'
+ * parameter, but some protocols have more complex requirements;
+ * for instance, on IRC, the 'account' (nickname) is insufficient,
+ * and must be combined with a server or network name.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_protocol_identify_account_async (TpProtocol *self,
+ GVariant *vardict,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GHashTable *asv;
+
+ g_return_if_fail (TP_IS_PROTOCOL (self));
+ g_return_if_fail (vardict != NULL);
+ g_return_if_fail (g_variant_is_of_type (vardict, G_VARIANT_TYPE_VARDICT));
+ /* 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_identify_account_async);
+ g_variant_ref_sink (vardict);
+ asv = _tp_asv_from_vardict (vardict);
+ tp_cli_protocol_call_identify_account (self, -1, asv,
+ tp_protocol_async_string_cb, task, g_object_unref, NULL);
+ g_hash_table_unref (asv);
+ g_variant_unref (vardict);
+}
+
+/**
+ * tp_protocol_identify_account_finish:
+ * @self: a protocol
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Interpret the result of tp_protocol_identify_account_async().
+ *
+ * Returns: (transfer full): a string identifying the account,
+ * or %NULL on error
+ * Since: 0.UNRELEASED
+ */
+gchar *
+tp_protocol_identify_account_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_identify_account_async), NULL);
+
+ 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_addressing1_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_addressing1_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;
+}
+
+/**
+ * tp_protocol_dup_presence_statuses:
+ * @self: a protocol object
+ *
+ * Return the presence statuses that might be supported by connections
+ * to this protocol.
+ *
+ * It is possible that some of these statuses will not actually be supported
+ * by a connection: for instance, an XMPP connection manager would
+ * include "hidden" in this list, even though not all XMPP servers allow
+ * users to be online-but-hidden.
+ *
+ * Returns: (transfer full) (element-type TelepathyGLib.PresenceStatusSpec): a
+ * list of statuses, or %NULL if unknown
+ */
+GList *
+tp_protocol_dup_presence_statuses (TpProtocol *self)
+{
+ GHashTableIter iter;
+ gpointer k, v;
+ GList *l = NULL;
+
+ g_return_val_if_fail (TP_IS_PROTOCOL (self), NULL);
+
+ if (self->priv->presence_statuses == NULL)
+ return NULL;
+
+ g_hash_table_iter_init (&iter, self->priv->presence_statuses);
+
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ guint type;
+ gboolean on_self, message;
+
+ tp_value_array_unpack (v, 3,
+ &type,
+ &on_self,
+ &message);
+
+ l = g_list_prepend (l, tp_presence_status_spec_new (k, type,
+ on_self, message));
+ }
+
+ return g_list_reverse (l);
+}
diff --git a/telepathy-glib/protocol.h b/telepathy-glib/protocol.h
index 51dd904b5..eef1895c6 100644
--- a/telepathy-glib/protocol.h
+++ b/telepathy-glib/protocol.h
@@ -98,6 +98,20 @@ 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);
+
+_TP_AVAILABLE_IN_UNRELEASED
+GList *tp_protocol_dup_presence_statuses (TpProtocol *self)
+ G_GNUC_WARN_UNUSED_RESULT;
+
#define TP_PROTOCOL_FEATURE_CORE \
(tp_protocol_get_feature_quark_core ())
GQuark tp_protocol_get_feature_quark_core (void) G_GNUC_CONST;
@@ -110,6 +124,55 @@ TpCapabilities *tp_protocol_get_capabilities (TpProtocol *self);
_TP_AVAILABLE_IN_0_16
TpAvatarRequirements * tp_protocol_get_avatar_requirements (TpProtocol *self);
+_TP_AVAILABLE_IN_UNRELEASED
+void tp_protocol_normalize_contact_async (TpProtocol *self,
+ const gchar *contact,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+_TP_AVAILABLE_IN_UNRELEASED
+gchar *tp_protocol_normalize_contact_finish (TpProtocol *self,
+ GAsyncResult *result,
+ GError **error);
+
+_TP_AVAILABLE_IN_UNRELEASED
+void tp_protocol_identify_account_async (TpProtocol *self,
+ GVariant *vardict,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+_TP_AVAILABLE_IN_UNRELEASED
+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
#endif
diff --git a/tests/dbus/protocol-objects.c b/tests/dbus/protocol-objects.c
index 5ccadfbbe..5cba6dff9 100644
--- a/tests/dbus/protocol-objects.c
+++ b/tests/dbus/protocol-objects.c
@@ -442,6 +442,154 @@ test_protocol_object_from_file (Test *test,
check_avatar_requirements (req);
}
+static void
+test_normalize (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ GAsyncResult *result = NULL;
+ gchar *s;
+
+ tp_tests_proxy_run_until_prepared (test->cm, NULL);
+ test->protocol = g_object_ref (
+ tp_connection_manager_get_protocol (test->cm, "example"));
+
+ tp_protocol_normalize_contact_async (test->protocol,
+ "MiXeDcAsE", NULL, tp_tests_result_ready_cb, &result);
+ tp_tests_run_until_result (&result);
+ s = tp_protocol_normalize_contact_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_contact_async (test->protocol,
+ "", NULL, tp_tests_result_ready_cb, &result);
+ tp_tests_run_until_result (&result);
+ s = tp_protocol_normalize_contact_finish (test->protocol, result,
+ &test->error);
+ g_assert_error (test->error, TP_ERROR, TP_ERROR_INVALID_HANDLE);
+ 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
+test_id (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ GAsyncResult *result = NULL;
+ gchar *s;
+
+ tp_tests_proxy_run_until_prepared (test->cm, NULL);
+ test->protocol = g_object_ref (
+ tp_connection_manager_get_protocol (test->cm, "example"));
+
+ tp_protocol_identify_account_async (test->protocol,
+ g_variant_new_parsed ("{ 'account': <'Hello'> }"),
+ NULL, tp_tests_result_ready_cb, &result);
+ tp_tests_run_until_result (&result);
+ s = tp_protocol_identify_account_finish (test->protocol, result,
+ &test->error);
+ g_assert_no_error (test->error);
+ g_assert_cmpstr (s, ==, "hello");
+ g_clear_object (&result);
+ g_free (s);
+
+ tp_protocol_identify_account_async (test->protocol,
+ g_variant_new_parsed ("{ 'account': <'Hello'>, 'unknown-param': <42> }"),
+ NULL, tp_tests_result_ready_cb, &result);
+ tp_tests_run_until_result (&result);
+ s = tp_protocol_identify_account_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_identify_account_async (test->protocol,
+ g_variant_new_parsed ("@a{sv} {}"),
+ NULL, tp_tests_result_ready_cb, &result);
+ tp_tests_run_until_result (&result);
+ s = tp_protocol_identify_account_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_identify_account_async (test->protocol,
+ g_variant_new_parsed ("@a{sv} { 'account': <''> }"),
+ NULL, tp_tests_result_ready_cb, &result);
+ tp_tests_run_until_result (&result);
+ s = tp_protocol_identify_account_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);
+}
+
int
main (int argc,
char **argv)
@@ -461,6 +609,10 @@ main (int argc,
test_protocol_object, teardown);
g_test_add ("/protocol-objects/object-from-file", Test, NULL, setup,
test_protocol_object_from_file, teardown);
+ g_test_add ("/protocol-objects/normalize", Test, NULL, setup,
+ test_normalize, teardown);
+ g_test_add ("/protocol-objects/id", Test, NULL, setup,
+ test_id, teardown);
return tp_tests_run_with_bus ();
}