diff options
author | Niels De Graef <nielsdegraef@gmail.com> | 2022-09-06 22:09:25 +0200 |
---|---|---|
committer | Niels De Graef <nielsdegraef@gmail.com> | 2023-03-02 07:31:29 +0000 |
commit | d455b34b76a25b116b7fa82d4cb5fcf862546bfa (patch) | |
tree | e41be2b1469648334a4f2c581b38747fe2ddc1e6 | |
parent | 99af58107b2e49ad1b5a7fc91d290126fb8636ee (diff) | |
download | gnome-contacts-d455b34b76a25b116b7fa82d4cb5fcf862546bfa.tar.gz |
Move GVariant serialization into Chunk
Rather than building a big if-else block in the `Contacts.Io` namespace,
it's much more interesting to move the GVariant serialization into the
`Contacts.Chunk` objects themselves. That allows us to keep the
serialization logic for a specific field in one place and makes sure we
don't forget about any properties as they're not part of that big
if-else block that checks on property name.
This commit also make sure a lot of the functionality here is now unit
tested, to make sure we're not accidentally regressing.
43 files changed, 786 insertions, 973 deletions
diff --git a/src/contacts-app.vala b/src/contacts-app.vala index 7765e40..b146820 100644 --- a/src/contacts-app.vala +++ b/src/contacts-app.vala @@ -357,7 +357,7 @@ public class Contacts.App : Adw.Application { private async void import_file (GLib.File file) { // First step: parse the data var parse_op = new Io.ParseOperation (file); - HashTable<string, Value?>[]? parse_result = null; + Contact[]? parse_result = null; try { yield parse_op.execute (); debug ("Successfully parsed a contact"); diff --git a/src/contacts-import-operation.vala b/src/contacts-import-operation.vala index bb860f8..ef788be 100644 --- a/src/contacts-import-operation.vala +++ b/src/contacts-import-operation.vala @@ -24,7 +24,7 @@ using Folks; */ public class Contacts.ImportOperation : Operation { - private HashTable<string, Value?>[] to_import; + private Contact[] to_import; private unowned Store store; @@ -33,7 +33,7 @@ public class Contacts.ImportOperation : Operation { private string _description; public override string description { owned get { return this._description; } } - public ImportOperation (Store store, HashTable<string, Value?>[] to_import) { + public ImportOperation (Store store, Contact[] to_import) { this.to_import = to_import; this.store = store; @@ -48,10 +48,12 @@ public class Contacts.ImportOperation : Operation { this.to_import.length, primary_store.display_name); uint new_count = 0; - foreach (unowned var hashtable in this.to_import) { - var persona = yield primary_store.add_persona_from_details (hashtable); - if (persona != null) { - debug ("Created new persona"); + foreach (unowned var contact in this.to_import) { + unowned var individual = + yield contact.apply_changes (this.store.aggregator.primary_store); + if (individual != null) { + debug ("Created new individual (%s)", + (individual != null)? individual.id : "null"); new_count++; } else { debug ("Added persona; no new created"); diff --git a/src/core/contacts-addresses-chunk.vala b/src/core/contacts-addresses-chunk.vala index b4a2c85..a48e1b2 100644 --- a/src/core/contacts-addresses-chunk.vala +++ b/src/core/contacts-addresses-chunk.vala @@ -172,4 +172,41 @@ public class Contacts.Address : BinChunkChild { copy_parameters (address); return address; } + + protected override Variant? to_gvariant_internal () { + return new Variant ("(sssssssv)", + this.address.po_box, + this.address.extension, + this.address.street, + this.address.locality, + this.address.region, + this.address.postal_code, + this.address.country, + parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(sssssssv)"))) { + + string po_box, extension, street, locality, region, postal_code, country; + Variant params_variant; + variant.get ("(sssssssv)", + out po_box, + out extension, + out street, + out locality, + out region, + out postal_code, + out country, + out params_variant); + + this.address.po_box = po_box; + this.address.extension = extension; + this.address.street = street; + this.address.locality = locality; + this.address.region = region; + this.address.postal_code = postal_code; + this.address.country = country; + apply_gvariant_parameters (params_variant); + } } diff --git a/src/core/contacts-alias-chunk.vala b/src/core/contacts-alias-chunk.vala index e2d0f20..412b538 100644 --- a/src/core/contacts-alias-chunk.vala +++ b/src/core/contacts-alias-chunk.vala @@ -65,4 +65,19 @@ public class Contacts.AliasChunk : Chunk { yield ((AliasDetails) this.persona).change_alias (this.alias); } + + public override Variant? to_gvariant () { + return new Variant.string (this.alias); + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) + requires (variant.get_type ().equal (VariantType.STRING)) { + + unowned string alias = variant.get_string (); + if (!mark_dirty) { + this.original_alias = alias; + } + this.alias = alias; + } } diff --git a/src/core/contacts-avatar-chunk.vala b/src/core/contacts-avatar-chunk.vala index 850c6cf..bf15627 100644 --- a/src/core/contacts-avatar-chunk.vala +++ b/src/core/contacts-avatar-chunk.vala @@ -58,4 +58,14 @@ public class Contacts.AvatarChunk : Chunk { requires (this.persona is AvatarDetails) { yield ((AvatarDetails) this.persona).change_avatar (this.avatar); } + + public override Variant? to_gvariant () { + // FIXME: implement + return null; + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) { + // FIXME: implement + } } diff --git a/src/core/contacts-bin-chunk.vala b/src/core/contacts-bin-chunk.vala index cfaba43..3395ed0 100644 --- a/src/core/contacts-bin-chunk.vala +++ b/src/core/contacts-bin-chunk.vala @@ -168,6 +168,40 @@ public abstract class Contacts.BinChunk : Chunk, GLib.ListModel { emptiness_check (); } + // Variant (de)serialization + + public override Variant? to_gvariant () { + if (this.is_empty) + return null; + + var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY); + for (uint i = 0; i < this.elements.length; i++) { + var child_variant = this.elements[i].to_gvariant (); + if (child_variant != null) + builder.add_value (child_variant); + } + + return builder.end (); + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) + requires (variant.get_type ().is_array ()) { + + var iter = variant.iterator (); + var child_variant = iter.next_value (); + while (child_variant != null) { + var child = create_empty_child (); + child.apply_gvariant (child_variant); + add_child (child); + + child_variant = iter.next_value (); + } + if (!mark_dirty) { + finish_initialization (); + } + } + // ListModel implementation public uint n_items { get { return this.elements.length; } } @@ -194,7 +228,20 @@ public abstract class Contacts.BinChunk : Chunk, GLib.ListModel { */ public abstract class Contacts.BinChunkChild : GLib.Object { - public Gee.MultiMap<string, string> parameters { get; set; } + public Gee.MultiMap<string, string> parameters { + get { return this._parameters; } + set { + if (value == this._parameters) + return; + + this._parameters.clear (); + var iter = value.map_iterator (); + while (iter.next ()) + this._parameters[iter.get_key ()] = iter.get_value (); + } + } + private Gee.HashMultiMap<string, string> _parameters + = new Gee.HashMultiMap<string, string> (); /** * Whether this BinChunkChild is empty. You can use the notify signal to @@ -227,6 +274,48 @@ public abstract class Contacts.BinChunkChild : GLib.Object { copy.parameters[iter.get_key ()] = iter.get_value (); } + /** See Contacts.Chunk.to_gvariant() */ + public Variant? to_gvariant () { + if (this.is_empty) + return null; + return to_gvariant_internal (); + } + + protected abstract Variant? to_gvariant_internal (); + + // Helper to serialize the parameters field + protected Variant parameters_to_gvariant () { + if (this.parameters.size == 0) { + return new GLib.Variant ("a(ss)", null); // Empty array + } + + var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY); + var iter = this.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 (); + } + + public abstract void apply_gvariant (Variant variant); + + protected void apply_gvariant_parameters (Variant parameters) + requires (parameters.get_type ().equal (new VariantType ("a(ss)"))) { + + 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) + add_parameter (param_name, param_value.down ()); + else + add_parameter (param_name, param_value); + } + } + // A helper to change a string field with the proper propery notifies protected void change_string_prop (string prop_name, ref string old_value, @@ -317,4 +406,8 @@ public abstract class Contacts.BinChunkChild : GLib.Object { return 0; } + + public void add_parameter (string param_name, string param_value) { + this._parameters[param_name] = param_value; + } } diff --git a/src/core/contacts-birthday-chunk.vala b/src/core/contacts-birthday-chunk.vala index 6a87640..ef47a4c 100644 --- a/src/core/contacts-birthday-chunk.vala +++ b/src/core/contacts-birthday-chunk.vala @@ -100,4 +100,27 @@ public class Contacts.BirthdayChunk : Chunk { private bool is_leap_day (int month, int day) { return month == 2 && day == 29; } + + public override Variant? to_gvariant () { + if (this.birthday == null) + return null; + + int year, month, day; + this.birthday.get_ymd (out year, out month, out day); + return new GLib.Variant ("(iii)", year, month, day); + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) + requires (variant.get_type ().equal (new VariantType ("(iii)"))) { + + int year, month, day; + variant.get ("(iii)", out year, out month, out day); + + var bd = new DateTime.utc (year, month, day, 0, 0, 0.0); + if (!mark_dirty) { + this.original_birthday = bd; + } + this.birthday = bd; + } } diff --git a/src/core/contacts-chunk.vala b/src/core/contacts-chunk.vala index 328a042..333ad7e 100644 --- a/src/core/contacts-chunk.vala +++ b/src/core/contacts-chunk.vala @@ -61,4 +61,22 @@ public abstract class Contacts.Chunk : GLib.Object { */ public abstract async void save_to_persona () throws GLib.Error requires (this.persona != null); + + /** + * Serializes this chunk into a {@link GLib.Variant} accordding to an + * internal format (which can be deserialized later using apply_gvariant()) + * + * If the field is empty or non-existent, it should return null. + */ + public abstract Variant? to_gvariant (); + + /** + * Takes the given variant describing this chunk (in other words, the result + * of to_gvariant() and copies the values accordingly. + * + * If the variant represents the *original* value for this chunk (as there's + * no appropriate construct property), then you can set mark_dirty to false. + */ + public abstract void apply_gvariant (Variant variant, + bool mark_dirty = true); } diff --git a/src/core/contacts-contact.vala b/src/core/contacts-contact.vala index 742bab7..d441f60 100644 --- a/src/core/contacts-contact.vala +++ b/src/core/contacts-contact.vala @@ -44,9 +44,6 @@ public class Contacts.Contact : GLib.Object, GLib.ListModel { on_individual_personas_changed (this.individual, this.individual.personas, Gee.Set.empty<Persona> ()); - } else { - // At the very least let's add an empty full-name chunk - create_chunk ("full-name", null); } } @@ -55,9 +52,31 @@ public class Contacts.Contact : GLib.Object, GLib.ListModel { Object (individual: individual); } + /** Creates a Contact by deserializing the given GVariant */ + public Contact.for_gvariant (Variant variant) + requires (variant.get_type ().equal (VariantType.VARDICT)) { + Object (individual: null); + + var iter = variant.iterator (); + string prop; + Variant val; + while (iter.next ("{sv}", out prop, out val)) { + var pos = create_chunk_internal (prop, null); + if (pos == -1) + continue; + + unowned var chunk = this.chunks[pos]; + chunk.apply_gvariant (val, false); + } + items_changed (0, 0, this.chunks.length); + } + /** Creates a new empty contact */ public Contact.empty () { Object (individual: null); + + // At the very least let's add an empty full-name chunk + create_chunk ("full-name", null); } private void on_individual_personas_changed (Individual individual, @@ -326,4 +345,28 @@ public class Contacts.Contact : GLib.Object, GLib.ListModel { return individual; } + + /** + * Serializes the given contact into a {@link GLib.Variant} (which can then + * be transmitted over a socket). Note that this serialization format is only + * intended for internal purposes and can change over time. + * + * For several reasons (for example easier deserialization and size decrease) + * it will not serialize empty fields but omit them instead. + */ + public Variant to_gvariant () { + var dict = new GLib.VariantDict (); + + for (uint i = 0; i < this.chunks.length; i++) { + unowned var chunk = this.chunks[i]; + + var variant = chunk.to_gvariant (); + if (variant == null) + continue; + + dict.insert_value (chunk.property_name, variant); + } + + return dict.end (); + } } diff --git a/src/core/contacts-email-addresses-chunk.vala b/src/core/contacts-email-addresses-chunk.vala index 36f5715..a03b2c2 100644 --- a/src/core/contacts-email-addresses-chunk.vala +++ b/src/core/contacts-email-addresses-chunk.vala @@ -102,6 +102,21 @@ public class Contacts.EmailAddress : BinChunkChild { return email_address; } + protected override Variant? to_gvariant_internal () { + return new Variant ("(sv)", this.raw_address, parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(sv)"))) { + + string email_addr; + Variant params_variant; + variant.get ("(sv)", out email_addr, out params_variant); + + this.raw_address = email_addr; + apply_gvariant_parameters (params_variant); + } + public string get_mailto_uri () { return "mailto:" + Uri.escape_string (this.raw_address, "@" , false); } diff --git a/src/core/contacts-full-name-chunk.vala b/src/core/contacts-full-name-chunk.vala index e59fb38..bafa424 100644 --- a/src/core/contacts-full-name-chunk.vala +++ b/src/core/contacts-full-name-chunk.vala @@ -61,6 +61,11 @@ public class Contacts.FullNameChunk : Chunk { this.original_full_name = this.full_name; } + public FullNameChunk.from_gvariant (GLib.Variant variant) { + unowned var fn = variant.get_string (); + Object (persona: null, full_name: fn); + } + public override Value? to_value () { return this.full_name; } @@ -69,4 +74,21 @@ public class Contacts.FullNameChunk : Chunk { requires (this.persona is NameDetails) { yield ((NameDetails) this.persona).change_full_name (this.full_name); } + + public override Variant? to_gvariant () { + if (this.full_name == "") + return null; + return new Variant.string (this.full_name); + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) + requires (variant.get_type ().equal (VariantType.STRING)) { + + unowned string full_name = variant.get_string (); + if (!mark_dirty) { + this.original_full_name = full_name; + } + this.full_name = full_name; + } } diff --git a/src/core/contacts-im-addresses-chunk.vala b/src/core/contacts-im-addresses-chunk.vala index 95cdd3a..4d3effb 100644 --- a/src/core/contacts-im-addresses-chunk.vala +++ b/src/core/contacts-im-addresses-chunk.vala @@ -119,4 +119,23 @@ public class Contacts.ImAddress : BinChunkChild { copy_parameters (ima); return ima; } + + protected override Variant? to_gvariant_internal () { + return new Variant ("(ssv)", + this.protocol, + this.address, + parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(ssv)"))) { + + string protocol, address; + Variant params_variant; + variant.get ("(ssv)", out protocol, out address, out params_variant); + + this.protocol = protocol; + this.address = address; + apply_gvariant_parameters (params_variant); + } } diff --git a/src/core/contacts-nickname-chunk.vala b/src/core/contacts-nickname-chunk.vala index 81cf1d9..5dd7f79 100644 --- a/src/core/contacts-nickname-chunk.vala +++ b/src/core/contacts-nickname-chunk.vala @@ -68,4 +68,21 @@ public class Contacts.NicknameChunk : Chunk { yield ((NameDetails) this.persona).change_nickname (this.nickname); } + + public override Variant? to_gvariant () { + if (this.nickname == "") + return null; + return new Variant.string (this.nickname); + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) + requires (variant.get_type ().equal (VariantType.STRING)) { + + unowned string nickname = variant.get_string (); + if (!mark_dirty) { + this.original_nickname = nickname; + } + this.nickname = nickname; + } } diff --git a/src/core/contacts-notes-chunk.vala b/src/core/contacts-notes-chunk.vala index 2f1ee3a..f2b71ca 100644 --- a/src/core/contacts-notes-chunk.vala +++ b/src/core/contacts-notes-chunk.vala @@ -94,4 +94,19 @@ public class Contacts.Note : BinChunkChild { copy_parameters (note); return note; } + + protected override Variant? to_gvariant_internal () { + return new Variant ("(sv)", this.text, parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(sv)"))) { + + string note; + Variant params_variant; + variant.get ("(sv)", out note, out params_variant); + + this.text = note; + apply_gvariant_parameters (params_variant); + } } diff --git a/src/core/contacts-phones-chunk.vala b/src/core/contacts-phones-chunk.vala index c8e0ce3..5d8a347 100644 --- a/src/core/contacts-phones-chunk.vala +++ b/src/core/contacts-phones-chunk.vala @@ -110,4 +110,19 @@ public class Contacts.Phone : BinChunkChild { copy_parameters (phone); return phone; } + + protected override Variant? to_gvariant_internal () { + return new Variant ("(sv)", this.raw_number, parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(sv)"))) { + + string phone_nr; + Variant params_variant; + variant.get ("(sv)", out phone_nr, out params_variant); + + this.raw_number = phone_nr; + apply_gvariant_parameters (params_variant); + } } diff --git a/src/core/contacts-roles-chunk.vala b/src/core/contacts-roles-chunk.vala index 948c42b..0fa8e4b 100644 --- a/src/core/contacts-roles-chunk.vala +++ b/src/core/contacts-roles-chunk.vala @@ -98,6 +98,25 @@ public class Contacts.OrgRole : BinChunkChild { return org_role; } + protected override Variant? to_gvariant_internal () { + return new Variant ("(ssv)", + this.role.organisation_name, + this.role.title, + parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(ssv)"))) { + + string org, title; + Variant params_variant; + variant.get ("(ssv)", out org, out title, out params_variant); + + this.role.organisation_name = org; + this.role.title = title; + apply_gvariant_parameters (params_variant); + } + public string to_string () { if (this.role.title != "") { if (this.role.organisation_name != "") { diff --git a/src/core/contacts-structured-name-chunk.vala b/src/core/contacts-structured-name-chunk.vala index 3ac8016..96f2888 100644 --- a/src/core/contacts-structured-name-chunk.vala +++ b/src/core/contacts-structured-name-chunk.vala @@ -74,4 +74,38 @@ public class Contacts.StructuredNameChunk : Chunk { requires (this.persona is NameDetails) { yield ((NameDetails) this.persona).change_structured_name (this.structured_name); } + + public override Variant? to_gvariant () { + if (this.is_empty) + return null; + return new Variant ("(sssss)", + this.structured_name.family_name, + this.structured_name.given_name, + this.structured_name.additional_names, + this.structured_name.prefixes, + this.structured_name.suffixes); + } + + public override void apply_gvariant (Variant variant, + bool mark_dirty = true) + requires (variant.get_type ().equal (new VariantType ("(sssss)"))) { + + string family_name, given_name, additional_names, prefixes, suffixes; + variant.get ("(sssss)", + out family_name, + out given_name, + out additional_names, + out prefixes, + out suffixes); + + var structured_name = new StructuredName (family_name, + given_name, + additional_names, + prefixes, + suffixes); + if (!mark_dirty) { + this.original_structured_name = structured_name; + } + this.structured_name = structured_name; + } } diff --git a/src/core/contacts-urls-chunk.vala b/src/core/contacts-urls-chunk.vala index 62b02c0..2e927d6 100644 --- a/src/core/contacts-urls-chunk.vala +++ b/src/core/contacts-urls-chunk.vala @@ -104,4 +104,19 @@ public class Contacts.Url : BinChunkChild { copy_parameters (url); return url; } + + protected override Variant? to_gvariant_internal () { + return new Variant ("(sv)", this.raw_url, parameters_to_gvariant ()); + } + + public override void apply_gvariant (Variant variant) + requires (variant.get_type ().equal (new VariantType ("(sv)"))) { + + string url; + Variant params_variant; + variant.get ("(sv)", out url, out params_variant); + + this.raw_url = url; + apply_gvariant_parameters (params_variant); + } } diff --git a/src/io/contacts-io-parse-main.vala b/src/io/contacts-io-parse-main.vala index 5c44a3f..b302758 100644 --- a/src/io/contacts-io-parse-main.vala +++ b/src/io/contacts-io-parse-main.vala @@ -38,7 +38,7 @@ int main (string[] args) { error ("Unknown import type '%s'", import_type); } - HashTable<string, Value?>[] details_list; + Contacts.Contact[]? details_list; try { var file = File.new_for_path (path); var file_stream = file.read (null); diff --git a/src/io/contacts-io-parse-operation.vala b/src/io/contacts-io-parse-operation.vala index 8666b06..03ecabc 100644 --- a/src/io/contacts-io-parse-operation.vala +++ b/src/io/contacts-io-parse-operation.vala @@ -33,8 +33,7 @@ public class Contacts.Io.ParseOperation : Operation { public override string description { owned get { return this._description; } } /** The parsed output */ - private GenericArray<HashTable<string, Value?>> parsed - = new GenericArray<HashTable<string, Value?>> (); + private GenericArray<Contact> parsed = new GenericArray<Contact> (); public ParseOperation (File file) { this._description = _("Importing contacts from '%s'").printf (file.get_uri ()); @@ -80,15 +79,15 @@ public class Contacts.Io.ParseOperation : Operation { unowned var serialized_str = (string) stdout_stream.get_data (); var variant = Variant.parse (new VariantType ("aa{sv}"), serialized_str); - // Now parse each into a hashtables - var new_details_list = Contacts.Io.deserialize_gvariant (variant); - foreach (unowned var new_details in new_details_list) { - if (new_details.size () == 0) { + // Now parse each into a Contact + var parsed_contacts = Contacts.Io.deserialize_gvariant (variant); + foreach (unowned var parsed_contact in parsed_contacts) { + if (parsed_contact.get_n_items () == 0) { warning ("Imported contact has zero fields, ignoring"); return; } - this.parsed.add (new_details); + this.parsed.add (parsed_contact); } } @@ -96,11 +95,11 @@ public class Contacts.Io.ParseOperation : Operation { return_if_reached (); } - public unowned HashTable<string, Value?>[] get_parsed_result () { + public unowned Contact[] get_parsed_result () { return this.parsed.data; } - public HashTable<string, Value?>[] steal_parsed_result () { + public Contact[] steal_parsed_result () { return this.parsed.steal (); } } diff --git a/src/io/contacts-io-parser.vala b/src/io/contacts-io-parser.vala index 7c04a26..32a53ba 100644 --- a/src/io/contacts-io-parser.vala +++ b/src/io/contacts-io-parser.vala @@ -18,22 +18,18 @@ using Folks; /** - * An Parser is an object that can deal with importing a specific format - * of describing a Contact (VCard is the most common example, but there exist + * A Parser is an object that can deal with importing a specific format of + * describing a Contact (vCard is the most common example, but there exist * also CSV based formats and others). * - * The main purpose of an Io.Parser is to parser whatever input it gets into a - * {@link GLib.HashTable} with string keys and {@link Value} as values. After - * that, we can choose to either serialize (using the serializing methods in - * Contacts.Io), or to immediately import it in folks using - * {@link Folks.PersonaStore.add_from_details}. + * The main purpose of an Io.Parser is to parser whatever input it gets into an + * array of {@link Contacts.Contact}s. After that, we can for example either + * serialize the contact into a contact again. */ public abstract class Contacts.Io.Parser : Object { /** - * Takes the given input stream 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}. + * Takes the given input stream and tries to parse it into a set of contacts. */ - public abstract GLib.HashTable<string, Value?>[] parse (InputStream input) throws GLib.Error; + public abstract Contact[] parse (InputStream input) throws GLib.Error; } diff --git a/src/io/contacts-io-vcard-parser.vala b/src/io/contacts-io-vcard-parser.vala index 97cff61..3db9f56 100644 --- a/src/io/contacts-io-vcard-parser.vala +++ b/src/io/contacts-io-vcard-parser.vala @@ -25,7 +25,7 @@ public class Contacts.Io.VCardParser : Contacts.Io.Parser { public VCardParser () { } - public override HashTable<string, Value?>[] parse (InputStream input) throws GLib.Error { + public override Contact[] parse (InputStream input) throws GLib.Error { // Read the whole input into a string. // We can probably do better, but that takes a bit of extra work var memory_stream = new MemoryOutputStream.resizable (); @@ -34,7 +34,7 @@ public class Contacts.Io.VCardParser : Contacts.Io.Parser { memory_stream.close (); var input_str = (string) memory_stream.get_data (); - var result = new GenericArray<HashTable<string, Value?>> (); + var result = new GenericArray<Contact> (); // Parse the input stream into a set of vcards int begin_index = input_str.index_of ("BEGIN:VCARD"); @@ -51,44 +51,44 @@ public class Contacts.Io.VCardParser : Contacts.Io.Parser { unowned var vcard_attrs = vcard.get_attributes (); debug ("Got %u attributes in this vcard", vcard_attrs.length ()); - var details = new HashTable<string, Value?> (GLib.str_hash, GLib.str_equal); + var contact = new Contact.empty (); foreach (unowned E.VCardAttribute attr in vcard_attrs) { switch (attr.get_name ()) { // Identification Properties case E.EVC_FN: - handle_fn (details, attr); + handle_fn (contact, attr); break; case E.EVC_N: - handle_n (details, attr); + handle_n (contact, attr); break; case E.EVC_NICKNAME: - handle_nickname (details, attr); + handle_nickname (contact, attr); break; /* FIXME case E.EVC_PHOTO: - handle_photo (details, attr); + handle_photo (contact, attr); break; */ case E.EVC_BDAY: - handle_bday (details, attr); + handle_bday (contact, attr); break; // Delivery Addressing Properties case E.EVC_ADR: - handle_adr (details, attr); + handle_adr (contact, attr); break; // Communications Properties case E.EVC_TEL: - handle_tel (details, attr); + handle_tel (contact, attr); break; case E.EVC_EMAIL: - handle_email (details, attr); + handle_email (contact, attr); break; // Explanatory Properties case E.EVC_NOTE: - handle_note (details, attr); + handle_note (contact, attr); break; case E.EVC_URL: - handle_url (details, attr); + handle_url (contact, attr); break; default: @@ -97,7 +97,7 @@ public class Contacts.Io.VCardParser : Contacts.Io.Parser { } } - result.add (details); + result.add (contact); begin_index = input_str.index_of ("BEGIN:VCARD", end_index); } @@ -106,193 +106,146 @@ public class Contacts.Io.VCardParser : Contacts.Io.Parser { } // Handles the "FN" (Full Name) attribute - private void handle_fn (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_fn (Contact contact, E.VCardAttribute attr) { var full_name = attr.get_value (); debug ("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); + // Note that the full-name chunk is a bit special since it's usually + // added as a chunk, even for empty contacts + var chunk = contact.get_most_relevant_chunk ("full-name", true) ?? + contact.create_chunk ("full-name", null); + unowned var fn_chunk = (FullNameChunk) chunk; + fn_chunk.full_name = full_name; } // Handles the "N" (structured Name) attribute - private void handle_n (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_n (Contact contact, 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); + var sn_chunk = (StructuredNameChunk) contact.create_chunk ("structured-name", null); + sn_chunk.structured_name.family_name = values.nth_data (0) ?? ""; + sn_chunk.structured_name.given_name = values.nth_data (1) ?? ""; + sn_chunk.structured_name.additional_names = values.nth_data (2) ?? ""; + sn_chunk.structured_name.prefixes = values.nth_data (3) ?? ""; + sn_chunk.structured_name.suffixes = values.nth_data (4) ?? ""; } - private void handle_nickname (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_nickname (Contact contact, E.VCardAttribute attr) { var nickname = attr.get_value (); debug ("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); + var nick_chunk = (NicknameChunk) contact.create_chunk ("nickname", null); + nick_chunk.nickname = nickname; } // Handles the "BDAY" (birthday) attribute - private void handle_bday (HashTable<string, Value?> details, - E.VCardAttribute attr) { - // Get the attribute valuec + private void handle_bday (Contact contact, E.VCardAttribute attr) { 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); + var bd_chunk = (BirthdayChunk) contact.create_chunk ("birthday", null); + bd_chunk.birthday = datetime; } - private void handle_email (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_email (Contact contact, 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); + var child = add_chunk_child_for_property (contact, "email-addresses"); + ((EmailAddress) child).raw_address = email; + add_params (child, attr); } - private void handle_tel (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_tel (Contact contact, 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); + var child = add_chunk_child_for_property (contact, "phone-numbers"); + ((Phone) child).raw_number = phone_nr; + add_params (child, attr); } // Handles the ADR (postal address) attributes - private void handle_adr (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_adr (Contact contact, E.VCardAttribute attr) { unowned var values = attr.get_values (); + var child = add_chunk_child_for_property (contact, "postal-addresses"); + unowned var address = ((Address) child).address; + // 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); + address.po_box = values.nth_data (0) ?? ""; + address.extension = values.nth_data (1) ?? ""; + address.street = values.nth_data (2) ?? ""; + address.locality = values.nth_data (3) ?? ""; + address.region = values.nth_data (4) ?? ""; + address.postal_code = values.nth_data (5) ?? ""; + address.country = values.nth_data (6) ?? ""; + + add_params (child, attr); } - private void handle_url (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_url (Contact contact, 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); + var child = add_chunk_child_for_property (contact, "urls"); + ((Contacts.Url) child).raw_url = url; + add_params (child, attr); } - private void handle_note (HashTable<string, Value?> details, - E.VCardAttribute attr) { + private void handle_note (Contact contact, 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); - + var child = add_chunk_child_for_property (contact, "notes"); + ((Contacts.Note) child).text = note; + add_params (child, attr); } // 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); + private BinChunkChild add_chunk_child_for_property (Contact contact, + string property_name) { + var chunk = (BinChunk) contact.get_most_relevant_chunk (property_name, true); + if (chunk == null) + chunk = (BinChunk) contact.create_chunk (property_name, null); + + // BinChunk guarantees there will always be an empty child, so return the + // first one we can find + for (uint i = 0; i < chunk.get_n_items (); i++) { + var child = (BinChunkChild) chunk.get_item (i); + if (child.is_empty) + return child; } - 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); + return_val_if_reached (null); } - // Helper method to get VCard parameters into an AbstractFieldDetails object. + // Helper method to get VCard parameters into a BinChunkChild // Will take care of setting the correct "type" - private void add_params (AbstractFieldDetails details, E.VCardAttribute attr) { + private void add_params (BinChunkChild chunk_child, 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 ()); + if (param_name == "type") + chunk_child.add_parameter (param_name, param_value.down ()); else - details.add_parameter (param_name, param_value); + chunk_child.add_parameter (param_name, param_value); } } } diff --git a/src/io/contacts-io.vala b/src/io/contacts-io.vala index 094298d..d87eb9f 100644 --- a/src/io/contacts-io.vala +++ b/src/io/contacts-io.vala @@ -25,441 +25,39 @@ using Folks; namespace Contacts.Io { /** - * Serializes a list of {@link GLib.HashTable}s as returned by a + * Serializes a list of {@link Contact}s as returned by a * {@link Contacts.Io.Parser} 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_list) { + public GLib.Variant serialize_to_gvariant (Contacts.Contact[]? contacts) { var builder = new GLib.VariantBuilder (new VariantType ("aa{sv}")); - foreach (unowned var details in details_list) { - builder.add_value (serialize_to_gvariant_single (details)); + foreach (unowned var contact in contacts) { + var variant = contact.to_gvariant (); + if (variant.n_children () == 0) + continue; + builder.add_value (variant); } return builder.end (); } /** - * Serializes a single {@link GLib.HashTable} into a {@link GLib.Variant}. - */ - public GLib.Variant serialize_to_gvariant_single (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.STRUCTURED_NAME)) { - serialize_structured_name (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.NICKNAME)) { - serialize_nickname (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY)) { - serialize_birthday (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)) { - serialize_addresses (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)) { - serialize_phone_nrs (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)) { - serialize_emails (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.NOTES)) { - serialize_notes (dict, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.URLS)) { - serialize_urls (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}. + * Deserializes the {@link GLib.Variant} back into a list of + * {@link Contacts.Contact}s. */ - public HashTable<string, Value?>[] deserialize_gvariant (GLib.Variant variant) + public Contacts.Contact[] deserialize_gvariant (Variant variant) requires (variant.get_type ().equal (new VariantType ("aa{sv}"))) { - var result = new GenericArray<HashTable<string, Value?>> (); + var result = new GenericArray<Contacts.Contact> (); var iter = variant.iterator (); GLib.Variant element; while (iter.next ("@a{sv}", out element)) { - result.add (deserialize_gvariant_single (element)); + var contact = new Contact.for_gvariant (element); + result.add (contact); } return result.steal (); } - - /** - * Deserializes the {@link GLib.Variant} back into a {@link GLib.HashTable}. - */ - public HashTable<string, Value?> deserialize_gvariant_single (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.STRUCTURED_NAME)) { - deserialize_structured_name (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.NICKNAME)) { - deserialize_nickname (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY)) { - deserialize_birthday (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)) { - deserialize_addresses (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)) { - deserialize_phone_nrs (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)) { - deserialize_emails (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.NOTES)) { - deserialize_notes (details, prop, val); - } else if (prop == Folks.PersonaStore.detail_key (PersonaDetail.URLS)) { - deserialize_urls (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); - - unowned string full_name = variant.get_string (); - return_val_if_fail (full_name != null, false); - - details.insert (prop, full_name); - - return true; - } - - // - // NICKNAME - // ----------------------------------- - private const string STRUCTURED_NAME_TYPE = "(sssss)"; - - private bool serialize_structured_name (GLib.VariantDict dict, string prop, Value? val) { - return_val_if_fail (val.type () == typeof (StructuredName), false); - - unowned var name = val as StructuredName; - return_val_if_fail (name != null, false); - - dict.insert (prop, STRUCTURED_NAME_TYPE, - name.family_name, name.given_name, name.additional_names, - name.prefixes, name.suffixes); - - return true; - } - - private bool deserialize_structured_name (HashTable<string, Value?> details, string prop, Variant variant) { - return_val_if_fail (variant.get_type ().equal (new VariantType (STRUCTURED_NAME_TYPE)), false); - - string family_name, given_name, additional_names, prefixes, suffixes; - variant.get (STRUCTURED_NAME_TYPE, - out family_name, - out given_name, - out additional_names, - out prefixes, - out suffixes); - - var structured_name = new StructuredName (family_name, given_name, additional_names, - prefixes, suffixes); - details.insert (prop, structured_name); - - return true; - } - - // - // NICKNAME - // ----------------------------------- - private const string NICKNAME_TYPE = "s"; - - private bool serialize_nickname (GLib.VariantDict dict, string prop, Value? val) { - return_val_if_fail (val.type () == typeof (string), false); - - unowned string nickname = val as string; - return_val_if_fail (nickname != null, false); - - dict.insert (prop, NICKNAME_TYPE, nickname); - - return true; - } - - private bool deserialize_nickname (HashTable<string, Value?> details, string prop, Variant variant) { - return_val_if_fail (variant.get_type ().equal (VariantType.STRING), false); - - unowned string nickname = variant.get_string (); - return_val_if_fail (nickname != null, false); - - details.insert (prop, nickname); - - return true; - } - - // - // BIRTHDAY - // ----------------------------------- - private const string BIRTHDAY_TYPE = "(iii)"; // Year-Month-Day - - private bool serialize_birthday (GLib.VariantDict dict, string prop, Value? val) { - return_val_if_fail (val.type () == typeof (DateTime), false); - - unowned var bd = val as DateTime; - return_val_if_fail (bd != null, false); - - int year, month, day; - bd.get_ymd (out year, out month, out day); - dict.insert (prop, BIRTHDAY_TYPE, year, month, day); - - return true; - } - - private bool deserialize_birthday (HashTable<string, Value?> details, string prop, Variant variant) { - return_val_if_fail (variant.get_type ().equal (new VariantType (BIRTHDAY_TYPE)), false); - - int year, month, day; - variant.get (BIRTHDAY_TYPE, out year, out month, out day); - - var bd = new DateTime.utc (year, month, day, 0, 0, 0.0); - - details.insert (prop, bd); - - return true; - } - - // - // POSTAL ADDRESSES - // ----------------------------------- - private const string ADDRESS_TYPE = "(sssssssv)"; - private const string ADDRESSES_TYPE = "a" + ADDRESS_TYPE; - - private bool serialize_addresses (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 afds = val as Gee.Set<PostalAddressFieldDetails>; - return_val_if_fail (afds != null, false); - - // Turn the set of field details into an array Variant - var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY); - foreach (var afd in afds) { - unowned PostalAddress addr = afd.value; - - builder.add (ADDRESS_TYPE, - addr.po_box, - addr.extension, - addr.street, - addr.locality, - addr.region, - addr.postal_code, - addr.country, - serialize_parameters (afd)); - } - - dict.insert_value (prop, builder.end ()); - - return true; - } - - private bool deserialize_addresses (HashTable<string, Value?> details, string prop, Variant variant) { - return_val_if_fail (variant.get_type ().equal (new VariantType ("a" + ADDRESS_TYPE)), false); - - var afds = new Gee.HashSet<PostalAddressFieldDetails> (); - - // Turn the array variant into a set of field details - var iter = variant.iterator (); - - string po_box, extension, street, locality, region, postal_code, country; - GLib.Variant parameters; - while (iter.next (ADDRESS_TYPE, - out po_box, - out extension, - out street, - out locality, - out region, - out postal_code, - out country, - out parameters)) { - if (po_box == "" && extension == "" && street == "" && locality == "" - && region == "" && postal_code == "" && country == "") { - warning ("Got empty postal address"); - continue; - } - - var addr = new PostalAddress (po_box, extension, street, locality, region, - postal_code, country, "", null); - - var afd = new PostalAddressFieldDetails (addr); - deserialize_parameters (parameters, afd); - - afds.add (afd); - } - - details.insert (prop, afds); - - return true; - } - - // - // PHONE NUMBERS - // ----------------------------------- - private bool serialize_phone_nrs (GLib.VariantDict dict, string prop, Value? val) { - return serialize_afd_strings (dict, prop, val); - } - - private bool deserialize_phone_nrs (HashTable<string, Value?> details, string prop, Variant variant) { - return deserialize_afd_str (details, prop, variant, - (str) => { return new PhoneFieldDetails (str); }); - } - - // - // EMAILS - // ----------------------------------- - private bool serialize_emails (GLib.VariantDict dict, string prop, Value? val) { - return serialize_afd_strings (dict, prop, val); - } - - private bool deserialize_emails (HashTable<string, Value?> details, string prop, Variant variant) { - return deserialize_afd_str (details, prop, variant, - (str) => { return new EmailFieldDetails (str); }); - } - - // - // NOTES - // ----------------------------------- - private bool serialize_notes (GLib.VariantDict dict, string prop, Value? val) { - return serialize_afd_strings (dict, prop, val); - } - - private bool deserialize_notes (HashTable<string, Value?> details, string prop, Variant variant) { - return deserialize_afd_str (details, prop, variant, - (str) => { return new NoteFieldDetails (str); }); - } - - // - // URLS - // ----------------------------------- - private bool serialize_urls (GLib.VariantDict dict, string prop, Value? val) { - return serialize_afd_strings (dict, prop, val); - } - - private bool deserialize_urls (HashTable<string, Value?> details, string prop, Variant variant) { - return deserialize_afd_str (details, prop, variant, - (str) => { return new UrlFieldDetails (str); }); - } - - // - // HELPER: AbstractFielDdetail<string> - // ----------------------------------- - private const string AFD_STRING_TYPE = "(sv)"; - - private bool serialize_afd_strings (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 afds = val as Gee.Set<AbstractFieldDetails<string>>; - return_val_if_fail (afds != null, false); - - // Turn the set of field details into an array Variant - var builder = new GLib.VariantBuilder (GLib.VariantType.ARRAY); - foreach (var afd in afds) { - builder.add (AFD_STRING_TYPE, afd.value, serialize_parameters (afd)); - } - - dict.insert_value (prop, builder.end ()); - - return true; - } - - // In an ideal world, we wouldn't need this delegate and we could just use - // GLib.Object.new(), but this is Vala and generics, so we find ourselves in - // a big mess here - delegate AbstractFieldDetails<string> CreateAbstractFieldStrFunc(string value); - - private bool deserialize_afd_str (HashTable<string, Value?> details, - string prop, - Variant variant, - CreateAbstractFieldStrFunc create_afd_func) { - return_val_if_fail (variant.get_type ().equal (new VariantType ("a" + AFD_STRING_TYPE)), false); - - var afds = new Gee.HashSet<AbstractFieldDetails> (); - - // Turn the array variant into a set of field details - var iter = variant.iterator (); - string str; - GLib.Variant parameters; - while (iter.next (AFD_STRING_TYPE, out str, out parameters)) { - AbstractFieldDetails afd = create_afd_func (str); - deserialize_parameters (parameters, afd); - - afds.add (afd); - } - - details.insert (prop, afds); - - 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/io/meson.build b/src/io/meson.build index b4bb512..a5ea1df 100644 --- a/src/io/meson.build +++ b/src/io/meson.build @@ -17,6 +17,7 @@ contacts_c_args = [ ] contacts_io_deps = [ + libcontactscore_dep, folks, folks_eds, gee, diff --git a/tests/core/meson.build b/tests/core/meson.build index 4e38475..86101d1 100644 --- a/tests/core/meson.build +++ b/tests/core/meson.build @@ -7,6 +7,7 @@ test_names = [ 'test-notes-chunk', 'test-phones-chunk', 'test-roles-chunk', + 'test-structured-name-chunk', 'test-urls-chunk', ] diff --git a/tests/core/test-birthday-chunk.vala b/tests/core/test-birthday-chunk.vala index d112379..2135c49 100644 --- a/tests/core/test-birthday-chunk.vala +++ b/tests/core/test-birthday-chunk.vala @@ -20,6 +20,8 @@ void main (string[] args) { Test.add_func ("/core/birthday-chunk/property_name_chunk", test_property_name); Test.add_func ("/core/birthday-chunk/is-empty", test_is_empty); Test.add_func ("/core/birthday-chunk/leap-day-birthday", test_leap_day_birthday); + Test.add_func ("/core/birthday-chunk/serialize-basic", test_serialize_basic); + Test.add_func ("/core/birthday-chunk/serialize-pre-epoch", test_serialize_pre_epoch); Test.run (); } @@ -46,7 +48,7 @@ private void test_is_empty () { assert_true (chunk.is_empty); } -void test_leap_day_birthday () { +private void test_leap_day_birthday () { var contact = new Contacts.Contact.empty (); var chunk = (Contacts.BirthdayChunk) contact.create_chunk ("birthday", null); assert_nonnull (chunk); @@ -60,4 +62,42 @@ void test_leap_day_birthday () { var feb_28_non_leap_year = new DateTime.local (2023, 2, 28, 0, 0, 0); assert_true (chunk.is_today (feb_28_non_leap_year)); -}
\ No newline at end of file +} + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.BirthdayChunk) contact.create_chunk ("birthday", null); + + // If the birthday is not set, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If the birthday is set, we should have a variant. Without checking its + // contents, it should deserialize in a new contact + var old_bd = new DateTime.utc (1992, 8, 1, 0, 0, 0); + chunk.birthday = old_bd; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.BirthdayChunk) contact2.create_chunk ("birthday", null); + chunk2.apply_gvariant (serialized); + assert_true (old_bd.equal (chunk2.birthday)); +} + +private void test_serialize_pre_epoch () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.BirthdayChunk) contact.create_chunk ("birthday", null); + + // Check that we didn't try to use something that doesn't allow dates before + // epoch (eg struct tm) + var old_bd = new DateTime.utc (1961, 7, 3, 0, 0, 0); + chunk.birthday = old_bd; + var serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.BirthdayChunk) contact2.create_chunk ("birthday", null); + chunk2.apply_gvariant (serialized); + assert_true (old_bd.equal (chunk2.birthday)); +} diff --git a/tests/core/test-email-addresses-chunk.vala b/tests/core/test-email-addresses-chunk.vala index d2d10c7..90f9247 100644 --- a/tests/core/test-email-addresses-chunk.vala +++ b/tests/core/test-email-addresses-chunk.vala @@ -17,8 +17,9 @@ void main (string[] args) { Test.init (ref args); - Test.add_func ("/core/addresses-chunk/property-name-chunk", test_property_name); - Test.add_func ("/core/addresses-chunk/get-is-empty", test_is_empty); + Test.add_func ("/core/email-addresses-chunk/property-name-chunk", test_property_name); + Test.add_func ("/core/email-addresses-chunk/get-is-empty", test_is_empty); + Test.add_func ("/core/email-addresses-chunk/serialize-basic", test_serialize_basic); Test.run (); } @@ -51,3 +52,25 @@ private void test_is_empty () { assert_true (address.is_empty); assert_true (chunk.is_empty); } + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.EmailAddressesChunk) contact.create_chunk ("email-addresses", null); + + // If the emailaddresss are empty, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If a email address is added, we should have a variant. We don't need to + // inspect the variant, we just need to know it properly deserializes + var email_addr = (Contacts.EmailAddress) chunk.get_item (0); + email_addr.raw_address = "nielsdegraef@gmail.com"; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.EmailAddressesChunk) contact2.create_chunk ("email-addresses", null); + chunk2.apply_gvariant (serialized); + assert_nonnull (chunk.get_item (0)); + assert_true (((Contacts.EmailAddress) chunk.get_item (0)).raw_address == "nielsdegraef@gmail.com"); +} diff --git a/tests/core/test-full-name-chunk.vala b/tests/core/test-full-name-chunk.vala index 4648b0c..9dd78c1 100644 --- a/tests/core/test-full-name-chunk.vala +++ b/tests/core/test-full-name-chunk.vala @@ -19,6 +19,7 @@ void main (string[] args) { Test.init (ref args); Test.add_func ("/core/full-name-chunk/property_name_chunk", test_property_name); Test.add_func ("/core/full-name-chunk/is-empty", test_is_empty); + Test.add_func ("/core/full-name-chunk/serialize-basic", test_serialize_basic); Test.run (); } @@ -44,3 +45,23 @@ private void test_is_empty () { chunk.full_name = ""; assert_true (chunk.is_empty); } + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.FullNameChunk) contact.create_chunk ("full-name", null); + + // If the full name is not set, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If full name is set, we should have a variant. We don't need to inspect + // the variant, we just need to know it properly deserializes + chunk.full_name = "Niels De Graef"; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.FullNameChunk) contact2.create_chunk ("full-name", null); + chunk2.apply_gvariant (serialized); + assert_true (chunk2.full_name == "Niels De Graef"); +} diff --git a/tests/core/test-nickname-chunk.vala b/tests/core/test-nickname-chunk.vala index e32ae52..e46efc1 100644 --- a/tests/core/test-nickname-chunk.vala +++ b/tests/core/test-nickname-chunk.vala @@ -19,6 +19,7 @@ void main (string[] args) { Test.init (ref args); Test.add_func ("/core/nickname-chunk/property_name_chunk", test_property_name); Test.add_func ("/core/nickname-chunk/is-empty", test_is_empty); + Test.add_func ("/core/nickname-chunk/serialize-basic", test_serialize_basic); Test.run (); } @@ -44,3 +45,23 @@ private void test_is_empty () { chunk.nickname = ""; assert_true (chunk.is_empty); } + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.NicknameChunk) contact.create_chunk ("nickname", null); + + // If the nickname is not set, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If nickname is set, we should have a variant. We don't need to inspect the + // variant, we just need to know it properly deserializes + chunk.nickname = "ndegraef"; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.NicknameChunk) contact2.create_chunk ("nickname", null); + chunk2.apply_gvariant (serialized); + assert_true (chunk2.nickname == "ndegraef"); +} diff --git a/tests/core/test-phones-chunk.vala b/tests/core/test-phones-chunk.vala index 9fb6c65..bea8784 100644 --- a/tests/core/test-phones-chunk.vala +++ b/tests/core/test-phones-chunk.vala @@ -19,6 +19,7 @@ void main (string[] args) { Test.init (ref args); Test.add_func ("/core/phones-chunk/property-name-chunk", test_property_name); Test.add_func ("/core/phones-chunk/get-is-empty", test_is_empty); + Test.add_func ("/core/phones-chunk/serialize-basic", test_serialize_basic); Test.run (); } @@ -51,3 +52,25 @@ private void test_is_empty () { assert_true (phone.is_empty); assert_true (chunk.is_empty); } + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.PhonesChunk) contact.create_chunk ("phone-numbers", null); + + // If the phones are empty, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If a phone is added, we should have a variant. We don't need to inspect + // the variant, we just need to know it properly deserializes + var phone = (Contacts.Phone) chunk.get_item (0); + phone.raw_number = "+321234567"; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.PhonesChunk) contact2.create_chunk ("phone-numbers", null); + chunk2.apply_gvariant (serialized); + assert_nonnull (chunk.get_item (0)); + assert_true (((Contacts.Phone) chunk.get_item (0)).raw_number == "+321234567"); +} diff --git a/tests/core/test-structured-name-chunk.vala b/tests/core/test-structured-name-chunk.vala new file mode 100644 index 0000000..3283f88 --- /dev/null +++ b/tests/core/test-structured-name-chunk.vala @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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/>. + */ + +void main (string[] args) { + Test.init (ref args); + Test.add_func ("/core/structured-name-chunk/property_name_chunk", test_property_name); + Test.add_func ("/core/structured-name-chunk/is-empty", test_is_empty); + Test.add_func ("/core/structured-name-chunk/serialize-basic", test_serialize_basic); + Test.run (); +} + +// Make sure that "structured-name" maps to a StructuredNameChunk +private void test_property_name () { + var contact = new Contacts.Contact.empty (); + + var chunk = contact.create_chunk ("structured-name", null); + assert_nonnull (chunk); + assert_true (chunk is Contacts.StructuredNameChunk); + assert_true (chunk.property_name == "structured-name"); +} + +private void test_is_empty () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.StructuredNameChunk) contact.create_chunk ("structured-name", null); + assert_nonnull (chunk); + assert_true (chunk.is_empty); + + chunk.structured_name = new Folks.StructuredName.simple ("Niels", "De Graef"); + assert_false (chunk.is_empty); +} + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.StructuredNameChunk) contact.create_chunk ("structured-name", null); + + // If the name is not set, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If name is set, we should have a variant. We don't need to inspect the + // variant, we just need to know it properly deserializes + var old_name = new Folks.StructuredName.simple ("Niels", "De Graef"); + chunk.structured_name = old_name; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.StructuredNameChunk) contact2.create_chunk ("structured-name", null); + chunk2.apply_gvariant (serialized); + assert_true (chunk2.structured_name.equal (old_name)); +} diff --git a/tests/core/test-urls-chunk.vala b/tests/core/test-urls-chunk.vala index 183db5c..b70964e 100644 --- a/tests/core/test-urls-chunk.vala +++ b/tests/core/test-urls-chunk.vala @@ -20,6 +20,7 @@ void main (string[] args) { Test.add_func ("/core/urls-chunk/property-name-chunk", test_property_name); Test.add_func ("/core/urls-chunk/get-absolute-url", test_get_absolute_url); Test.add_func ("/core/urls-chunk/get-is-empty", test_is_empty); + Test.add_func ("/core/urls-chunk/serialize-basic", test_serialize_basic); Test.run (); } @@ -74,3 +75,25 @@ private void test_is_empty () { assert_true (url.is_empty); assert_true (chunk.is_empty); } + +private void test_serialize_basic () { + var contact = new Contacts.Contact.empty (); + var chunk = (Contacts.UrlsChunk) contact.create_chunk ("urls", null); + + // If the urls are empty, serialization should give a null result + var serialized = chunk.to_gvariant (); + assert_null (serialized); + + // If a url is added, we should have a variant. We don't need to inspect + // the variant, we just need to know it properly deserializes + var url = (Contacts.Url) chunk.get_item (0); + url.raw_url = "https://gnome.org"; + serialized = chunk.to_gvariant (); + assert_nonnull (serialized); + + var contact2 = new Contacts.Contact.empty (); + var chunk2 = (Contacts.UrlsChunk) contact2.create_chunk ("urls", null); + chunk2.apply_gvariant (serialized); + assert_nonnull (chunk.get_item (0)); + assert_true (((Contacts.Url) chunk.get_item (0)).raw_url == "https://gnome.org"); +} diff --git a/tests/io/internal/meson.build b/tests/io/internal/meson.build deleted file mode 100644 index 82590ef..0000000 --- a/tests/io/internal/meson.build +++ /dev/null @@ -1,29 +0,0 @@ -io_internal_testlib = library('io-internal-testlib', - files('test-serialise-common.vala'), - dependencies: libcontacts_dep, -) - -io_internal_testlib_dep = declare_dependency( - link_with: io_internal_testlib, - include_directories: include_directories('.'), -) - -io_internal_test_names = [ - 'serialise-full-name', - 'serialise-structured-name', - 'serialise-nickname', - 'serialise-birthday', - 'serialise-emails', - 'serialise-urls', -] - -foreach _test : io_internal_test_names - test_bin = executable(_test, - files('test-'+_test+'.vala'), - dependencies: [ libcontacts_dep, io_internal_testlib_dep ], - ) - - test(_test, test_bin, - suite: 'io-internal', - ) -endforeach diff --git a/tests/io/internal/test-serialise-birthday.vala b/tests/io/internal/test-serialise-birthday.vala deleted file mode 100644 index 46beef2..0000000 --- a/tests/io/internal/test-serialise-birthday.vala +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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_birthday", - Contacts.Tests.Io.test_serialize_birthday); - Test.add_func ("/io/serialize_birthday_pre_epoch", - Contacts.Tests.Io.test_serialize_birthday_pre_epoch); - Test.run (); -} - -namespace Contacts.Tests.Io { - - private void test_serialize_birthday () { - unowned var bd_key = PersonaStore.detail_key (PersonaDetail.BIRTHDAY); - - DateTime old_bd = new GLib.DateTime.utc (1992, 8, 1, 0, 0, 0); - var old_bd_val = Value (typeof (DateTime)); - old_bd_val.set_boxed (old_bd); - - var new_bd_val = _transform_single_value (bd_key, old_bd_val); - assert_true (new_bd_val.type () == typeof (DateTime)); - assert_true (old_bd.equal ((DateTime) new_bd_val.get_boxed ())); - } - - private void test_serialize_birthday_pre_epoch () { - unowned var bd_key = PersonaStore.detail_key (PersonaDetail.BIRTHDAY); - - DateTime old_bd = new GLib.DateTime.utc (1961, 7, 3, 0, 0, 0); - var old_bd_val = Value (typeof (DateTime)); - old_bd_val.set_boxed (old_bd); - - var new_bd_val = _transform_single_value (bd_key, old_bd_val); - assert_true (new_bd_val.type () == typeof (DateTime)); - assert_true (old_bd.equal ((DateTime) new_bd_val.get_boxed ())); - } -} diff --git a/tests/io/internal/test-serialise-common.vala b/tests/io/internal/test-serialise-common.vala deleted file mode 100644 index 8407e2c..0000000 --- a/tests/io/internal/test-serialise-common.vala +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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; - -namespace Contacts.Tests.Io { - - // Helper to serialize and deserialize an AbstractFieldDetails - public T _transform_single_afd<T> (string prop_key, T afd) { - Gee.Set<T> afd_set = new Gee.HashSet<T> (); - afd_set.add (afd); - - Value val = Value (typeof (Gee.Set)); - val.set_object (afd_set); - - Value emails_value = _transform_single_value (prop_key, val); - var emails_set = emails_value.get_object () as Gee.Set<T>; - if (emails_set == null) - error ("GValue has null value"); - if (emails_set.size != 1) - error ("Expected %d elements but got %d", 1, emails_set.size); - - 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 - public 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 - Variant serialized = Contacts.Io.serialize_to_gvariant_single (details); - if (serialized == null) - error ("Couldn't serialize single-value table for property %s", prop_key); - - // Deserialize - var details_deserialized = Contacts.Io.deserialize_gvariant_single (serialized); - if (details_deserialized == null) - error ("Couldn't deserialize details for property %s", prop_key); - - if (!details_deserialized.contains (prop_key)) - error ("Deserialized details doesn't contain value for property %s", prop_key); - Value? val_deserialized = details_deserialized.lookup (prop_key); - if (val_deserialized.type() == GLib.Type.NONE) - error ("Deserialized Value is unset"); - - return val_deserialized; - } -} diff --git a/tests/io/internal/test-serialise-emails.vala b/tests/io/internal/test-serialise-emails.vala deleted file mode 100644 index 27b15ac..0000000 --- a/tests/io/internal/test-serialise-emails.vala +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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_emails", - Contacts.Tests.Io.test_serialize_emails); - Test.run (); -} - -namespace Contacts.Tests.Io { - - 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); - - if (!(new_fd is EmailFieldDetails)) - error ("Expected EmailFieldDetails but got %s", new_fd.get_type ().name ()); - - if (old_fd.value != new_fd.value) - error ("Expected '%s' but got '%s'", old_fd.value, new_fd.value); - } -} diff --git a/tests/io/internal/test-serialise-full-name.vala b/tests/io/internal/test-serialise-full-name.vala deleted file mode 100644 index 9da8319..0000000 --- a/tests/io/internal/test-serialise-full-name.vala +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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_simple", - Contacts.Tests.Io.test_serialize_full_name_simple); - Test.run (); -} - -namespace Contacts.Tests.Io { - - private void test_serialize_full_name_simple () { - unowned var fn_key = PersonaStore.detail_key (PersonaDetail.FULL_NAME); - - string old_fn = "Niels De Graef"; - Value 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); - if (new_fn_val.type () != typeof (string)) - error ("Expected G_TYPE_STRING but got %s", new_fn_val.type ().name ()); - if (old_fn != new_fn_val.get_string ()) - error ("Expected '%s' but got '%s'", old_fn, new_fn_val.get_string ()); - } -} diff --git a/tests/io/internal/test-serialise-nickname.vala b/tests/io/internal/test-serialise-nickname.vala deleted file mode 100644 index 649b638..0000000 --- a/tests/io/internal/test-serialise-nickname.vala +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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_nickame", - Contacts.Tests.Io.test_serialize_nickname); - Test.run (); -} - -namespace Contacts.Tests.Io { - - private void test_serialize_nickname () { - unowned var nick_key = PersonaStore.detail_key (PersonaDetail.NICKNAME); - - string old_nick = "nielsdg"; - var old_nick_val = Value (typeof (string)); - old_nick_val.set_string (old_nick); - - var new_nick_val = _transform_single_value (nick_key, old_nick_val); - assert_true (new_nick_val.type () == typeof (string)); - assert_true (old_nick == new_nick_val.get_string ()); - } -} diff --git a/tests/io/internal/test-serialise-structured-name.vala b/tests/io/internal/test-serialise-structured-name.vala deleted file mode 100644 index 45f2093..0000000 --- a/tests/io/internal/test-serialise-structured-name.vala +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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_structured_name_simple", - Contacts.Tests.Io.test_serialize_structured_name_simple); - Test.run (); -} - -namespace Contacts.Tests.Io { - - private void test_serialize_structured_name_simple () { - unowned var sn_key = PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME); - - var old_sn = new StructuredName.simple ("Niels", "De Graef"); - Value old_sn_val = Value (typeof (StructuredName)); - old_sn_val.set_object (old_sn); - - var new_sn_val = _transform_single_value (sn_key, old_sn_val); - - if (new_sn_val.type () != typeof (StructuredName)) - error ("Expected FOLKS_TYPE_STRUCTURED_NAME but got %s", new_sn_val.type ().name ()); - - var new_sn = new_sn_val.get_object () as StructuredName; - if (!old_sn.equal (new_sn)) - error ("Expected '%s' but got '%s'", old_sn.to_string (), new_sn.to_string ()); - } -} diff --git a/tests/io/internal/test-serialise-urls.vala b/tests/io/internal/test-serialise-urls.vala deleted file mode 100644 index cf4cdf9..0000000 --- a/tests/io/internal/test-serialise-urls.vala +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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_urls_single", - Contacts.Tests.Io.test_serialize_urls_single); - Test.run (); -} - -namespace Contacts.Tests.Io { - - private void test_serialize_urls_single () { - unowned var urls_key = PersonaStore.detail_key (PersonaDetail.URLS); - - var old_fd = new UrlFieldDetails ("http://www.islinuxaboutchoice.com/"); - var new_fd = _transform_single_afd<UrlFieldDetails> (urls_key, old_fd); - - if (!(new_fd is UrlFieldDetails)) - error ("Expected UrlFieldDetails but got %s", new_fd.get_type ().name ()); - - if (old_fd.value != new_fd.value) - error ("Expected '%s' but got '%s'", old_fd.value, new_fd.value); - } -} diff --git a/tests/io/meson.build b/tests/io/meson.build index 2f34960..99bbbc0 100644 --- a/tests/io/meson.build +++ b/tests/io/meson.build @@ -1,2 +1 @@ -subdir('internal') subdir('vcard') diff --git a/tests/io/vcard/meson.build b/tests/io/vcard/meson.build index 9967815..38f2a66 100644 --- a/tests/io/vcard/meson.build +++ b/tests/io/vcard/meson.build @@ -6,6 +6,7 @@ test_deps = [ gee, folks, libebook, + libcontactscore_dep, ] foreach vcard_name : io_vcard_files diff --git a/tests/io/vcard/test-vcard-minimal-import.vala b/tests/io/vcard/test-vcard-minimal-import.vala index bef6596..544fdad 100644 --- a/tests/io/vcard/test-vcard-minimal-import.vala +++ b/tests/io/vcard/test-vcard-minimal-import.vala @@ -19,43 +19,38 @@ using Folks; void main (string[] args) { Test.init (ref args); - Test.add_func ("/io/test_vcard_minimal", - Contacts.Tests.Io.test_vcard_minimal); + Test.add_func ("/io/test_vcard_minimal", test_vcard_minimal); Test.run (); } -namespace Contacts.Tests.Io { - private void test_vcard_minimal () { - unowned var vcf_path = Environment.get_variable ("_VCF_FILE"); - if (vcf_path == null || vcf_path == "") - error ("No .vcf file set as envvar. Please use the meson test suite"); - - var file = GLib.File.new_for_path (vcf_path); - if (!file.query_exists ()) - error (".vcf file that is used as test input doesn't exist"); - - var parser = new Contacts.Io.VCardParser (); - HashTable<string, Value?>[] details_list = null; - try { - details_list = parser.parse (file.read (null)); - } catch (Error err) { - error ("Error while importing: %s", err.message); - } - if (details_list == null) - error ("VCardParser returned null"); - - if (details_list.length != 1) - error ("VCardParser parsed %u elements instead of 1", details_list.length); - - unowned var details = details_list[0]; - - unowned var fn_key = PersonaStore.detail_key (PersonaDetail.FULL_NAME); - if (!details.contains (fn_key)) - error ("No FN value"); - - var fn_value = details.lookup (fn_key); - unowned var fn = fn_value as string; - if (fn != "Niels De Graef") - error ("Expected '%s' but got '%s'", "Niels De Graef", fn); +private void test_vcard_minimal () { + unowned var vcf_path = Environment.get_variable ("_VCF_FILE"); + if (vcf_path == null || vcf_path == "") + error ("No .vcf file set as envvar. Please use the meson test suite"); + + var file = File.new_for_path (vcf_path); + if (!file.query_exists ()) + error (".vcf file that is used as test input doesn't exist"); + + var parser = new Contacts.Io.VCardParser (); + Contacts.Contact[]? contacts = null; + try { + contacts = parser.parse (file.read (null)); + } catch (Error err) { + error ("Error while importing: %s", err.message); } + + if (contacts == null) + error ("VCardParser returned null"); + if (contacts.length != 1) + error ("VCardParser parsed %u elements instead of 1", contacts.length); + + unowned var contact = contacts[0]; + var chunk = contact.get_most_relevant_chunk ("full-name", true); + if (chunk == null) + error ("Expected FullNameChunk, but got null"); + + unowned var fn_chunk = (Contacts.FullNameChunk) chunk; + if (fn_chunk.full_name != "Niels De Graef") + error ("Expected '%s' but got '%s'", "Niels De Graef", fn_chunk.full_name); } |