[gnome-contacts/wip/nielsdg/vcard-import] Create an object to import VCard
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-contacts/wip/nielsdg/vcard-import] Create an object to import VCard
- Date: Sun, 17 Jan 2021 21:05:12 +0000 (UTC)
commit 5db6a23b0bd9f3fdf47438d2ff955e2b48c79654
Author: Niels De Graef <nielsdegraef gmail com>
Date: Mon Jan 11 19:22:17 2021 +0100
Create an object to import VCard
data/ui/contacts-main-window.ui | 8 +
src/contacts-app.vala | 42 ++++-
src/io/contacts-io-importer.vala | 46 ++++++
src/io/contacts-io-vcard-importer.vala | 279 +++++++++++++++++++++++++++++++++
src/io/contacts-io.vala | 188 ++++++++++++++++++++++
src/meson.build | 4 +
tests/io/meson.build | 14 ++
tests/io/test-serialise.vala | 88 +++++++++++
tests/meson.build | 6 +-
9 files changed, 672 insertions(+), 3 deletions(-)
---
diff --git a/data/ui/contacts-main-window.ui b/data/ui/contacts-main-window.ui
index de3e0de6..a3e7bf15 100644
--- a/data/ui/contacts-main-window.ui
+++ b/data/ui/contacts-main-window.ui
@@ -43,6 +43,14 @@
<property name="visible">True</property>
</object>
</child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="can_focus">True</property>
+ <property name="text" translatable="yes">Import</property>
+ <property name="action-name">app.import</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
<child>
<object class="GtkModelButton">
<property name="can_focus">True</property>
diff --git a/src/contacts-app.vala b/src/contacts-app.vala
index b1adaf40..c317dbff 100644
--- a/src/contacts-app.vala
+++ b/src/contacts-app.vala
@@ -31,7 +31,8 @@ public class Contacts.App : Gtk.Application {
{ "change-book", change_address_book },
{ "online-accounts", online_accounts },
{ "new-contact", new_contact },
- { "show-contact", on_show_contact, "s"}
+ { "show-contact", on_show_contact, "s"},
+ { "import", on_import }
};
private const OptionEntry[] options = {
@@ -305,4 +306,43 @@ public class Contacts.App : Gtk.Application {
show_individual.begin (individual);
}
+ private void on_import(SimpleAction action, Variant? param) {
+ var chooser = new Gtk.FileChooserNative ("Select contact file",
+ this.window,
+ Gtk.FileChooserAction.OPEN,
+ _("Import"),
+ _("Cancel"));
+ chooser.modal = true;
+ chooser.select_multiple = false;
+
+ // TODO: somehow get this from the list of importers we have
+ var filter = new Gtk.FileFilter ();
+ filter.set_filter_name ("VCard files");
+ filter.add_pattern ("*.vcf");
+ filter.add_pattern ("*.vcard");
+ chooser.add_filter (filter);
+
+ chooser.response.connect ((response) => {
+ if (response != Gtk.ResponseType.ACCEPT) {
+ chooser.destroy ();
+ return;
+ }
+
+ if (chooser.get_filename () == null) {
+ debug ("No file selected, or no path available");
+ chooser.destroy ();
+ }
+
+ try {
+ var file = File.new_for_path (chooser.get_filename ());
+ var importer = new Io.VCardImporter ();
+ var details = importer.import_file (file);
+ } catch (GLib.Error err) {
+ warning ("Couldn't import file: %s", err.message);
+ }
+
+ chooser.destroy ();
+ });
+ chooser.run ();
+ }
}
diff --git a/src/io/contacts-io-importer.vala b/src/io/contacts-io-importer.vala
new file mode 100644
index 00000000..114c5483
--- /dev/null
+++ b/src/io/contacts-io-importer.vala
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+/**
+ * A generic interface for importing
+ */
+public abstract class Contacts.Io.Importer {
+
+ /**
+ * Takes the given {@link GLib.File} containing a VCard string and tries to
+ * parse it into a {@link GLib.HashTable}, which can then be used for methods
+ * like {@link Folks.PersonaStore.add_persona_from_details}.
+ */
+ public HashTable<string, Value?> import_file (GLib.File file) throws GLib.Error {
+ var path = file.get_path ();
+ if (path == null)
+ throw new GLib.IOError.INVALID_FILENAME ("Couldn't import file: file doesn't have a path");
+
+ string vcard_str;
+ FileUtils.get_contents (path, out vcard_str);
+ return import_string (vcard_str);
+ }
+
+ /**
+ * Takes the given input string and tries to parse it into a
+ * {@link GLib.HashTable}, which can then be used for methods like
+ * {@link Folks.PersonaStore.add_persona_from_details}.
+ */
+ public abstract GLib.HashTable<string, Value?> import_string (string vcard_str);
+}
diff --git a/src/io/contacts-io-vcard-importer.vala b/src/io/contacts-io-vcard-importer.vala
new file mode 100644
index 00000000..a43b8a50
--- /dev/null
+++ b/src/io/contacts-io-vcard-importer.vala
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+/**
+ *
+ */
+public class Contacts.Io.VCardImporter : Contacts.Io.Importer {
+
+ public VCardImporter () {
+ }
+
+ /**
+ * Takes the given VCard string and tries to parse it into a
+ * {@link GLib.HashTable}, which can then be used for methods like
+ * {@link Folks.PersonaStore.add_persona_from_details}.
+ */
+ public override HashTable<string, Value?> import_string (string vcard_str) {
+ var details = new HashTable<string, Value?> (GLib.str_hash, GLib.str_equal);
+ var vcard = new E.VCard.from_string (vcard_str);
+
+ unowned var vcard_attrs = vcard.get_attributes ();
+ message ("Got %u attributes in this vcard", vcard_attrs.length ());
+
+ foreach (unowned E.VCardAttribute attr in vcard_attrs) {
+ switch (attr.get_name ()) {
+ // Identification Properties
+ case E.EVC_FN:
+ handle_fn (details, attr);
+ break;
+ case E.EVC_N:
+ handle_n (details, attr);
+ break;
+ case E.EVC_NICKNAME:
+ handle_nickname (details, attr);
+ break;
+/*
+ case E.EVC_PHOTO:
+ handle_photo (details, attr);
+ break;
+*/
+ case E.EVC_BDAY:
+ handle_bday (details, attr);
+ break;
+ // Delivery Addressing Properties
+ case E.EVC_ADR:
+ handle_adr (details, attr);
+ break;
+ // Communications Properties
+ case E.EVC_TEL:
+ handle_tel (details, attr);
+ break;
+ case E.EVC_EMAIL:
+ handle_email (details, attr);
+ break;
+ // Explanatory Properties
+ case E.EVC_NOTE:
+ handle_note (details, attr);
+ break;
+ case E.EVC_URL:
+ handle_url (details, attr);
+ break;
+
+ default:
+ debug ("Unknown property name '%s'", attr.get_name ());
+ break;
+ }
+ }
+
+ return details;
+ }
+
+ // Handles the "FN" (Full Name) attribute
+ private void handle_fn (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ var full_name = attr.get_value ();
+ message ("Got FN '%s'", full_name);
+
+ Value? fn_v = Value (typeof (string));
+ fn_v.set_string (full_name);
+ details.insert (Folks.PersonaStore.detail_key (PersonaDetail.FULL_NAME),
+ (owned) fn_v);
+ }
+
+ // Handles the "N" (structured Name) attribute
+ private void handle_n (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ unowned var values = attr.get_values ();
+
+ // From the VCard spec:
+ // The structured property value corresponds, in sequence, to the Family
+ // Names (also known as surnames), Given Names, Additional Names, Honorific
+ // Prefixes, and Honorific Suffixes.
+ unowned var family_name = values.nth_data (0) ?? "";
+ unowned var given_name = values.nth_data (1) ?? "";
+ unowned var additional_names = values.nth_data (2) ?? "";
+ unowned var prefixes = values.nth_data (3) ?? "";
+ unowned var suffixes = values.nth_data (4) ?? "";
+
+ var structured_name = new StructuredName (family_name, given_name,
+ additional_names,
+ prefixes, suffixes);
+ Value? n_v = Value (typeof (StructuredName));
+ n_v.take_object ((owned) structured_name);
+ details.insert (Folks.PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME),
+ (owned) n_v);
+ }
+
+ private void handle_nickname (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ var nickname = attr.get_value ();
+ message ("Got nickname '%s'", nickname);
+
+ Value? nick_v = Value (typeof (string));
+ nick_v.set_string (nickname);
+ details.insert (Folks.PersonaStore.detail_key (PersonaDetail.NICKNAME),
+ (owned) nick_v);
+ }
+
+ // Handles the "BDAY" (birthday) attribute
+ private void handle_bday (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ // Get the attribute valuec
+ var bday = attr.get_value ();
+
+ // Parse it using the logic in E.ContactDate
+ var e_date = E.ContactDate.from_string (bday);
+
+ // Turn it into a GLib.DateTime
+ var datetime = new DateTime.utc ((int) e_date.year,
+ (int) e_date.month,
+ (int) e_date.day,
+ 0, 0, 0.0);
+
+ // Insert it into the hashtable as a GLib.Value
+ Value? bday_val = Value (typeof (DateTime));
+ bday_val.take_boxed ((owned) datetime);
+ details.insert (Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY),
+ (owned) bday_val);
+ }
+
+ private void handle_email (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ var email = attr.get_value ();
+ if (email == null || email == "")
+ return;
+
+ var email_fd = new EmailFieldDetails (email);
+ add_params (email_fd, attr);
+ insert_field_details<EmailFieldDetails> (details, PersonaDetail.EMAIL_ADDRESSES,
+ email_fd,
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ }
+
+ private void handle_tel (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ var phone_nr = attr.get_value ();
+ if (phone_nr == null || phone_nr == "")
+ return;
+
+ var phone_fd = new PhoneFieldDetails (phone_nr);
+ add_params (phone_fd, attr);
+ insert_field_details<PhoneFieldDetails> (details, PersonaDetail.PHONE_NUMBERS,
+ phone_fd,
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ }
+
+ // Handles the ADR (postal address) attributes
+ private void handle_adr (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ unowned var values = attr.get_values ();
+
+ // From the VCard spec:
+ // ADR-value = ADR-component-pobox ";" ADR-component-ext ";"
+ // ADR-component-street ";" ADR-component-locality ";"
+ // ADR-component-region ";" ADR-component-code ";"
+ // ADR-component-country
+ unowned var po_box = values.nth_data (0) ?? "";
+ unowned var extension = values.nth_data (1) ?? "";
+ unowned var street = values.nth_data (2) ?? "";
+ unowned var locality = values.nth_data (3) ?? "";
+ unowned var region = values.nth_data (4) ?? "";
+ unowned var postal_code = values.nth_data (5) ?? "";
+ unowned var country = values.nth_data (6) ?? "";
+
+ var addr = new PostalAddress (po_box, extension, street, locality, region,
+ postal_code, country, "", null);
+ var addr_fd = new PostalAddressFieldDetails ((owned) addr);
+ add_params (addr_fd, attr);
+
+ insert_field_details<PostalAddressFieldDetails> (details,
+ PersonaDetail.POSTAL_ADDRESSES,
+ addr_fd,
+ AbstractFieldDetails<PostalAddress>.hash_static,
+ AbstractFieldDetails<PostalAddress>.equal_static);
+ }
+
+ private void handle_url (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ var url = attr.get_value ();
+ if (url == null || url == "")
+ return;
+
+ var url_fd = new UrlFieldDetails (url);
+ add_params (url_fd, attr);
+ insert_field_details<UrlFieldDetails> (details, PersonaDetail.URLS,
+ url_fd,
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+ }
+
+ private void handle_note (HashTable<string, Value?> details,
+ E.VCardAttribute attr) {
+ var note = attr.get_value ();
+ if (note == null || note == "")
+ return;
+
+ var note_fd = new NoteFieldDetails (note);
+ add_params (note_fd, attr);
+ insert_field_details<NoteFieldDetails> (details, PersonaDetail.NOTES,
+ note_fd,
+ AbstractFieldDetails<string>.hash_static,
+ AbstractFieldDetails<string>.equal_static);
+
+ }
+
+ // Helper method for inserting aggregated properties
+ private bool insert_field_details<T> (HashTable<string, Value?> details,
+ PersonaDetail key,
+ T field_details,
+ owned Gee.HashDataFunc<T>? hash_func,
+ owned Gee.EqualDataFunc<T>? equal_func) {
+
+ // Get the existing set, or create a new one and add it
+ unowned var old_val = details.lookup (Folks.PersonaStore.detail_key (key));
+ if (old_val != null) {
+ unowned var values = old_val as Gee.HashSet<T>;
+ return values.add (field_details);
+ }
+
+ var values = new Gee.HashSet<T> ((owned) hash_func, (owned) equal_func);
+ Value? new_val = Value (typeof (Gee.Set));
+ new_val.set_object (values);
+ details.insert (Folks.PersonaStore.detail_key (key), (owned) new_val);
+
+ return values.add (field_details);
+ }
+
+ // Helper method to get VCard parameters into an AbstractFieldDetails object.
+ // Will take care of setting the correct "type"
+ private void add_params (AbstractFieldDetails details, E.VCardAttribute attr) {
+ foreach (unowned E.VCardAttributeParam param in attr.get_params ()) {
+ string param_name = param.get_name ().down ();
+ foreach (unowned string param_value in param.get_values ()) {
+ if (param_name == AbstractFieldDetails.PARAM_TYPE)
+ details.add_parameter (param_name, param_value.down ());
+ else
+ details.add_parameter (param_name, param_value);
+ }
+ }
+ }
+}
diff --git a/src/io/contacts-io.vala b/src/io/contacts-io.vala
new file mode 100644
index 00000000..386b7e4f
--- /dev/null
+++ b/src/io/contacts-io.vala
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+/**
+ * Importing and exporting contacts, both internally (using
+ * GVariant serialization) and externally (VCard, CSV, ...)
+ */
+namespace Contacts.Io {
+
+ /**
+ * Serializes the {@link GLib.HashTable} as returned by a
+ * {@link Contacts.Io.Importer} into a {@link GLib.Variant} so it can be sent
+ * from one process to another.
+ */
+ public GLib.Variant serialize_to_gvariant (HashTable<string, Value?> details) {
+ var dict = new GLib.VariantDict ();
+
+ var iter = HashTableIter<string, Value?> (details);
+ unowned string prop;
+ unowned Value? val;
+ while (iter.next (out prop, out val)) {
+
+ if (prop == Folks.PersonaStore.detail_key (PersonaDetail.FULL_NAME)) {
+ serialize_full_name (dict, prop, val);
+ } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)) {
+ serialize_emails (dict, prop, val);
+ } else {
+ warning ("Couldn't serialize unknown property '%s'", prop);
+ }
+ }
+
+ return dict.end ();
+ }
+
+ /**
+ * Deserializes the {@link GLib.Variant} back into a {@link GLib.HashTable}.
+ */
+ public HashTable<string, Value?> deserialize_gvariant (GLib.Variant variant) {
+ return_val_if_fail (variant.get_type ().equal (VariantType.VARDICT), null);
+
+ var details = new HashTable<string, Value?> (GLib.str_hash, GLib.str_equal);
+
+ var iter = variant.iterator ();
+ string prop;
+ GLib.Variant val;
+ while (iter.next ("{sv}", out prop, out val)) {
+
+ if (prop == Folks.PersonaStore.detail_key (PersonaDetail.FULL_NAME)) {
+ deserialize_full_name (details, prop, val);
+ } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)) {
+ deserialize_emails (details, prop, val);
+ } else {
+ warning ("Couldn't serialize unknown property '%s'", prop);
+ }
+ }
+
+ return details;
+ }
+
+ //
+ // FULL NAME
+ // -----------------------------------
+ private const string FULL_NAME_TYPE = "s";
+
+ private bool serialize_full_name (GLib.VariantDict dict, string prop, Value? val) {
+ return_val_if_fail (val.type () == typeof (string), false);
+
+ unowned string full_name = val as string;
+ return_val_if_fail (full_name != null, false);
+
+ dict.insert (prop, FULL_NAME_TYPE, full_name);
+
+ return true;
+ }
+
+ private bool deserialize_full_name (HashTable<string, Value?> details, string prop, Variant variant) {
+ return_val_if_fail (variant.get_type ().equal (VariantType.STRING), false);
+
+ string full_name = variant.get_string ();
+ return_val_if_fail (full_name != null, false);
+
+ details.insert (prop, full_name);
+
+ return true;
+ }
+
+ //
+ // EMAILS
+ // -----------------------------------
+ private const string EMAILS_TYPE = "a(sv)";
+
+ private bool serialize_emails (GLib.VariantDict dict, string prop, Value? val) {
+ return_val_if_fail (val.type () == typeof (Gee.Set), false);
+
+ // Get the list of field details
+ unowned var email_fds = val as Gee.Set<EmailFieldDetails>;
+ return_val_if_fail (email_fds != null, false);
+
+ // Turn the set of field details into an array Variant
+ var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY);
+ foreach (var email_fd in email_fds) {
+ builder.add ("(sv)", email_fd.value, serialize_parameters (email_fd));
+ }
+
+ dict.insert_value (prop, builder.end ());
+
+ return true;
+ }
+
+ private bool deserialize_emails (HashTable<string, Value?> details, string prop, Variant variant) {
+ return_val_if_fail (variant.get_type ().equal (new VariantType (EMAILS_TYPE)), false);
+
+ var email_fds = new Gee.HashSet<EmailFieldDetails> ();
+
+ // Turn the array variant into a set of field details
+ var iter = variant.iterator ();
+ string email;
+ GLib.Variant parameters;
+ while (iter.next ("(sv)", out email, out parameters)) {
+ if (email == "") {
+ warning ("Got empty e-mail address");
+ continue;
+ }
+
+ var email_fd = new EmailFieldDetails (email);
+ deserialize_parameters (parameters, email_fd);
+
+ email_fds.add (email_fd);
+ }
+
+ details.insert (prop, email_fds);
+
+ return true;
+ }
+
+ //
+ // HELPER: PARAMETERS
+ // -----------------------------------
+ // We can't use a vardict here, since one key can map to multiple values.
+ private const string PARAMS_TYPE = "a(ss)";
+
+ private Variant serialize_parameters (AbstractFieldDetails details) {
+
+ if (details.parameters == null || details.parameters.size == 0) {
+ return new GLib.Variant (PARAMS_TYPE, null); // Empty array
+ }
+
+ var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY);
+ var iter = details.parameters.map_iterator ();
+ while (iter.next ()) {
+ string param_name = iter.get_key ();
+ string param_value = iter.get_value ();
+
+ builder.add ("(ss)", param_name, param_value);
+ }
+
+ return builder.end ();
+ }
+
+ private void deserialize_parameters (Variant parameters, AbstractFieldDetails details) {
+ return_if_fail (parameters.get_type ().is_array ());
+
+ var iter = parameters.iterator ();
+ string param_name, param_value;
+ while (iter.next ("(ss)", out param_name, out param_value)) {
+ if (param_name == AbstractFieldDetails.PARAM_TYPE)
+ details.add_parameter (param_name, param_value.down ());
+ else
+ details.add_parameter (param_name, param_value);
+ }
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index d9776384..4be40a75 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,6 +6,10 @@ install_data('org.gnome.Contacts.gschema.xml',
# Common library
libcontacts_sources = files(
+ 'io/contacts-io.vala',
+ 'io/contacts-io-importer.vala',
+ 'io/contacts-io-vcard-importer.vala',
+
'contacts-esd-setup.vala',
'contacts-fake-persona-store.vala',
'contacts-im-service.vala',
diff --git a/tests/io/meson.build b/tests/io/meson.build
new file mode 100644
index 00000000..5b58cbac
--- /dev/null
+++ b/tests/io/meson.build
@@ -0,0 +1,14 @@
+test_names = [
+ 'test-serialise',
+]
+
+foreach _test : test_names
+ test_bin = executable(_test,
+ files('@0@.vala'.format(_test)),
+ dependencies: libcontacts_dep,
+ )
+
+ test(_test, test_bin,
+ suite: 'io',
+ )
+endforeach
diff --git a/tests/io/test-serialise.vala b/tests/io/test-serialise.vala
new file mode 100644
index 00000000..35ffe93b
--- /dev/null
+++ b/tests/io/test-serialise.vala
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+void main (string[] args) {
+ Test.init (ref args);
+ Test.add_func ("/io/serialize_full_name", Contacts.Tests.Io.test_serialize_full_name);
+ Test.add_func ("/io/serialize_emails", Contacts.Tests.Io.test_serialize_emails);
+ Test.run ();
+}
+
+namespace Contacts.Tests.Io {
+
+ private void test_serialize_full_name () {
+ unowned var fn_key = PersonaStore.detail_key (PersonaDetail.FULL_NAME);
+
+ var old_fn = "Niels De Graef";
+ var old_fn_val = Value (typeof (string));
+ old_fn_val.set_string (old_fn);
+
+ var new_fn_val = _transform_single_value (fn_key, old_fn_val);
+ assert_true (new_fn_val.type () == typeof (string));
+ assert_true (old_fn == new_fn_val.get_string ());
+ }
+
+ private void test_serialize_emails () {
+ unowned var emails_key = PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES);
+
+ var old_fd = new EmailFieldDetails ("nielsdegraef gmail com");
+ var new_fd = _transform_single_afd<EmailFieldDetails> (emails_key, old_fd);
+
+ assert_true (old_fd.value == new_fd.value);
+ }
+
+ // Helper to serialize and deserialize an AbstractFieldDetails
+ private T _transform_single_afd<T> (string prop_key, T afd) {
+ var afd_set = new Gee.HashSet<T> ();
+ afd_set.add (afd);
+
+ var val = Value (typeof (Gee.Set));
+ val.set_object (afd_set);
+
+ var emails_value = _transform_single_value (prop_key, val);
+ var emails_set = emails_value.get_object () as Gee.Set<T>;
+ assert_nonnull (emails_set);
+ assert_true (emails_set.size == 1);
+
+ var deserialized_fd = Utils.get_first<T> (emails_set);
+ assert_nonnull (deserialized_fd);
+
+ return deserialized_fd;
+ }
+
+ // Helper to serialize and deserialize a single property with a GLib.Value
+ private GLib.Value _transform_single_value (string prop_key, GLib.Value val) {
+ var details = new HashTable<string, Value?> (GLib.str_hash, GLib.str_equal);
+ details.insert (prop_key, val);
+
+ // Serialize
+ var serialized = Contacts.Io.serialize_to_gvariant (details);
+ assert_nonnull (serialized);
+
+ // Deserialize
+ var details_deserialized = Contacts.Io.deserialize_gvariant (serialized);
+ assert_nonnull (details_deserialized);
+
+ assert_true (details_deserialized.contains (prop_key));
+ var val_deserialized = details_deserialized.lookup (prop_key);
+ assert_true (val_deserialized != null);
+
+ return val_deserialized;
+ }
+}
diff --git a/tests/meson.build b/tests/meson.build
index 92c35863..6dcfcf12 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,14 +1,16 @@
+subdir('io')
+
test_names = [
'basic-test',
]
foreach _test : test_names
test_bin = executable(_test,
- '@0@.vala'.format(_test),
+ files('@0@.vala'.format(_test)),
dependencies: libcontacts_dep,
)
test(_test, test_bin,
- suite: 'gnome-contacts',
+ suite: 'src',
)
endforeach
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]