Bug 665692 — Use constructors correctly
[platform/upstream/folks.git] / folks / individual.vala
1 /*
2  * Copyright (C) 2010 Collabora Ltd.
3  * Copyright (C) 2011 Philip Withnall
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 2.1 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors:
19  *       Travis Reitter <travis.reitter@collabora.co.uk>
20  *       Philip Withnall <philip@tecnocode.co.uk>
21  */
22
23 using Gee;
24 using GLib;
25
26 /**
27  * Trust level for an {@link Individual} for use in the UI.
28  *
29  * @since 0.1.15
30  */
31 public enum Folks.TrustLevel
32 {
33   /**
34    * The {@link Individual}'s {@link Persona}s aren't trusted at all.
35    *
36    * This is the trust level for an {@link Individual} which contains one or
37    * more {@link Persona}s which cannot be guaranteed to be the same
38    * {@link Persona}s as were originally linked together.
39    *
40    * For example, an {@link Individual} containing a link-local XMPP
41    * {@link Persona} would have this trust level, since someone else could
42    * easily spoof the link-local XMPP {@link Persona}'s identity.
43    *
44    * @since 0.1.15
45    */
46   NONE,
47
48   /**
49    * The {@link Individual}'s {@link Persona}s are trusted.
50    *
51    * This trust level is for {@link Individual}s where it can be guaranteed
52    * that all the {@link Persona}s are the same ones as when they were
53    * originally linked together.
54    *
55    * Note that this doesn't guarantee that the user who behind each
56    * {@link Persona} is who they claim to be.
57    *
58    * @since 0.1.15
59    */
60   PERSONAS
61 }
62
63 /**
64  * A physical person, aggregated from the various {@link Persona}s the person
65  * might have, such as their different IM addresses or vCard entries.
66  *
67  * When choosing the values of single-valued properties (such as
68  * {@link Individual.alias} and {@link Individual.avatar}; but not multi-valued
69  * properties such as {@link Individual.groups} and
70  * {@link Individual.im_addresses}) from the {@link Persona}s in the
71  * individual to present as the values of those properties of the individual,
72  * it is guaranteed that if the individual contains a persona from the primary
73  * persona store (see {@link IndividualAggregator.primary_store}), its property
74  * values will be chosen above all others. This means that any changes to
75  * property values made through folks (which are normally written to the primary
76  * store) will always be used by {@link Individual}s.
77  *
78  * No further guarantees are made about the order of preference used for
79  * choosing which property values to use for the {@link Individual}, other than
80  * that the order may vary between properties, but is guaranteed to be stable
81  * for a given property.
82  */
83 public class Folks.Individual : Object,
84     AliasDetails,
85     AvatarDetails,
86     BirthdayDetails,
87     EmailDetails,
88     FavouriteDetails,
89     GenderDetails,
90     GroupDetails,
91     ImDetails,
92     LocalIdDetails,
93     NameDetails,
94     NoteDetails,
95     PresenceDetails,
96     PhoneDetails,
97     PostalAddressDetails,
98     RoleDetails,
99     UrlDetails,
100     WebServiceDetails
101 {
102   /* Stores the Personas contained in this Individual. */
103   private HashSet<Persona> _persona_set =
104       new HashSet<Persona> (direct_hash, direct_equal);
105   /* Read-only view of the above set */
106   private Set<Persona> _persona_set_ro;
107   /* Mapping from PersonaStore -> number of Personas from that store contained
108    * in this Individual. There shouldn't be any entries with a number < 1.
109    * This is used for working out when to disconnect from store signals. */
110   private HashMap<PersonaStore, uint> _stores =
111       new HashMap<PersonaStore, uint> (null, null);
112   /* The number of Personas in this Individual which have
113    * Persona.is_user == true. Iff this is > 0, Individual.is_user == true. */
114   private uint _persona_user_count = 0;
115
116   /**
117    * The trust level of the Individual.
118    *
119    * This specifies how far the Individual can be trusted to be who it claims
120    * to be. See the descriptions for the elements of {@link TrustLevel}.
121    *
122    * Clients should ''not'' allow linking of Individuals who have a trust level
123    * of {@link TrustLevel.NONE}.
124    *
125    * @since 0.1.15
126    */
127   public TrustLevel trust_level { get; private set; }
128
129   private LoadableIcon? _avatar = null;
130
131   /**
132    * {@inheritDoc}
133    *
134    * @since 0.6.0
135    */
136   [CCode (notify = false)]
137   public LoadableIcon? avatar
138     {
139       get { return this._avatar; }
140       set { this.change_avatar.begin (value); } /* not writeable */
141     }
142
143   /*
144    * Change the individual's avatar.
145    *
146    * It's preferred to call this rather than setting {@link Individual.avatar}
147    * directly, as this method gives error notification and will only return once
148    * the avatar has been written to the relevant backing stores (or the
149    * operation's failed).
150    *
151    * Setting this property is only guaranteed to succeed (and be written to
152    * the backing store) if
153    * {@link IndividualAggregator.ensure_individual_property_writeable} has been
154    * called successfully on the individual for the property name `avatar`.
155    *
156    * @param avatar the new avatar (or `null` to unset the avatar)
157    * @throws PropertyError if setting the avatar failed
158    * @since 0.6.3
159    */
160   public async void change_avatar (LoadableIcon? avatar) throws PropertyError
161     {
162       if ((this._avatar != null && this._avatar.equal (avatar)) ||
163           (this._avatar == null && avatar == null))
164         {
165           return;
166         }
167
168       debug ("Setting avatar of individual '%s' to '%p'…", this.id, avatar);
169
170       PropertyError? persona_error = null;
171       var avatar_changed = false;
172
173       /* Try to write it to only the writeable Personas which have the
174        * "avatar" property as writeable. */
175       foreach (var p in this._persona_set)
176         {
177           var a = p as AvatarDetails;
178           if (a != null && "avatar" in p.writeable_properties)
179             {
180               try
181                 {
182                   yield a.change_avatar (avatar);
183                   debug ("    written to writeable persona '%s'", p.uid);
184                   avatar_changed = true;
185                 }
186               catch (PropertyError e)
187                 {
188                   /* Store the first error so we can throw it if setting the
189                    * avatar fails on every other persona. */
190                   if (persona_error == null)
191                     {
192                       persona_error = e;
193                     }
194                 }
195             }
196         }
197
198       /* Failure? */
199       if (avatar_changed == false)
200         {
201           assert (persona_error != null);
202           throw persona_error;
203         }
204     }
205
206   /**
207    * {@inheritDoc}
208    */
209   public Folks.PresenceType presence_type { get; private set; }
210
211   /**
212    * {@inheritDoc}
213    *
214    * @since 0.6.0
215    */
216   public string presence_status { get; private set; }
217
218   /**
219    * {@inheritDoc}
220    */
221   public string presence_message { get; private set; }
222
223   /**
224    * Whether the Individual is the user.
225    *
226    * Iff the Individual represents the user – the person who owns the
227    * account in the backend for each {@link Persona} in the Individual –
228    * this is `true`.
229    *
230    * It is //not// guaranteed that every {@link Persona} in the Individual has
231    * its {@link Persona.is_user} set to the same value as the Individual. For
232    * example, the user could own two Telepathy accounts, and have added the
233    * other account as a contact in each account. The accounts will expose a
234    * {@link Persona} for the user (which will have {@link Persona.is_user} set
235    * to `true`) //and// a {@link Persona} for the contact for the other account
236    * (which will have {@link Persona.is_user} set to `false`).
237    *
238    * It is guaranteed that iff this property is set to `true` on an Individual,
239    * there will be at least one {@link Persona} in the Individual with its
240    * {@link Persona.is_user} set to `true`.
241    *
242    * It is guaranteed that there will only ever be one Individual with this
243    * property set to `true`.
244    *
245    * @since 0.3.0
246    */
247   public bool is_user { get; private set; }
248
249   /**
250    * A unique identifier for the Individual.
251    *
252    * This uniquely identifies the Individual, and persists across
253    * {@link IndividualAggregator} instances. It may not persist across linking
254    * the Individual with other Individuals.
255    *
256    * This is an opaque string and has no structure.
257    *
258    * If an identifier is required which will be used for a long-lived link
259    * between different stored data, it may be more desirable to use the
260    * {@link Persona.uid} of the most relevant {@link Persona} in the Individual
261    * instead. For example, if storing references to Individuals who are tagged
262    * in a photo, it may be safer to store the UID of the Persona whose backend
263    * provided the photo (e.g. Facebook).
264    */
265   public string id { get; private set; }
266
267   /**
268    * Emitted when the last of the Individual's {@link Persona}s has been
269    * removed.
270    *
271    * At this point, the Individual is invalid, so any client referencing it
272    * should unreference it and remove it from their UI.
273    *
274    * @param replacement_individual the individual which has replaced this one
275    * due to linking, or `null` if this individual was removed for another reason
276    * @since 0.1.13
277    */
278   public signal void removed (Individual? replacement_individual);
279
280   private string _alias = "";
281
282   /**
283    * {@inheritDoc}
284    */
285   [CCode (notify = false)]
286   public string alias
287     {
288       get { return this._alias; }
289       set { this.change_alias.begin (value); }
290     }
291
292   /**
293    * {@inheritDoc}
294    *
295    * @since 0.6.2
296    */
297   public async void change_alias (string alias) throws PropertyError
298     {
299       if (this._alias == alias)
300         {
301           return;
302         }
303
304       debug ("Setting alias of individual '%s' to '%s'…", this.id, alias);
305
306       PropertyError? persona_error = null;
307       var alias_changed = false;
308
309       /* Try to write it to only the writeable Personas which have "alias"
310        * as a writeable property. */
311       foreach (var p in this._persona_set)
312         {
313           var a = p as AliasDetails;
314           if (a != null && "alias" in p.writeable_properties)
315             {
316               try
317                 {
318                   yield a.change_alias (alias);
319                   debug ("    written to writeable persona '%s'", p.uid);
320                   alias_changed = true;
321                 }
322               catch (PropertyError e)
323                 {
324                   /* Store the first error so we can throw it if setting the
325                    * alias fails on every other persona. */
326                   if (persona_error == null)
327                     {
328                       persona_error = e;
329                     }
330                 }
331             }
332         }
333
334       /* Failure? */
335       if (alias_changed == false)
336         {
337           assert (persona_error != null);
338           throw persona_error;
339         }
340
341       /* Update our copy of the alias. */
342       this._alias = alias;
343       this.notify_property ("alias");
344     }
345
346   private StructuredName? _structured_name = null;
347
348   /**
349    * {@inheritDoc}
350    */
351   [CCode (notify = false)]
352   public StructuredName? structured_name
353     {
354       get { return this._structured_name; }
355       set { this.change_structured_name.begin (value); } /* not writeable */
356     }
357
358   private string _full_name = "";
359
360   /**
361    * {@inheritDoc}
362    */
363   [CCode (notify = false)]
364   public string full_name
365     {
366       get { return this._full_name; }
367       set { this.change_full_name.begin (value); } /* not writeable */
368     }
369
370   private string _nickname = "";
371
372   /**
373    * {@inheritDoc}
374    */
375   [CCode (notify = false)]
376   public string nickname
377     {
378       get { return this._nickname; }
379       set { this.change_nickname.begin (value); }
380     }
381
382   /**
383    * {@inheritDoc}
384    *
385    * @since 0.6.2
386    */
387   public async void change_nickname (string nickname) throws PropertyError
388     {
389       // Normalise null values to the empty string
390       if (nickname == null)
391         {
392           nickname = "";
393         }
394
395       if (this._nickname == nickname)
396         {
397           return;
398         }
399
400       debug ("Setting nickname of individual '%s' to '%s'…", this.id, nickname);
401
402       PropertyError? persona_error = null;
403       var nickname_changed = false;
404
405       /* Try to write it to only the writeable Personas which have "nickname"
406        * as a writeable property. */
407       foreach (var p in this._persona_set)
408         {
409           var n = p as NameDetails;
410           if (n != null && "nickname" in p.writeable_properties)
411             {
412               try
413                 {
414                   yield n.change_nickname (nickname);
415                   debug ("    written to writeable persona '%s'", p.uid);
416                   nickname_changed = true;
417                 }
418               catch (PropertyError e)
419                 {
420                   /* Store the first error so we can throw it if setting the
421                    * nickname fails on every other persona. */
422                   if (persona_error == null)
423                     {
424                       persona_error = e;
425                     }
426                 }
427             }
428         }
429
430       /* Failure? */
431       if (nickname_changed == false)
432         {
433           assert (persona_error != null);
434           throw persona_error;
435         }
436
437       /* Update our copy of the nickname. */
438       this._nickname = nickname;
439       this.notify_property ("nickname");
440     }
441
442   private Gender _gender = Gender.UNSPECIFIED;
443   /**
444    * {@inheritDoc}
445    */
446   [CCode (notify = false)]
447   public Gender gender
448     {
449       get { return this._gender; }
450       set { this.change_gender.begin (value); } /* not writeable */
451     }
452
453   private HashSet<UrlFieldDetails> _urls = new HashSet<UrlFieldDetails> (
454       (GLib.HashFunc) UrlFieldDetails.hash,
455       (GLib.EqualFunc) UrlFieldDetails.equal);
456   private Set<UrlFieldDetails> _urls_ro;
457
458   /**
459    * {@inheritDoc}
460    */
461   [CCode (notify = false)]
462   public Set<UrlFieldDetails> urls
463     {
464       get { return this._urls_ro; }
465       set { this.change_urls.begin (value); } /* not writeable */
466     }
467
468   private HashSet<PhoneFieldDetails> _phone_numbers =
469       new HashSet<PhoneFieldDetails> (
470           (GLib.HashFunc) PhoneFieldDetails.hash,
471           (GLib.EqualFunc) PhoneFieldDetails.equal);
472   private Set<PhoneFieldDetails> _phone_numbers_ro;
473
474   /**
475    * {@inheritDoc}
476    */
477   [CCode (notify = false)]
478   public Set<PhoneFieldDetails> phone_numbers
479     {
480       get { return this._phone_numbers_ro; }
481       set { this.change_phone_numbers.begin (value); } /* not writeable */
482     }
483
484   private HashSet<EmailFieldDetails> _email_addresses =
485       new HashSet<EmailFieldDetails> (
486           (GLib.HashFunc) EmailFieldDetails.hash,
487           (GLib.EqualFunc) EmailFieldDetails.equal);
488   private Set<EmailFieldDetails> _email_addresses_ro;
489
490   /**
491    * {@inheritDoc}
492    */
493   [CCode (notify = false)]
494   public Set<EmailFieldDetails> email_addresses
495     {
496       get { return this._email_addresses_ro; }
497       set { this.change_email_addresses.begin (value); } /* not writeable */
498     }
499
500   private HashSet<RoleFieldDetails> _roles = new HashSet<RoleFieldDetails> (
501       (GLib.HashFunc) RoleFieldDetails.hash,
502       (GLib.EqualFunc) RoleFieldDetails.equal);
503   private Set<RoleFieldDetails> _roles_ro;
504
505   /**
506    * {@inheritDoc}
507    */
508   [CCode (notify = false)]
509   public Set<RoleFieldDetails> roles
510     {
511       get { return this._roles_ro; }
512       set { this.change_roles.begin (value); } /* not writeable */
513     }
514
515   private HashSet<string> _local_ids = new HashSet<string> ();
516   private Set<string> _local_ids_ro;
517
518   /**
519    * {@inheritDoc}
520    */
521   [CCode (notify = false)]
522   public Set<string> local_ids
523     {
524       get { return this._local_ids_ro; }
525       set { this.change_local_ids.begin (value); } /* not writeable */
526     }
527
528   private DateTime? _birthday = null;
529
530   /**
531    * {@inheritDoc}
532    */
533   [CCode (notify = false)]
534   public DateTime? birthday
535     {
536       get { return this._birthday; }
537       set { this.change_birthday.begin (value); } /* not writeable */
538     }
539
540   private string? _calendar_event_id = null;
541
542   /**
543    * {@inheritDoc}
544    */
545   [CCode (notify = false)]
546   public string? calendar_event_id
547     {
548       get { return this._calendar_event_id; }
549       set { this.change_calendar_event_id.begin (value); } /* not writeable */
550     }
551
552   private HashSet<NoteFieldDetails> _notes = new HashSet<NoteFieldDetails> (
553       (GLib.HashFunc) NoteFieldDetails.hash,
554       (GLib.EqualFunc) NoteFieldDetails.equal);
555   private Set<NoteFieldDetails> _notes_ro;
556
557   /**
558    * {@inheritDoc}
559    */
560   [CCode (notify = false)]
561   public Set<NoteFieldDetails> notes
562     {
563       get { return this._notes_ro; }
564       set { this.change_notes.begin (value); } /* not writeable */
565     }
566
567   private HashSet<PostalAddressFieldDetails> _postal_addresses =
568       new HashSet<PostalAddressFieldDetails> (
569           (GLib.HashFunc) PostalAddressFieldDetails.hash,
570           (GLib.EqualFunc) PostalAddressFieldDetails.equal);
571   private Set<PostalAddressFieldDetails> _postal_addresses_ro;
572
573   /**
574    * {@inheritDoc}
575    */
576   [CCode (notify = false)]
577   public Set<PostalAddressFieldDetails> postal_addresses
578     {
579       get { return this._postal_addresses_ro; }
580       set { this.change_postal_addresses.begin (value); } /* not writeable */
581     }
582
583   private bool _is_favourite = false;
584
585   /**
586    * Whether this Individual is a user-defined favourite.
587    *
588    * This property is `true` if any of this Individual's {@link Persona}s are
589    * favourites).
590    */
591   [CCode (notify = false)]
592   public bool is_favourite
593     {
594       get { return this._is_favourite; }
595       set { this.change_is_favourite.begin (value); }
596     }
597
598   /**
599    * {@inheritDoc}
600    *
601    * @since 0.6.2
602    */
603   public async void change_is_favourite (bool is_favourite) throws PropertyError
604     {
605       if (this._is_favourite == is_favourite)
606         {
607           return;
608         }
609
610       debug ("Setting '%s' favourite status to %s…", this.id,
611         is_favourite ? "TRUE" : "FALSE");
612
613       PropertyError? persona_error = null;
614       var is_favourite_changed = false;
615
616       /* Try to write it to only the Personas which have "is-favourite" as a
617        * writeable property.
618        *
619        * NOTE: We don't check whether the persona's store is writeable, as we
620        * want is-favourite status to propagate to all stores, if possible. This
621        * is one property which is harmless to propagate. */
622       foreach (var p in this._persona_set)
623         {
624           var a = p as FavouriteDetails;
625           if (a != null && "is-favourite" in p.writeable_properties)
626             {
627               try
628                 {
629                   yield a.change_is_favourite (is_favourite);
630                   debug ("    written to persona '%s'", p.uid);
631                   is_favourite_changed = true;
632                 }
633               catch (PropertyError e)
634                 {
635                   /* Store the first error so we can throw it if setting the
636                    * property fails on every other persona. */
637                   if (persona_error == null)
638                     {
639                       persona_error = e;
640                     }
641                 }
642             }
643         }
644
645       /* Failure? */
646       if (is_favourite_changed == false)
647         {
648           assert (persona_error != null);
649           throw persona_error;
650         }
651
652       /* Update our copy of the property. */
653       this._is_favourite = is_favourite;
654       this.notify_property ("is-favourite");
655     }
656
657   private HashSet<string> _groups = new HashSet<string> ();
658   private Set<string> _groups_ro;
659
660   /**
661    * {@inheritDoc}
662    */
663   [CCode (notify = false)]
664   public Set<string> groups
665     {
666       get { return this._groups_ro; }
667       set { this.change_groups.begin (value); }
668     }
669
670   /**
671    * {@inheritDoc}
672    *
673    * @since 0.6.2
674    */
675   public async void change_groups (Set<string> groups) throws PropertyError
676     {
677       debug ("Setting '%s' groups…", this.id);
678
679       PropertyError? persona_error = null;
680       var groups_changed = false;
681
682       /* Try to write it to only the Personas which have "groups" as a
683        * writeable property. */
684       foreach (var p in this._persona_set)
685         {
686           var g = p as GroupDetails;
687           if (g != null && "groups" in p.writeable_properties)
688             {
689               try
690                 {
691                   yield g.change_groups (groups);
692                   debug ("    written to persona '%s'", p.uid);
693                   groups_changed = true;
694                 }
695               catch (PropertyError e)
696                 {
697                   /* Store the first error so we can throw it if setting the
698                    * property fails on every other persona. */
699                   if (persona_error == null)
700                     {
701                       persona_error = e;
702                     }
703                 }
704             }
705         }
706
707       /* Failure? */
708       if (groups_changed == false)
709         {
710           assert (persona_error != null);
711           throw persona_error;
712         }
713
714       /* Update our copy of the property. */
715       this._update_groups ();
716     }
717
718   private HashMultiMap<string, ImFieldDetails> _im_addresses =
719       new HashMultiMap<string, ImFieldDetails> (
720           null, null, ImFieldDetails.hash, (EqualFunc) ImFieldDetails.equal);
721
722   /**
723    * {@inheritDoc}
724    */
725   [CCode (notify = false)]
726   public MultiMap<string, ImFieldDetails> im_addresses
727     {
728       get { return this._im_addresses; }
729       set { this.change_im_addresses.begin (value); } /* not writeable */
730     }
731
732   private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses =
733       new HashMultiMap<string, WebServiceFieldDetails> (null, null,
734           (GLib.HashFunc) WebServiceFieldDetails.hash,
735           (GLib.EqualFunc) WebServiceFieldDetails.equal);
736
737   /**
738    * {@inheritDoc}
739    */
740   [CCode (notify = false)]
741   public MultiMap<string, WebServiceFieldDetails> web_service_addresses
742     {
743       get { return this._web_service_addresses; }
744       /* Not writeable: */
745       set { this.change_web_service_addresses.begin (value); }
746     }
747
748   /**
749    * The set of {@link Persona}s encapsulated by this Individual.
750    *
751    * No order is specified over the set of personas, as such an order may be
752    * different across each of the properties implemented by the personas (e.g.
753    * should they be ordered by presence, name, star sign, etc.?).
754    *
755    * Changing the set of personas may cause updates to the aggregated properties
756    * provided by the Individual, resulting in property notifications for them.
757    *
758    * Changing the set of personas will not cause permanent linking/unlinking of
759    * the added/removed personas to/from this Individual. To do that, call
760    * {@link IndividualAggregator.link_personas} or
761    * {@link IndividualAggregator.unlink_individual}, which will ensure the link
762    * changes are written to the appropriate backend.
763    *
764    * @since 0.5.1
765    */
766   public Set<Persona> personas
767     {
768       get { return this._persona_set_ro; }
769       set { this._set_personas (value, null); }
770     }
771
772   /**
773    * Emitted when one or more {@link Persona}s are added to or removed from
774    * the Individual. As the parameters are (unordered) sets, the orders of their
775    * elements are undefined.
776    *
777    * @param added a set of {@link Persona}s which have been added
778    * @param removed a set of {@link Persona}s which have been removed
779    *
780    * @since 0.5.1
781    */
782   public signal void personas_changed (Set<Persona> added,
783       Set<Persona> removed);
784
785   private void _notify_alias_cb (Object obj, ParamSpec ps)
786     {
787       this._update_alias ();
788     }
789
790   private void _notify_avatar_cb (Object obj, ParamSpec ps)
791     {
792       this._update_avatar ();
793     }
794
795   private void _notify_full_name_cb ()
796     {
797       this._update_full_name ();
798     }
799
800   private void _notify_structured_name_cb ()
801     {
802       this._update_structured_name ();
803     }
804
805   private void _notify_nickname_cb ()
806     {
807       this._update_nickname ();
808     }
809
810   private void _persona_group_changed_cb (string group, bool is_member)
811     {
812       this._update_groups ();
813     }
814
815   private void _notify_gender_cb ()
816     {
817       this._update_gender ();
818     }
819
820   private void _notify_urls_cb ()
821     {
822       this._update_urls ();
823     }
824
825   private void _notify_phone_numbers_cb ()
826     {
827       this._update_phone_numbers ();
828     }
829
830   private void _notify_postal_addresses_cb ()
831     {
832       this._update_postal_addresses ();
833     }
834
835   private void _notify_email_addresses_cb ()
836     {
837       this._update_email_addresses ();
838     }
839
840   private void _notify_roles_cb ()
841     {
842       this._update_roles ();
843     }
844
845   private void _notify_birthday_cb ()
846     {
847       this._update_birthday ();
848     }
849
850   private void _notify_notes_cb ()
851     {
852       this._update_notes ();
853     }
854
855   private void _notify_local_ids_cb ()
856     {
857       this._update_local_ids ();
858     }
859
860   /**
861    * Add or remove the Individual from the specified group.
862    *
863    * If `is_member` is `true`, the Individual will be added to the `group`. If
864    * it is `false`, they will be removed from the `group`.
865    *
866    * The group membership change will propagate to every {@link Persona} in
867    * the Individual.
868    *
869    * @param group a freeform group identifier
870    * @param is_member whether the Individual should be a member of the group
871    * @since 0.1.11
872    */
873   public async void change_group (string group, bool is_member)
874     {
875       foreach (var p in this._persona_set)
876         {
877           if (p is GroupDetails)
878             ((GroupDetails) p).change_group.begin (group, is_member);
879         }
880
881       /* don't notify, since it hasn't happened in the persona backing stores
882        * yet; react to that directly */
883     }
884
885   private void _notify_presence_cb (Object obj, ParamSpec ps)
886     {
887       this._update_presence ();
888     }
889
890   private void _notify_im_addresses_cb (Object obj, ParamSpec ps)
891     {
892       this._update_im_addresses ();
893     }
894
895   private void _notify_web_service_addresses_cb (Object obj, ParamSpec ps)
896     {
897       this._update_web_service_addresses ();
898     }
899
900   private void _notify_is_favourite_cb (Object obj, ParamSpec ps)
901     {
902       this._update_is_favourite ();
903     }
904
905   /**
906    * Create a new Individual.
907    *
908    * The Individual can optionally be seeded with the {@link Persona}s in
909    * `personas`. Otherwise, it will have to have personas added using the
910    * {@link Folks.Individual.personas} property after construction.
911    *
912    * @param personas a list of {@link Persona}s to initialise the
913    * {@link Individual} with, or `null`
914    * @return a new Individual
915    *
916    * @since 0.5.1
917    */
918   public Individual (Set<Persona>? personas)
919     {
920       Object (personas: personas);
921     }
922
923   construct
924     {
925       debug ("Creating new Individual with %u Personas: %p",
926           (this.personas != null ? this.personas.size : 0), this);
927
928       this._persona_set_ro = this._persona_set.read_only_view;
929       this._urls_ro = this._urls.read_only_view;
930       this._phone_numbers_ro = this._phone_numbers.read_only_view;
931       this._email_addresses_ro = this._email_addresses.read_only_view;
932       this._roles_ro = this._roles.read_only_view;
933       this._local_ids_ro = this._local_ids.read_only_view;
934       this._postal_addresses_ro = this._postal_addresses.read_only_view;
935       this._notes_ro = this._notes.read_only_view;
936       this._groups_ro = this._groups.read_only_view;
937     }
938
939   ~Individual ()
940     {
941       debug ("Destroying Individual '%s': %p", this.id, this);
942     }
943
944   /* Emit the personas-changed signal, turning null parameters into empty sets
945    * and ensuring that the signal is emitted with read-only views of the sets
946    * so that signal handlers can't modify the sets. */
947   private void _emit_personas_changed (Set<Persona>? added,
948       Set<Persona>? removed)
949     {
950       var _added = added;
951       var _removed = removed;
952
953       if ((added == null || added.size == 0) &&
954           (removed == null || removed.size == 0))
955         {
956           /* Emitting it with no added or removed personas is pointless */
957           return;
958         }
959       else if (added == null)
960         {
961           _added = new HashSet<Persona> ();
962         }
963       else if (removed == null)
964         {
965           _removed = new HashSet<Persona> ();
966         }
967
968       this.personas_changed (_added.read_only_view, _removed.read_only_view);
969     }
970
971   private void _store_removed_cb (PersonaStore store)
972     {
973       var remaining_personas = new HashSet<Persona> ();
974
975       /* Build a set of the remaining personas (those which weren't in the
976        * removed store. */
977       foreach (var persona in this._persona_set)
978         {
979           if (persona.store != store)
980             {
981               remaining_personas.add (persona);
982             }
983         }
984
985       this._set_personas (remaining_personas, null);
986     }
987
988   private void _store_personas_changed_cb (PersonaStore store,
989       Set<Persona> added,
990       Set<Persona> removed,
991       string? message,
992       Persona? actor,
993       GroupDetails.ChangeReason reason)
994     {
995       var remaining_personas = new HashSet<Persona> ();
996
997       /* Build a set of the remaining personas (those which aren't in the
998        * set of removed personas). */
999       foreach (var persona in this._persona_set)
1000         {
1001           if (!removed.contains (persona))
1002             {
1003               remaining_personas.add (persona);
1004             }
1005         }
1006
1007       this._set_personas (remaining_personas, null);
1008     }
1009
1010   private void _update_fields ()
1011     {
1012       this._update_groups ();
1013       this._update_presence ();
1014       this._update_is_favourite ();
1015       this._update_avatar ();
1016       this._update_alias ();
1017       this._update_trust_level ();
1018       this._update_im_addresses ();
1019       this._update_web_service_addresses ();
1020       this._update_structured_name ();
1021       this._update_full_name ();
1022       this._update_nickname ();
1023       this._update_gender ();
1024       this._update_urls ();
1025       this._update_phone_numbers ();
1026       this._update_email_addresses ();
1027       this._update_roles ();
1028       this._update_birthday ();
1029       this._update_notes ();
1030       this._update_postal_addresses ();
1031       this._update_local_ids ();
1032     }
1033
1034   /* Delegate to update the value of a property on this individual from the
1035    * given chosen persona. The chosen_persona may be null, in which case we have
1036    * to set a default value.
1037    *
1038    * Used in _update_single_valued_property(), below. */
1039   private delegate void SingleValuedPropertySetter (Persona? chosen_persona);
1040
1041   /* Delegate to filter a persona based on whether a given property is set.
1042    *
1043    * Used in _update_single_valued_property(), below. */
1044   private delegate bool PropertyFilter (Persona persona);
1045
1046   /*
1047    * Update a single-valued property from the values in the personas.
1048    *
1049    * Single-valued properties are ones such as {@link Individual.alias} or
1050    * {@link Individual.gender} — as opposed to multi-valued ones (which are
1051    * generally sets) such as {@link Individual.im_addresses} or
1052    * {@link Individual.groups}.
1053    *
1054    * This function uses the given comparison function to order the personas in
1055    * this individual, with the highest-positioned persona (the “greatest”
1056    * persona in the total order) finally being passed to the setter function to
1057    * use in updating the individual's value for the given property. i.e. If
1058    * `compare_func(a, b)` is called and returns > 0, persona `a` will be passed
1059    * to the setter.
1060    *
1061    * At a level above `compare_func`, the function always prefers personas from
1062    * the primary store (see {@link IndividualAggregator.primary_store}) over
1063    * those which aren't.
1064    *
1065    * Note that if a suitable persona isn't found in the individual (if, for
1066    * example, no personas in the individual implement the desired interface),
1067    * `null` will be passed to `setter`, which should then set the individual's
1068    * property to a default value.
1069    *
1070    * @param interface_type the type of interface which all personas under
1071    * consideration must implement ({@link Persona} to select all personas)
1072    * @param compare_func comparison function to order personas for selection
1073    * @param prop_name name of the property being set, as used in
1074    * {@link Persona.writeable_properties}
1075    * @param setter function to update the individual with the chosen value
1076    * @since 0.6.2
1077    */
1078   private void _update_single_valued_property (Type interface_type,
1079       PropertyFilter filter_func,
1080       CompareFunc<Persona> compare_func, string prop_name,
1081       SingleValuedPropertySetter setter)
1082     {
1083       CompareDataFunc<Persona> primary_compare_func = (a, b) =>
1084         {
1085           assert (a != null);
1086           assert (b != null);
1087
1088           /* Always prefer values which are set over those which aren't. */
1089           var a_is_set = filter_func (a);
1090           var b_is_set = filter_func (b);
1091
1092           if (a_is_set != b_is_set)
1093             {
1094               return (a_is_set ? 1 : 0) - (b_is_set ? 1 : 0);
1095             }
1096
1097           var a_is_primary = a.store.is_primary_store;
1098           var b_is_primary = b.store.is_primary_store;
1099
1100           if (a_is_primary != b_is_primary)
1101             {
1102               return (a_is_primary ? 1 : 0) - (b_is_primary ? 1 : 0);
1103             }
1104
1105           /* If both personas have the same is-primary value, prefer personas
1106            * which have the given property as writeable over those which
1107            * don't. */
1108           var a_is_writeable = (prop_name in a.writeable_properties);
1109           var b_is_writeable = (prop_name in b.writeable_properties);
1110
1111           if (a_is_writeable != b_is_writeable)
1112             {
1113               return (a_is_writeable ? 1 : 0) - (b_is_writeable ? 1 : 0);
1114             }
1115
1116           /* If both personas have the same writeability for this property, fall
1117            * back to the given comparison function. If the comparison function
1118            * gives them an equal order, we use the personas' UIDs to ensure that
1119            * we end up with a total order over all personas in the individual
1120            * (otherwise we might end up with unstable property values). */
1121           var order = compare_func (a, b);
1122
1123           if (order == 0)
1124             {
1125               order = strcmp (a.uid, b.uid);
1126             }
1127
1128           return order;
1129         };
1130
1131       Persona? candidate_p = null;
1132
1133       foreach (var p in this._persona_set)
1134         {
1135           /* We only care about personas implementing the given interface. */
1136           if (p.get_type ().is_a (interface_type))
1137             {
1138               if (candidate_p == null ||
1139                   primary_compare_func (p, candidate_p) > 0)
1140                 {
1141                   candidate_p = p;
1142                 }
1143             }
1144         }
1145
1146       /* Update the property with the values from the best candidate persona we
1147        * found. Note that it's possible for candidate_p to be null if (e.g.)
1148        * none of this._persona_set implemented the interface. */
1149       setter (candidate_p);
1150     }
1151
1152   private void _update_groups ()
1153     {
1154       var new_groups = new HashSet<string> ();
1155
1156       /* FIXME: this should partition the personas by store (maybe we should
1157        * keep that mapping in general in this class), and execute
1158        * "groups-changed" on the store (with the set of personas), to allow the
1159        * back-end to optimize it (like Telepathy will for MembersChanged for the
1160        * groups channel list) */
1161       foreach (var p in this._persona_set)
1162         {
1163           if (p is GroupDetails)
1164             {
1165               var persona = (GroupDetails) p;
1166
1167               foreach (var group in persona.groups)
1168                 {
1169                   new_groups.add (group);
1170                 }
1171             }
1172         }
1173
1174       foreach (var group in new_groups)
1175         {
1176           if (!this._groups.contains (group))
1177             {
1178               this._groups.add (group);
1179               foreach (var g in this._groups)
1180                 {
1181                   debug ("   %s", g);
1182                 }
1183
1184               this.group_changed (group, true);
1185             }
1186         }
1187
1188       /* buffer the removals, so we don't remove while iterating */
1189       var removes = new GLib.List<string> ();
1190       foreach (var group in this._groups)
1191         {
1192           if (!new_groups.contains (group))
1193             removes.prepend (group);
1194         }
1195
1196       removes.foreach ((l) =>
1197         {
1198           unowned string group = (string) l;
1199           this._groups.remove (group);
1200           this.group_changed (group, false);
1201         });
1202     }
1203
1204   private void _update_presence ()
1205     {
1206       this._update_single_valued_property (typeof (PresenceDetails), (p) =>
1207         {
1208           return (p as PresenceDetails).presence_type != PresenceType.UNSET;
1209         }, (a, b) =>
1210         {
1211           var a_presence = (a as PresenceDetails).presence_type;
1212           var b_presence = (b as PresenceDetails).presence_type;
1213
1214           return PresenceDetails.typecmp (a_presence, b_presence);
1215         }, "presence", (p) =>
1216         {
1217           var presence_message = ""; /* must not be null */
1218           var presence_status = ""; /* must not be null */
1219           var presence_type = Folks.PresenceType.UNSET;
1220
1221           if (p != null)
1222             {
1223               presence_type = (p as PresenceDetails).presence_type;
1224               presence_message = (p as PresenceDetails).presence_message;
1225               presence_status = (p as PresenceDetails).presence_status;
1226             }
1227
1228           /* Only notify if any of the values have changed. */
1229           if (this.presence_type != presence_type ||
1230               this.presence_message != presence_message ||
1231               this.presence_status != presence_status)
1232             {
1233               this.freeze_notify ();
1234               this.presence_message = presence_message;
1235               this.presence_type = presence_type;
1236               this.presence_status = presence_status;
1237               this.thaw_notify ();
1238             }
1239         });
1240     }
1241
1242   private void _update_is_favourite ()
1243     {
1244       this._update_single_valued_property (typeof (FavouriteDetails), (p) =>
1245         {
1246           return true;
1247         }, (a, b) =>
1248         {
1249           var a_is_favourite = (a as FavouriteDetails).is_favourite;
1250           var b_is_favourite = (b as FavouriteDetails).is_favourite;
1251
1252           return ((a_is_favourite == true) ? 1 : 0) -
1253                  ((b_is_favourite == true) ? 1 : 0);
1254         }, "is-favourite", (p) =>
1255         {
1256           var favourite = false;
1257
1258           if (p != null)
1259             {
1260               favourite = (p as FavouriteDetails).is_favourite;
1261             }
1262
1263           /* Only notify if the value has changed. We have to set the private
1264            * member and notify manually, or we'd end up propagating the new
1265            * favourite status back down to all our Personas. */
1266           if (this._is_favourite != favourite)
1267             {
1268               this._is_favourite = favourite;
1269               this.notify_property ("is-favourite");
1270             }
1271         });
1272     }
1273
1274   private void _update_alias ()
1275     {
1276       this._update_single_valued_property (typeof (AliasDetails), (p) =>
1277         {
1278           var alias = (p as AliasDetails).alias;
1279           assert (alias != null);
1280
1281           return (alias.strip () != ""); /* empty aliases are unset */
1282         }, (a, b) =>
1283         {
1284           var a_alias = (a as AliasDetails).alias;
1285           var b_alias = (b as AliasDetails).alias;
1286
1287           assert (a_alias != null);
1288           assert (b_alias != null);
1289
1290           var a_is_empty = (a_alias.strip () == "") ? 1 : 0;
1291           var b_is_empty = (b_alias.strip () == "") ? 1 : 0;
1292
1293           /* We prefer to not have an alias which is the same as the Persona's
1294            * display-id, since having such an alias implies that it's the
1295            * default. However, we prefer using such an alias to using the
1296            * Persona's UID, which is our ultimate fallback (below). */
1297           var a_is_display_id = (a_alias == a.display_id) ? 1 : 0;
1298           var b_is_display_id = (b_alias == b.display_id) ? 1 : 0;
1299
1300           return (b_is_empty + b_is_display_id) -
1301                  (a_is_empty + a_is_display_id);
1302         }, "alias", (p) =>
1303         {
1304           string alias = ""; /* must not be null */
1305
1306           if (p != null)
1307             {
1308               alias = (p as AliasDetails).alias.strip ();
1309             }
1310
1311           /* Only notify if the value has changed. We have to set the private
1312            * member and notify manually, or we'd end up propagating the new
1313            * alias back down to all our Personas, even if it's a fallback
1314            * display ID or something else undesirable. */
1315           if (this._alias != alias)
1316             {
1317               this._alias = alias;
1318               this.notify_property ("alias");
1319             }
1320         });
1321     }
1322
1323   private void _update_avatar ()
1324     {
1325       this._update_single_valued_property (typeof (AvatarDetails), (p) =>
1326         {
1327           return (p as AvatarDetails).avatar != null;
1328         }, (a, b) =>
1329         {
1330           /* We can't compare two set avatars efficiently. See: bgo#652721. */
1331           return 0;
1332         }, "avatar", (p) =>
1333         {
1334           LoadableIcon? avatar = null;
1335
1336           if (p != null)
1337             {
1338               avatar = (p as AvatarDetails).avatar;
1339             }
1340
1341           /* only notify if the value has changed */
1342           if ((this._avatar == null && avatar != null) ||
1343               (this._avatar != null &&
1344                (avatar == null || !this._avatar.equal (avatar))))
1345             {
1346               this._avatar = avatar;
1347               this.notify_property ("avatar");
1348             }
1349         });
1350     }
1351
1352   private void _update_trust_level ()
1353     {
1354       var trust_level = TrustLevel.PERSONAS;
1355
1356       foreach (var p in this._persona_set)
1357         {
1358           if (p.is_user == false &&
1359               p.store.trust_level == PersonaStoreTrust.NONE)
1360             trust_level = TrustLevel.NONE;
1361         }
1362
1363       /* Only notify if the value has changed */
1364       if (this.trust_level != trust_level)
1365         this.trust_level = trust_level;
1366     }
1367
1368   private void _update_im_addresses ()
1369     {
1370       /* populate the IM addresses as the union of our Personas' addresses */
1371       this._im_addresses.clear ();
1372
1373       foreach (var persona in this._persona_set)
1374         {
1375           if (persona is ImDetails)
1376             {
1377               var im_details = (ImDetails) persona;
1378               foreach (var cur_protocol in im_details.im_addresses.get_keys ())
1379                 {
1380                   var cur_addresses =
1381                       im_details.im_addresses.get (cur_protocol);
1382
1383                   foreach (var address in cur_addresses)
1384                     {
1385                       this._im_addresses.set (cur_protocol, address);
1386                     }
1387                 }
1388             }
1389         }
1390       this.notify_property ("im-addresses");
1391     }
1392
1393   private void _update_web_service_addresses ()
1394     {
1395       /* populate the web service addresses as the union of our Personas' addresses */
1396       this._web_service_addresses.clear ();
1397
1398       foreach (var persona in this.personas)
1399         {
1400           if (persona is WebServiceDetails)
1401             {
1402               var web_service_details = (WebServiceDetails) persona;
1403               foreach (var cur_web_service in
1404                   web_service_details.web_service_addresses.get_keys ())
1405                 {
1406                   var cur_addresses =
1407                       web_service_details.web_service_addresses.get (
1408                           cur_web_service);
1409
1410                   foreach (var ws_fd in cur_addresses)
1411                     this._web_service_addresses.set (cur_web_service, ws_fd);
1412                 }
1413             }
1414         }
1415       this.notify_property ("web-service-addresses");
1416     }
1417
1418   private void _connect_to_persona (Persona persona)
1419     {
1420       persona.individual = this;
1421
1422       persona.notify["alias"].connect (this._notify_alias_cb);
1423       persona.notify["avatar"].connect (this._notify_avatar_cb);
1424       persona.notify["presence-message"].connect (this._notify_presence_cb);
1425       persona.notify["presence-type"].connect (this._notify_presence_cb);
1426       persona.notify["im-addresses"].connect (this._notify_im_addresses_cb);
1427       persona.notify["web-service-addresses"].connect
1428               (this._notify_web_service_addresses_cb);
1429       persona.notify["is-favourite"].connect (this._notify_is_favourite_cb);
1430       persona.notify["structured-name"].connect (
1431           this._notify_structured_name_cb);
1432       persona.notify["full-name"].connect (this._notify_full_name_cb);
1433       persona.notify["nickname"].connect (this._notify_nickname_cb);
1434       persona.notify["gender"].connect (this._notify_gender_cb);
1435       persona.notify["urls"].connect (this._notify_urls_cb);
1436       persona.notify["phone-numbers"].connect (this._notify_phone_numbers_cb);
1437       persona.notify["email-addresses"].connect (
1438           this._notify_email_addresses_cb);
1439       persona.notify["roles"].connect (this._notify_roles_cb);
1440       persona.notify["birthday"].connect (this._notify_birthday_cb);
1441       persona.notify["notes"].connect (this._notify_notes_cb);
1442       persona.notify["postal-addresses"].connect
1443           (this._notify_postal_addresses_cb);
1444       persona.notify["local-ids"].connect
1445           (this._notify_local_ids_cb);
1446
1447
1448       if (persona is GroupDetails)
1449         {
1450           ((GroupDetails) persona).group_changed.connect (
1451               this._persona_group_changed_cb);
1452         }
1453     }
1454
1455   private void _update_structured_name ()
1456     {
1457       this._update_single_valued_property (typeof (NameDetails), (p) =>
1458         {
1459           var name = (p as NameDetails).structured_name;
1460           return (name != null && !name.is_empty ());
1461         }, (a, b) =>
1462         {
1463           /* Can't compare two set names. */
1464           return 0;
1465         }, "structured-name", (p) =>
1466         {
1467           StructuredName? name = null;
1468
1469           if (p != null)
1470             {
1471               name = (p as NameDetails).structured_name;
1472
1473               if (name != null && name.is_empty ())
1474                 {
1475                   name = null;
1476                 }
1477             }
1478
1479           if ((this._structured_name == null && name != null) ||
1480               (this._structured_name != null &&
1481                (name == null || !this._structured_name.equal (name))))
1482             {
1483               this._structured_name = name;
1484               this.notify_property ("structured-name");
1485             }
1486         });
1487     }
1488
1489   private void _update_full_name ()
1490     {
1491       this._update_single_valued_property (typeof (NameDetails), (p) =>
1492         {
1493           var name = (p as NameDetails).full_name;
1494           assert (name != null);
1495
1496           return (name.strip () != ""); /* empty names are unset */
1497         }, (a, b) =>
1498         {
1499           /* Can't compare two set names. */
1500           return 0;
1501         }, "full-name", (p) =>
1502         {
1503           string new_full_name = ""; /* must not be null */
1504
1505           if (p != null)
1506             {
1507               new_full_name = (p as NameDetails).full_name.strip ();
1508             }
1509
1510           if (new_full_name != this._full_name)
1511             {
1512               this._full_name = new_full_name;
1513               this.notify_property ("full-name");
1514             }
1515         });
1516     }
1517
1518   private void _update_nickname ()
1519     {
1520       this._update_single_valued_property (typeof (NameDetails), (p) =>
1521         {
1522           var nickname = (p as NameDetails).nickname;
1523           assert (nickname != null);
1524
1525           return (nickname.strip () != ""); /* empty names are unset */
1526         }, (a, b) =>
1527         {
1528           /* Can't compare two set names. */
1529           return 0;
1530         }, "nickname", (p) =>
1531         {
1532           string new_nickname = ""; /* must not be null */
1533
1534           if (p != null)
1535             {
1536               new_nickname = (p as NameDetails).nickname.strip ();
1537             }
1538
1539           if (new_nickname != this._nickname)
1540             {
1541               this._nickname = new_nickname;
1542               this.notify_property ("nickname");
1543             }
1544         });
1545     }
1546
1547   private void _disconnect_from_persona (Persona persona,
1548       Individual? replacement_individual)
1549     {
1550       persona.notify["alias"].disconnect (this._notify_alias_cb);
1551       persona.notify["avatar"].disconnect (this._notify_avatar_cb);
1552       persona.notify["presence-message"].disconnect (
1553           this._notify_presence_cb);
1554       persona.notify["presence-type"].disconnect (this._notify_presence_cb);
1555       persona.notify["im-addresses"].disconnect (
1556           this._notify_im_addresses_cb);
1557       persona.notify["web-service-addresses"].disconnect (
1558           this._notify_web_service_addresses_cb);
1559       persona.notify["is-favourite"].disconnect (
1560           this._notify_is_favourite_cb);
1561       persona.notify["structured-name"].disconnect (
1562           this._notify_structured_name_cb);
1563       persona.notify["full-name"].disconnect (this._notify_full_name_cb);
1564       persona.notify["nickname"].disconnect (this._notify_nickname_cb);
1565       persona.notify["gender"].disconnect (this._notify_gender_cb);
1566       persona.notify["urls"].disconnect (this._notify_urls_cb);
1567       persona.notify["phone-numbers"].disconnect (
1568           this._notify_phone_numbers_cb);
1569       persona.notify["email-addresses"].disconnect (
1570           this._notify_email_addresses_cb);
1571       persona.notify["roles"].disconnect (this._notify_roles_cb);
1572       persona.notify["birthday"].disconnect (this._notify_birthday_cb);
1573       persona.notify["notes"].disconnect (this._notify_notes_cb);
1574       persona.notify["postal-addresses"].disconnect
1575           (this._notify_postal_addresses_cb);
1576       persona.notify["local-ids"].disconnect (this._notify_local_ids_cb);
1577
1578
1579       if (persona is GroupDetails)
1580         {
1581           ((GroupDetails) persona).group_changed.disconnect (
1582               this._persona_group_changed_cb);
1583         }
1584
1585       /* Don't update the individual if the persona's been added to the new one
1586        * already (and thus the new individual has already changed
1587        * persona.individual).
1588        *
1589        * FIXME: Ideally, we'd assert that a persona can't be added to a new
1590        * individual before it's removed from the old one. However, this
1591        * currently isn't possible due to the way the aggregator works. When the
1592        * aggregator's rewritten, it would be nice to fix this. */
1593       if (persona.individual == this)
1594         {
1595           /* It may be the case that the persona's being removed from the
1596            * individual (i.e. the replacement individual is non-null, but
1597            * doesn't contain this persona). In this case, we need to set the
1598            * persona's individual to null. */
1599           if (replacement_individual != null &&
1600               persona in replacement_individual.personas)
1601             {
1602               persona.individual = replacement_individual;
1603             }
1604           else
1605             {
1606               persona.individual = null;
1607             }
1608         }
1609     }
1610
1611   private void _update_gender ()
1612     {
1613       this._update_single_valued_property (typeof (GenderDetails), (p) =>
1614         {
1615           return (p as GenderDetails).gender != Gender.UNSPECIFIED;
1616         }, (a, b) =>
1617         {
1618           /* It would be sexist to rank one gender over another.
1619            * Besides, how often will we see two personas in the same individual
1620            * which have different genders? */
1621           return 0;
1622         }, "gender", (p) =>
1623         {
1624           var new_gender = Gender.UNSPECIFIED;
1625
1626           if (p != null)
1627             {
1628               new_gender = (p as GenderDetails).gender;
1629             }
1630
1631           if (new_gender != this.gender)
1632             {
1633               this._gender = new_gender;
1634               this.notify_property ("gender");
1635             }
1636         });
1637     }
1638
1639   private void _update_urls ()
1640     {
1641       /* Populate the URLs as the union of our Personas' URLs.
1642        * If the same URL exists multiple times we merge the parameters. */
1643       var urls_set = new HashMap<unowned string, unowned UrlFieldDetails> (
1644           null, null, (GLib.EqualFunc) UrlFieldDetails.equal);
1645
1646       this._urls.clear ();
1647
1648       foreach (var persona in this._persona_set)
1649         {
1650           var url_details = persona as UrlDetails;
1651           if (url_details != null)
1652             {
1653               foreach (var url_fd in url_details.urls)
1654                 {
1655                   if (url_fd.value == null)
1656                     continue;
1657
1658                   var existing = urls_set.get (url_fd.value);
1659                   if (existing != null)
1660                     existing.extend_parameters (url_fd.parameters);
1661                   else
1662                     {
1663                       var new_url_fd = new UrlFieldDetails (url_fd.value);
1664                       new_url_fd.extend_parameters (url_fd.parameters);
1665                       urls_set.set (url_fd.value, new_url_fd);
1666                       this._urls.add (new_url_fd);
1667                     }
1668                 }
1669             }
1670         }
1671
1672       this.notify_property ("urls");
1673     }
1674
1675   private void _update_phone_numbers ()
1676     {
1677       /* Populate the phone numbers as the union of our Personas' numbers
1678        * If the same number exists multiple times we merge the parameters. */
1679       var phone_numbers_set =
1680           new HashMap<unowned string, unowned PhoneFieldDetails> (
1681               null, null, (GLib.EqualFunc) PhoneFieldDetails.equal);
1682
1683       this._phone_numbers.clear ();
1684
1685       foreach (var persona in this._persona_set)
1686         {
1687           var phone_details = persona as PhoneDetails;
1688           if (phone_details != null)
1689             {
1690               foreach (var phone_fd in phone_details.phone_numbers)
1691                 {
1692                   if (phone_fd.value == null)
1693                     continue;
1694
1695                   var existing = phone_numbers_set.get (phone_fd.value);
1696                   if (existing != null)
1697                     existing.extend_parameters (phone_fd.parameters);
1698                   else
1699                     {
1700                       var new_fd = new PhoneFieldDetails (phone_fd.value);
1701                       new_fd.extend_parameters (phone_fd.parameters);
1702                       phone_numbers_set.set (phone_fd.value, new_fd);
1703                       this._phone_numbers.add (new_fd);
1704                     }
1705                 }
1706             }
1707         }
1708
1709       this.notify_property ("phone-numbers");
1710     }
1711
1712   private void _update_email_addresses ()
1713     {
1714       /* Populate the email addresses as the union of our Personas' addresses.
1715        * If the same address exists multiple times we merge the parameters. */
1716       var emails_set = new HashMap<unowned string, unowned EmailFieldDetails> (
1717           null, null, (GLib.EqualFunc) EmailFieldDetails.equal);
1718
1719       this._email_addresses.clear ();
1720
1721       foreach (var persona in this._persona_set)
1722         {
1723           var email_details = persona as EmailDetails;
1724           if (email_details != null)
1725             {
1726               foreach (var email_fd in email_details.email_addresses)
1727                 {
1728                   if (email_fd.value == null)
1729                     continue;
1730
1731                   var existing = emails_set.get (email_fd.value);
1732                   if (existing != null)
1733                     existing.extend_parameters (email_fd.parameters);
1734                   else
1735                     {
1736                       var new_email_fd = new EmailFieldDetails (email_fd.value,
1737                           email_fd.parameters);
1738                       emails_set.set (email_fd.value, new_email_fd);
1739                       this._email_addresses.add (new_email_fd);
1740                     }
1741                 }
1742             }
1743         }
1744
1745       this.notify_property ("email-addresses");
1746     }
1747
1748   private void _update_roles ()
1749     {
1750       this._roles.clear ();
1751
1752       foreach (var persona in this._persona_set)
1753         {
1754           var role_details = persona as RoleDetails;
1755           if (role_details != null)
1756             {
1757               foreach (var role_fd in role_details.roles)
1758                 {
1759                   this._roles.add (role_fd);
1760                 }
1761             }
1762         }
1763
1764       this.notify_property ("roles");
1765     }
1766
1767   private void _update_local_ids ()
1768     {
1769       this._local_ids.clear ();
1770
1771       foreach (var persona in this._persona_set)
1772         {
1773           var local_ids_details = persona as LocalIdDetails;
1774           if (local_ids_details != null)
1775             {
1776               foreach (var id in local_ids_details.local_ids)
1777                 {
1778                   this._local_ids.add (id);
1779                 }
1780             }
1781         }
1782
1783       this.notify_property ("local-ids");
1784     }
1785
1786   private void _update_postal_addresses ()
1787     {
1788       this._postal_addresses.clear ();
1789
1790       /* FIXME: Detect duplicates somehow? */
1791       foreach (var persona in this._persona_set)
1792         {
1793           var address_details = persona as PostalAddressDetails;
1794           if (address_details != null)
1795             {
1796               foreach (var pafd in address_details.postal_addresses)
1797                 this._postal_addresses.add (pafd);
1798             }
1799         }
1800
1801       this.notify_property ("postal-addresses");
1802     }
1803
1804   private void _update_birthday ()
1805     {
1806       this._update_single_valued_property (typeof (BirthdayDetails), (p) =>
1807         {
1808           var details = (p as BirthdayDetails);
1809           return details.birthday != null && details.calendar_event_id != null;
1810         }, (a, b) =>
1811         {
1812           var a_birthday = (a as BirthdayDetails).birthday;
1813           var b_birthday = (b as BirthdayDetails).birthday;
1814           var a_event_id = (a as BirthdayDetails).calendar_event_id;
1815           var b_event_id = (b as BirthdayDetails).calendar_event_id;
1816
1817           var a_birthday_is_set = (a_birthday != null) ? 1 : 0;
1818           var b_birthday_is_set = (b_birthday != null) ? 1 : 0;
1819
1820           /* We consider the empty string as “set” because it's an opaque ID. */
1821           var a_event_id_is_set = (a_event_id != null) ? 1 : 0;
1822           var b_event_id_is_set = (b_event_id != null) ? 1 : 0;
1823
1824           /* Prefer personas which have both properties set over those who have
1825            * only one set. We don't consider the case where the birthdays from
1826            * different personas don't match, because that's just scary. */
1827           return (a_birthday_is_set + a_event_id_is_set) -
1828                  (b_birthday_is_set + b_event_id_is_set);
1829         }, "birthday", (p) =>
1830         {
1831           unowned DateTime? bday = null;
1832           unowned string? calendar_event_id = null;
1833
1834           if (p != null)
1835             {
1836               bday = (p as BirthdayDetails).birthday;
1837               calendar_event_id = (p as BirthdayDetails).calendar_event_id;
1838             }
1839
1840           if ((this._birthday == null && bday != null) ||
1841               (this._birthday != null &&
1842                (bday == null || !this._birthday.equal (bday))) ||
1843               (this._calendar_event_id != calendar_event_id))
1844             {
1845               this._birthday = bday;
1846               this._calendar_event_id = calendar_event_id;
1847
1848               this.freeze_notify ();
1849               this.notify_property ("birthday");
1850               this.notify_property ("calendar-event-id");
1851               this.thaw_notify ();
1852             }
1853         });
1854     }
1855
1856   private void _update_notes ()
1857     {
1858       this._notes.clear ();
1859
1860       foreach (var persona in this._persona_set)
1861         {
1862           var note_details = persona as NoteDetails;
1863           if (note_details != null)
1864             {
1865               foreach (var n in note_details.notes)
1866                 {
1867                   this._notes.add (n);
1868                 }
1869             }
1870         }
1871
1872       this.notify_property ("notes");
1873     }
1874
1875   private void _set_personas (Set<Persona>? personas,
1876       Individual? replacement_individual)
1877     {
1878       assert (replacement_individual == null || replacement_individual != this);
1879
1880       var added = new HashSet<Persona> ();
1881       var removed = new HashSet<Persona> ();
1882
1883       /* Determine which Personas have been added. If personas == null, we
1884        * assume it's an empty set. */
1885       if (personas != null)
1886         {
1887           foreach (var p in personas)
1888             {
1889               if (!this._persona_set.contains (p))
1890                 {
1891                   /* Keep track of how many Personas are users */
1892                   if (p.is_user)
1893                     this._persona_user_count++;
1894
1895                   added.add (p);
1896
1897                   this._persona_set.add (p);
1898                   this._connect_to_persona (p);
1899
1900                   /* Increment the Persona count for this PersonaStore */
1901                   var store = p.store;
1902                   var num_from_store = this._stores.get (store);
1903                   if (num_from_store == 0)
1904                     {
1905                       this._stores.set (store, num_from_store + 1);
1906                     }
1907                   else
1908                     {
1909                       this._stores.set (store, 1);
1910
1911                       store.removed.connect (this._store_removed_cb);
1912                       store.personas_changed.connect (
1913                           this._store_personas_changed_cb);
1914                     }
1915                 }
1916             }
1917         }
1918
1919       /* Determine which Personas have been removed */
1920       var iter = this._persona_set.iterator ();
1921       while (iter.next ())
1922         {
1923           var p = iter.get ();
1924
1925           if (personas == null || !personas.contains (p))
1926             {
1927               /* Keep track of how many Personas are users */
1928               if (p.is_user)
1929                 this._persona_user_count--;
1930
1931               removed.add (p);
1932
1933               /* Decrement the Persona count for this PersonaStore */
1934               var store = p.store;
1935               var num_from_store = this._stores.get (store);
1936               if (num_from_store > 1)
1937                 {
1938                   this._stores.set (store, num_from_store - 1);
1939                 }
1940               else
1941                 {
1942                   store.removed.disconnect (this._store_removed_cb);
1943                   store.personas_changed.disconnect (
1944                       this._store_personas_changed_cb);
1945
1946                   this._stores.unset (store);
1947                 }
1948
1949               this._disconnect_from_persona (p, replacement_individual);
1950               iter.remove ();
1951             }
1952         }
1953
1954       this._emit_personas_changed (added, removed);
1955
1956       /* Update this.is_user */
1957       var new_is_user = (this._persona_user_count > 0) ? true : false;
1958       if (new_is_user != this.is_user)
1959         this.is_user = new_is_user;
1960
1961       /* If all the Personas have been removed, remove the Individual */
1962       if (this._persona_set.size < 1)
1963         {
1964           this.removed (replacement_individual);
1965           return;
1966         }
1967
1968       /* Update the ID. We choose the most interesting Persona in the
1969        * Individual and hash their UID. This is guaranteed to be globally
1970        * unique, and may not change (for one of the two Individuals) if we link
1971        * two Individuals together, which is nice though we can't rely on this
1972        * behaviour.
1973        *
1974        * This method of constructing an ID ensures that it'll be unique and
1975        * stable for a given Individual once the IndividualAggregator reaches
1976        * a quiescent state after startup. It guarantees that the ID will be
1977        * the same every time folks is used, until the Individual is linked
1978        * or unlinked to another Individual.
1979        *
1980        * We choose the most interesting Persona by ranking all the Personas
1981        * in the Individual by:
1982        *  1. store.is-primary-store
1983        *  2. store.trust-level
1984        *  3. store.id (alphabetically)
1985        *
1986        * Note that this heuristic shouldn't be changed without careful thought,
1987        * since stored references to IDs may be broken by the change.
1988        */
1989       if (this._persona_set.size > 0)
1990         {
1991           Persona? chosen_persona = null;
1992
1993           foreach (var persona in this._persona_set)
1994             {
1995               if (chosen_persona == null ||
1996                   (chosen_persona.store.is_primary_store == false &&
1997                       persona.store.is_primary_store == true) ||
1998                   (chosen_persona.store.is_primary_store ==
1999                           persona.store.is_primary_store &&
2000                       chosen_persona.store.trust_level >
2001                           persona.store.trust_level) ||
2002                   (chosen_persona.store.is_primary_store ==
2003                           persona.store.is_primary_store &&
2004                       chosen_persona.store.trust_level ==
2005                           persona.store.trust_level &&
2006                       chosen_persona.store.id > persona.store.id)
2007                  )
2008                {
2009                  chosen_persona = persona;
2010                }
2011             }
2012
2013           // Hash the chosen persona's UID
2014           this.id = Checksum.compute_for_string (ChecksumType.SHA1,
2015               chosen_persona.uid);
2016         }
2017
2018       /* Update our aggregated fields and notify the changes */
2019       this._update_fields ();
2020     }
2021
2022   internal void replace (Individual replacement_individual)
2023     {
2024       this._set_personas (null, replacement_individual);
2025     }
2026 }