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