summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2011-08-24 15:21:32 +0100
committerWill Thompson <will.thompson@collabora.co.uk>2011-08-24 15:21:58 +0100
commitb81086c2a76b2e5ca3ca421deebc51122fb26069 (patch)
tree57abbf812e35e2df64cf832878ce2f9c026c6ae3
parente97a10ac5c84088967b3740c593372ccdbca4731 (diff)
parent79c310dfcae1ee9de84448b0c0ee0dbc48431f5e (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.am2
-rw-r--r--src/avahi-contact.c48
-rw-r--r--src/connection-contact-info.c375
-rw-r--r--src/connection-contact-info.h38
-rw-r--r--src/connection.c24
-rw-r--r--src/contact.c60
-rw-r--r--src/contact.h25
-rw-r--r--src/debug.c1
-rw-r--r--tests/twisted/avahi/aliases.py161
-rw-r--r--tests/twisted/constants.py3
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