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