From 8d01414fdb7fd0c1140c16b0c2d678d8486de2f1 Mon Sep 17 00:00:00 2001 From: Mathias Hasselmann Date: Mon, 10 Dec 2012 15:24:57 +0100 Subject: sqlitedb: Store E.164 param in vcards The E.164 normalized phone number is of interest for handset related applications. With this change a X-EVOLUTION-E164 attribute is added to each TEL attribute if the contact summary contains a E.164 formatted variant of the phone number. This shall avoid overhead and inconsistency that would occur if clients would use their own mechanism to compute that already stored information. --- addressbook/libebook/e-vcard.h | 1 + .../libedata-book/e-book-backend-sqlitedb.c | 86 +++++- tests/libebook/client/Makefile.am | 4 + tests/libebook/client/client-test-utils.c | 23 ++ tests/libebook/client/client-test-utils.h | 1 + .../client/test-client-change-country-code.c | 317 +++++++++++++++++++++ 6 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 tests/libebook/client/test-client-change-country-code.c diff --git a/addressbook/libebook/e-vcard.h b/addressbook/libebook/e-vcard.h index 5f4156c1a..c783a9475 100644 --- a/addressbook/libebook/e-vcard.h +++ b/addressbook/libebook/e-vcard.h @@ -81,6 +81,7 @@ G_BEGIN_DECLS #define EVC_X_DEST_EMAIL_NUM "X-EVOLUTION-DEST-EMAIL-NUM" #define EVC_X_DEST_HTML_MAIL "X-EVOLUTION-DEST-HTML-MAIL" #define EVC_X_DEST_SOURCE_UID "X-EVOLUTION-DEST-SOURCE-UID" +#define EVC_X_E164 "X-EVOLUTION-E164" #define EVC_X_FILE_AS "X-EVOLUTION-FILE-AS" #define EVC_X_GADUGADU "X-GADUGADU" #define EVC_X_GROUPWISE "X-GROUPWISE" diff --git a/addressbook/libedata-book/e-book-backend-sqlitedb.c b/addressbook/libedata-book/e-book-backend-sqlitedb.c index b07a32c7a..e6b47645c 100644 --- a/addressbook/libedata-book/e-book-backend-sqlitedb.c +++ b/addressbook/libedata-book/e-book-backend-sqlitedb.c @@ -1548,6 +1548,77 @@ mprintf_phone (const gchar *normal, return stmt; } +static EVCardAttributeParam * +find_param (EVCardAttribute *attr, + const gchar *name) +{ + GList *l; + + for (l = e_vcard_attribute_get_params (attr); l; l = l->next) { + EVCardAttributeParam *const param = l->data; + + if (strcmp (e_vcard_attribute_param_get_name (param), name) == 0) + return param; + } + + return NULL; +} + +static gboolean +update_e164_params (EVCard *vcard, + const gchar *country_code) +{ + const GList *attr_list = e_vcard_get_attributes (vcard); + gboolean modified = FALSE; + + if (!e_phone_number_is_supported ()) + return FALSE; + + for (; attr_list; attr_list = attr_list->next) { + EVCardAttribute *const attr = attr_list->data; + char *normalized_number = NULL; + char *formatted_number = NULL; + EVCardAttributeParam *param = NULL; + + /* Skip all attributes but phone numbers. */ + if (strcmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0) + continue; + + /* Compute normalized phone number. */ + param = find_param (attr, EVC_X_E164); + formatted_number = e_vcard_attribute_get_value (attr); + + if (formatted_number) + normalized_number = convert_phone (formatted_number, country_code); + + /* Update the phone number attribute. */ + if (normalized_number) { + if (param == NULL) { + param = e_vcard_attribute_param_new (EVC_X_E164); + e_vcard_attribute_add_param_with_value (attr, param, normalized_number); + modified = TRUE; + } else { + GList *values = e_vcard_attribute_param_get_values (param); + + if (values == NULL + || g_strcmp0 (values->data, normalized_number) + || values->next) { + e_vcard_attribute_param_remove_values (param); + e_vcard_attribute_param_add_value (param, normalized_number); + modified = TRUE; + } + } + + g_free (normalized_number); + } else if (param) { + e_vcard_attribute_remove_param (attr, EVC_X_E164); + modified = TRUE; + } + } + + return modified; +} + /* Add Contact (free the result with g_free() ) */ static gchar * insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb, @@ -1617,7 +1688,13 @@ insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb, g_warn_if_reached (); } - vcard_str = store_vcard ? e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30) : NULL; + if (store_vcard) { + update_e164_params (E_VCARD (contact), ebsdb->priv->country_code); + vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30); + } else { + vcard_str = NULL; + } + str = sqlite3_mprintf (", %Q, %Q)", vcard_str, NULL); g_string_append (string, str); @@ -4222,8 +4299,8 @@ validate_county_code (EBookBackendSqliteDB *ebsdb, if (vcard_data) { g_print ("The country code has changed to \"%s\". " - "Must rebuild phone number indexes for stored vCards.\n", - ebsdb->priv->country_code); + "Must rebuild %s parameters and indexes for stored vCards.\n", + ebsdb->priv->country_code, EVC_X_E164); } for (l = vcard_data; success && l; l = l->next) { @@ -4233,7 +4310,8 @@ validate_county_code (EBookBackendSqliteDB *ebsdb, if (contact == NULL) continue; - success = insert_contact (ebsdb, contact, folderid, error); + if (update_e164_params (E_VCARD (contact), ebsdb->priv->country_code)) + success = insert_contact (ebsdb, contact, folderid, error); g_object_unref (contact); } diff --git a/tests/libebook/client/Makefile.am b/tests/libebook/client/Makefile.am index 9c582c7ee..348c762cd 100644 --- a/tests/libebook/client/Makefile.am +++ b/tests/libebook/client/Makefile.am @@ -13,6 +13,7 @@ libclient_test_utils_la_CPPFLAGS = \ -I$(top_srcdir)/tests/test-server-utils \ -I$(top_builddir)/tests/test-server-utils \ -DSRCDIR=\""$(abs_srcdir)"\" \ + -DBUILDDIR=\""$(abs_topbuilddir)"\" \ $(EVOLUTION_ADDRESSBOOK_CFLAGS) \ $(CAMEL_CFLAGS) \ $(NULL) @@ -42,6 +43,7 @@ TESTS = \ test-client-remove-contact-by-uid \ test-client-remove-contacts \ test-client-photo-is-uri \ + test-client-change-country-code \ $(NULL) # The noinst tests are functional tests, not unit tests. @@ -112,6 +114,8 @@ test_client_remove_contacts_LDADD=$(TEST_LIBS) test_client_remove_contacts_CPPFLAGS=$(TEST_CPPFLAGS) test_client_photo_is_uri_LDADD=$(TEST_LIBS) test_client_photo_is_uri_CPPFLAGS=$(TEST_CPPFLAGS) +test_client_change_country_code_LDADD=$(TEST_LIBS) +test_client_change_country_code_CPPFLAGS=$(TEST_CPPFLAGS) test_client_stress_factory__fifo_LDADD=$(TEST_LIBS) test_client_stress_factory__fifo_CPPFLAGS=$(TEST_CPPFLAGS) test_client_stress_factory__serial_LDADD=$(TEST_LIBS) diff --git a/tests/libebook/client/client-test-utils.c b/tests/libebook/client/client-test-utils.c index f9bda66a2..23e325740 100644 --- a/tests/libebook/client/client-test-utils.c +++ b/tests/libebook/client/client-test-utils.c @@ -22,6 +22,7 @@ * Tristan Van Berkom */ +#include #include #include @@ -96,6 +97,12 @@ main_initialize (void) if (initialized) return; + /* hasselmm: The locale doesn't only affect obvious settings like + * program messages, or date formats. It also changes more subtile + * aspects like the sorting order. Therefore (IMHO) it's generally + * a good idea to use a fixed locale for regression tests. */ + setlocale (LC_ALL, "en_US.UTF-8"); + g_type_init (); e_gdbus_templates_init_main_thread (); @@ -224,6 +231,22 @@ stop_main_loop (gint stop_result) g_main_loop_quit (loop); } +static gboolean +sleep_in_main_loop_cb (gpointer data) +{ + g_main_loop_quit (data); + return FALSE; +} + +void +sleep_in_main_loop (guint msec) +{ + GMainLoop *loop = g_main_loop_new (NULL, FALSE); + g_timeout_add (msec, sleep_in_main_loop_cb, loop); + g_main_loop_run (loop); + g_main_loop_unref (loop); +} + /* returns value used in stop_main_loop() */ gint get_main_loop_stop_result (void) diff --git a/tests/libebook/client/client-test-utils.h b/tests/libebook/client/client-test-utils.h index 25377354a..510f34128 100644 --- a/tests/libebook/client/client-test-utils.h +++ b/tests/libebook/client/client-test-utils.h @@ -35,6 +35,7 @@ void main_initialize (void); void start_main_loop (GThreadFunc func, gpointer data); void start_in_thread_with_main_loop (GThreadFunc func, gpointer data); void start_in_idle_with_main_loop (GThreadFunc func, gpointer data); +void sleep_in_main_loop (guint msec); void stop_main_loop (gint stop_result); gint get_main_loop_stop_result (void); diff --git a/tests/libebook/client/test-client-change-country-code.c b/tests/libebook/client/test-client-change-country-code.c new file mode 100644 index 000000000..786079db9 --- /dev/null +++ b/tests/libebook/client/test-client-change-country-code.c @@ -0,0 +1,317 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012,2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Mathias Hasselmann + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#include "client-test-utils.h" +#include "e-test-server-utils.h" + + +static void +setup_custom_book (ESource *scratch, + ETestServerClosure *closure) +{ + ESourceBackendSummarySetup *setup; + + g_type_ensure (E_TYPE_SOURCE_BACKEND_SUMMARY_SETUP); + setup = e_source_get_extension (scratch, E_SOURCE_EXTENSION_BACKEND_SUMMARY_SETUP); + e_source_backend_summary_setup_set_summary_fields (setup, + E_CONTACT_TEL, + 0); + e_source_backend_summary_setup_set_indexed_fields (setup, + E_CONTACT_TEL, E_BOOK_INDEX_PHONE, + 0); +} + +static gboolean +query_service_pid (const gchar *name, + GPid *pid, + GError **error) +{ + GDBusConnection *connection; + GVariant *rv; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) + return FALSE; + + rv = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + g_variant_new ("(s)", name), + NULL, G_DBUS_CALL_FLAGS_NONE, -1, + NULL, error); + + g_object_unref (connection); + + if (rv == NULL) + return FALSE; + + g_variant_get (rv, "(u)", pid); + g_variant_unref (rv); + + return TRUE; +} + +static gboolean +spawn_addressbook_factory (GPid *pid, GError **error) +{ + gint i; + GPid child_pid = 0; + GPid service_pid = 0; + gboolean success; + + gchar *argv[] = { + g_build_filename (BUILDDIR, "services/evolution-addressbook-factory/evolution-addressbook-factory", NULL), + NULL + }; + + success = g_spawn_async ( + NULL, argv, NULL, + g_test_verbose () ? 0 : G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &child_pid, error); + + g_free (argv[0]); + + /* Wait a bit for the addressbook factory starting up so that it can claim its D-Bus name */ + if (success) { + for (i = 0; service_pid == 0 && i < 10; ++i) { + if (error && *error) + g_clear_error (error); + + sleep_in_main_loop (150); + success = query_service_pid (ADDRESS_BOOK_DBUS_SERVICE_NAME, &service_pid, error); + } + } + + if (success) { + g_assert_cmpuint (service_pid, ==, child_pid); + + if (pid) + *pid = child_pid; + } + + return success; +} + +static void +test_client_change_country_code (void) +{ + ETestServerFixture fixture; + ETestServerClosure data; + + GError *error = NULL; + EContact *contact; + gchar *contact_uid; + EVCardAttribute *attr; + GList *e164_values; + gboolean success; + GPid factory_pid = 0; + GSList *fetched_uids; + + /* Initialize e-test-server-utils structures */ + memset (&fixture, 0, sizeof fixture); + + memset (&data, 0, sizeof data); + data.type = E_TEST_SERVER_ADDRESS_BOOK; + data.customize = setup_custom_book; + data.keep_work_directory = TRUE; + + /************************************** + * Create contacts within U.S. locale * + **************************************/ + + /* Set with U.S. locale for addresses */ + g_setenv ("LC_ADDRESS", "en_US.UTF-8", TRUE); + setlocale (LC_ADDRESS, ""); + g_assert_cmpstr (nl_langinfo (_NL_ADDRESS_COUNTRY_AB2), ==, "US"); + + /* Launch addressbook factory on fake-dbus */ + success = spawn_addressbook_factory (&factory_pid, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert_cmpuint (factory_pid, !=, 0); + g_assert (success); + + /* Create the test addressbook */ + e_test_server_utils_setup (&fixture, &data); + g_assert (fixture.service.book_client != NULL); + + success = e_client_open_sync + (E_CLIENT (fixture.service.book_client), FALSE, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + /* Add test contact */ + contact = e_contact_new_from_vcard ("BEGIN:VCARD\nTEL:221.542.3789\nEND:VCARD"); + success = e_book_client_add_contact_sync + (fixture.service.book_client, contact, &contact_uid, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + g_object_unref (contact); + + /************************************** + * Verify contacts within U.S. locale * + **************************************/ + + /* Fetch the contact by UID and check EVC_TEL attribute and its EVC_X_E164 parameter */ + success = e_book_client_get_contact_sync + (fixture.service.book_client, contact_uid, &contact, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + attr = e_vcard_get_attribute (E_VCARD (contact), EVC_TEL); + + g_assert (attr != NULL); + g_assert_cmpstr (e_vcard_attribute_get_value (attr), ==, "221.542.3789"); + + e164_values = e_vcard_attribute_get_param (attr, EVC_X_E164); + + g_assert (e164_values != NULL); + g_assert_cmpstr (e164_values->data, ==, "+12215423789"); + + /* Now resolve the contact via its phone number, assuming indexes are used */ + success = e_book_client_get_contacts_uids_sync ( + fixture.service.book_client, "(eqphone \"phone\" \"+1/221/5423789\")", + &fetched_uids, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + g_assert_cmpuint (g_slist_length (fetched_uids), ==, 1); + g_assert_cmpstr (fetched_uids->data, ==, contact_uid); + g_slist_free_full (fetched_uids, g_free); + + success = e_book_client_get_contacts_uids_sync ( + fixture.service.book_client, "(eqphone \"phone\" \"+49 (221) 5423789\")", + &fetched_uids, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + g_assert_cmpuint (g_slist_length (fetched_uids), ==, 0); + g_slist_free_full (fetched_uids, g_free); + + /*************************** + * Switch to German locale * + ***************************/ + + /* Shutdown address book factory on fake D-Bus */ + e_test_server_utils_teardown (&fixture, &data); + g_assert (fixture.service.book_client == NULL); + data.keep_work_directory = g_test_verbose (); + + success = (kill (factory_pid, SIGTERM) == 0); + g_assert (success); + factory_pid = 0; + + /* Wait a bit to let GDBus notice what happened... */ + sleep_in_main_loop (1500); + + /* Switch to German locale */ + g_setenv ("LC_ADDRESS", "de_DE.UTF-8", TRUE); + setlocale (LC_ADDRESS, ""); + + g_assert_cmpstr (nl_langinfo (_NL_ADDRESS_COUNTRY_AB2), ==, "DE"); + + /* Respawn the addressbook factory */ + success = spawn_addressbook_factory (&factory_pid, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert_cmpuint (factory_pid, !=, 0); + g_assert (success); + + /* Reopen the book */ + e_test_server_utils_setup (&fixture, &data); + g_assert (fixture.service.book_client != NULL); + + success = e_client_open_sync + (E_CLIENT (fixture.service.book_client), FALSE, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + /**************************************** + * Verify contacts within German locale * + ****************************************/ + + /* Fetch the contact by UID and check EVC_TEL attribute and its EVC_X_E164 parameter */ + success = e_book_client_get_contact_sync + (fixture.service.book_client, contact_uid, &contact, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + attr = e_vcard_get_attribute (E_VCARD (contact), EVC_TEL); + + g_assert (attr != NULL); + g_assert_cmpstr (e_vcard_attribute_get_value (attr), ==, "221.542.3789"); + + e164_values = e_vcard_attribute_get_param (attr, EVC_X_E164); + + g_assert (e164_values != NULL); + g_assert_cmpstr (e164_values->data, ==, "+492215423789"); + + /* Now resolve the contact via its phone number, assuming indexes are used */ + success = e_book_client_get_contacts_uids_sync ( + fixture.service.book_client, "(eqphone \"phone\" \"+1/221/5423789\")", + &fetched_uids, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + g_assert_cmpuint (g_slist_length (fetched_uids), ==, 0); + g_slist_free_full (fetched_uids, g_free); + + success = e_book_client_get_contacts_uids_sync ( + fixture.service.book_client, "(eqphone \"phone\" \"+49 (221) 5423789\")", + &fetched_uids, NULL, &error); + g_assert_cmpstr (error ? error->message : NULL, ==, NULL); + g_assert (success); + + g_assert_cmpuint (g_slist_length (fetched_uids), ==, 1); + g_assert_cmpstr (fetched_uids->data, ==, contact_uid); + g_slist_free_full (fetched_uids, g_free); + + /*********** + * Cleanup * + ***********/ + + e_test_server_utils_teardown (&fixture, &data); + g_assert (fixture.service.book_client == NULL); +} + +gint +main (gint argc, + gchar **argv) +{ + g_test_init (&argc, &argv, NULL); + + main_initialize (); + sleep_in_main_loop (500); + + g_test_add_func ("/client/e164/change-country-code", test_client_change_country_code); + + return e_test_server_utils_run (); +} -- cgit v1.2.1