[gnome-contacts] Refactor link/unlink operations



commit aa2433307d485b020b8630601dbd1e91b569cea9
Author: Julian Sparber <julian sparber net>
Date:   Thu Jul 18 14:42:02 2019 +0200

    Refactor link/unlink operations
    
    This cleans up how linking and unlinking is done, by using the methodes
    provided by folks.

 src/contacts-contact-editor.vala         |   6 +
 src/contacts-contact-list.vala           |   5 +-
 src/contacts-contact-pane.vala           |  16 +-
 src/contacts-linked-personas-dialog.vala |  10 +-
 src/contacts-linking.vala                | 780 +++----------------------------
 src/contacts-store.vala                  |  91 +---
 src/contacts-utils.vala                  |  12 +
 src/contacts-window.vala                 |   8 +-
 8 files changed, 110 insertions(+), 818 deletions(-)
---
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 8a45cce..396a681 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -100,6 +100,7 @@ public class Contacts.ContactEditor : ContactForm {
     HashMap<int, RowData?> rows;
   }
 
+  private HashSet<Persona> unlink_personas;
   /* the key of the hash_map is the uid of the persona */
   private HashMap<string, HashMap<string, Field?>> writable_personas;
 
@@ -116,6 +117,7 @@ public class Contacts.ContactEditor : ContactForm {
   }
 
   construct {
+    this.unlink_personas = new HashSet<Persona> ();
     this.writable_personas = new HashMap<string, HashMap<string, Field?>> ();
     this.container_grid.size_allocate.connect(on_container_grid_size_allocate);
   }
@@ -872,6 +874,10 @@ public class Contacts.ContactEditor : ContactForm {
     return props_set;
   }
 
