[folks] Support writing extended info for Telepathy user contacts
- From: Travis Reitter <treitter src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [folks] Support writing extended info for Telepathy user contacts
- Date: Tue, 11 Oct 2011 01:13:59 +0000 (UTC)
commit 0d22764d076f2eb0fd14629cf0191ed8d62c1b2f
Author: Travis Reitter <travis reitter collabora co uk>
Date: Fri Sep 30 10:54:15 2011 -0700
Support writing extended info for Telepathy user contacts
Helps: bgo#657602 - Telepathy backend fails to set Personas' phone
numbers from ContactInfo
backends/telepathy/lib/tpf-persona-store.vala | 104 +++++++++++++
backends/telepathy/lib/tpf-persona.vala | 158 ++++++++++++++++++--
tests/lib/telepathy/contactlist/conn.c | 30 ++++
.../telepathy/contactlist/contact-list-manager.c | 50 ++++++
.../telepathy/contactlist/contact-list-manager.h | 2 +
tests/telepathy/individual-properties.vala | 138 +++++++++++++++++
6 files changed, 472 insertions(+), 10 deletions(-)
---
diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala
index 14b07a4..3f8c6ce 100644
--- a/backends/telepathy/lib/tpf-persona-store.vala
+++ b/backends/telepathy/lib/tpf-persona-store.vala
@@ -2125,4 +2125,108 @@ public class Tpf.PersonaStore : Folks.PersonaStore
FolksTpLowlevel.connection_set_contact_alias (this._conn,
(Handle) persona.contact.handle, alias);
}
+
+ internal async void change_user_full_name (Tpf.Persona persona,
+ string full_name) throws PersonaStoreError
+ {
+ /* Deal with badly-behaved callers */
+ if (full_name == null)
+ {
+ full_name = "";
+ }
+
+ var info_set = new HashSet<ContactInfoField> ();
+ string[] values = { full_name };
+ string[] parameters = { null };
+
+ var field = new ContactInfoField ("fn", parameters, values);
+ info_set.add (field);
+
+ yield this._change_user_contact_info (persona, info_set);
+ }
+
+ internal async void _change_user_details (
+ Tpf.Persona persona, Set<AbstractFieldDetails<string>> details,
+ string field_name)
+ throws PersonaStoreError
+ {
+ var info_set = new HashSet<ContactInfoField> ();
+
+ foreach (var afd in details)
+ {
+ string[] values = { afd.value };
+ string[] parameters = {};
+
+ foreach (var param_name in afd.parameters.get_keys ())
+ {
+ var param_values = afd.parameters[param_name];
+ foreach (var param_value in param_values)
+ {
+ parameters += @"$param_name=$param_value";
+ }
+ }
+
+ if (parameters.length == 0)
+ parameters = { null };
+
+ var field = new ContactInfoField (field_name, parameters, values);
+ info_set.add (field);
+ }
+
+ yield this._change_user_contact_info (persona, info_set);
+ }
+
+ private async void _change_user_contact_info (Tpf.Persona persona,
+ HashSet<ContactInfoField> info_set) throws PersonaStoreError
+ {
+ if (!persona.is_user)
+ {
+ throw new PersonaStoreError.INVALID_ARGUMENT (
+ _("Extended information may only be set on the user's Telepathy contact."));
+ }
+
+ var info_list = this._contact_info_set_to_list (info_set);
+ if (this.account.connection != null)
+ {
+ GLib.Error? error = null;
+ bool success = false;
+ try
+ {
+ success =
+ yield this.account.connection.set_contact_info_async (
+ info_list);
+ }
+ catch (GLib.Error e)
+ {
+ error = e;
+ }
+
+ if (error != null || !success)
+ {
+ warning ("Failed to set extended information on user's " +
+ "Telepathy contact: %s",
+ error != null ? error.message : "(reason unknown)");
+ }
+ }
+ else
+ {
+ throw new PersonaStoreError.STORE_OFFLINE (
+ _("Extended information cannot be written because the store is disconnected."));
+ }
+ }
+
+ private static GLib.List<ContactInfoField> _contact_info_set_to_list (
+ HashSet<ContactInfoField> info_set)
+ {
+ var info_list = new GLib.List<ContactInfoField> ();
+ foreach (var info_field in info_set)
+ {
+ info_list.prepend (new ContactInfoField (
+ info_field.field_name, info_field.parameters,
+ info_field.field_value));
+ }
+ info_list.reverse ();
+
+ return info_list;
+ }
}
diff --git a/backends/telepathy/lib/tpf-persona.vala b/backends/telepathy/lib/tpf-persona.vala
index 8e05a34..1eb299c 100644
--- a/backends/telepathy/lib/tpf-persona.vala
+++ b/backends/telepathy/lib/tpf-persona.vala
@@ -33,6 +33,7 @@ public class Tpf.Persona : Folks.Persona,
FavouriteDetails,
GroupDetails,
ImDetails,
+ NameDetails,
PhoneDetails,
PresenceDetails
{
@@ -40,6 +41,7 @@ public class Tpf.Persona : Folks.Persona,
private Set<string> _groups_ro;
private bool _is_favourite;
private string _alias; /* must never be null */
+ private string _full_name; /* must never be null */
private HashMultiMap<string, ImFieldDetails> _im_addresses;
private const string[] _linkable_properties = { "im-addresses" };
private const string[] _writeable_properties =
@@ -84,6 +86,78 @@ public class Tpf.Persona : Folks.Persona,
}
/**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public StructuredName? structured_name
+ {
+ get { return null; }
+ set { this.change_structured_name.begin (value); } /* not writeable */
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public string full_name
+ {
+ get { return this._full_name; }
+ set { this.change_full_name.begin (value); }
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_full_name (string full_name) throws PropertyError
+ {
+ var tpf_store = this.store as Tpf.PersonaStore;
+
+ if (full_name == this._full_name)
+ return;
+
+ if (this._is_constructed)
+ {
+ try
+ {
+ yield tpf_store.change_user_full_name (this, full_name);
+ }
+ catch (PersonaStoreError.INVALID_ARGUMENT e1)
+ {
+ throw new PropertyError.NOT_WRITEABLE (e1.message);
+ }
+ catch (PersonaStoreError.STORE_OFFLINE e2)
+ {
+ throw new PropertyError.UNKNOWN_ERROR (e2.message);
+ }
+ catch (PersonaStoreError e3)
+ {
+ throw new PropertyError.UNKNOWN_ERROR (e3.message);
+ }
+ }
+
+ /* the change will be notified when we receive changes to
+ * contact.contact_info */
+ }
+
+ /**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ [CCode (notify = false)]
+ public string nickname
+ {
+ get { return ""; }
+ set { this.change_nickname.begin (value); } /* not writeable */
+ }
+
+ /**
* The Persona's presence type.
*
* See { link Folks.PresenceDetails.presence_type}.
@@ -303,6 +377,56 @@ public class Tpf.Persona : Folks.Persona,
}
/**
+ * { inheritDoc}
+ *
+ * @since UNRELEASED
+ */
+ public async void change_phone_numbers (
+ Set<PhoneFieldDetails> phone_numbers) throws PropertyError
+ {
+ yield this._change_details<PhoneFieldDetails> (phone_numbers,
+ this._phone_numbers, "tel");
+ }
+
+ private async void _change_details<T> (
+ Set<AbstractFieldDetails<string>> details,
+ Set<AbstractFieldDetails<string>> member_set,
+ string field_name)
+ throws PropertyError
+ {
+ var tpf_store = this.store as Tpf.PersonaStore;
+
+ if (Folks.PersonaStore.equal_sets<PhoneFieldDetails> (phone_numbers,
+ this._phone_numbers))
+ {
+ return;
+ }
+
+ if (this._is_constructed)
+ {
+ try
+ {
+ yield tpf_store._change_user_details (this, details, field_name);
+ }
+ catch (PersonaStoreError.INVALID_ARGUMENT e1)
+ {
+ throw new PropertyError.NOT_WRITEABLE (e1.message);
+ }
+ catch (PersonaStoreError.STORE_OFFLINE e2)
+ {
+ throw new PropertyError.UNKNOWN_ERROR (e2.message);
+ }
+ catch (PersonaStoreError e3)
+ {
+ throw new PropertyError.UNKNOWN_ERROR (e3.message);
+ }
+ }
+
+ /* the change will be notified when we receive changes to
+ * contact.contact_info */
+ }
+
+ /**
* Create a new persona.
*
* Create a new persona for the { link PersonaStore} `store`, representing
@@ -330,6 +454,8 @@ public class Tpf.Persona : Folks.Persona,
store: store,
is_user: contact.handle == connection.self_handle);
+ this._full_name = "";
+
contact.notify["alias"].connect ((s, p) =>
{
/* Tp guarantees that aliases are always non-null. */
@@ -398,9 +524,9 @@ public class Tpf.Persona : Folks.Persona,
contact.notify["contact-info"].connect ((s, p) =>
{
- this._contact_notify_phones ();
+ this._contact_notify_contact_info ();
});
- this._contact_notify_phones ();
+ this._contact_notify_contact_info ();
((Tpf.PersonaStore) this.store).group_members_changed.connect (
(s, group, added, removed) =>
@@ -428,8 +554,9 @@ public class Tpf.Persona : Folks.Persona,
});
}
- private void _contact_notify_phones ()
+ private void _contact_notify_contact_info ()
{
+ var new_full_name = "";
var new_phone_numbers = new HashSet<PhoneFieldDetails> (
(GLib.HashFunc) PhoneFieldDetails.hash,
(GLib.EqualFunc) PhoneFieldDetails.equal);
@@ -437,17 +564,28 @@ public class Tpf.Persona : Folks.Persona,
var contact_info = this.contact.get_contact_info ();
foreach (var info in contact_info)
{
- if (info.field_name != "tel")
- continue;
-
- foreach (var phone_num in info.field_value)
+ if (info.field_name == "") {}
+ else if (info.field_name == "fn")
+ {
+ new_full_name = info.field_value[0];
+ }
+ else if (info.field_name == "tel")
{
- var parameters = this._afd_params_from_strv (info.parameters);
- var phone_fd = new PhoneFieldDetails (phone_num, parameters);
- new_phone_numbers.add (phone_fd);
+ foreach (var phone_num in info.field_value)
+ {
+ var parameters = this._afd_params_from_strv (info.parameters);
+ var phone_fd = new PhoneFieldDetails (phone_num, parameters);
+ new_phone_numbers.add (phone_fd);
+ }
}
}
+ if (new_full_name != this._full_name)
+ {
+ this._full_name = new_full_name;
+ this.notify_property ("full-name");
+ }
+
if (!Folks.PersonaStore.equal_sets<PhoneFieldDetails> (new_phone_numbers,
this._phone_numbers))
{
diff --git a/tests/lib/telepathy/contactlist/conn.c b/tests/lib/telepathy/contactlist/conn.c
index cc35c4b..dbd403e 100644
--- a/tests/lib/telepathy/contactlist/conn.c
+++ b/tests/lib/telepathy/contactlist/conn.c
@@ -411,6 +411,13 @@ conn_contact_info_properties_getter (GObject *object,
supported_fields = g_ptr_array_new ();
g_ptr_array_add (supported_fields, tp_value_array_build (4,
+ G_TYPE_STRING, "fn",
+ G_TYPE_STRV, NULL,
+ G_TYPE_UINT, 0,
+ G_TYPE_UINT, 1,
+ G_TYPE_INVALID));
+
+ g_ptr_array_add (supported_fields, tp_value_array_build (4,
G_TYPE_STRING, "tel",
G_TYPE_STRV, NULL,
G_TYPE_UINT, 0,
@@ -884,6 +891,28 @@ request_contact_info (TpSvcConnectionInterfaceContactInfo *iface,
}
static void
+set_contact_info (TpSvcConnectionInterfaceContactInfo *iface,
+ const GPtrArray *contact_info,
+ DBusGMethodInvocation *context)
+{
+ TpTestContactListConnection *self = TP_TEST_CONTACT_LIST_CONNECTION (iface);
+ GError *error = NULL;
+
+ if (contact_info == NULL)
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_test_contact_list_manager_set_contact_info (self->priv->list_manager,
+ contact_info);
+
+ tp_svc_connection_interface_contact_info_return_from_set_contact_info (
+ context);
+}
+
+static void
init_contact_info (gpointer iface,
gpointer iface_data G_GNUC_UNUSED)
{
@@ -894,6 +923,7 @@ init_contact_info (gpointer iface,
IMPLEMENT(get_contact_info);
IMPLEMENT(refresh_contact_info);
IMPLEMENT(request_contact_info);
+ IMPLEMENT(set_contact_info);
#undef IMPLEMENT
}
diff --git a/tests/lib/telepathy/contactlist/contact-list-manager.c b/tests/lib/telepathy/contactlist/contact-list-manager.c
index e979f8e..1e047b1 100644
--- a/tests/lib/telepathy/contactlist/contact-list-manager.c
+++ b/tests/lib/telepathy/contactlist/contact-list-manager.c
@@ -522,6 +522,11 @@ receive_contact_lists (gpointer p)
_insert_contact_field (d->contact_info, "fn", NULL,
(const gchar * const *) values);
}
+ {
+ const gchar * values[] = { id, NULL };
+ _insert_contact_field (d->contact_info, "email", NULL,
+ (const gchar * const *) values);
+ }
tp_handle_unref (self->priv->contact_repo, handle);
id = "travis example com";
@@ -1763,3 +1768,48 @@ tp_test_contact_list_manager_get_contact_info (TpTestContactListManager *self,
return NULL;
}
+
+void
+tp_test_contact_list_manager_set_contact_info (TpTestContactListManager *self,
+ const GPtrArray *contact_info)
+{
+ TpTestContactList *stored = self->priv->lists[
+ TP_TEST_CONTACT_LIST_STORED];
+ TpTestContactDetails *d = ensure_contact (self, self->priv->conn->self_handle,
+ NULL);
+ GPtrArray *old = d->contact_info;
+
+ /* FIXME: if stored list hasn't been retrieved yet, queue the change for
+ * later */
+
+ /* if shutting down, do nothing */
+ if (stored == NULL)
+ return;
+
+ d->contact_info = dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+ {
+ guint i;
+ for (i = 0; i < contact_info->len; i++)
+ {
+ const gchar *name;
+ const gchar * const * params;
+ const gchar * const * values;
+ GValueArray *va = g_ptr_array_index (contact_info, i);
+
+ tp_value_array_unpack (va, 3,
+ &name,
+ ¶ms,
+ &values);
+
+ _insert_contact_field (d->contact_info, name, params, values);
+ }
+ }
+
+ /* always send the updated roster, since it's not worth checking the
+ * contact_info for changes */
+ send_updated_roster (self, self->priv->conn->self_handle);
+
+ if (old != NULL)
+ g_ptr_array_unref (old);
+}
diff --git a/tests/lib/telepathy/contactlist/contact-list-manager.h b/tests/lib/telepathy/contactlist/contact-list-manager.h
index 797f3fb..4d9f050 100644
--- a/tests/lib/telepathy/contactlist/contact-list-manager.h
+++ b/tests/lib/telepathy/contactlist/contact-list-manager.h
@@ -104,6 +104,8 @@ void tp_test_contact_list_manager_set_alias (
TpTestContactListManager *self, TpHandle contact, const gchar *alias);
GPtrArray * tp_test_contact_list_manager_get_contact_info (
TpTestContactListManager *self, TpHandle contact);
+void tp_test_contact_list_manager_set_contact_info (
+ TpTestContactListManager *self, const GPtrArray *contact_info);
G_END_DECLS
diff --git a/tests/telepathy/individual-properties.vala b/tests/telepathy/individual-properties.vala
index ae8e84c..b56245b 100644
--- a/tests/telepathy/individual-properties.vala
+++ b/tests/telepathy/individual-properties.vala
@@ -29,6 +29,7 @@ public class IndividualPropertiesTests : Folks.TestCase
private TpTest.Backend tp_backend;
private void* _account_handle;
private int _test_timeout = 3;
+ private HashSet<string> _changes_pending;
public IndividualPropertiesTests ()
{
@@ -42,6 +43,8 @@ public class IndividualPropertiesTests : Folks.TestCase
this.test_individual_properties_change_alias_through_tp_backend);
this.add_test ("individual properties:change alias through test cm",
this.test_individual_properties_change_alias_through_test_cm);
+ this.add_test ("individual properties:change contact info",
+ this.test_individual_properties_change_contact_info);
if (Environment.get_variable ("FOLKS_TEST_VALGRIND") != null)
this._test_timeout = 10;
@@ -52,6 +55,7 @@ public class IndividualPropertiesTests : Folks.TestCase
this.tp_backend.set_up ();
this._account_handle = this.tp_backend.add_account ("protocol",
"me example com", "cm", "account");
+ this._changes_pending = new HashSet<string> ();
}
public override void tear_down ()
@@ -281,6 +285,140 @@ public class IndividualPropertiesTests : Folks.TestCase
/* necessary to reset the aggregator for the next test */
aggregator = null;
}
+
+ public void test_individual_properties_change_contact_info ()
+ {
+ var main_loop = new GLib.MainLoop (null, false);
+ this._changes_pending.add ("phone-numbers");
+ this._changes_pending.add ("full-name");
+
+ /* Set up the aggregator */
+ var aggregator = new IndividualAggregator ();
+ aggregator.individuals_changed_detailed.connect ((changes) =>
+ {
+ this._change_contact_info_aggregator_individuals_added (changes);
+ });
+
+ aggregator.prepare ();
+
+ /* Kill the main loop after a few seconds. If the alias hasn't been
+ * notified, something along the way failed or been too slow (which we can
+ * consider to be failure). */
+ Timeout.add_seconds (this._test_timeout, () =>
+ {
+ main_loop.quit ();
+ return false;
+ });
+
+ main_loop.run ();
+
+ assert (this._changes_pending.size == 0);
+
+ /* necessary to reset the aggregator for the next test */
+ aggregator = null;
+ }
+
+ private async void _change_contact_info_aggregator_individuals_added (
+ MultiMap<Individual?, Individual?> changes)
+ {
+ var added = changes.get_values ();
+ var removed = changes.get_keys ();
+
+ var new_phone_fd = new PhoneFieldDetails ("+112233445566");
+ new_phone_fd.set_parameter (AbstractFieldDetails.PARAM_TYPE,
+ AbstractFieldDetails.PARAM_TYPE_HOME);
+ var new_full_name = "Cave Johnson";
+
+ foreach (Individual i in added)
+ {
+ assert (i != null);
+
+ /* Check properties */
+ assert (new_full_name != i.full_name);
+ assert (!(new_phone_fd in i.phone_numbers));
+
+ i.notify["full-name"].connect ((s, p) =>
+ {
+ /* we can't re-use i here due to Vala's implementation */
+ var ind = (Individual) s;
+
+ if (ind.full_name == new_full_name)
+ this._changes_pending.remove ("full-name");
+ });
+
+ i.notify["phone-numbers"].connect ((s, p) =>
+ {
+ /* we can't re-use i here due to Vala's implementation */
+ var ind = (Individual) s;
+
+ if (new_phone_fd in ind.phone_numbers)
+ {
+ this._changes_pending.remove ("phone-numbers");
+ }
+ });
+
+ /* the contact list this aggregator is based upon has exactly 1
+ * Tpf.Persona per Individual */
+ Folks.Persona persona = null;
+ foreach (var p in i.personas)
+ {
+ persona = p;
+ break;
+ }
+ assert (persona is Tpf.Persona);
+
+ var phones = new HashSet<PhoneFieldDetails> (
+ (GLib.HashFunc) PhoneFieldDetails.hash,
+ (GLib.EqualFunc) PhoneFieldDetails.equal);
+ phones.add (new_phone_fd);
+
+ /* set the extended info through Telepathy's ContactInfo interface and
+ * wait for it to hit our notification callback above */
+
+ /* setting the extended info on a non-user is invalid for the
+ * Telepathy backend, so this tracks the number of expected errors for
+ * intentionally-invalid property changes */
+ int uncaught_errors = 0;
+
+ if (!i.is_user)
+ uncaught_errors++;
+ try
+ {
+ yield ((Tpf.Persona) persona).change_full_name (new_full_name);
+ }
+ catch (PropertyError e1)
+ {
+ if (!i.is_user)
+ uncaught_errors--;
+ }
+
+ if (!i.is_user)
+ uncaught_errors++;
+ try
+ {
+ yield ((Tpf.Persona) persona).change_phone_numbers (phones);
+ }
+ catch (PropertyError e2)
+ {
+ /* setting the extended info on a non-user is invalid for the
+ * Telepathy backend */
+ if (!i.is_user)
+ uncaught_errors--;
+ }
+
+ if (!i.is_user)
+ {
+ assert (uncaught_errors == 0);
+ }
+ }
+
+ assert (removed.size == 1);
+
+ foreach (var r in removed)
+ {
+ assert (r == null);
+ }
+ }
}
public int main (string[] args)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]