diff options
author | Will Thompson <will.thompson@collabora.co.uk> | 2011-08-24 15:21:32 +0100 |
---|---|---|
committer | Will Thompson <will.thompson@collabora.co.uk> | 2011-08-24 15:21:58 +0100 |
commit | b81086c2a76b2e5ca3ca421deebc51122fb26069 (patch) | |
tree | 57abbf812e35e2df64cf832878ce2f9c026c6ae3 | |
parent | e97a10ac5c84088967b3740c593372ccdbca4731 (diff) | |
parent | 79c310dfcae1ee9de84448b0c0ee0dbc48431f5e (diff) |
Merge branch 'contact-info'
https://bugs.freedesktop.org/show_bug.cgi?id=40035
Reviewed-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
Branch-quality: impeccable
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/avahi-contact.c | 48 | ||||
-rw-r--r-- | src/connection-contact-info.c | 375 | ||||
-rw-r--r-- | src/connection-contact-info.h | 38 | ||||
-rw-r--r-- | src/connection.c | 24 | ||||
-rw-r--r-- | src/contact.c | 60 | ||||
-rw-r--r-- | src/contact.h | 25 | ||||
-rw-r--r-- | src/debug.c | 1 | ||||
-rw-r--r-- | tests/twisted/avahi/aliases.py | 161 | ||||
-rw-r--r-- | tests/twisted/constants.py | 3 |
10 files changed, 673 insertions, 64 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index dd80eaf9..26ae6dd1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -60,6 +60,8 @@ CORE_SOURCES = \ contact-channel.h \ connection.c \ connection.h \ + connection-contact-info.c \ + connection-contact-info.h \ presence.h \ contact-channel.h \ contact-channel.c \ diff --git a/src/avahi-contact.c b/src/avahi-contact.c index 9024a614..b0ac4f15 100644 --- a/src/avahi-contact.c +++ b/src/avahi-contact.c @@ -586,46 +586,25 @@ find_resolver (SalutAvahiContact *contact, static void update_alias (SalutAvahiContact *self, - const gchar *nick, - const gchar *first, - const gchar *last) + const gchar *nick) { -#define STREMPTY(x) (x == NULL || *x == '\0') - - if (!STREMPTY(nick)) - { - salut_contact_change_alias (SALUT_CONTACT (self), nick); - return; - } - - if (!STREMPTY(first) && !STREMPTY(last)) - { - gchar *s = g_strdup_printf ("%s %s", first, last); - - salut_contact_change_alias (SALUT_CONTACT (self), s); - - g_free (s); - return; - } + SalutContact *contact = SALUT_CONTACT (self); - if (!STREMPTY(first)) + if (!tp_str_empty (nick)) { - salut_contact_change_alias (SALUT_CONTACT (self), first); + salut_contact_change_alias (contact, nick); return; } - if (!STREMPTY(last)) + if (!tp_str_empty (contact->full_name)) { - salut_contact_change_alias (SALUT_CONTACT (self), last); + salut_contact_change_alias (contact, contact->full_name); return; } - salut_contact_change_alias (SALUT_CONTACT (self), NULL); - -#undef STREMPTY + salut_contact_change_alias (contact, NULL); } - /* Returned string needs to be freed with avahi_free ! */ static char * _avahi_txt_get_keyval_with_size (AvahiStringList *txt, @@ -707,12 +686,14 @@ contact_resolved_cb (GaServiceResolver *resolver, salut_contact_change_status_message (contact, s); avahi_free (s); - /* nick */ + /* real name and nick */ nick = _avahi_txt_get_keyval (txt, "nick"); first = _avahi_txt_get_keyval (txt, "1st"); last = _avahi_txt_get_keyval (txt, "last"); - update_alias (self, nick, first, last); + salut_contact_change_real_name (contact, first, last); + update_alias (self, nick); + avahi_free (nick); avahi_free (first); avahi_free (last); @@ -731,12 +712,17 @@ contact_resolved_cb (GaServiceResolver *resolver, salut_contact_change_avatar_token (contact, s); avahi_free (s); + /* email */ + s = _avahi_txt_get_keyval (txt, "email"); + salut_contact_change_email (contact, s); + avahi_free (s); + /* jid */ -#ifdef ENABLE_OLPC s = _avahi_txt_get_keyval (txt, "jid"); salut_contact_change_jid (contact, s); avahi_free (s); +#ifdef ENABLE_OLPC /* OLPC color */ s = _avahi_txt_get_keyval (txt, "olpc-color"); salut_contact_change_olpc_color (contact, s); diff --git a/src/connection-contact-info.c b/src/connection-contact-info.c new file mode 100644 index 00000000..5e235fdb --- /dev/null +++ b/src/connection-contact-info.c @@ -0,0 +1,375 @@ +/* + * connection-contact-info.c - ContactInfo implementation + * Copyright © 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "connection-contact-info.h" + +#include <telepathy-glib/interfaces.h> +/* Slightly sketchy; included for TpContactInfoFieldSpec. */ +#include <telepathy-glib/connection.h> +#include <telepathy-glib/gtypes.h> + +#include "contact-manager.h" + +enum { + PROP_CONTACT_INFO_FLAGS, + PROP_SUPPORTED_FIELDS +}; + +static gchar *i_heart_the_internet[] = { "type=internet", NULL }; + +static GPtrArray * +get_supported_fields (void) +{ + static TpContactInfoFieldSpec supported_fields[] = { + /* We omit 'nickname' because it shows up, unmodifiably, as the alias. */ + { "n", NULL, + TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 }, + /* It's a little bit sketchy to expose 1st + ' ' + last as FN. But such + * are the limitations of the protocol. + */ + { "fn", NULL, + TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 }, + { "email", i_heart_the_internet, + TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 }, + /* x-jabber is used for compatibility with Gabble */ + { "x-jabber", NULL, + TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1 }, + /* Heh, we could also include the contact's IP address(es) here. */ + { NULL } + }; + static gsize supported_fields_ptr_array = 0; + + if (g_once_init_enter (&supported_fields_ptr_array)) + { + GPtrArray *fields = dbus_g_type_specialized_construct ( + TP_ARRAY_TYPE_FIELD_SPECS); + TpContactInfoFieldSpec *spec; + + for (spec = supported_fields; spec->name != NULL; spec++) + g_ptr_array_add (fields, + tp_value_array_build (4, + G_TYPE_STRING, spec->name, + G_TYPE_STRV, spec->parameters, + G_TYPE_UINT, spec->flags, + G_TYPE_UINT, spec->max, + G_TYPE_INVALID)); + + g_once_init_leave (&supported_fields_ptr_array, (gsize) fields); + } + + return (GPtrArray *) supported_fields_ptr_array; +} + +static void +salut_conn_contact_info_get_property ( + GObject *object, + GQuark iface, + GQuark name, + GValue *value, + gpointer getter_data) +{ + switch (GPOINTER_TO_UINT (getter_data)) + { + case PROP_CONTACT_INFO_FLAGS: + g_value_set_uint (value, TP_CONTACT_INFO_FLAG_PUSH); + break; + case PROP_SUPPORTED_FIELDS: + g_value_set_boxed (value, get_supported_fields ()); + break; + default: + g_assert_not_reached (); + } +} + +void +salut_conn_contact_info_class_init ( + SalutConnectionClass *klass) +{ + static TpDBusPropertiesMixinPropImpl props[] = { + { "ContactInfoFlags", GUINT_TO_POINTER (PROP_CONTACT_INFO_FLAGS), NULL }, + { "SupportedFields", GUINT_TO_POINTER (PROP_SUPPORTED_FIELDS), NULL }, + { NULL } + }; + + tp_dbus_properties_mixin_implement_interface ( + G_OBJECT_CLASS (klass), + TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO, + salut_conn_contact_info_get_property, + NULL, + props); +} + +static void +add_singleton_field ( + GPtrArray *contact_info, + const gchar *field_name, + gchar **parameters, + const gchar *value) +{ + const gchar *field_value[] = { value, NULL }; + + g_ptr_array_add (contact_info, + tp_value_array_build (3, + G_TYPE_STRING, field_name, + G_TYPE_STRV, parameters, + G_TYPE_STRV, field_value, + G_TYPE_INVALID)); +} + +static GPtrArray * +build_contact_info ( + const gchar *first, + const gchar *last, + const gchar *full_name, + const gchar *email, + const gchar *jid) +{ + GPtrArray *contact_info = dbus_g_type_specialized_construct ( + TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST); + + if (first != NULL || last != NULL) + { + const gchar *field_value[] = { + last != NULL ? last : "", + first != NULL ? first : "", + "", + "", + "", + NULL + }; + + g_ptr_array_add (contact_info, + tp_value_array_build (3, + G_TYPE_STRING, "n", + G_TYPE_STRV, NULL, + G_TYPE_STRV, field_value, + G_TYPE_INVALID)); + + g_warn_if_fail (full_name != NULL); + add_singleton_field (contact_info, "fn", NULL, full_name); + } + + if (email != NULL) + add_singleton_field (contact_info, "email", i_heart_the_internet, email); + + if (jid != NULL) + add_singleton_field (contact_info, "x-jabber", NULL, jid); + + return contact_info; +} + +static GPtrArray * +build_contact_info_for_contact ( + SalutContact *contact) +{ + g_return_val_if_fail (contact != NULL, NULL); + + return build_contact_info (contact->first, contact->last, contact->full_name, + contact->email, contact->jid); +} + +static void +salut_conn_contact_info_fill_contact_attributes ( + GObject *obj, + const GArray *contacts, + GHashTable *attributes_hash) +{ + guint i; + SalutConnection *self = SALUT_CONNECTION (obj); + TpBaseConnection *base = TP_BASE_CONNECTION (self); + SalutContactManager *contact_manager; + + g_object_get (self, "contact-manager", &contact_manager, NULL); + + for (i = 0; i < contacts->len; i++) + { + TpHandle handle = g_array_index (contacts, TpHandle, i); + GPtrArray *contact_info = NULL; + + if (base->self_handle == handle) + { + /* TODO: dig contact info out of SalutSelf. There's overlap with + * connection parameters here … should they be DBus_Property + * parameters? Should we have a new flag which means “you set this on + * ContactInfo”? What? + */ + } + else + { + SalutContact *contact = salut_contact_manager_get_contact ( + contact_manager, handle); + if (contact != NULL) + { + contact_info = build_contact_info_for_contact (contact); + g_object_unref (contact); + } + } + + if (contact_info != NULL) + tp_contacts_mixin_set_contact_attribute (attributes_hash, + handle, TP_TOKEN_CONNECTION_INTERFACE_CONTACT_INFO_INFO, + tp_g_value_slice_new_take_boxed ( + TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info)); + } + + g_object_unref (contact_manager); +} + +void salut_conn_contact_info_init ( + SalutConnection *self) +{ + tp_contacts_mixin_add_contact_attributes_iface ( + G_OBJECT (self), + TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, + salut_conn_contact_info_fill_contact_attributes); +} + +void +salut_conn_contact_info_changed ( + SalutConnection *self, + SalutContact *contact, + TpHandle handle) +{ + GPtrArray *contact_info = build_contact_info_for_contact (contact); + + tp_svc_connection_interface_contact_info_emit_contact_info_changed (self, + handle, contact_info); + g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info); +} + +static void +salut_conn_contact_info_get_contact_info ( + TpSvcConnectionInterfaceContactInfo *iface, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandleRepoIface *contacts_repo = + tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); + SalutContactManager *contact_manager; + guint i; + GHashTable *ret; + GError *error = NULL; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface), + context); + + if (!tp_handles_are_valid (contacts_repo, contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + g_object_get (self, "contact-manager", &contact_manager, NULL); + ret = dbus_g_type_specialized_construct (TP_HASH_TYPE_CONTACT_INFO_MAP); + + for (i = 0; i < contacts->len; i++) + { + TpHandle handle = g_array_index (contacts, TpHandle, i); + SalutContact *contact = salut_contact_manager_get_contact ( + contact_manager, handle); + + if (contact != NULL) + { + g_hash_table_insert (ret, GUINT_TO_POINTER (handle), + build_contact_info_for_contact (contact)); + g_object_unref (contact); + } + } + + tp_svc_connection_interface_contact_info_return_from_get_contact_info ( + context, ret); + g_boxed_free (TP_HASH_TYPE_CONTACT_INFO_MAP, ret); + g_object_unref (contact_manager); +} + +static void +salut_conn_contact_info_request_contact_info ( + TpSvcConnectionInterfaceContactInfo *iface, + guint handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandleRepoIface *contacts_repo = + tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); + GError *error = NULL; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface), + context); + + if (!tp_handle_is_valid (contacts_repo, handle, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + } + else + { + SalutContactManager *contact_manager; + SalutContact *contact; + + g_object_get (self, "contact-manager", &contact_manager, NULL); + contact = salut_contact_manager_get_contact (contact_manager, handle); + g_object_unref (contact_manager); + + if (contact != NULL) + { + GPtrArray *contact_info = build_contact_info_for_contact (contact); + + tp_svc_connection_interface_contact_info_return_from_request_contact_info ( + context, contact_info); + g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info); + } + else + { + error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "No information available for '%s'", + tp_handle_inspect (contacts_repo, handle)); + dbus_g_method_return_error (context, error); + g_error_free (error); + } + } +} + +static void +salut_conn_contact_info_refresh_contact_info ( + TpSvcConnectionInterfaceContactInfo *iface, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + /* This is a no-op on link-local XMPP: everything's always pushed to us. */ + tp_svc_connection_interface_contact_info_return_from_refresh_contact_info (context); +} + +void +salut_conn_contact_info_iface_init ( + gpointer g_iface, + gpointer iface_data) +{ + TpSvcConnectionInterfaceContactInfoClass *klass = g_iface; + +#define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x \ + (klass, salut_conn_contact_info_##x) + IMPLEMENT (get_contact_info); + IMPLEMENT (request_contact_info); + IMPLEMENT (refresh_contact_info); +#undef IMPLEMENT +} diff --git a/src/connection-contact-info.h b/src/connection-contact-info.h new file mode 100644 index 00000000..04ece8d6 --- /dev/null +++ b/src/connection-contact-info.h @@ -0,0 +1,38 @@ +/* + * connection-contact-info.h - header for ContactInfo implementation + * Copyright © 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef SALUT_CONNECTION_CONTACT_INFO_H +#define SALUT_CONNECTION_CONTACT_INFO_H + +#include "connection.h" +#include "contact.h" + +void salut_conn_contact_info_iface_init ( + gpointer g_iface, + gpointer iface_data); +void salut_conn_contact_info_class_init ( + SalutConnectionClass *klass); +void salut_conn_contact_info_init ( + SalutConnection *self); + +void salut_conn_contact_info_changed ( + SalutConnection *self, + SalutContact *contact, + TpHandle handle); + +#endif // SALUT_CONNECTION_CONTACT_INFO_H diff --git a/src/connection.c b/src/connection.c index f340cf61..70da22dd 100644 --- a/src/connection.c +++ b/src/connection.c @@ -47,10 +47,10 @@ #include <wocky/wocky-data-form.h> #include <wocky/wocky-xep-0115-capabilities.h> -#include "capabilities.h" #include "avahi-discovery-client.h" #include "capabilities.h" #include "caps-hash.h" +#include "connection-contact-info.h" #include "contact-channel.h" #include "contact.h" #include "contact-manager.h" @@ -80,9 +80,6 @@ #define DEBUG_FLAG DEBUG_CONNECTION #include "debug.h" -#define SALUT_TP_ALIAS_PAIR_TYPE (dbus_g_type_get_struct ("GValueArray", \ - G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID)) - #ifdef ENABLE_OLPC #define ACTIVITY_PAIR_TYPE \ @@ -132,6 +129,8 @@ G_DEFINE_TYPE_WITH_CODE(SalutConnection, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, salut_conn_contact_caps_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_INFO, + salut_conn_contact_info_iface_init); G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_CONNECTION_FUTURE, salut_conn_future_iface_init); #ifdef ENABLE_OLPC @@ -402,6 +401,8 @@ salut_connection_constructor (GType type, g_signal_connect (self, "status-changed", (GCallback) sidecars_conn_status_changed_cb, NULL); + salut_conn_contact_info_init (self); + return obj; } @@ -721,6 +722,7 @@ static const gchar *interfaces [] = { TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_REQUESTS, TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, + TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, SALUT_IFACE_CONNECTION_FUTURE, #ifdef ENABLE_OLPC SALUT_IFACE_OLPC_BUDDY_INFO, @@ -789,6 +791,8 @@ salut_connection_class_init (SalutConnectionClass *salut_connection_class) tp_contacts_mixin_class_init (object_class, G_STRUCT_OFFSET (SalutConnectionClass, contacts_mixin)); + salut_conn_contact_info_class_init (salut_connection_class); + param_spec = g_param_spec_string ("nickname", "nickname", "Nickname used in the published data", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); @@ -1534,9 +1538,9 @@ _contact_manager_contact_alias_changed (SalutConnection *self, GPtrArray *aliases; GValue entry = {0, }; - g_value_init (&entry, SALUT_TP_ALIAS_PAIR_TYPE); + g_value_init (&entry, TP_STRUCT_TYPE_ALIAS_PAIR); g_value_take_boxed (&entry, - dbus_g_type_specialized_construct (SALUT_TP_ALIAS_PAIR_TYPE)); + dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ALIAS_PAIR)); dbus_g_type_struct_set (&entry, 0, handle, 1, salut_contact_get_alias (contact), G_MAXUINT); @@ -3369,6 +3373,14 @@ _contact_manager_contact_change_cb (SalutContactManager *mgr, _contact_manager_contact_avatar_changed (self, contact, handle); } + if (changes & ( SALUT_CONTACT_REAL_NAME_CHANGED + | SALUT_CONTACT_EMAIL_CHANGED + | SALUT_CONTACT_JID_CHANGED + )) + { + salut_conn_contact_info_changed (self, contact, handle); + } + #ifdef ENABLE_OLPC if (changes & SALUT_CONTACT_OLPC_PROPERTIES) _contact_manager_contact_olpc_properties_changed (self, contact, handle); diff --git a/src/contact.c b/src/contact.c index c7e807e4..66f090af 100644 --- a/src/contact.c +++ b/src/contact.c @@ -341,6 +341,10 @@ salut_contact_finalize (GObject *object) g_free (self->status_message); g_free (priv->alias); g_free (self->avatar_token); + g_free (self->first); + g_free (self->last); + g_free (self->full_name); + g_free (self->email); g_free (self->jid); #ifdef ENABLE_OLPC @@ -524,6 +528,40 @@ salut_contact_change (SalutContact *self, guint changes) } void +salut_contact_change_real_name ( + SalutContact *self, + const gchar *first, + const gchar *last) +{ + if (tp_str_empty (first)) + first = NULL; + + if (tp_str_empty (last)) + last = NULL; + + if (tp_strdiff (self->first, first) || tp_strdiff (self->last, last)) + { + g_free (self->first); + self->first = g_strdup (first); + g_free (self->last); + self->last = g_strdup (last); + + g_free (self->full_name); + + if (first != NULL && last != NULL) + self->full_name = g_strdup_printf ("%s %s", first, last); + else if (first != NULL) + self->full_name = g_strdup (first); + else if (last != NULL) + self->full_name = g_strdup (last); + else + self->full_name = NULL; + + salut_contact_change (self, SALUT_CONTACT_REAL_NAME_CHANGED); + } +} + +void salut_contact_change_alias (SalutContact *self, const gchar *alias) { SalutContactPrivate *priv = self->priv; @@ -570,15 +608,35 @@ salut_contact_change_avatar_token (SalutContact *self, } void +salut_contact_change_email (SalutContact *self, gchar *email) +{ + if (tp_str_empty (email)) + email = NULL; + + if (tp_strdiff (self->email, email)) + { + g_free (self->email); + self->email = g_strdup (email); + salut_contact_change (self, SALUT_CONTACT_EMAIL_CHANGED); + } +} + +void salut_contact_change_jid (SalutContact *self, gchar *jid) { + if (tp_str_empty (jid)) + jid = NULL; + if (tp_strdiff (self->jid, jid)) { g_free (self->jid); self->jid = g_strdup (jid); + salut_contact_change (self, + SALUT_CONTACT_JID_CHANGED #ifdef ENABLE_OLPC - salut_contact_change (self, SALUT_CONTACT_OLPC_PROPERTIES); + | SALUT_CONTACT_OLPC_PROPERTIES #endif + ); } } diff --git a/src/contact.h b/src/contact.h index 43ddbefc..bfddd887 100644 --- a/src/contact.h +++ b/src/contact.h @@ -36,14 +36,19 @@ G_BEGIN_DECLS -#define SALUT_CONTACT_ALIAS_CHANGED 0x1 -#define SALUT_CONTACT_STATUS_CHANGED 0x2 -#define SALUT_CONTACT_AVATAR_CHANGED 0x4 +enum { + SALUT_CONTACT_ALIAS_CHANGED = 0x1, + SALUT_CONTACT_STATUS_CHANGED = 0x2, + SALUT_CONTACT_AVATAR_CHANGED = 0x4, #ifdef ENABLE_OLPC -#define SALUT_CONTACT_OLPC_PROPERTIES 0x8 -#define SALUT_CONTACT_OLPC_CURRENT_ACTIVITY 0x10 -#define SALUT_CONTACT_OLPC_ACTIVITIES 0x20 + SALUT_CONTACT_OLPC_PROPERTIES = 0x8, + SALUT_CONTACT_OLPC_CURRENT_ACTIVITY = 0x10, + SALUT_CONTACT_OLPC_ACTIVITIES = 0x20, #endif /* ENABLE_OLPC */ + SALUT_CONTACT_JID_CHANGED = 0x40, + SALUT_CONTACT_EMAIL_CHANGED = 0x80, + SALUT_CONTACT_REAL_NAME_CHANGED = 0x100, +}; typedef struct _SalutContact SalutContact; typedef struct _SalutContactClass SalutContactClass; @@ -67,6 +72,11 @@ struct _SalutContact { SalutPresenceId status; gchar *avatar_token; gchar *status_message; + gchar *first; + gchar *last; + /* synthesized from first and last */ + gchar *full_name; + gchar *email; gchar *jid; /* XEP-0115 Capabilities */ @@ -147,12 +157,15 @@ void salut_contact_left_activity (SalutContact *self, #endif /* restricted methods */ +void salut_contact_change_real_name (SalutContact *self, const gchar *first, + const gchar *last); void salut_contact_change_alias (SalutContact *self, const gchar *alias); void salut_contact_change_status (SalutContact *self, SalutPresenceId); void salut_contact_change_status_message (SalutContact *self, const gchar *message); void salut_contact_change_avatar_token (SalutContact *self, const gchar *avatar_token); +void salut_contact_change_email (SalutContact *self, gchar *email); void salut_contact_change_jid (SalutContact *self, gchar *jid); void salut_contact_change_capabilities (SalutContact *self, const gchar *hash, const gchar *node, const gchar *ver); diff --git a/src/debug.c b/src/debug.c index fc2841cb..97e2e807 100644 --- a/src/debug.c +++ b/src/debug.c @@ -39,7 +39,6 @@ GDebugKey keys[] = { { "olpc-activity", DEBUG_OLPC_ACTIVITY }, { "ft", DEBUG_FT }, { "plugin", DEBUG_PLUGIN }, - { "all", ~0 }, { 0, }, }; diff --git a/tests/twisted/avahi/aliases.py b/tests/twisted/avahi/aliases.py index cf93177d..02887803 100644 --- a/tests/twisted/avahi/aliases.py +++ b/tests/twisted/avahi/aliases.py @@ -1,23 +1,94 @@ +""" +Test that aliases are built as expected from contacts' TXT records, and that +the details show up correctly in ContactInfo. +""" + +from servicetest import assertContains, assertEquals, assertLength, call_async from saluttest import exec_test, wait_for_contact_in_publish from avahitest import AvahiAnnouncer from avahitest import get_host_name +import constants as cs import time -HT_CONTACT = 1 -HT_CONTACT_LIST = 3 - def wait_for_aliases_changed(q, handle): - while True: - e = q.expect('dbus-signal', signal='AliasesChanged') - for x in e.args: - (h, a) = x[0] - if h == handle: - return a + e = q.expect('dbus-signal', signal='AliasesChanged', + predicate=lambda e: e.args[0][0][0] == handle) + _, alias = e.args[0][0] + return alias + +def wait_for_contact_info_changed(q, handle): + e = q.expect('dbus-signal', signal='ContactInfoChanged', + predicate=lambda e: e.args[0] == handle) + _, info = e.args + return info + +def assertOmitsField(field_name, fields): + def matches(field): + return field[0] == field_name + + assertLength(0, filter(matches, fields)) + +def check_contact_info(info, txt): + first = txt.get('1st', '') + last = txt.get('last', '') + + if first != '' or last != '': + values = [last, first, '', '', ''] + assertContains(('n', [], values), info) + + fn = ' '.join([ x for x in [first, last] if x != '']) + assertContains(('fn', [], [fn]), info) + else: + assertOmitsField('n', info) + assertOmitsField('fn', info) + + email = txt.get('email', '') + if email != '': + assertContains(('email', ['type=internet'], [email]), info) + else: + assertOmitsField('email', info) + + jid = txt.get('jid', '') + if jid != '': + assertContains(('x-jabber', [], [jid]), info) + else: + assertOmitsField('x-jabber', info) + +def check_all_contact_info_methods(conn, handle, keys): + attrs = conn.Contacts.GetContactAttributes([handle], + [cs.CONN_IFACE_CONTACT_INFO], True)[handle] + info = attrs[cs.CONN_IFACE_CONTACT_INFO + "/info"] + check_contact_info(info, keys) + + info = conn.ContactInfo.GetContactInfo([handle])[handle] + check_contact_info(info, keys) + + info = conn.ContactInfo.RequestContactInfo(handle) + check_contact_info(info, keys) def test(q, bus, conn): conn.Connect() - q.expect('dbus-signal', signal='StatusChanged', args=[0L, 0L]) + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_NONE_SPECIFIED]) + + assertContains(cs.CONN_IFACE_CONTACT_INFO, + conn.Properties.Get(cs.CONN, "Interfaces")) + ci_props = conn.Properties.GetAll(cs.CONN_IFACE_CONTACT_INFO) + assertEquals(cs.CONTACT_INFO_FLAG_PUSH, ci_props['ContactInfoFlags']) + assertEquals( + [ ('n', [], cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1), + ('fn', [], cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1), + ('email', ['type=internet'], + cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1), + ('x-jabber', [], cs.CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT, 1), + + ], + ci_props['SupportedFields']) + + # Just to verify that RCI does approximately nothing and doesn't crash. + conn.ContactInfo.RefreshContactInfo([21,42,88]) + basic_txt = { "txtvers": "1", "status": "avail" } contact_name = "aliastest@" + get_host_name() @@ -25,14 +96,27 @@ def test(q, bus, conn): handle = wait_for_contact_in_publish(q, bus, conn, contact_name) alias = wait_for_aliases_changed(q, handle) - assert alias == contact_name, alias - - for (alias, dict) in [ - ("last", { "last": "last" }), - ("1st", { "1st": "1st"}), - ("1st last", { "1st": "1st", "last": "last" }), - ("nickname", { "1st": "1st", "last": "last", "nick": "nickname" }), - (contact_name, { }) ]: + assertEquals(contact_name, alias) + + for (alias, dict, expect_contact_info_changed) in [ + # Contact publishes just one of 1st and last + ("last", { "last": "last" }, True), + ("1st", { "1st": "1st"}, True), + # Contact publishes a meaningful value for one of 1st and last, and an + # empty value for the other one and for "nick". Empty values should be + # treated as if missing. + ("last", { "last": "last", "1st": "", "nick": "" }, True), + ("1st", { "1st": "1st", "last": "", "nick": "" }, True), + # When a contact publishes both 1st and last, we have to join them + # together in a stupid anglo-centric way, like iChat does. + ("1st last", { "1st": "1st", "last": "last" }, True), + # Nickname should be preferred as the alias to 1st or last. Since we + # don't report nicknames in ContactInfo, and nothing else has changed + # from the last update, no ContactInfo changes should be announced. + ("nickname", { "1st": "1st", "last": "last", "nick": "nickname" }, False), + # If the contact stops publishing any of this stuff, we should fall back + # to their JID as their alias. + (contact_name, {}, True) ]: txt = basic_txt.copy() txt.update(dict) @@ -41,5 +125,46 @@ def test(q, bus, conn): a = wait_for_aliases_changed (q, handle) assert a == alias, (a, alias, txt) + if expect_contact_info_changed: + info = wait_for_contact_info_changed(q, handle) + check_contact_info(info, dict) + + attrs = conn.Contacts.GetContactAttributes([handle], + [cs.CONN_IFACE_ALIASING], True)[handle] + assertEquals(alias, attrs[cs.CONN_IFACE_ALIASING + "/alias"]) + + check_all_contact_info_methods(conn, handle, dict) + + for keys in [ # Check a few neat transitions, with no empty fields + { "email": "foo@bar.com" }, + { "jid": "nyan@gmail.com", "email": "foo@bar.com" }, + { "jid": "orly@example.com" }, + # Check that empty fields are treated as if omitted + { "email": "foo@bar.com", "jid": "" }, + { "jid": "orly@example.com", "email": "" }, + ]: + txt = basic_txt.copy() + txt.update(keys) + + announcer.set(txt) + info = wait_for_contact_info_changed(q, handle) + check_contact_info(info, keys) + + check_all_contact_info_methods(conn, handle, keys) + + # Try an invalid handle. Both Get and Request should return InvalidHandle. + # (Technically so should RefreshContactInfo but I am lazy.) + call_async(q, conn.ContactInfo, 'GetContactInfo', [42]) + q.expect('dbus-error', method='GetContactInfo', name=cs.INVALID_HANDLE) + call_async(q, conn.ContactInfo, 'RequestContactInfo', 42) + q.expect('dbus-error', method='RequestContactInfo', name=cs.INVALID_HANDLE) + + # Try a valid handle for whom we have no data from the network. Get should + # just omit them; Request should fail. + h = conn.RequestHandles(cs.HT_CONTACT, ['rthrtha@octopus'])[0] + assertEquals({}, conn.ContactInfo.GetContactInfo([h])) + call_async(q, conn.ContactInfo, 'RequestContactInfo', h) + q.expect('dbus-error', method='RequestContactInfo', name=cs.NOT_AVAILABLE) + if __name__ == '__main__': exec_test(test) diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py index debc09ff..cc9be081 100644 --- a/tests/twisted/constants.py +++ b/tests/twisted/constants.py @@ -355,7 +355,8 @@ PRESENCE_ERROR = 8 CONTACT_INFO_FLAG_CAN_SET = 1 CONTACT_INFO_FLAG_PUSH = 2 -CONTACT_INFO_FIELD_FLAG_PARAMETERS_MANDATORY = 1 +CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT = 1 +CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME = 2 # Channel_Interface_SaslAuthentication SASL_STATUS_NOT_STARTED = 0 |