+  public HashSet<Persona> get_unlink_personas () {
+    return unlink_personas;
+  }
+
   public void add_new_row_for_property (Persona? persona, string prop_name, string? type = null) {
     int next_idx = 0;
     foreach (var fields in writable_personas.values) {
diff --git a/src/contacts-contact-list.vala b/src/contacts-contact-list.vala
index 5b7385e..aa3cd33 100644
--- a/src/contacts-contact-list.vala
+++ b/src/contacts-contact-list.vala
@@ -70,6 +70,7 @@ public class Contacts.ContactList : ListBox {
     }
 
     private void on_contact_changed (Object obj, ParamSpec pspec) {
+      //TODO: Update also the Avatar
       this.label.set_text (this.individual.display_name);
       changed ();
     }
@@ -203,8 +204,8 @@ public class Contacts.ContactList : ListBox {
   }
 
   private void contact_added_cb (Store store, Individual i) {
-    // Don't create a row for ignorable contacts
-    if (!Contacts.Utils.is_ignorable (i)) {
+    // Don't create a row for ignorable contacts are the individual already has a row
+    if (!Contacts.Utils.is_ignorable (i) && find_row_for_contact(i) == null) {
       var row =  new ContactDataRow (i);
       row.selector_button.toggled.connect ( () => { on_row_checkbox_toggled (row); });
       row.selector_button.visible = (this.state == UiState.SELECTING);
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index 9e048ef..adaafeb 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -89,10 +89,12 @@ public class Contacts.ContactPane : Stack {
 
     this.suggestion_grid.suggestion_accepted.connect ( () => {
         var linked_contact = this.individual.display_name;
-        link_contacts.begin (this.individual, i, this.store, (obj, result) => {
-            var operation = link_contacts.end (result);
-            this.contacts_linked (null, linked_contact, operation);
-          });
+        var operation = new LinkOperation (this.store);
+        var to_link = new LinkedList<Individual> ();
+        to_link.add (this.individual);
+        to_link.add (i);
+        operation.do.begin (to_link);
+        this.contacts_linked (null, linked_contact, operation);
         remove_suggestion_grid ();
       });
 
@@ -260,6 +262,12 @@ public class Contacts.ContactPane : Stack {
         show_message (e.message);
       }
     }
+
+    /* unlink personas */
+    if (this.editor.get_unlink_personas ().size > 0) {
+      var operation = new UnLinkOperation (this.store);
+      operation.do.begin (this.individual, this.editor.get_unlink_personas ());
+    }
   }
 
   public void new_contact () {
diff --git a/src/contacts-linked-personas-dialog.vala b/src/contacts-linked-personas-dialog.vala
index 6af7aef..8ae9413 100644
--- a/src/contacts-linked-personas-dialog.vala
+++ b/src/contacts-linked-personas-dialog.vala
@@ -79,15 +79,7 @@ public class Contacts.LinkedPersonasDialog : Dialog {
 
       /* signal */
       button.clicked.connect (() => {
-          unlink_persona.begin (store, individual, p, (obj, result) => {
-              unlink_persona.end (result);
-
-              row_grid.destroy ();
-
-              this.any_unlinked = true;
-              /* TODO: Support undo */
-              /* TODO: Ensure we don't get suggestion for this linkage again */
-            });
+        // TODO: handly unlinking
         });
 
       row_grid.show_all ();
diff --git a/src/contacts-linking.vala b/src/contacts-linking.vala
index 76043de..d5ddd2f 100644
--- a/src/contacts-linking.vala
+++ b/src/contacts-linking.vala
@@ -22,751 +22,87 @@ using Gee;
 
 namespace Contacts {
   public class LinkOperation : Object {
-    internal class Change {
-      public PersonaAttribute attribute;
-      public Persona persona;
-      public Object old_value;
-    }
-    private Persona? _added_persona;
-    private Individual? main_contact;
-    private ArrayList<Persona>? split_out_personas;
-    ArrayList<Change> changes;
-
-    public LinkOperation() {
-      changes = new ArrayList<Change> ();
-    }
-
-    public void set_main_contact (Individual? individual) {
-      main_contact = individual;
-    }
-
-    public void set_split_out_contact (Individual? individual) {
-      if (individual != null) {
-       split_out_personas = new ArrayList<Persona> ();
-       split_out_personas.add_all (individual.personas);
+    private weak Store store;
+    private HashSet<HashSet<Persona>> personas_to_link;
+    private bool finished { get; set; default = false; }
+
+    public LinkOperation(Store store) {
+      this.store = store;
+      this.personas_to_link = new HashSet<HashSet<Persona>> ();
+    }
+
+    /* Link individuals */
+    public async void do (LinkedList<Individual> individuals) {
+      var personas_to_link = new HashSet<Persona> ();
+      foreach (var i in individuals) {
+        var saved_personas = new HashSet<Persona> ();
+        foreach (var persona in i.personas) {
+          personas_to_link.add (persona);
+          saved_personas.add (persona);
+        }
+        this.personas_to_link.add (saved_personas);
       }
-    }
 
-    public void added_persona (Persona persona) {
-      _added_persona = persona;
-    }
+      // We don't need to unlink the individuals because we are using every persona
+      yield link_personas(this.store, this.store.aggregator, personas_to_link);
 
-    public void add_change (PersonaAttribute attribute, Persona persona, Object old_value) {
-      var c = new Change ();
-      c.attribute = attribute;
-      c.persona = persona;
-      c.old_value = old_value;
-      changes.add (c);
+      finished = true;
     }
 
+    /* Undo the linking */
     public async void undo () {
-      if (main_contact != null)
-       main_contact.set_data ("contacts-master-at-join", true);
-      if (split_out_personas != null) {
-       foreach (var p in split_out_personas)
-         p.set_data ("contacts-new-contact", true);
-      }
-
-      try {
-       if (_added_persona != null) {
-         yield _added_persona.store.remove_persona (_added_persona);
-       }
-       foreach (var c in changes) {
-         if (c.persona != _added_persona) {
-           yield c.attribute.set_value (c.persona, c.old_value);
-         }
-       }
-      } catch (GLib.Error e) {
-       warning ("Error when undoing linking: %s\n", e.message);
-      }
-
-      if (main_contact != null)
-       main_contact.set_data ("contacts-master-at-join", false);
-
-      if (split_out_personas != null) {
-       foreach (var p in split_out_personas)
-         p.set_data ("contacts-new-contact", false);
-      }
-    }
-  }
-
-  public abstract class PersonaAttribute : Object {
-    public string property_name;
-
-    public static HashSet<PersonaAttribute> create_set () {
-      return new HashSet<PersonaAttribute>((HashDataFunc<PersonaAttribute>) PersonaAttribute.hash,
-                                          (EqualDataFunc<PersonaAttribute>) PersonaAttribute.equal);
-    }
-
-    public virtual bool is_removable (Persona from_persona) {
-      return (property_name in from_persona.writeable_properties);
-    }
-
-    public abstract bool is_referenced_by_persona (Persona persona);
-
-    public abstract string to_string ();
-
-    public virtual bool equal (PersonaAttribute that) {
-      return this.property_name == that.property_name;
-    }
-
-    public virtual uint hash () {
-      return this.property_name.hash ();
-    }
-
-    public abstract async void persona_apply_attributes (Persona persona,
-                                                        Set<PersonaAttribute> added_attributes,
-                                                        Set<PersonaAttribute> removed_attributes,
-                                                        LinkOperation operation);
-    public abstract async void set_value (Persona persona, Object value);
-  }
-
-  internal class PersonaAttributeLocalId : PersonaAttribute {
-    string value;
-
-    public PersonaAttributeLocalId (string value) {
-      property_name = "local-ids";
-      this.value = value;
-    }
-
-    public override bool is_removable (Persona from_persona) {
-      return base.is_removable (from_persona) && value != from_persona.iid;
-    }
-
-    public override string to_string () {
-      return "local_id: " + value;
-    }
-
-    public override bool is_referenced_by_persona (Persona persona) {
-      var details = persona as LocalIdDetails;
-      if (details == null)
-       return false;
-
-      return value in details.local_ids;
-    }
-
-    public override async void persona_apply_attributes (Persona persona,
-                                                        Set<PersonaAttribute> added_attributes,
-                                                        Set<PersonaAttribute> removed_attributes,
-                                                        LinkOperation operation) {
-      var details = persona as LocalIdDetails;
-      if (details == null)
-       return;
-
-      var added_values = new HashSet<string> ();
-      foreach (var added in added_attributes) {
-       added_values.add (((PersonaAttributeLocalId)added).value);
-      }
-
-      var removed_values = new HashSet<string> ();
-      foreach (var removed in removed_attributes) {
-       removed_values.add (((PersonaAttributeLocalId)removed).value);
-      }
-
-      var new_values = new HashSet<string> ();
-      bool changed = false;
-      foreach (var v in details.local_ids) {
-       if (v in removed_values) {
-         changed = true;
-         continue;
-       }
-       new_values.add (v);
-       if (v in added_values)
-         added_values.remove (v);
-      }
-      foreach (var v2 in added_values) {
-       changed = true;
-       new_values.add (v2);
-      }
-
-      if (changed) {
-       try {
-         var old_value = new HashSet<string> ();
-         old_value.add_all (details.local_ids);
-         yield details.change_local_ids (new_values);
-         operation.add_change (this, persona, old_value);
-       } catch (GLib.Error e) {
-         warning ("Unable to set local ids when linking: %s\n", e.message);
-       }
-      }
-    }
-
-    public override async void set_value (Persona persona, Object value) {
-      var details = persona as LocalIdDetails;
-      if (details == null)
-       return;
-
-      try {
-       var v = value as HashSet<string>;
-       yield details.change_local_ids (v);
-      } catch (GLib.Error e) {
-       warning ("Unable to set local ids when undoing link: %s\n", e.message);
+      var individual = this.personas_to_link.first_match(() => {return true;})
+        .first_match(() => {return true;}).individual;
+      yield store.aggregator.unlink_individual (individual);
+      foreach (var personas in personas_to_link) {
+        yield link_personas (this.store, this.store.aggregator, personas);
       }
     }
-
-    public override bool equal (PersonaAttribute _that) {
-      var that = _that as PersonaAttributeLocalId;
-      return
-       that != null &&
-       base.equal (that) &&
-       this.value == that.value;
-    }
-
-    public override uint hash () {
-      return this.value.hash () ^ base.hash ();
-    }
   }
 
-  internal class PersonaAttributeImAddress : PersonaAttribute {
-    string protocol;
-    ImFieldDetails detail;
-
-    public PersonaAttributeImAddress (string protocol, ImFieldDetails detail) {
-      property_name = "im-addresses";
-      this.protocol = protocol;
-      this.detail = detail;
-    }
-
-    public override string to_string () {
-      return "im_addresses: " + protocol + ":" + detail.value;
-    }
-
-    public override bool is_referenced_by_persona (Persona persona) {
-      var details = persona as ImDetails;
-      if (details == null)
-       return false;
-
-      return detail in details.im_addresses.get (protocol);
-    }
-
-    public override async void persona_apply_attributes (Persona persona,
-                                                        Set<PersonaAttribute> added_attributes,
-                                                        Set<PersonaAttribute> removed_attributes,
-                                                        LinkOperation operation) {
-      var details = persona as ImDetails;
-      if (details == null)
-       return;
-
-      var added_values = new HashMultiMap<string, ImFieldDetails> (null, null,
-                                                                  AbstractFieldDetails<string>.hash_static,
-                                                                  AbstractFieldDetails<string>.equal_static);
-      foreach (var added in added_attributes) {
-       added_values.set (((PersonaAttributeImAddress)added).protocol, 
((PersonaAttributeImAddress)added).detail);
-      }
-
-      var removed_values = new HashMultiMap<string, ImFieldDetails> (null, null,
-                                                                    AbstractFieldDetails<string>.hash_static,
-                                                                    
AbstractFieldDetails<string>.equal_static);
-
-      foreach (var removed in removed_attributes) {
-       removed_values.set (((PersonaAttributeImAddress)removed).protocol, 
((PersonaAttributeImAddress)removed).detail);
-      }
-
-      var new_values =
-       new HashMultiMap<string, ImFieldDetails> (null, null,
-                                                 AbstractFieldDetails<string>.hash_static,
-                                                 AbstractFieldDetails<string>.equal_static);
-      bool changed = false;
-      foreach (var proto1 in details.im_addresses.get_keys ()) {
-       foreach (var detail1 in details.im_addresses.get (proto1)) {
-         if (removed_values.get (proto1).contains (detail1)) {
-           changed = true;
-           continue;
-         }
-         new_values.set (proto1, detail1);
-         if (added_values.get (proto1).contains (detail1)) {
-           added_values.remove (proto1, detail1);
-         }
-       }
-      }
-      foreach (var proto2 in added_values.get_keys ()) {
-       foreach (var detail2 in added_values.get (proto2)) {
-         changed = true;
-         new_values.set (proto2, detail2);
-       }
-      }
-
-      if (changed) {
-       try {
-         var old_value = details.im_addresses;
-         yield details.change_im_addresses (new_values);
-         operation.add_change (this, persona, old_value);
-       } catch (GLib.Error e) {
-         warning ("Unable to set im address when linking: %s\n", e.message);
-       }
-      }
+  public class UnLinkOperation : Object {
+    private weak Store store;
+    public UnLinkOperation(Store store) {
+      this.store = store;
     }
 
-    public override async void set_value (Persona persona, Object value) {
-      var details = persona as ImDetails;
-      if (details == null)
-       return;
+    /* Remove a personas from individual */
+    public async void do (Individual main, Set<Persona> personas_to_remove) {
+      var personas_to_keep = new HashSet<Persona> ();
+      foreach (var persona in main.personas)
+        if (!personas_to_remove.contains (persona))
+          personas_to_keep.add (persona);
 
       try {
-       var v = value as HashMultiMap<string, ImFieldDetails>;
-       yield details.change_im_addresses (v);
-      } catch (GLib.Error e) {
-       warning ("Unable to set local ids when undoing link: %s\n", e.message);
-      }
-    }
-
-    public override bool equal (PersonaAttribute _that) {
-      var that = _that as PersonaAttributeImAddress;
-      return
-       that != null &&
-       base.equal (that) &&
-       this.protocol == that.protocol &&
-       this.detail.equal (that.detail);
-    }
-
-    public override uint hash () {
-      return this.protocol.hash () ^ this.detail.hash () ^ base.hash ();
-    }
-  }
-
-  internal class PersonaAttributeWebService : PersonaAttribute {
-    string service;
-    WebServiceFieldDetails detail;
-
-    public PersonaAttributeWebService (string service, WebServiceFieldDetails detail) {
-      property_name = "web-service-addresses";
-      this.service = service;
-      this.detail = detail;
-    }
-
-    public override string to_string () {
-      return "web_service_addresses: " + service + ":" + detail.value;
-    }
-
-    public override bool is_referenced_by_persona (Persona persona) {
-      var details = persona as WebServiceDetails;
-      if (details == null)
-       return false;
-
-      return detail in details.web_service_addresses.get (service);
-    }
-
-    public override async void persona_apply_attributes (Persona persona,
-                                                        Set<PersonaAttribute> added_attributes,
-                                                        Set<PersonaAttribute> removed_attributes,
-                                                        LinkOperation operation) {
-      var details = persona as WebServiceDetails;
-      if (details == null)
-       return;
-
-      var added_values = new HashMultiMap<string, WebServiceFieldDetails> (null, null,
-                                                                          
AbstractFieldDetails<string>.hash_static,
-                                                                          
AbstractFieldDetails<string>.equal_static);
-      foreach (var added in added_attributes) {
-       added_values.set (((PersonaAttributeWebService)added).service, 
((PersonaAttributeWebService)added).detail);
-      }
-
-      var removed_values = new HashMultiMap<string, WebServiceFieldDetails> (null, null,
-                                                                            
AbstractFieldDetails<string>.hash_static,
-                                                                            
AbstractFieldDetails<string>.equal_static);
-      foreach (var removed in removed_attributes) {
-       removed_values.set (((PersonaAttributeWebService)removed).service, 
((PersonaAttributeWebService)removed).detail);
-      }
-
-      var new_values =
-       new HashMultiMap<string, WebServiceFieldDetails> (null, null,
-                                                         AbstractFieldDetails<string>.hash_static,
-                                                         AbstractFieldDetails<string>.equal_static);
-      bool changed = false;
-      foreach (var srv1 in details.web_service_addresses.get_keys ()) {
-       foreach (var detail1 in details.web_service_addresses.get (srv1)) {
-         if (removed_values.get (srv1).contains (detail1)) {
-           changed = true;
-           continue;
-         }
-         new_values.set (srv1, detail1);
-         if (added_values.get (srv1).contains (detail1)) {
-           added_values.remove (srv1, detail1);
-         }
-       }
-      }
-      foreach (var srv2 in added_values.get_keys ()) {
-       foreach (var detail2 in added_values.get (srv2)) {
-         changed = true;
-         new_values.set (srv2, detail2);
-       }
-      }
-
-      if (changed) {
-       try {
-         var old_value = details.web_service_addresses;
-         yield details.change_web_service_addresses (new_values);
-         operation.add_change (this, persona, old_value);
-       } catch (GLib.Error e) {
-         warning ("Unable to set web service when linking: %s\n", e.message);
-       }
-      }
-    }
-
-    public override async void set_value (Persona persona, Object value) {
-      var details = persona as WebServiceDetails;
-      if (details == null)
-       return;
-
-      try {
-       var v = value as HashMultiMap<string, WebServiceFieldDetails>;
-       yield details.change_web_service_addresses (v);
-      } catch (GLib.Error e) {
-       warning ("Unable to set local ids when undoing link: %s\n", e.message);
-      }
-    }
-
-    public override bool equal (PersonaAttribute _that) {
-      var that = _that as PersonaAttributeWebService;
-      return
-       that != null &&
-       base.equal (that) &&
-       this.service == that.service &&
-       this.detail.equal (that.detail);
-    }
-
-    public override uint hash () {
-      return this.service.hash () ^ this.detail.hash () ^ base.hash ();
-    }
-  }
-
-  public static void add_linkable_attributes (HashSet<PersonaAttribute> set, Persona persona) {
-    if (persona is LocalIdDetails) {
-      foreach (var id in ((LocalIdDetails) persona).local_ids) {
-       set.add (new PersonaAttributeLocalId (id));
-      }
-    }
-
-    if (persona is ImDetails) {
-      foreach (var proto in ((ImDetails) persona).im_addresses.get_keys ()) {
-       foreach (var im in ((ImDetails) persona).im_addresses.get (proto)) {
-         set.add (new PersonaAttributeImAddress (proto, im));
-       }
-      }
-    }
-
-    if (persona is WebServiceDetails) {
-      foreach (var srv in ((WebServiceDetails) persona).web_service_addresses.get_keys ()) {
-       foreach (var web in ((WebServiceDetails) persona).web_service_addresses.get (srv)) {
-         set.add (new PersonaAttributeWebService (srv, web));
-       }
-      }
-    }
-  }
-
-  public static Set<PersonaAttribute> get_linkable_attributes (Persona persona) {
-    var res = PersonaAttribute.create_set ();
-    add_linkable_attributes (res, persona);
-    return res;
-  }
-
-  public static Set<PersonaAttribute> get_linkable_attributes_for_individual (Individual individual) {
-    var res = PersonaAttribute.create_set ();
-    foreach (var persona in individual.personas)
-      add_linkable_attributes (res, persona);
-    return res;
-  }
-
-  public static bool persona_can_link_to (Persona persona, Set<PersonaAttribute> attributes) {
-    var property_names = new HashSet<string>();
-    foreach (var a in attributes)
-      property_names.add (a.property_name);
-
-    foreach (var p in property_names) {
-      if (! (p in persona.writeable_properties))
-       return false;
-    }
-    return true;
-  }
-
-  internal bool attr_type_equal (PersonaAttribute a, PersonaAttribute b) {
-    return
-      a.get_type() == b.get_type() &&
-      a.property_name == b.property_name;
-  }
-
-  public static async void persona_apply_attributes (Persona persona,
-                                                    Set<PersonaAttribute>? added_attributes,
-                                                    Set<PersonaAttribute>? removed_attributes,
-                                                    LinkOperation operation) {
-    var properties = new HashSet<PersonaAttribute>();
-
-    if (added_attributes != null) {
-      foreach (var a1 in added_attributes) {
-       properties.add (a1);
-      }
-    }
-    if (removed_attributes != null) {
-      foreach (var a2 in removed_attributes) {
-       properties.add (a2);
-      }
-    }
-
-    foreach (var property in properties) {
-      var added = PersonaAttribute.create_set ();
-      var removed = PersonaAttribute.create_set ();
-      if (added_attributes != null) {
-       foreach (var a3 in added_attributes) {
-         if (attr_type_equal (a3, property))
-           added.add (a3);
-       }
-      }
-      if (removed_attributes != null) {
-       foreach (var a4 in removed_attributes) {
-         if (attr_type_equal (a4, property))
-           removed.add (a4);
-       }
-      }
-      yield property.persona_apply_attributes (persona, added, removed, operation);
-    }
-  }
-
-  public async LinkOperation link_contacts (Individual main, Individual? other, Store contacts_store) {
-    // This should not be used as being replaced with the new individual
-    // instead we should always pick this contact to keep around
-    main.set_data ("contacts-master-at-join", true);
-
-    var operation = new LinkOperation ();
-    operation.set_split_out_contact (other);
-
-    var main_linkables = get_linkable_attributes_for_individual (main);
-    Set<PersonaAttribute>? other_linkables = null;
-    if (other != null)
-      other_linkables = get_linkable_attributes_for_individual (other);
-    Set<PersonaAttribute>? linkables = null;
-
-    // Remove all linkable data from each contact that is already in the other contact
-    if (other_linkables != null) {
-      main_linkables.remove_all (other_linkables);
-      other_linkables.remove_all (main_linkables);
-    }
-
-    Persona? write_persona = null;
-    foreach (var p1 in main.personas) {
-      if (other_linkables != null &&
-         persona_can_link_to (p1, other_linkables)) {
-       write_persona = p1;
-       linkables = other_linkables;
-       if (write_persona.store.is_primary_store)
-         break; // Exit if we find a primary persona, as we prefer these
-      }
-    }
-
-    if (other != null &&
-       (write_persona == null || !write_persona.store.is_primary_store)) {
-      foreach (var p2 in other.personas) {
-       if (persona_can_link_to (p2, main_linkables)) {
-         // Only override main persona if its a primary store persona
-         if (write_persona == null || p2.store.is_primary_store) {
-           write_persona = p2;
-           linkables = main_linkables;
-           if (write_persona.store.is_primary_store)
-             break; // Exit if we find a primary persona, as we prefer these
-         }
-       }
-      }
-    }
-
-    if (write_persona == null) {
-      var details = new HashTable<string, Value?> (str_hash, str_equal);
-      try {
-       var v = Value (typeof (string));
-       v.set_string (main.display_name);
-       details.set ("full-name", v);
-       write_persona = yield contacts_store.aggregator.primary_store.add_persona_from_details (details);
-       operation.added_persona (write_persona);
-       linkables = main_linkables;
-       if (other_linkables != null)
-         linkables.add_all (other_linkables);
-      } catch (GLib.Error e) {
-       main.set_data ("contacts-master-at-join", false);
-       warning ("Unable to create new persona when linking: %s\n", e.message);
-       return operation;
-      }
-    }
-
-    yield persona_apply_attributes (write_persona, linkables, null, operation);
-
-    main.set_data ("contacts-master-at-join", false);
-
-    return operation;
-  }
-
-  public async LinkOperation unlink_persona (Store store, Individual individual, Persona persona_to_unlink) {
-    var persona_to_unlink_removals = PersonaAttribute.create_set ();
-    var other_personas_removals = PersonaAttribute.create_set ();
-
-    var operation = new LinkOperation ();
-    operation.set_main_contact (individual);
-
-    foreach (PersonaAttribute a1 in get_linkable_attributes (persona_to_unlink)) {
-      // Check that this attribute actually is used to link this persona to the individual
-      bool used_to_link = false;
-      foreach (var persona in individual.personas) {
-       if (persona != persona_to_unlink &&
-           a1.is_referenced_by_persona (persona)) {
-         used_to_link = true;
-         break;
-       }
-      }
-      if (!used_to_link)
-       continue; // Wasn't used, no need to do anything about it
-
-      if (a1.is_removable (persona_to_unlink)) {
-       // We can remove the attribute from the persona, which should completely break any linkage
-       // due to this attribute
-       persona_to_unlink_removals.add (a1);
-      } else {
-       // We can't remove the attribute from the persona, need to make sure no other persona
-       // references this
-       other_personas_removals.add (a1);
-      }
-    }
-
-    // At this point we know how to unlink the persona from the individual, however
-    // doing so may cause the remaining personas to form disjoint sets rather than
-    // a single Individual. Consider two subsets of personas A and B, and the unlinked
-    // persona u which make up the original individual. When unlinking u A and B may be
-    // disjoint if:
-    // * A links to u and u Links to B, then the data from u that linked it to B was
-    //   removed (and no other links go between A and B)
-    //  or
-    // * A and B both link to u, but to unlink them from u we removed the data in A and
-    //   B that caused this link (and no other links go between A and B)
-    //
-    // To fix this up we need to ensure that all the remaining personas in the inidivudal
-    // do have links by picking (or creating if there is none) a persona where all linkable
-    // attributes are writeable and ensuring that it can reach all the other remaining
-    // personas. We do this the easy way by just adding all linkable attributes to this
-    // persona
-    var main_persona_additions = PersonaAttribute.create_set ();
-    foreach (var p1 in individual.personas) {
-      if (p1 == persona_to_unlink)
-       continue;
-      foreach (PersonaAttribute a2 in get_linkable_attributes (p1)) {
-       if (a2 in other_personas_removals)
-         continue;
-       main_persona_additions.add (a2);
-      }
-    }
-
-    // Find tha main persona that will be used to add the extra linking info to
-    // avoid disjoint sets
-    Persona? main_persona = null;
-    foreach (var p2 in individual.personas) {
-      if (p2 != persona_to_unlink && persona_can_link_to (p2, main_persona_additions)) {
-       main_persona = p2;
-       if (main_persona.store.is_primary_store)
-         break; // Exit if we find a primary persona, as we prefer these
-      }
-    }
-
-    // We make a copy of the personas as the on in the individual may start
-    // changing now
-    var other_personas = new HashSet<Persona>();
-    foreach (var p3 in individual.personas) {
-      if (p3 != persona_to_unlink &&
-         p3 != main_persona)
-       other_personas.add (p3);
-    }
-
-    // If we didn't find a main persona, and we need one because there are
-    // other personas that we need to ensure linking in, then create one
-    if (main_persona == null && other_personas.size > 1) {
-      var details = new HashTable<string, Value?> (str_hash, str_equal);
-      try {
-        main_persona = yield store.aggregator.primary_store.add_persona_from_details (details);
-        yield (main_persona as NameDetails).change_full_name (individual.display_name);
-        operation.added_persona (main_persona);
-      } catch (GLib.Error e) {
-       warning ("Unable to create new persona when unlinking: %s\n", e.message);
-       return operation;
-      }
-    }
-
-    persona_to_unlink.set_data ("contacts-new-contact", true);
-
-    // First apply all additions on the primary persona so that we avoid temporarily being
-    // unlinked and then relinked
-    if (main_persona != null)
-      yield persona_apply_attributes (main_persona, main_persona_additions, other_personas_removals, 
operation);
-    foreach (var p in other_personas) {
-      yield persona_apply_attributes (p, null, other_personas_removals, operation);
-    }
-    // Last we do the removals on the persona_to_unlink
-    yield persona_apply_attributes (persona_to_unlink, null, persona_to_unlink_removals, operation);
-
-    persona_to_unlink.set_data ("contacts-new-contact", false);
-
-    return operation;
-  }
-
-  public class LinkOperation2 : Object {
-    private Store contacts_store;
-
-    /* One Set<Persona> per individual linked, with the intention
-     * of restore the old perosonas set on undo operation */
-    LinkedList< HashSet<Persona> > old_personas_distribution;
-
-    public LinkOperation2 (Store contacts_store) {
-      this.contacts_store = contacts_store;
-
-      old_personas_distribution = new  LinkedList< HashSet<Persona> > ();
-    }
-
-    public void add_persona_set (Set<Persona> persona_set) {
-      if (persona_set.size > 0) {
-       var s = new HashSet<Persona> ();
-       foreach (var p in persona_set) {
-         s.add (p);
-       }
-       old_personas_distribution.add (s);
+        yield store.aggregator.unlink_individual (main);
+      } catch (Error e) {
+        debug ("Couldn't link personas");
       }
+      yield link_personas(this.store, this.store.aggregator, personas_to_keep);
     }
 
+    /* Undo the unlinking */
     public async void undo () {
-      Individual ind = null;
-      if (old_personas_distribution.size > 0) {
-       var ps = old_personas_distribution.first ();
-       foreach (var p in ps) {
-         ind = p.individual;
-         break;
-       }
-      }
-      if (ind != null) {
-        try {
-          yield this.contacts_store.aggregator.unlink_individual (ind);
-        } catch (GLib.Error e1) {
-          warning ("Error unlinking individual ā€˜%sā€™: %s", ind.id, e1.message);
-        }
-      }
-
-      foreach (var ps in old_personas_distribution) {
-        try {
-          yield this.contacts_store.aggregator.link_personas (ps);
-        } catch (GLib.Error e1) {
-          warning ("Error linking personas: %s", e1.message);
-        }
-      }
     }
   }
 
-  public async LinkOperation2 link_contacts_list (LinkedList<Individual> contact_list, Store contacts_store) 
{
-    var operation = new LinkOperation2 (contacts_store);
-
-    var all_personas = new HashSet<Persona> ();
-    foreach (var i in contact_list) {
-      var ps = i.personas;
-      all_personas.add_all (ps);
-      operation.add_persona_set (ps);
-    }
-
+ /* Workaround: link_personas creates a new persona in the primary-store,
+  * For some reason we can't change the primary-store directly,
+  * but we can change the gsettings property.
+  * Before linking we set the primary-store to be "key-file"
+  * that the linking persona isn't written to a real store
+  */
+  private async void link_personas (Store store, IndividualAggregator aggregator, Set<Persona> personas) {
+    var settings = new GLib.Settings ("org.freedesktop.folks");
+    var default_store = settings.get_string ("primary-store");
+    settings.set_string ("primary-store", "key-file:relationships.ini");
     try {
-      yield contacts_store.aggregator.link_personas (all_personas);
-    } catch (GLib.Error e1) {
-      warning ("Error linking personas: %s", e1.message);
+      yield aggregator.link_personas (personas);
+    } catch (Error e) {
+      debug ("%s", e.message);
     }
 
-    return operation;
+    // Rest primary-store
+    settings.set_string ("primary-store", default_store);
   }
 }
diff --git a/src/contacts-store.vala b/src/contacts-store.vala
index 9334900..4f1568f 100644
--- a/src/contacts-store.vala
+++ b/src/contacts-store.vala
@@ -42,19 +42,6 @@ public class Contacts.Store : GLib.Object {
     get { return this.aggregator.is_prepared; }
   }
 
-  private bool individual_can_replace_at_split (Individual new_individual) {
-    foreach (var p in new_individual.personas) {
-      if (p.get_data<bool> ("contacts-new-contact"))
-       return false;
-    }
-    return true;
-  }
-
-  private bool individual_should_replace_at_join (Individual old_individual) {
-    var c = old_individual;
-    return c.get_data<bool> ("contacts-master-at-join");
-  }
-
   private void read_dont_suggest_db () {
     dont_suggest_link.clear ();
     try {
@@ -151,73 +138,25 @@ public class Contacts.Store : GLib.Object {
   }
 
   private void on_individuals_changed_detailed (MultiMap<Individual?,Individual?> changes) {
-    // Note: Apparently the current implementation doesn't necessarily pick
-    // up unlinked individual as replacements.
-    var replaced_individuals = new HashMap<Individual?, Individual?> ();
-    var old_individuals = changes.get_keys();
-
-    debug ("Individuals changed: %d old, %d new", old_individuals.size - 1, changes[null].size);
-
-    // Pick best replacements at joins
-    foreach (var old_individual in old_individuals) {
-      if (old_individual == null)
-        continue;
-      foreach (var new_individual in changes[old_individual]) {
-        if (new_individual == null)
-          continue;
-        if (!replaced_individuals.has_key (new_individual)
-            || individual_should_replace_at_join (old_individual)) {
-          replaced_individuals[new_individual] = old_individual;
-        }
+    var to_add = new HashSet<Individual> ();
+    var to_remove = new HashSet<Individual> ();
+    foreach (var i in changes.get_keys()) {
+      if (i != null)
+        to_remove.add (i);
+      foreach (var new_i in changes[i]) {
+        to_add.add (new_i);
       }
     }
 
-    foreach (var old_individual in old_individuals) {
-      HashSet<Individual>? replacements = null;
-      foreach (var new_individual in changes[old_individual]) {
-        if (old_individual != null && new_individual != null &&
-            replaced_individuals[new_individual] == old_individual) {
-          if (replacements == null)
-            replacements = new HashSet<Individual> ();
-          replacements.add (new_individual);
-        } else if (old_individual != null) {
-          // Removing an old individual.
-          removed (old_individual);
-        } else if (new_individual != null) {
-          // Adding a new individual.
-          added (new_individual);
-        }
-      }
-
-      // This old_individual was split up into one or more new ones
-      // We have to pick one to be the one that we keep around
-      // in the old Contact, the rest gets new Contacts
-      // This is important to get right, as we might be displaying
-      // the contact and unlink a specific persona from the contact
-      if (replacements != null) {
-        Individual? main_individual = null;
-        foreach (var i in replacements) {
-          main_individual = i;
-          // If this was marked as being possible to replace the
-          // contact on split then we can otherwise bail immediately
-          // Otherwise need to look for other possible better
-          // replacements that we should reuse
-          if (individual_can_replace_at_split (i))
-            break;
-        }
+    // Add new individuals
+    foreach (var i in to_add) {
+      if (i.personas.size > 0)
+        added (i);
+    }
 
-        /* Not sure
-        var c = Contact.from_individual (old_individual);
-        c.replace_individual (main_individual);
-        */
-        foreach (var i in replacements) {
-          if (i != main_individual) {
-            // Already replaced this old_individual, i.e. we're splitting
-            // old_individual. We just make this a new one.
-            added (i);
-          }
-        }
-      }
+    // Remove old individuals
+    foreach (var i in to_remove) {
+      removed (i);
     }
   }
 
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index ccaec8d..f25c3a6 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -164,6 +164,18 @@ namespace Contacts.Utils {
     return stores;
   }
 
+  public PersonaStore? get_key_file_address_book (Store contacts_store) {
+    foreach (var backend in contacts_store.backend_store.enabled_backends.values) {
+      foreach (var persona_store in backend.persona_stores.values) {
+        if (persona_store.type_id == "key-file") {
+          return persona_store;
+        }
+      }
+    }
+    return null;
+  }
+
+
   public void show_error_dialog (string error, Gtk.Window toplevel) {
     var dialog = new Gtk.MessageDialog (toplevel,
                                         Gtk.DialogFlags.MODAL,
diff --git a/src/contacts-window.vala b/src/contacts-window.vala
index 8c156a5..0ea8f1a 100644
--- a/src/contacts-window.vala
+++ b/src/contacts-window.vala
@@ -419,18 +419,16 @@ public class Contacts.Window : Gtk.ApplicationWindow {
     set_shown_contact (null);
     this.state = UiState.NORMAL;
 
-    LinkOperation2 operation = null;
-    link_contacts_list.begin (contact_list, this.store, (obj, result) => {
-        operation = link_contacts_list.end (result);
-      });
+    var operation = new LinkOperation (this.store);
+    operation.do.begin (contact_list);
 
     string msg = ngettext ("%d contacts linked",
                            "%d contacts linked",
                            contact_list.size).printf (contact_list.size);
 
     var b = new Button.with_mnemonic (_("_Undo"));
+    var notification = new InAppNotification (msg, b);
 
-    var notification = new InAppNotification (msg);
     /* signal handlers */
     b.clicked.connect ( () => {
         /* here, we will unlink the thing in question */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]