summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiels De Graef <nielsdegraef@gmail.com>2022-09-06 22:09:25 +0200
committerNiels De Graef <nielsdegraef@gmail.com>2023-03-02 07:31:29 +0000
commitd455b34b76a25b116b7fa82d4cb5fcf862546bfa (patch)
treee41be2b1469648334a4f2c581b38747fe2ddc1e6
parent99af58107b2e49ad1b5a7fc91d290126fb8636ee (diff)
downloadgnome-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.
-rw-r--r--src/contacts-app.vala2
-rw-r--r--src/contacts-import-operation.vala14
-rw-r--r--src/core/contacts-addresses-chunk.vala37
-rw-r--r--src/core/contacts-alias-chunk.vala15
-rw-r--r--src/core/contacts-avatar-chunk.vala10
-rw-r--r--src/core/contacts-bin-chunk.vala95
-rw-r--r--src/core/contacts-birthday-chunk.vala23
-rw-r--r--src/core/contacts-chunk.vala18
-rw-r--r--src/core/contacts-contact.vala49
-rw-r--r--src/core/contacts-email-addresses-chunk.vala15
-rw-r--r--src/core/contacts-full-name-chunk.vala22
-rw-r--r--src/core/contacts-im-addresses-chunk.vala19
-rw-r--r--src/core/contacts-nickname-chunk.vala17
-rw-r--r--src/core/contacts-notes-chunk.vala15
-rw-r--r--src/core/contacts-phones-chunk.vala15
-rw-r--r--src/core/contacts-roles-chunk.vala19
-rw-r--r--src/core/contacts-structured-name-chunk.vala34
-rw-r--r--src/core/contacts-urls-chunk.vala15
-rw-r--r--src/io/contacts-io-parse-main.vala2
-rw-r--r--src/io/contacts-io-parse-operation.vala17
-rw-r--r--src/io/contacts-io-parser.vala18
-rw-r--r--src/io/contacts-io-vcard-parser.vala209
-rw-r--r--src/io/contacts-io.vala428
-rw-r--r--src/io/meson.build1
-rw-r--r--tests/core/meson.build1
-rw-r--r--tests/core/test-birthday-chunk.vala44
-rw-r--r--tests/core/test-email-addresses-chunk.vala27
-rw-r--r--tests/core/test-full-name-chunk.vala21
-rw-r--r--tests/core/test-nickname-chunk.vala21
-rw-r--r--tests/core/test-phones-chunk.vala23
-rw-r--r--tests/core/test-structured-name-chunk.vala65
-rw-r--r--tests/core/test-urls-chunk.vala23
-rw-r--r--tests/io/internal/meson.build29
-rw-r--r--tests/io/internal/test-serialise-birthday.vala54
-rw-r--r--tests/io/internal/test-serialise-common.vala66
-rw-r--r--tests/io/internal/test-serialise-emails.vala41
-rw-r--r--tests/io/internal/test-serialise-full-name.vala42
-rw-r--r--tests/io/internal/test-serialise-nickname.vala40
-rw-r--r--tests/io/internal/test-serialise-structured-name.vala45
-rw-r--r--tests/io/internal/test-serialise-urls.vala41
-rw-r--r--tests/io/meson.build1
-rw-r--r--tests/io/vcard/meson.build1
-rw-r--r--tests/io/vcard/test-vcard-minimal-import.vala65
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);
}