7fb026d2d4adf21cc24b26eb5356ebbb0907ae4b
[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. An
66  * individual must always contain at least one {@link Persona}.
67  *
68  * When choosing the values of single-valued properties (such as
69  * {@link Individual.alias} and {@link Individual.avatar}; but not multi-valued
70  * properties such as {@link Individual.groups} and
71  * {@link Individual.im_addresses}) from the {@link Persona}s in the
72  * individual to present as the values of those properties of the individual,
73  * it is guaranteed that if the individual contains a persona from the primary
74  * persona store (see {@link IndividualAggregator.primary_store}), its property
75  * values will be chosen above all others. This means that any changes to
76  * property values made through folks (which are normally written to the primary
77  * store) will always be used by {@link Folks.Individual}s.
78  *
79  * No further guarantees are made about the order of preference used for
80  * choosing which property values to use for the {@link Folks.Individual}, other
81  * than that the order may vary between properties, but is guaranteed to be
82  * stable for a given property.
83  */
84 public class Folks.Individual : Object,
85     AliasDetails,
86     AvatarDetails,
87     BirthdayDetails,
88     EmailDetails,
89     FavouriteDetails,
90     GenderDetails,
91     GroupDetails,
92     ImDetails,
93     InteractionDetails,
94     LocalIdDetails,
95     NameDetails,
96     NoteDetails,
97     PresenceDetails,
98     PhoneDetails,
99     PostalAddressDetails,
100     RoleDetails,
101     UrlDetails,
102     WebServiceDetails
103 {
104   /* Stores the Personas contained in this Individual. */
105   private HashSet<Persona> _persona_set =
106       new HashSet<Persona> ();
107   /* Read-only view of the above set */
108   private Set<Persona> _persona_set_ro;
109   /* Mapping from PersonaStore -> number of Personas from that store contained
110    * in this Individual. There shouldn't be any entries with a number < 1.
111    * This is used for working out when to disconnect from store signals. */
112   private HashMap<unowned PersonaStore, uint> _stores =
113       new HashMap<unowned PersonaStore, uint> (null, null);
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
118   /**
119    * The trust level of the Individual.
120    *
121    * This specifies how far the Individual can be trusted to be who it claims
122    * to be. See the descriptions for the elements of {@link TrustLevel}.
123    *
124    * Clients should ''not'' allow linking of Individuals who have a trust level
125    * of {@link TrustLevel.NONE}.
126    *
127    * @since 0.1.15
128    */
129   public TrustLevel trust_level { get; private set; }
130
131   private LoadableIcon? _avatar = null;
132
133   /**
134    * {@inheritDoc}
135    *
136    * @since 0.6.0
137    */
138   [CCode (notify = false)]
139   public LoadableIcon? avatar
140     {
141       get { return this._avatar; }
142       set { this.change_avatar.begin (value); } /* not writeable */
143     }
144
145   /*
146    * Change the individual's avatar.
147    *
148    * It's preferred to call this rather than setting {@link Individual.avatar}
149    * directly, as this method gives error notification and will only return once
150    * the avatar has been written to the relevant backing stores (or the
151    * operation's failed).
152    *
153    * Setting this property is only guaranteed to succeed (and be written to
154    * the backing store) if
155    * {@link IndividualAggregator.ensure_individual_property_writeable} has been
156    * called successfully on the individual for the property name ``avatar``.
157    *
158    * @param avatar the new avatar (or ``null`` to unset the avatar)
159    * @throws PropertyError if setting the avatar failed
160    * @since 0.6.3
161    */
162   public async void change_avatar (LoadableIcon? avatar) throws PropertyError
163     {
164       /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
165        * this should be rewritten to use async delegates passed to a generic
166        * _change_single_valued_property() method. */
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 prop_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)
184             {
185               continue;
186             }
187           var a = (!) _a;
188
189           if ("avatar" in p.writeable_properties)
190             {
191               try
192                 {
193                   yield a.change_avatar (avatar);
194                   debug ("    written to writeable persona '%s'", p.uid);
195                   prop_changed = true;
196                 }
197               catch (PropertyError e)
198                 {
199                   /* Store the first error so we can throw it if setting the
200                    * avatar fails on every other persona. */
201                   if (persona_error == null)
202                     {
203                       persona_error = e;
204                     }
205                 }
206             }
207         }
208
209       /* Failure? Changing the property failed on every suitable persona found
210        * (and potentially zero suitable personas were found). */
211       if (prop_changed == false)
212         {
213           if (persona_error == null)
214             {
215               persona_error = new PropertyError.NOT_WRITEABLE (
216                   _("Failed to change property ‘%s’: No suitable personas were found."),
217                   "avatar");
218             }
219
220           throw persona_error;
221         }
222     }
223
224   /**
225    * {@inheritDoc}
226    */
227   public Folks.PresenceType presence_type { get; set; }
228
229   /**
230    * {@inheritDoc}
231    *
232    * @since 0.6.0
233    */
234   public string presence_status { get; set; }
235
236   /**
237    * {@inheritDoc}
238    */
239   public string presence_message { get; set; }
240
241   /**
242    * Whether the Individual is the user.
243    *
244    * Iff the Individual represents the user – the person who owns the
245    * account in the backend for each {@link Persona} in the Individual –
246    * this is ``true``.
247    *
248    * It is //not// guaranteed that every {@link Persona} in the Individual has
249    * its {@link Persona.is_user} set to the same value as the Individual. For
250    * example, the user could own two Telepathy accounts, and have added the
251    * other account as a contact in each account. The accounts will expose a
252    * {@link Persona} for the user (which will have {@link Persona.is_user} set
253    * to ``true``) //and// a {@link Persona} for the contact for the other
254    * account (which will have {@link Persona.is_user} set to ``false``).
255    *
256    * It is guaranteed that iff this property is set to ``true`` on an
257    * Individual, there will be at least one {@link Persona} in the Individual
258    * with its {@link Persona.is_user} set to ``true``.
259    *
260    * It is guaranteed that there will only ever be one Individual with this
261    * property set to ``true``.
262    *
263    * @since 0.3.0
264    */
265   public bool is_user { get; private set; }
266
267   /**
268    * A unique identifier for the Individual.
269    *
270    * This uniquely identifies the Individual, and persists across
271    * {@link IndividualAggregator} instances. It may not persist across linking
272    * the Individual with other Individuals.
273    *
274    * This is an opaque string and has no structure.
275    *
276    * If an identifier is required which will be used for a long-lived link
277    * between different stored data, it may be more desirable to use the
278    * {@link Persona.uid} of the most relevant {@link Persona} in the Individual
279    * instead. For example, if storing references to Individuals who are tagged
280    * in a photo, it may be safer to store the UID of the Persona whose backend
281    * provided the photo (e.g. Facebook).
282    */
283   public string id { get; private set; }
284
285   /**
286    * Emitted when the last of the Individual's {@link Persona}s has been
287    * removed.
288    *
289    * At this point, the Individual is invalid, so any client referencing it
290    * should unreference it and remove it from their UI.
291    *
292    * @param replacement_individual the individual which has replaced this one
293    * due to linking, or ``null`` if this individual was removed for another
294    * reason
295    * @since 0.1.13
296    */
297   public signal void removed (Individual? replacement_individual);
298
299   private string _alias = "";
300
301   /**
302    * {@inheritDoc}
303    */
304   [CCode (notify = false)]
305   public string alias
306     {
307       get { return this._alias; }
308       set { this.change_alias.begin (value); }
309     }
310
311   /**
312    * {@inheritDoc}
313    *
314    * @since 0.6.2
315    */
316   public async void change_alias (string alias) throws PropertyError
317     {
318       /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
319        * this should be rewritten to use async delegates passed to a generic
320        * _change_single_valued_property() method. */
321       if (this._alias == alias)
322         {
323           return;
324         }
325
326       debug ("Setting alias of individual '%s' to '%s'…", this.id, alias);
327
328       PropertyError? persona_error = null;
329       var prop_changed = false;
330
331       /* Try to write it to only the writeable Personas which have "alias"
332        * as a writeable property. */
333       foreach (var p in this._persona_set)
334         {
335           var _a = p as AliasDetails;
336           if (_a == null)
337             {
338               continue;
339             }
340           var a = (!) _a;
341
342           if ("alias" in p.writeable_properties)
343             {
344               try
345                 {
346                   yield a.change_alias (alias);
347                   debug ("    written to writeable persona '%s'", p.uid);
348                   prop_changed = true;
349                 }
350               catch (PropertyError e)
351                 {
352                   /* Store the first error so we can throw it if setting the
353                    * alias fails on every other persona. */
354                   if (persona_error == null)
355                     {
356                       persona_error = e;
357                     }
358                 }
359             }
360         }
361
362       /* Failure? Changing the property failed on every suitable persona found
363        * (and potentially zero suitable personas were found). */
364       if (prop_changed == false)
365         {
366           if (persona_error == null)
367             {
368               persona_error = new PropertyError.NOT_WRITEABLE (
369                   _("Failed to change property ‘%s’: No suitable personas were found."),
370                   "alias");
371             }
372
373           throw persona_error;
374         }
375     }
376
377   private StructuredName? _structured_name = null;
378
379   /**
380    * {@inheritDoc}
381    */
382   [CCode (notify = false)]
383   public StructuredName? structured_name
384     {
385       get { return this._structured_name; }
386       set { this.change_structured_name.begin (value); } /* not writeable */
387     }
388
389   private string _full_name = "";
390
391   /**
392    * {@inheritDoc}
393    */
394   [CCode (notify = false)]
395   public string full_name
396     {
397       get { return this._full_name; }
398       set { this.change_full_name.begin (value); } /* not writeable */
399     }
400
401   private string _nickname = "";
402
403   /**
404    * {@inheritDoc}
405    */
406   [CCode (notify = false)]
407   public string nickname
408     {
409       get { return this._nickname; }
410       set { this.change_nickname.begin (value); }
411     }
412
413   /**
414    * {@inheritDoc}
415    *
416    * @since 0.6.2
417    */
418   public async void change_nickname (string nickname) throws PropertyError
419     {
420       /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
421        * this should be rewritten to use async delegates passed to a generic
422        * _change_single_valued_property() method. */
423
424       // Normalise null values to the empty string
425       if (nickname == null)
426         {
427           nickname = "";
428         }
429
430       if (this._nickname == nickname)
431         {
432           return;
433         }
434
435       debug ("Setting nickname of individual '%s' to '%s'…", this.id, nickname);
436
437       PropertyError? persona_error = null;
438       var prop_changed = false;
439
440       /* Try to write it to only the writeable Personas which have "nickname"
441        * as a writeable property. */
442       foreach (var p in this._persona_set)
443         {
444           var _n = p as NameDetails;
445           if (_n == null)
446             {
447               continue;
448             }
449           var n = (!) _n;
450
451           if ("nickname" in p.writeable_properties)
452             {
453               try
454                 {
455                   yield n.change_nickname (nickname);
456                   debug ("    written to writeable persona '%s'", p.uid);
457                   prop_changed = true;
458                 }
459               catch (PropertyError e)
460                 {
461                   /* Store the first error so we can throw it if setting the
462                    * nickname fails on every other persona. */
463                   if (persona_error == null)
464                     {
465                       persona_error = e;
466                     }
467                 }
468             }
469         }
470
471       /* Failure? Changing the property failed on every suitable persona found
472        * (and potentially zero suitable personas were found). */
473       if (prop_changed == false)
474         {
475           if (persona_error == null)
476             {
477               persona_error = new PropertyError.NOT_WRITEABLE (
478                   _("Failed to change property ‘%s’: No suitable personas were found."),
479                   "nickname");
480             }
481
482           throw persona_error;
483         }
484     }
485
486   private Gender _gender = Gender.UNSPECIFIED;
487   /**
488    * {@inheritDoc}
489    */
490   [CCode (notify = false)]
491   public Gender gender
492     {
493       get { return this._gender; }
494       set { this.change_gender.begin (value); } /* not writeable */
495     }
496
497   private HashSet<UrlFieldDetails>? _urls = null;
498   private Set<UrlFieldDetails>? _urls_ro = null;
499
500   /**
501    * {@inheritDoc}
502    */
503   [CCode (notify = false)]
504   public Set<UrlFieldDetails> urls
505     {
506       get
507         {
508           this._update_urls (true, false, false);
509           return this._urls_ro;
510         }
511       set { this.change_urls.begin (value); } /* not writeable */
512     }
513
514   private HashSet<PhoneFieldDetails>? _phone_numbers = null;
515   private Set<PhoneFieldDetails>? _phone_numbers_ro = null;
516
517   /**
518    * {@inheritDoc}
519    */
520   [CCode (notify = false)]
521   public Set<PhoneFieldDetails> phone_numbers
522     {
523       get
524         {
525           this._update_phone_numbers (true, false, false);
526           return this._phone_numbers_ro;
527         }
528       set { this.change_phone_numbers.begin (value); } /* not writeable */
529     }
530
531   private HashSet<EmailFieldDetails>? _email_addresses = null;
532   private Set<EmailFieldDetails>? _email_addresses_ro = null;
533
534   /**
535    * {@inheritDoc}
536    */
537   [CCode (notify = false)]
538   public Set<EmailFieldDetails> email_addresses
539     {
540       get
541         {
542           this._update_email_addresses (true, false, false);
543           return this._email_addresses_ro;
544         }
545       set { this.change_email_addresses.begin (value); } /* not writeable */
546     }
547
548   private HashSet<RoleFieldDetails>? _roles = null;
549   private Set<RoleFieldDetails>? _roles_ro = null;
550
551   /**
552    * {@inheritDoc}
553    */
554   [CCode (notify = false)]
555   public Set<RoleFieldDetails> roles
556     {
557       get
558         {
559           this._update_roles (true, false, false);
560           return this._roles_ro;
561         }
562       set { this.change_roles.begin (value); } /* not writeable */
563     }
564
565   private HashSet<string>? _local_ids = null;
566   private Set<string>? _local_ids_ro = null;
567
568   /**
569    * {@inheritDoc}
570    */
571   [CCode (notify = false)]
572   public Set<string> local_ids
573     {
574       get
575         {
576           this._update_local_ids (true, false, false);
577           return this._local_ids_ro;
578         }
579       set { this.change_local_ids.begin (value); } /* not writeable */
580     }
581
582   private DateTime? _birthday = null;
583
584   /**
585    * {@inheritDoc}
586    */
587   [CCode (notify = false)]
588   public DateTime? birthday
589     {
590       get { return this._birthday; }
591       set { this.change_birthday.begin (value); } /* not writeable */
592     }
593
594   private string? _calendar_event_id = null;
595
596   /**
597    * {@inheritDoc}
598    */
599   [CCode (notify = false)]
600   public string? calendar_event_id
601     {
602       get { return this._calendar_event_id; }
603       set { this.change_calendar_event_id.begin (value); } /* not writeable */
604     }
605
606   private HashSet<NoteFieldDetails>? _notes = null;
607   private Set<NoteFieldDetails>? _notes_ro = null;
608
609   /**
610    * {@inheritDoc}
611    */
612   [CCode (notify = false)]
613   public Set<NoteFieldDetails> notes
614     {
615       get
616         {
617           this._update_notes (true, false, false);
618           return this._notes_ro;
619         }
620       set { this.change_notes.begin (value); } /* not writeable */
621     }
622
623   private HashSet<PostalAddressFieldDetails>? _postal_addresses = null;
624   private Set<PostalAddressFieldDetails>? _postal_addresses_ro = null;
625
626   /**
627    * {@inheritDoc}
628    */
629   [CCode (notify = false)]
630   public Set<PostalAddressFieldDetails> postal_addresses
631     {
632       get
633         {
634           this._update_postal_addresses (true, false, false);
635           return this._postal_addresses_ro;
636         }
637       set { this.change_postal_addresses.begin (value); } /* not writeable */
638     }
639
640   private bool _is_favourite = false;
641
642   /**
643    * Whether this Individual is a user-defined favourite.
644    *
645    * This property is ``true`` if any of this Individual's {@link Persona}s are
646    * favourites).
647    */
648   [CCode (notify = false)]
649   public bool is_favourite
650     {
651       get { return this._is_favourite; }
652       set { this.change_is_favourite.begin (value); }
653     }
654
655   /**
656    * {@inheritDoc}
657    *
658    * @since 0.6.2
659    */
660   public async void change_is_favourite (bool is_favourite) throws PropertyError
661     {
662       /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
663        * this should be rewritten to use async delegates passed to a generic
664        * _change_single_valued_property() method. */
665       if (this._is_favourite == is_favourite)
666         {
667           return;
668         }
669
670       debug ("Setting '%s' favourite status to %s…", this.id,
671         is_favourite ? "TRUE" : "FALSE");
672
673       PropertyError? persona_error = null;
674       var prop_changed = false;
675
676       /* Try to write it to only the Personas which have "is-favourite" as a
677        * writeable property.
678        *
679        * NOTE: We don't check whether the persona's store is writeable, as we
680        * want is-favourite status to propagate to all stores, if possible. This
681        * is one property which is harmless to propagate. */
682       foreach (var p in this._persona_set)
683         {
684           var _a = p as FavouriteDetails;
685           if (_a == null)
686             {
687               continue;
688             }
689           var a = (!) _a;
690
691           if ("is-favourite" in p.writeable_properties)
692             {
693               try
694                 {
695                   yield a.change_is_favourite (is_favourite);
696                   debug ("    written to persona '%s'", p.uid);
697                   prop_changed = true;
698                 }
699               catch (PropertyError e)
700                 {
701                   /* Store the first error so we can throw it if setting the
702                    * property fails on every other persona. */
703                   if (persona_error == null)
704                     {
705                       persona_error = e;
706                     }
707                 }
708             }
709         }
710
711       /* Failure? Changing the property failed on every suitable persona found
712        * (and potentially zero suitable personas were found). */
713       if (prop_changed == false)
714         {
715           if (persona_error == null)
716             {
717               persona_error = new PropertyError.NOT_WRITEABLE (
718                   _("Failed to change property ‘%s’: No suitable personas were found."),
719                   "is-favourite");
720             }
721
722           throw persona_error;
723         }
724     }
725
726   private HashSet<string>? _groups = null;
727   private Set<string>? _groups_ro = null;
728
729   /**
730    * {@inheritDoc}
731    */
732   [CCode (notify = false)]
733   public Set<string> groups
734     {
735       get
736         {
737           this._update_groups (true, false, false);
738           return this._groups_ro;
739         }
740       set { this.change_groups.begin (value); }
741     }
742
743   /**
744    * {@inheritDoc}
745    *
746    * @since 0.6.2
747    */
748   public async void change_groups (Set<string> groups) throws PropertyError
749     {
750       /* FIXME: Once https://bugzilla.gnome.org/show_bug.cgi?id=604827 is fixed,
751        * this should be rewritten to use async delegates passed to a generic
752        * _change_single_valued_property() method. */
753       debug ("Setting '%s' groups…", this.id);
754
755       PropertyError? persona_error = null;
756       var prop_changed = false;
757
758       /* Try to write it to only the Personas which have "groups" as a
759        * writeable property. */
760       foreach (var p in this._persona_set)
761         {
762           var _g = p as GroupDetails;
763           if (_g == null)
764             {
765               continue;
766             }
767           var g = (!) _g;
768
769           if ("groups" in p.writeable_properties)
770             {
771               try
772                 {
773                   yield g.change_groups (groups);
774                   debug ("    written to persona '%s'", p.uid);
775                   prop_changed = true;
776                 }
777               catch (PropertyError e)
778                 {
779                   /* Store the first error so we can throw it if setting the
780                    * property fails on every other persona. */
781                   if (persona_error == null)
782                     {
783                       persona_error = e;
784                     }
785                 }
786             }
787         }
788
789       /* Failure? Changing the property failed on every suitable persona found
790        * (and potentially zero suitable personas were found). */
791       if (prop_changed == false)
792         {
793           if (persona_error == null)
794             {
795               persona_error = new PropertyError.NOT_WRITEABLE (
796                   _("Failed to change property ‘%s’: No suitable personas were found."),
797                   "groups");
798             }
799
800           throw persona_error;
801         }
802     }
803
804   private HashMultiMap<string, ImFieldDetails>? _im_addresses = null;
805
806   /**
807    * {@inheritDoc}
808    */
809   [CCode (notify = false)]
810   public MultiMap<string, ImFieldDetails> im_addresses
811     {
812       get
813         {
814           this._update_im_addresses (true, false, false);
815           return this._im_addresses;
816         }
817       set { this.change_im_addresses.begin (value); } /* not writeable */
818     }
819
820   private HashMultiMap<string, WebServiceFieldDetails>? _web_service_addresses =
821       null;
822
823   /**
824    * {@inheritDoc}
825    */
826   [CCode (notify = false)]
827   public MultiMap<string, WebServiceFieldDetails> web_service_addresses
828     {
829       get
830         {
831           this._update_web_service_addresses (true, false, false);
832           return this._web_service_addresses;
833         }
834       /* Not writeable: */
835       set { this.change_web_service_addresses.begin (value); }
836     }
837
838   /**
839    * {@inheritDoc}
840    */
841   public uint im_interaction_count
842     {
843       get
844         {
845           uint counter = 0;
846           /* Iterate over all personas and sum up their IM interaction counts*/
847           foreach (var persona in this._persona_set)
848             {
849               var my_interaction_details = persona as InteractionDetails;
850               if (my_interaction_details != null)
851                 {
852                   counter = counter + my_interaction_details.im_interaction_count;
853                 }
854             }
855           return counter;
856         }
857     }
858
859   /**
860    * {@inheritDoc}
861    */
862   private DateTime? _last_im_interaction_datetime = null;
863
864   public DateTime? last_im_interaction_datetime
865     {
866       get
867         {
868           if (this._last_im_interaction_datetime == null)
869             {
870               /* Iterate over all personas and get the latest IM interaction datetime */
871               foreach (var persona in this._persona_set)
872                 {
873                   var my_interaction_details = persona as InteractionDetails;
874                   if (my_interaction_details != null &&
875                       my_interaction_details.last_im_interaction_datetime != null)
876                     {
877                       DateTime interaction_datetime = my_interaction_details.last_im_interaction_datetime;
878                       if (this._last_im_interaction_datetime == null ||
879                           interaction_datetime.compare (this._last_im_interaction_datetime) == 1)
880                         {
881                           this._last_im_interaction_datetime = my_interaction_details.last_im_interaction_datetime;
882                         }
883                     }
884                 }
885             }
886           return this._last_im_interaction_datetime;
887         }
888     }
889
890   /**
891    * {@inheritDoc}
892    */
893   public uint call_interaction_count
894     {
895       get
896         {
897           uint counter = 0;
898           /* Iterate over all personas and sum up their call interaction counts*/
899           foreach (var persona in this._persona_set)
900             {
901               var my_interaction_details = persona as InteractionDetails;
902               if (my_interaction_details != null)
903                 {
904                   counter = counter + my_interaction_details.call_interaction_count;
905                 }
906             }
907           return counter;
908         }
909     }
910
911   /**
912    * {@inheritDoc}
913    */
914   private DateTime? _last_call_interaction_datetime = null;
915
916   public DateTime? last_call_interaction_datetime
917     {
918       get
919         {
920           if (this._last_call_interaction_datetime == null)
921             {
922               /* Iterate over all personas and get the latest IM interaction datetime */
923               foreach (var persona in this._persona_set)
924                 {
925                   var my_interaction_details = persona as InteractionDetails;
926                   if (my_interaction_details != null &&
927                       my_interaction_details.last_call_interaction_datetime != null)
928                     {
929                       var interaction_datetime = my_interaction_details.last_call_interaction_datetime;
930                       if (this._last_call_interaction_datetime == null ||
931                           interaction_datetime.compare (this._last_call_interaction_datetime) > 1)
932                         {
933                           this._last_call_interaction_datetime = my_interaction_details.last_call_interaction_datetime;
934                         }
935                     }
936                 }
937             }
938           return this._last_call_interaction_datetime;
939         }
940     }
941
942   /**
943    * The set of {@link Persona}s encapsulated by this Individual.
944    *
945    * There must always be at least one Persona in this set.
946    *
947    * No order is specified over the set of personas, as such an order may be
948    * different across each of the properties implemented by the personas (e.g.
949    * should they be ordered by presence, name, star sign, etc.?).
950    *
951    * Changing the set of personas may cause updates to the aggregated properties
952    * provided by the Individual, resulting in property notifications for them.
953    *
954    * Changing the set of personas will not cause permanent linking/unlinking of
955    * the added/removed personas to/from this Individual. To do that, call
956    * {@link IndividualAggregator.link_personas} or
957    * {@link IndividualAggregator.unlink_individual}, which will ensure the link
958    * changes are written to the appropriate backend.
959    *
960    * @since 0.5.1
961    */
962   public Set<Persona> personas
963     {
964       get { return this._persona_set_ro; }
965       set { this._set_personas (value, null); }
966     }
967
968   /**
969    * Emitted when one or more {@link Persona}s are added to or removed from
970    * the Individual. As the parameters are (unordered) sets, the orders of their
971    * elements are undefined.
972    *
973    * @param added a set of {@link Persona}s which have been added
974    * @param removed a set of {@link Persona}s which have been removed
975    *
976    * @since 0.5.1
977    */
978   public signal void personas_changed (Set<Persona> added,
979       Set<Persona> removed);
980
981   private void _notify_alias_cb (Object obj, ParamSpec ps)
982     {
983       this._update_alias ();
984     }
985
986   private void _notify_avatar_cb (Object obj, ParamSpec ps)
987     {
988       this._update_avatar ();
989     }
990
991   private void _notify_full_name_cb ()
992     {
993       this._update_full_name ();
994     }
995
996   private void _notify_structured_name_cb ()
997     {
998       this._update_structured_name ();
999     }
1000
1001   private void _notify_nickname_cb ()
1002     {
1003       this._update_nickname ();
1004     }
1005
1006   private void _persona_group_changed_cb (string group, bool is_member)
1007     {
1008       this._update_groups (false);
1009     }
1010
1011   private void _notify_gender_cb ()
1012     {
1013       this._update_gender ();
1014     }
1015
1016   private void _notify_urls_cb ()
1017     {
1018       this._update_urls (false);
1019     }
1020
1021   private void _notify_phone_numbers_cb ()
1022     {
1023       this._update_phone_numbers (false);
1024     }
1025
1026   private void _notify_postal_addresses_cb ()
1027     {
1028       this._update_postal_addresses (false);
1029     }
1030
1031   private void _notify_email_addresses_cb ()
1032     {
1033       this._update_email_addresses (false);
1034     }
1035
1036   private void _notify_roles_cb ()
1037     {
1038       this._update_roles (false);
1039     }
1040
1041   private void _notify_birthday_cb ()
1042     {
1043       this._update_birthday ();
1044     }
1045
1046   private void _notify_notes_cb ()
1047     {
1048       this._update_notes (false);
1049     }
1050
1051   private void _notify_local_ids_cb ()
1052     {
1053       this._update_local_ids (false);
1054     }
1055
1056   /**
1057    * Add or remove the Individual from the specified group.
1058    *
1059    * If ``is_member`` is ``true``, the Individual will be added to the
1060    * ``group``. If it is ``false``, they will be removed from the ``group``.
1061    *
1062    * The group membership change will propagate to every {@link Persona} in
1063    * the Individual.
1064    *
1065    * @param group a freeform group identifier
1066    * @param is_member whether the Individual should be a member of the group
1067    * @since 0.1.11
1068    */
1069   public async void change_group (string group, bool is_member)
1070     {
1071       foreach (var p in this._persona_set)
1072         {
1073           if (p is GroupDetails)
1074             ((GroupDetails) p).change_group.begin (group, is_member);
1075         }
1076
1077       /* don't notify, since it hasn't happened in the persona backing stores
1078        * yet; react to that directly */
1079     }
1080
1081   private void _notify_presence_cb (Object obj, ParamSpec ps)
1082     {
1083       this._update_presence ();
1084     }
1085
1086   private void _notify_im_addresses_cb (Object obj, ParamSpec ps)
1087     {
1088       this._update_im_addresses (false);
1089     }
1090
1091   private void _notify_web_service_addresses_cb (Object obj, ParamSpec ps)
1092     {
1093       this._update_web_service_addresses (false);
1094     }
1095
1096   private void _notify_is_favourite_cb (Object obj, ParamSpec ps)
1097     {
1098       this._update_is_favourite ();
1099     }
1100
1101   private void _notify_im_interaction_count_cb (Object obj, ParamSpec ps)
1102     {
1103       /**
1104        * The property is pull rather than push. This function is called in
1105        * response to personas emitting a similar notification.
1106        */
1107       this.notify_property ("im-interaction-count");
1108     }
1109
1110   private void _notify_call_interaction_count_cb (Object obj, ParamSpec ps)
1111     {
1112       /**
1113        * The property is pull rather than push. This function is called in
1114        * response to personas emitting a similar notification.
1115        */
1116       this.notify_property ("call-interaction-count");
1117     }
1118
1119   private void _notify_last_im_interaction_datetime_cb (Object obj, ParamSpec ps)
1120     {
1121       /**
1122        * The property is pull rather than push. This function is called in
1123        * response to personas emitting a similar notification.
1124        */
1125       this._last_im_interaction_datetime = null;
1126       this.notify_property ("last-im-interaction-datetime");
1127     }
1128
1129   private void _notify_last_call_interaction_datetime_cb (Object obj, ParamSpec ps)
1130     {
1131       /**
1132        * The property is pull rather than push. This function is called in
1133        * response to personas emitting a similar notification.
1134        */
1135       this._last_call_interaction_datetime = null;
1136       this.notify_property ("last-call-interaction-datetime");
1137     }
1138
1139   /**
1140    * Create a new Individual.
1141    *
1142    * The Individual can optionally be seeded with the {@link Persona}s in
1143    * ``personas``. Otherwise, it will have to have personas added using the
1144    * {@link Folks.Individual.personas} property after construction.
1145    *
1146    * @param personas a list of {@link Persona}s to initialise the
1147    * {@link Folks.Individual} with, or ``null``
1148    * @return a new Individual
1149    *
1150    * @since 0.5.1
1151    */
1152   public Individual (Set<Persona>? personas)
1153     {
1154       Object (personas: personas);
1155
1156       debug ("Creating new Individual with %u Personas: %p",
1157           this._persona_set.size, this);
1158     }
1159
1160   construct
1161     {
1162       this._persona_set_ro = this._persona_set.read_only_view;
1163     }
1164
1165   ~Individual ()
1166     {
1167       debug ("Destroying Individual '%s': %p", this.id, this);
1168     }
1169
1170   /* Emit the personas-changed signal, turning null parameters into empty sets
1171    * and ensuring that the signal is emitted with read-only views of the sets
1172    * so that signal handlers can't modify the sets. */
1173   private void _emit_personas_changed (Set<Persona>? added,
1174       Set<Persona>? removed)
1175     {
1176       var _added = added;
1177       var _removed = removed;
1178
1179       if ((added == null || ((!) added).size == 0) &&
1180           (removed == null || ((!) removed).size == 0))
1181         {
1182           /* Emitting it with no added or removed personas is pointless */
1183           return;
1184         }
1185       else if (added == null)
1186         {
1187           _added = new HashSet<Persona> ();
1188         }
1189       else if (removed == null)
1190         {
1191           _removed = new HashSet<Persona> ();
1192         }
1193
1194       // We've now guaranteed that both _added and _removed are non-null.
1195       this.personas_changed (((!) _added).read_only_view,
1196           ((!) _removed).read_only_view);
1197     }
1198
1199   private void _store_removed_cb (PersonaStore store)
1200     {
1201       var remaining_personas = new HashSet<Persona> ();
1202
1203       /* Build a set of the remaining personas (those which weren't in the
1204        * removed store. */
1205       foreach (var persona in this._persona_set)
1206         {
1207           if (persona.store != store)
1208             {
1209               remaining_personas.add (persona);
1210             }
1211         }
1212
1213       this._set_personas (remaining_personas, null);
1214     }
1215
1216   private void _store_personas_changed_cb (PersonaStore store,
1217       Set<Persona> added,
1218       Set<Persona> removed,
1219       string? message,
1220       Persona? actor,
1221       GroupDetails.ChangeReason reason)
1222     {
1223       var remaining_personas = new HashSet<Persona> ();
1224
1225       /* Build a set of the remaining personas (those which aren't in the
1226        * set of removed personas). */
1227       foreach (var persona in this._persona_set)
1228         {
1229           if (!removed.contains (persona))
1230             {
1231               remaining_personas.add (persona);
1232             }
1233         }
1234
1235       this._set_personas (remaining_personas, null);
1236     }
1237
1238   private void _update_fields ()
1239     {
1240       this._update_groups (false);
1241       this._update_presence ();
1242       this._update_is_favourite ();
1243       this._update_avatar ();
1244       this._update_alias ();
1245       this._update_trust_level ();
1246       this._update_im_addresses (false);
1247       this._update_web_service_addresses (false);
1248       this._update_structured_name ();
1249       this._update_full_name ();
1250       this._update_nickname ();
1251       this._update_gender ();
1252       this._update_urls (false);
1253       this._update_phone_numbers (false);
1254       this._update_email_addresses (false);
1255       this._update_roles (false);
1256       this._update_birthday ();
1257       this._update_notes (false);
1258       this._update_postal_addresses (false);
1259       this._update_local_ids (false);
1260     }
1261
1262   /* Delegate to update the value of a property on this individual from the
1263    * given chosen persona. The chosen_persona may be null, in which case we have
1264    * to set a default value.
1265    *
1266    * Used in _update_single_valued_property(), below. */
1267   private delegate void SingleValuedPropertySetter (Persona? chosen_persona);
1268
1269   /* Delegate to filter a persona based on whether a given property is set.
1270    *
1271    * Used in _update_single_valued_property(), below. */
1272   private delegate bool PropertyFilter (Persona persona);
1273
1274   /*
1275    * Update a single-valued property from the values in the personas.
1276    *
1277    * Single-valued properties are ones such as {@link Individual.alias} or
1278    * {@link Individual.gender} — as opposed to multi-valued ones (which are
1279    * generally sets) such as {@link Individual.im_addresses} or
1280    * {@link Individual.groups}.
1281    *
1282    * This function uses the given comparison function to order the personas in
1283    * this individual, with the highest-positioned persona (the “greatest”
1284    * persona in the total order) finally being passed to the setter function to
1285    * use in updating the individual's value for the given property. i.e. If
1286    * ``compare_func(a, b)`` is called and returns > 0, persona ``a`` will be
1287    * passed to the setter.
1288    *
1289    * At a level above ``compare_func``, the function always prefers personas
1290    * from the primary store (see {@link IndividualAggregator.primary_store})
1291    * over those which aren't.
1292    *
1293    * Note that if a suitable persona isn't found in the individual (if, for
1294    * example, no personas in the individual implement the desired interface),
1295    * ``null`` will be passed to ``setter``, which should then set the
1296    * individual's property to a default value.
1297    *
1298    * @param interface_type the type of interface which all personas under
1299    * consideration must implement ({@link Persona} to select all personas)
1300    * @param compare_func comparison function to order personas for selection
1301    * @param prop_name name of the property being set, as used in
1302    * {@link Persona.writeable_properties}
1303    * @param setter function to update the individual with the chosen value
1304    * @since 0.6.2
1305    */
1306   private void _update_single_valued_property (Type interface_type,
1307       PropertyFilter filter_func,
1308       CompareFunc<Persona> compare_func, string prop_name,
1309       SingleValuedPropertySetter setter)
1310     {
1311       CompareDataFunc<Persona> primary_compare_func = (a, b) =>
1312         {
1313           return_val_if_fail (a != null, 0);
1314           return_val_if_fail (b != null, 0);
1315
1316           /* Always prefer values which are set over those which aren't. */
1317           var a_is_set = filter_func (a);
1318           var b_is_set = filter_func (b);
1319
1320           if (a_is_set != b_is_set)
1321             {
1322               return (a_is_set ? 1 : 0) - (b_is_set ? 1 : 0);
1323             }
1324
1325           var a_is_primary = a.store.is_primary_store;
1326           var b_is_primary = b.store.is_primary_store;
1327
1328           if (a_is_primary != b_is_primary)
1329             {
1330               return (a_is_primary ? 1 : 0) - (b_is_primary ? 1 : 0);
1331             }
1332
1333           /* If both personas have the same is-primary value, prefer personas
1334            * which have the given property as writeable over those which
1335            * don't. */
1336           var a_is_writeable = (prop_name in a.writeable_properties);
1337           var b_is_writeable = (prop_name in b.writeable_properties);
1338
1339           if (a_is_writeable != b_is_writeable)
1340             {
1341               return (a_is_writeable ? 1 : 0) - (b_is_writeable ? 1 : 0);
1342             }
1343
1344           /* If both personas have the same writeability for this property, fall
1345            * back to the given comparison function. If the comparison function
1346            * gives them an equal order, we use the personas' UIDs to ensure that
1347            * we end up with a total order over all personas in the individual
1348            * (otherwise we might end up with unstable property values). */
1349           var order = compare_func (a, b);
1350
1351           if (order == 0)
1352             {
1353               order = strcmp (a.uid, b.uid);
1354             }
1355
1356           return order;
1357         };
1358
1359       Persona? candidate_p = null;
1360
1361       foreach (var p in this._persona_set)
1362         {
1363           /* We only care about personas implementing the given interface. */
1364           if (p.get_type ().is_a (interface_type))
1365             {
1366               if (candidate_p == null ||
1367                   primary_compare_func (p, (!) candidate_p) > 0)
1368                 {
1369                   candidate_p = p;
1370                 }
1371             }
1372         }
1373
1374       /* Update the property with the values from the best candidate persona we
1375        * found. Note that it's possible for candidate_p to be null if (e.g.)
1376        * none of this._persona_set implemented the interface. */
1377       setter (candidate_p);
1378     }
1379
1380   /* Delegate to add the values of a property from all personas to the
1381    * collection of values for that property in this individual.
1382    *
1383    * Used in _update_multi_valued_property(), below. */
1384   private delegate bool MultiValuedPropertySetter ();
1385
1386   /* Delegate to get whether a multi-valued property in this Individual has not
1387    * been initialised yet (and is thus still null).
1388    *
1389    * Used in _update_multi_valued_property(), below. */
1390   private delegate bool PropertyIsNull ();
1391
1392   /* Delegate to create a new empty collection for a multi-valued property in
1393    * this Individual and assign it to the property.
1394    *
1395    * Used in _update_multi_valued_property(), below. */
1396   private delegate void CollectionCreator ();
1397
1398   /*
1399    * Update a multi-valued property from the values in the personas.
1400    *
1401    * Multi-valued properties are ones such as {@link Individual.notes} or
1402    * {@link Individual.email_addresses} which have multiple values taken as the
1403    * union of the values listed by the personas for those properties.
1404    *
1405    * This function handles lazy instantiation of the multi-valued property. If
1406    * ``create_if_not_exist`` is ``true``, the property is guaranteed to be
1407    * created (by ``create_collection``) and set to a non-``null`` value before
1408    * this function returns.
1409    *
1410    * If ``create_if_not_exist`` is ``false``, however, the property may not be
1411    * instantiated if it hasn't already been accessed through its property
1412    * getter. In this case, a change notification will be emitted for the
1413    * property and this function will return immediately.
1414    *
1415    * If ``force_update`` is ``true``, then existing values get updated (if
1416    * the current value is different) or created (according to the
1417    * ``create_if_not_exist`` value). Otherwise the function only ensures
1418    * that there is a value (if ``create_if_not_exist`` is set) and leaves
1419    * existing values unchanged.
1420    *
1421    * If the property value is to be instantiated, or already has been
1422    * instantiated, its value is updated by ``setter`` from the values of the
1423    * property in the individual's personas.
1424    *
1425    * @param prop_name name of the property being set, as used in
1426    * {@link Persona.writeable_properties}
1427    * @param create_if_not_exist ``true`` to ensure the property is non-null;
1428    * ``false`` otherwise
1429    * @param prop_is_null function returning ``true`` iff the property is
1430    * currently ``null``
1431    * @param create_collection function creating a new collection/container for
1432    * the property values and assigning it to the property (and updating the
1433    * property's read-only view as necessary)
1434    * @param setter function which adds the values from the individual's
1435    * personas' values for the property to the individual's value for the
1436    * property; it returns ``true`` if the property value has changed
1437    * @since 0.7.4
1438    */
1439   private void _update_multi_valued_property (string prop_name,
1440       bool create_if_not_exist, PropertyIsNull prop_is_null,
1441       CollectionCreator create_collection, MultiValuedPropertySetter setter,
1442       bool emit_notification = true,
1443       bool force_update = true)
1444     {
1445       /* If the set of values doesn't exist, and we're not meant to lazily
1446        * create it, then simply emit a notification (since the set might've
1447        * changed — we can't be sure, but emitting is a safe over-estimate) and
1448        * return. */
1449       bool created = false;
1450       if (prop_is_null ())
1451         {
1452           /* Notify and return. */
1453           if (create_if_not_exist == false)
1454             {
1455               if (emit_notification)
1456                 {
1457                   this.notify_property (prop_name);
1458                 }
1459               return;
1460             }
1461
1462           /* Lazily instantiate the set of IM addresses. */
1463           create_collection ();
1464           created = true;
1465         }
1466
1467       /* Re-populate the collection as the union of the values in the
1468        * individual's personas. Do this when an empty property was just
1469        * created or we were asked to explicitly (usually because the caller
1470        * knows that the current value is out-dated).
1471        */
1472       if ((created || force_update) && setter () == true && emit_notification)
1473         {
1474           this.notify_property (prop_name);
1475         }
1476     }
1477
1478   private void _update_groups (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
1479     {
1480       /* If the set of groups doesn't exist, and we're not meant to lazily
1481        * create it, then simply emit a notification (since the set might've
1482        * changed — we can't be sure, but emitting is a safe over-estimate) and
1483        * return. */
1484       bool created = false;
1485       if (this._groups == null && create_if_not_exist == false)
1486         {
1487           if (emit_notification)
1488             {
1489               this.notify_property ("groups");
1490             }
1491           return;
1492         }
1493
1494       /* Lazily instantiate the set of groups. */
1495       else if (this._groups == null)
1496         {
1497           this._groups = new HashSet<string> ();
1498           this._groups_ro = this._groups.read_only_view;
1499           created = true;
1500         }
1501
1502       /* Don't touch existing content in get(). */
1503       if (!created && !force_update)
1504          return;
1505
1506       var new_groups = new HashSet<string> ();
1507
1508       /* FIXME: this should partition the personas by store (maybe we should
1509        * keep that mapping in general in this class), and execute
1510        * "groups-changed" on the store (with the set of personas), to allow the
1511        * back-end to optimize it (like Telepathy will for MembersChanged for the
1512        * groups channel list) */
1513       foreach (var p in this._persona_set)
1514         {
1515           if (p is GroupDetails)
1516             {
1517               var persona = (GroupDetails) p;
1518
1519               foreach (var group in persona.groups)
1520                 {
1521                   new_groups.add (group);
1522                 }
1523             }
1524         }
1525
1526       foreach (var group in new_groups)
1527         {
1528           if (this._groups.add (group) && emit_notification)
1529             {
1530               this.group_changed (group, true);
1531             }
1532         }
1533
1534       /* buffer the removals, so we don't remove while iterating */
1535       var removes = new GLib.List<string> ();
1536       foreach (var group in this._groups)
1537         {
1538           if (!new_groups.contains (group))
1539             removes.prepend (group);
1540         }
1541
1542       removes.foreach ((l) =>
1543         {
1544           unowned string group = (string) l;
1545           this._groups.remove (group);
1546           if (emit_notification)
1547             {
1548               this.group_changed (group, false);
1549             }
1550         });
1551     }
1552
1553   private void _update_presence ()
1554     {
1555       this._update_single_valued_property (typeof (PresenceDetails), (p) =>
1556         {
1557           return ((PresenceDetails) p).presence_type != PresenceType.UNSET;
1558         }, (a, b) =>
1559         {
1560           var a_presence = ((PresenceDetails) a).presence_type;
1561           var b_presence = ((PresenceDetails) b).presence_type;
1562
1563           return PresenceDetails.typecmp (a_presence, b_presence);
1564         }, "presence", (p) =>
1565         {
1566           var presence_message = ""; /* must not be null */
1567           var presence_status = ""; /* must not be null */
1568           var presence_type = Folks.PresenceType.UNSET;
1569
1570           if (p != null)
1571             {
1572               presence_type = ((PresenceDetails) p).presence_type;
1573               presence_message = ((PresenceDetails) p).presence_message;
1574               presence_status = ((PresenceDetails) p).presence_status;
1575             }
1576
1577           /* Only notify if any of the values have changed. */
1578           if (this.presence_type != presence_type ||
1579               this.presence_message != presence_message ||
1580               this.presence_status != presence_status)
1581             {
1582               this.freeze_notify ();
1583               this.presence_message = presence_message;
1584               this.presence_type = presence_type;
1585               this.presence_status = presence_status;
1586               this.thaw_notify ();
1587             }
1588         });
1589     }
1590
1591   private void _update_is_favourite ()
1592     {
1593       this._update_single_valued_property (typeof (FavouriteDetails), (p) =>
1594         {
1595           return true;
1596         }, (a, b) =>
1597         {
1598           var a_is_favourite = ((FavouriteDetails) a).is_favourite;
1599           var b_is_favourite = ((FavouriteDetails) b).is_favourite;
1600
1601           return ((a_is_favourite == true) ? 1 : 0) -
1602                  ((b_is_favourite == true) ? 1 : 0);
1603         }, "is-favourite", (p) =>
1604         {
1605           var favourite = false;
1606
1607           if (p != null)
1608             {
1609               favourite = ((FavouriteDetails) p).is_favourite;
1610             }
1611
1612           /* Only notify if the value has changed. We have to set the private
1613            * member and notify manually, or we'd end up propagating the new
1614            * favourite status back down to all our Personas. */
1615           if (this._is_favourite != favourite)
1616             {
1617               this._is_favourite = favourite;
1618               this.notify_property ("is-favourite");
1619             }
1620         });
1621     }
1622
1623   private void _update_alias ()
1624     {
1625       this._update_single_valued_property (typeof (AliasDetails), (p) =>
1626         {
1627           var alias = ((AliasDetails) p).alias;
1628           return_val_if_fail (alias != null, false);
1629
1630           return (alias.strip () != ""); /* empty aliases are unset */
1631         }, (a, b) =>
1632         {
1633           var a_alias = ((AliasDetails) a).alias;
1634           var b_alias = ((AliasDetails) b).alias;
1635
1636           return_val_if_fail (a_alias != null, 0);
1637           return_val_if_fail (b_alias != null, 0);
1638
1639           var a_is_empty = (a_alias.strip () == "") ? 1 : 0;
1640           var b_is_empty = (b_alias.strip () == "") ? 1 : 0;
1641
1642           /* We prefer to not have an alias which is the same as the Persona's
1643            * display-id, since having such an alias implies that it's the
1644            * default. However, we prefer using such an alias to using the
1645            * Persona's UID, which is our ultimate fallback (below). */
1646           var a_is_display_id = (a_alias == a.display_id) ? 1 : 0;
1647           var b_is_display_id = (b_alias == b.display_id) ? 1 : 0;
1648
1649           return (b_is_empty + b_is_display_id) -
1650                  (a_is_empty + a_is_display_id);
1651         }, "alias", (p) =>
1652         {
1653           string alias = ""; /* must not be null */
1654
1655           if (p != null)
1656             {
1657               alias = ((AliasDetails) p).alias.strip ();
1658             }
1659
1660           /* Only notify if the value has changed. We have to set the private
1661            * member and notify manually, or we'd end up propagating the new
1662            * alias back down to all our Personas, even if it's a fallback
1663            * display ID or something else undesirable. */
1664           if (this._alias != alias)
1665             {
1666               this._alias = alias;
1667               this.notify_property ("alias");
1668             }
1669         });
1670     }
1671
1672   private void _update_avatar ()
1673     {
1674       this._update_single_valued_property (typeof (AvatarDetails), (p) =>
1675         {
1676           return ((AvatarDetails) p).avatar != null;
1677         }, (a, b) =>
1678         {
1679           /* We can't compare two set avatars efficiently. See: bgo#652721. */
1680           return 0;
1681         }, "avatar", (p) =>
1682         {
1683           LoadableIcon? avatar = null;
1684
1685           if (p != null)
1686             {
1687               avatar = ((AvatarDetails) p).avatar;
1688             }
1689
1690           /* only notify if the value has changed */
1691           if ((this._avatar == null && avatar != null) ||
1692               (this._avatar != null &&
1693                (avatar == null || !((!) this._avatar).equal (avatar))))
1694             {
1695               this._avatar = avatar;
1696               this.notify_property ("avatar");
1697             }
1698         });
1699     }
1700
1701   private void _update_trust_level ()
1702     {
1703       var trust_level = TrustLevel.PERSONAS;
1704
1705       foreach (var p in this._persona_set)
1706         {
1707           if (p.is_user == false &&
1708               p.store.trust_level == PersonaStoreTrust.NONE)
1709             trust_level = TrustLevel.NONE;
1710         }
1711
1712       /* Only notify if the value has changed */
1713       if (this.trust_level != trust_level)
1714         this.trust_level = trust_level;
1715     }
1716
1717   private void _update_im_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
1718     {
1719       this._update_multi_valued_property ("im-addresses",
1720           create_if_not_exist, () => { return this._im_addresses == null; },
1721           () =>
1722             {
1723               this._im_addresses = new HashMultiMap<string, ImFieldDetails> (
1724                   null, null,
1725                   (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
1726                   (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
1727             },
1728           () =>
1729             {
1730               var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (
1731                   null, null,
1732                   (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
1733                   (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
1734
1735               foreach (var persona in this._persona_set)
1736                 {
1737                   /* We only care about personas implementing the given interface. */
1738                   var im_details = persona as ImDetails;
1739                   if (im_details != null)
1740                     {
1741                       foreach (var cur_protocol in
1742                           im_details.im_addresses.get_keys ())
1743                         {
1744                           var cur_addresses =
1745                               im_details.im_addresses.get (cur_protocol);
1746
1747                           foreach (var address in cur_addresses)
1748                             {
1749                               new_im_addresses.set (cur_protocol, address);
1750                             }
1751                         }
1752                     }
1753                 }
1754
1755               if (!Utils.multi_map_str_afd_equal (new_im_addresses,
1756                   this._im_addresses))
1757                 {
1758                   this._im_addresses = new_im_addresses;
1759                   return true;
1760                 }
1761
1762               return false;
1763             }, emit_notification, force_update);
1764     }
1765
1766   private void _update_web_service_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
1767     {
1768       this._update_multi_valued_property ("web-service-addresses",
1769           create_if_not_exist,
1770           () => { return this._web_service_addresses == null; },
1771           () =>
1772             {
1773               this._web_service_addresses =
1774                   new HashMultiMap<string, WebServiceFieldDetails> (null, null,
1775                       (Gee.HashDataFunc)
1776                           AbstractFieldDetails<string>.hash_static,
1777                       (Gee.EqualDataFunc)
1778                           AbstractFieldDetails<string>.equal_static);
1779             },
1780           () =>
1781             {
1782               var new_web_service_addresses =
1783                   new HashMultiMap<string, WebServiceFieldDetails> (null, null,
1784                       (Gee.HashDataFunc)
1785                           AbstractFieldDetails<string>.hash_static,
1786                       (Gee.EqualDataFunc)
1787                           AbstractFieldDetails<string>.equal_static);
1788
1789               foreach (var persona in this._persona_set)
1790                 {
1791                   /* We only care about personas implementing the given interface. */
1792                   var web_service_details = persona as WebServiceDetails;
1793                   if (web_service_details != null)
1794                     {
1795                       foreach (var cur_web_service in
1796                           web_service_details.web_service_addresses.get_keys ())
1797                         {
1798                           var cur_addresses =
1799                               web_service_details.web_service_addresses.get (
1800                                   cur_web_service);
1801
1802                           foreach (var ws_fd in cur_addresses)
1803                             {
1804                               new_web_service_addresses.set (cur_web_service,
1805                                   ws_fd);
1806                             }
1807                         }
1808                     }
1809                 }
1810
1811               if (!Utils.multi_map_str_afd_equal (new_web_service_addresses,
1812                   this._web_service_addresses))
1813                 {
1814                   this._web_service_addresses = new_web_service_addresses;
1815                   return true;
1816                 }
1817
1818               return false;
1819             }, emit_notification, force_update);
1820     }
1821
1822   private void _connect_to_persona (Persona persona)
1823     {
1824       persona.individual = this;
1825
1826       persona.notify["alias"].connect (this._notify_alias_cb);
1827       persona.notify["avatar"].connect (this._notify_avatar_cb);
1828       persona.notify["presence-message"].connect (this._notify_presence_cb);
1829       persona.notify["presence-type"].connect (this._notify_presence_cb);
1830       persona.notify["im-addresses"].connect (this._notify_im_addresses_cb);
1831       persona.notify["web-service-addresses"].connect
1832               (this._notify_web_service_addresses_cb);
1833       persona.notify["is-favourite"].connect (this._notify_is_favourite_cb);
1834       persona.notify["structured-name"].connect (
1835           this._notify_structured_name_cb);
1836       persona.notify["full-name"].connect (this._notify_full_name_cb);
1837       persona.notify["nickname"].connect (this._notify_nickname_cb);
1838       persona.notify["gender"].connect (this._notify_gender_cb);
1839       persona.notify["urls"].connect (this._notify_urls_cb);
1840       persona.notify["phone-numbers"].connect (this._notify_phone_numbers_cb);
1841       persona.notify["email-addresses"].connect (
1842           this._notify_email_addresses_cb);
1843       persona.notify["roles"].connect (this._notify_roles_cb);
1844       persona.notify["birthday"].connect (this._notify_birthday_cb);
1845       persona.notify["notes"].connect (this._notify_notes_cb);
1846       persona.notify["postal-addresses"].connect
1847           (this._notify_postal_addresses_cb);
1848       persona.notify["local-ids"].connect
1849           (this._notify_local_ids_cb);
1850
1851
1852       if (persona is GroupDetails)
1853         {
1854           ((GroupDetails) persona).group_changed.connect (
1855               this._persona_group_changed_cb);
1856         }
1857       /* Subscribe to the interactions signal for the persona */
1858       var p_interaction_details = persona as InteractionDetails;
1859       if (p_interaction_details != null)
1860         {
1861           persona.notify["im-interaction-count"].connect (this._notify_im_interaction_count_cb);
1862           persona.notify["call-interaction-count"].connect (this._notify_call_interaction_count_cb);
1863           persona.notify["last-im-interaction-datetime"].connect (this._notify_last_im_interaction_datetime_cb);
1864           persona.notify["last-call-interaction-datetime"].connect (this._notify_last_call_interaction_datetime_cb);
1865         }
1866     }
1867
1868   private void _update_structured_name ()
1869     {
1870       this._update_single_valued_property (typeof (NameDetails), (p) =>
1871         {
1872           var name = ((NameDetails) p).structured_name;
1873           return (name != null && !((!) name).is_empty ());
1874         }, (a, b) =>
1875         {
1876           /* Can't compare two set names. */
1877           return 0;
1878         }, "structured-name", (p) =>
1879         {
1880           StructuredName? name = null;
1881
1882           if (p != null)
1883             {
1884               name = ((NameDetails) p).structured_name;
1885
1886               if (name != null && ((!) name).is_empty ())
1887                 {
1888                   name = null;
1889                 }
1890             }
1891
1892           if ((this._structured_name == null && name != null) ||
1893               (this._structured_name != null &&
1894                (name == null || !((!) this._structured_name).equal ((!) name))))
1895             {
1896               this._structured_name = name;
1897               this.notify_property ("structured-name");
1898             }
1899         });
1900     }
1901
1902   private void _update_full_name ()
1903     {
1904       this._update_single_valued_property (typeof (NameDetails), (p) =>
1905         {
1906           var name = ((NameDetails) p).full_name;
1907           return_val_if_fail (name != null, false);
1908
1909           return (name.strip () != ""); /* empty names are unset */
1910         }, (a, b) =>
1911         {
1912           /* Can't compare two set names. */
1913           return 0;
1914         }, "full-name", (p) =>
1915         {
1916           string new_full_name = ""; /* must not be null */
1917
1918           if (p != null)
1919             {
1920               new_full_name = ((NameDetails) p).full_name.strip ();
1921             }
1922
1923           if (new_full_name != this._full_name)
1924             {
1925               this._full_name = new_full_name;
1926               this.notify_property ("full-name");
1927             }
1928         });
1929     }
1930
1931   private void _update_nickname ()
1932     {
1933       this._update_single_valued_property (typeof (NameDetails), (p) =>
1934         {
1935           var nickname = ((NameDetails) p).nickname;
1936           return_val_if_fail (nickname != null, false);
1937
1938           return (nickname.strip () != ""); /* empty names are unset */
1939         }, (a, b) =>
1940         {
1941           /* Can't compare two set names. */
1942           return 0;
1943         }, "nickname", (p) =>
1944         {
1945           string new_nickname = ""; /* must not be null */
1946
1947           if (p != null)
1948             {
1949               new_nickname = ((NameDetails) p).nickname.strip ();
1950             }
1951
1952           if (new_nickname != this._nickname)
1953             {
1954               this._nickname = new_nickname;
1955               this.notify_property ("nickname");
1956             }
1957         });
1958     }
1959
1960   private void _disconnect_from_persona (Persona persona,
1961       Individual? replacement_individual)
1962     {
1963       persona.notify["alias"].disconnect (this._notify_alias_cb);
1964       persona.notify["avatar"].disconnect (this._notify_avatar_cb);
1965       persona.notify["presence-message"].disconnect (
1966           this._notify_presence_cb);
1967       persona.notify["presence-type"].disconnect (this._notify_presence_cb);
1968       persona.notify["im-addresses"].disconnect (
1969           this._notify_im_addresses_cb);
1970       persona.notify["web-service-addresses"].disconnect (
1971           this._notify_web_service_addresses_cb);
1972       persona.notify["is-favourite"].disconnect (
1973           this._notify_is_favourite_cb);
1974       persona.notify["structured-name"].disconnect (
1975           this._notify_structured_name_cb);
1976       persona.notify["full-name"].disconnect (this._notify_full_name_cb);
1977       persona.notify["nickname"].disconnect (this._notify_nickname_cb);
1978       persona.notify["gender"].disconnect (this._notify_gender_cb);
1979       persona.notify["urls"].disconnect (this._notify_urls_cb);
1980       persona.notify["phone-numbers"].disconnect (
1981           this._notify_phone_numbers_cb);
1982       persona.notify["email-addresses"].disconnect (
1983           this._notify_email_addresses_cb);
1984       persona.notify["roles"].disconnect (this._notify_roles_cb);
1985       persona.notify["birthday"].disconnect (this._notify_birthday_cb);
1986       persona.notify["notes"].disconnect (this._notify_notes_cb);
1987       persona.notify["postal-addresses"].disconnect
1988           (this._notify_postal_addresses_cb);
1989       persona.notify["local-ids"].disconnect (this._notify_local_ids_cb);
1990
1991
1992       if (persona is GroupDetails)
1993         {
1994           ((GroupDetails) persona).group_changed.disconnect (
1995               this._persona_group_changed_cb);
1996         }
1997
1998       /* Unsubscribe from the interactions signal for the persona */
1999       var p_interaction_details = persona as InteractionDetails;
2000       if (p_interaction_details != null)
2001         {
2002           persona.notify["im-interaction-count"].disconnect (this._notify_im_interaction_count_cb);
2003           persona.notify["call-interaction-count"].disconnect (this._notify_call_interaction_count_cb);
2004           persona.notify["last-im-interaction-datetime"].disconnect (this._notify_last_im_interaction_datetime_cb);
2005           persona.notify["last-call-interaction-datetime"].disconnect (this._notify_last_call_interaction_datetime_cb);
2006         }
2007
2008       /* Don't update the individual if the persona's been added to the new one
2009        * already (and thus the new individual has already changed
2010        * persona.individual).
2011        *
2012        * FIXME: Ideally, we'd assert that a persona can't be added to a new
2013        * individual before it's removed from the old one. However, this
2014        * currently isn't possible due to the way the aggregator works. When the
2015        * aggregator's rewritten, it would be nice to fix this. */
2016       if (persona.individual == this)
2017         {
2018           /* It may be the case that the persona's being removed from the
2019            * individual (i.e. the replacement individual is non-null, but
2020            * doesn't contain this persona). In this case, we need to set the
2021            * persona's individual to null. */
2022           if (replacement_individual != null &&
2023               persona in ((!) replacement_individual).personas)
2024             {
2025               persona.individual = replacement_individual;
2026             }
2027           else
2028             {
2029               persona.individual = null;
2030             }
2031         }
2032     }
2033
2034   private void _update_gender ()
2035     {
2036       this._update_single_valued_property (typeof (GenderDetails), (p) =>
2037         {
2038           return ((GenderDetails) p).gender != Gender.UNSPECIFIED;
2039         }, (a, b) =>
2040         {
2041           /* It would be sexist to rank one gender over another.
2042            * Besides, how often will we see two personas in the same individual
2043            * which have different genders? */
2044           return 0;
2045         }, "gender", (p) =>
2046         {
2047           var new_gender = Gender.UNSPECIFIED;
2048
2049           if (p != null)
2050             {
2051               new_gender = ((GenderDetails) p).gender;
2052             }
2053
2054           if (new_gender != this.gender)
2055             {
2056               this._gender = new_gender;
2057               this.notify_property ("gender");
2058             }
2059         });
2060     }
2061
2062   private void _update_urls (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2063     {
2064       this._update_multi_valued_property ("urls", create_if_not_exist,
2065           () => { return this._urls == null; },
2066           () =>
2067             {
2068               this._urls = new HashSet<UrlFieldDetails> (
2069                   AbstractFieldDetails<string>.hash_static,
2070                   AbstractFieldDetails<string>.equal_static);
2071               this._urls_ro = this._urls.read_only_view;
2072             },
2073           () =>
2074             {
2075               var new_urls = new HashSet<UrlFieldDetails> (
2076                   AbstractFieldDetails<string>.hash_static,
2077                   AbstractFieldDetails<string>.equal_static);
2078               var urls_set = new HashMap<unowned string,
2079                   unowned UrlFieldDetails> (
2080                     null, null,  AbstractFieldDetails<string>.equal_static);
2081
2082               foreach (var persona in this._persona_set)
2083                 {
2084                   /* We only care about personas implementing the given
2085                    * interface. If the same URL exists multiple times we merge
2086                    * the parameters. */
2087                   var url_details = persona as UrlDetails;
2088                   if (url_details != null)
2089                     {
2090                       foreach (var url_fd in ((!) url_details).urls)
2091                         {
2092                           var existing = urls_set.get (url_fd.value);
2093                           if (existing != null)
2094                             {
2095                               existing.extend_parameters (url_fd.parameters);
2096                             }
2097                           else
2098                             {
2099                               var new_url_fd =
2100                                   new UrlFieldDetails (url_fd.value);
2101                               new_url_fd.extend_parameters (url_fd.parameters);
2102                               urls_set.set (new_url_fd.value, new_url_fd);
2103                               new_urls.add (new_url_fd);
2104                             }
2105                         }
2106                     }
2107                 }
2108
2109               if (!Utils.set_afd_equal (new_urls, this._urls))
2110                 {
2111                   this._urls = new_urls;
2112                   this._urls_ro = new_urls.read_only_view;
2113                   return true;
2114                 }
2115
2116               return false;
2117             }, emit_notification, force_update);
2118     }
2119
2120   private void _update_phone_numbers (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2121     {
2122       this._update_multi_valued_property ("phone-numbers", create_if_not_exist,
2123           () => { return this._phone_numbers == null; },
2124           () =>
2125             {
2126               this._phone_numbers = new HashSet<PhoneFieldDetails> (
2127                   AbstractFieldDetails<string>.hash_static,
2128                   AbstractFieldDetails<string>.equal_static);
2129               this._phone_numbers_ro = this._phone_numbers.read_only_view;
2130             },
2131           () =>
2132             {
2133               var new_phone_numbers = new HashSet<PhoneFieldDetails> (
2134                   AbstractFieldDetails<string>.hash_static,
2135                   AbstractFieldDetails<string>.equal_static);
2136               var phone_numbers_set = new HashMap<string, PhoneFieldDetails> (
2137                   null, null, AbstractFieldDetails<string>.equal_static);
2138
2139               foreach (var persona in this._persona_set)
2140                 {
2141                   /* We only care about personas implementing the given
2142                    * interface. If the same phone number exists multiple times
2143                    * we merge the parameters. */
2144                   var phone_details = persona as PhoneDetails;
2145                   if (phone_details != null)
2146                     {
2147                       foreach (var phone_fd in ((!) phone_details).phone_numbers)
2148                         {
2149                           var existing = phone_numbers_set.get (phone_fd.value);
2150                           if (existing != null)
2151                             {
2152                               existing.extend_parameters (phone_fd.parameters);
2153                             }
2154                           else
2155                             {
2156                               var new_fd =
2157                                   new PhoneFieldDetails (phone_fd.value);
2158                               new_fd.extend_parameters (phone_fd.parameters);
2159                               phone_numbers_set.set (new_fd.value, new_fd);
2160                               new_phone_numbers.add (new_fd);
2161                             }
2162                         }
2163                     }
2164                 }
2165
2166               if (!Utils.set_afd_equal (new_phone_numbers, this._phone_numbers))
2167                 {
2168                   this._phone_numbers = new_phone_numbers;
2169                   this._phone_numbers_ro = new_phone_numbers.read_only_view;
2170                   return true;
2171                 }
2172
2173               return false;
2174             }, emit_notification, force_update);
2175     }
2176
2177   private void _update_email_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2178     {
2179       this._update_multi_valued_property ("email-addresses",
2180           create_if_not_exist, () => { return this._email_addresses == null; },
2181           () =>
2182             {
2183               this._email_addresses = new HashSet<EmailFieldDetails> (
2184                   AbstractFieldDetails<string>.hash_static,
2185                   AbstractFieldDetails<string>.equal_static);
2186               this._email_addresses_ro = this._email_addresses.read_only_view;
2187             },
2188           () =>
2189             {
2190               var new_email_addresses = new HashSet<EmailFieldDetails> (
2191                   AbstractFieldDetails<string>.hash_static,
2192                   AbstractFieldDetails<string>.equal_static);
2193               var emails_set = new HashMap<string, EmailFieldDetails> (
2194                   null, null, AbstractFieldDetails<string>.equal_static);
2195
2196               foreach (var persona in this._persona_set)
2197                 {
2198                   /* We only care about personas implementing the given
2199                    * interface. If the same e-mail address exists multiple times
2200                    * we merge the parameters. */
2201                   var email_details = persona as EmailDetails;
2202                   if (email_details != null)
2203                     {
2204                       foreach (var email_fd in ((!) email_details).email_addresses)
2205                         {
2206                           var existing = emails_set.get (email_fd.value);
2207                           if (existing != null)
2208                             {
2209                               existing.extend_parameters (email_fd.parameters);
2210                             }
2211                           else
2212                             {
2213                               var new_email_fd =
2214                                   new EmailFieldDetails (email_fd.value,
2215                                       email_fd.parameters);
2216                               emails_set.set (new_email_fd.value, new_email_fd);
2217                               new_email_addresses.add (new_email_fd);
2218                             }
2219                         }
2220                     }
2221                 }
2222
2223               if (!Utils.set_afd_equal (new_email_addresses,
2224                   this._email_addresses))
2225                 {
2226                   this._email_addresses = new_email_addresses;
2227                   this._email_addresses_ro = new_email_addresses.read_only_view;
2228                   return true;
2229                 }
2230
2231               return false;
2232             }, emit_notification, force_update);
2233     }
2234
2235   private void _update_roles (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2236     {
2237       this._update_multi_valued_property ("roles", create_if_not_exist,
2238           () => { return this._roles == null; },
2239           () =>
2240             {
2241               this._roles = new HashSet<RoleFieldDetails> (
2242                   AbstractFieldDetails<Role>.hash_static,
2243                   AbstractFieldDetails<Role>.equal_static);
2244               this._roles_ro = this._roles.read_only_view;
2245             },
2246           () =>
2247             {
2248               var new_roles = new HashSet<RoleFieldDetails> (
2249                   AbstractFieldDetails<Role>.hash_static,
2250                   AbstractFieldDetails<Role>.equal_static);
2251
2252               foreach (var persona in this._persona_set)
2253                 {
2254                   var role_details = persona as RoleDetails;
2255                   if (role_details != null)
2256                     {
2257                       foreach (var role_fd in ((!) role_details).roles)
2258                         {
2259                           new_roles.add (role_fd);
2260                         }
2261                     }
2262                 }
2263
2264               if (!Utils.set_afd_equal (new_roles, this._roles))
2265                 {
2266                   this._roles = new_roles;
2267                   this._roles_ro = new_roles.read_only_view;
2268                   return true;
2269                 }
2270
2271               return false;
2272             }, emit_notification, force_update);
2273     }
2274
2275   private void _update_local_ids (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2276     {
2277       this._update_multi_valued_property ("local-ids", create_if_not_exist,
2278           () => { return this._local_ids == null; },
2279           () =>
2280             {
2281               this._local_ids = new HashSet<string> ();
2282               this._local_ids_ro = this._local_ids.read_only_view;
2283             },
2284           () =>
2285             {
2286               var new_local_ids = new HashSet<string> ();
2287
2288               foreach (var persona in this._persona_set)
2289                 {
2290                   var local_id_details = persona as LocalIdDetails;
2291                   if (local_id_details != null)
2292                     {
2293                       foreach (var id in ((!) local_id_details).local_ids)
2294                         {
2295                           new_local_ids.add (id);
2296                         }
2297                     }
2298                 }
2299
2300               if (new_local_ids.size != this._local_ids.size ||
2301                   !new_local_ids.contains_all (this._local_ids))
2302                 {
2303                   this._local_ids = new_local_ids;
2304                   this._local_ids_ro = new_local_ids.read_only_view;
2305                   return true;
2306                 }
2307
2308               return false;
2309             }, emit_notification, force_update);
2310     }
2311
2312   private void _update_postal_addresses (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2313     {
2314       /* FIXME: Detect duplicates somehow? */
2315       this._update_multi_valued_property ("postal-addresses",
2316           create_if_not_exist, () => { return this._postal_addresses == null; },
2317           () =>
2318             {
2319               this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
2320                   AbstractFieldDetails<PostalAddress>.hash_static,
2321                   AbstractFieldDetails<PostalAddress>.equal_static);
2322               this._postal_addresses_ro = this._postal_addresses.read_only_view;
2323             },
2324           () =>
2325             {
2326               var new_postal_addresses =
2327                   new HashSet<PostalAddressFieldDetails> (
2328                       AbstractFieldDetails<PostalAddress>.hash_static,
2329                       AbstractFieldDetails<PostalAddress>.equal_static);
2330
2331               foreach (var persona in this._persona_set)
2332                 {
2333                   var postal_address_details = persona as PostalAddressDetails;
2334                   if (postal_address_details != null)
2335                     {
2336                       foreach (var pafd in
2337                           ((!) postal_address_details).postal_addresses)
2338                         {
2339                           new_postal_addresses.add (pafd);
2340                         }
2341                     }
2342                 }
2343
2344               if (!Utils.set_afd_equal (new_postal_addresses,
2345                   this._postal_addresses))
2346                 {
2347                   this._postal_addresses = new_postal_addresses;
2348                   this._postal_addresses_ro =
2349                       new_postal_addresses.read_only_view;
2350                   return true;
2351                 }
2352
2353               return false;
2354             }, emit_notification, force_update);
2355     }
2356
2357   private void _update_birthday ()
2358     {
2359       this._update_single_valued_property (typeof (BirthdayDetails), (p) =>
2360         {
2361           var details = ((BirthdayDetails) p);
2362           return details.birthday != null && details.calendar_event_id != null;
2363         }, (a, b) =>
2364         {
2365           var a_birthday = ((BirthdayDetails) a).birthday;
2366           var b_birthday = ((BirthdayDetails) b).birthday;
2367           var a_event_id = ((BirthdayDetails) a).calendar_event_id;
2368           var b_event_id = ((BirthdayDetails) b).calendar_event_id;
2369
2370           var a_birthday_is_set = (a_birthday != null) ? 1 : 0;
2371           var b_birthday_is_set = (b_birthday != null) ? 1 : 0;
2372
2373           /* We consider the empty string as “set” because it's an opaque ID. */
2374           var a_event_id_is_set = (a_event_id != null) ? 1 : 0;
2375           var b_event_id_is_set = (b_event_id != null) ? 1 : 0;
2376
2377           /* Prefer personas which have both properties set over those who have
2378            * only one set. We don't consider the case where the birthdays from
2379            * different personas don't match, because that's just scary. */
2380           return (a_birthday_is_set + a_event_id_is_set) -
2381                  (b_birthday_is_set + b_event_id_is_set);
2382         }, "birthday", (p) =>
2383         {
2384           unowned DateTime? bday = null;
2385           unowned string? calendar_event_id = null;
2386
2387           if (p != null)
2388             {
2389               bday = ((BirthdayDetails) p).birthday;
2390               calendar_event_id = ((BirthdayDetails) p).calendar_event_id;
2391             }
2392
2393           if ((this._birthday == null && bday != null) ||
2394               (this._birthday != null &&
2395                (bday == null || !((!) this._birthday).equal ((!) bday))) ||
2396               (this._calendar_event_id != calendar_event_id))
2397             {
2398               this._birthday = bday;
2399               this._calendar_event_id = calendar_event_id;
2400
2401               this.freeze_notify ();
2402               this.notify_property ("birthday");
2403               this.notify_property ("calendar-event-id");
2404               this.thaw_notify ();
2405             }
2406         });
2407     }
2408
2409   private void _update_notes (bool create_if_not_exist, bool emit_notification = true, bool force_update = true)
2410     {
2411       this._update_multi_valued_property ("notes", create_if_not_exist,
2412           () => { return this._notes == null; },
2413           () =>
2414             {
2415               this._notes = new HashSet<NoteFieldDetails> (
2416                   AbstractFieldDetails<string>.hash_static,
2417                   AbstractFieldDetails<string>.equal_static);
2418               this._notes_ro = this._notes.read_only_view;
2419             },
2420           () =>
2421             {
2422               var new_notes = new HashSet<NoteFieldDetails> (
2423                   AbstractFieldDetails<string>.hash_static,
2424                   AbstractFieldDetails<string>.equal_static);
2425
2426               foreach (var persona in this._persona_set)
2427                 {
2428                   var note_details = persona as NoteDetails;
2429                   if (note_details != null)
2430                     {
2431                       foreach (var n in ((!) note_details).notes)
2432                         {
2433                           new_notes.add (n);
2434                         }
2435                     }
2436                 }
2437
2438               if (!Utils.set_afd_equal (new_notes, this._notes))
2439                 {
2440                   this._notes = new_notes;
2441                   this._notes_ro = new_notes.read_only_view;
2442                   return true;
2443                 }
2444
2445               return false;
2446             }, emit_notification, force_update);
2447     }
2448
2449   private void _set_personas (Set<Persona>? personas,
2450       Individual? replacement_individual)
2451     {
2452       assert (replacement_individual == null || replacement_individual != this);
2453
2454       var added = new HashSet<Persona> ();
2455       var removed = new HashSet<Persona> ();
2456
2457       /* Determine which Personas have been added. If personas == null, we
2458        * assume it's an empty set. */
2459       if (personas != null)
2460         {
2461           foreach (var p in (!) personas)
2462             {
2463               if (!this._persona_set.contains (p))
2464                 {
2465                   /* Keep track of how many Personas are users */
2466                   if (p.is_user)
2467                     this._persona_user_count++;
2468
2469                   added.add (p);
2470
2471                   this._persona_set.add (p);
2472                   this._connect_to_persona (p);
2473
2474                   /* Increment the Persona count for this PersonaStore */
2475                   var store = p.store;
2476                   var num_from_store = this._stores.get (store);
2477                   if (num_from_store == 0)
2478                     {
2479                       this._stores.set (store, num_from_store + 1);
2480                     }
2481                   else
2482                     {
2483                       this._stores.set (store, 1);
2484
2485                       store.removed.connect (this._store_removed_cb);
2486                       store.personas_changed.connect (
2487                           this._store_personas_changed_cb);
2488                     }
2489                 }
2490             }
2491         }
2492
2493       /* Determine which Personas have been removed */
2494       foreach (var p in this._persona_set)
2495         {
2496           if (personas == null || !((!) personas).contains (p))
2497             {
2498               /* Keep track of how many Personas are users */
2499               if (p.is_user)
2500                 this._persona_user_count--;
2501
2502               removed.add (p);
2503
2504               /* Decrement the Persona count for this PersonaStore */
2505               var store = p.store;
2506               var num_from_store = this._stores.get (store);
2507               if (num_from_store > 1)
2508                 {
2509                   this._stores.set (store, num_from_store - 1);
2510                 }
2511               else
2512                 {
2513                   store.removed.disconnect (this._store_removed_cb);
2514                   store.personas_changed.disconnect (
2515                       this._store_personas_changed_cb);
2516
2517                   this._stores.unset (store);
2518                 }
2519
2520               this._disconnect_from_persona (p, replacement_individual);
2521             }
2522         }
2523
2524       foreach (var p in removed)
2525         {
2526           this._persona_set.remove (p);
2527         }
2528
2529       this._emit_personas_changed (added, removed);
2530
2531       /* Update this.is_user */
2532       var new_is_user = (this._persona_user_count > 0) ? true : false;
2533       if (new_is_user != this.is_user)
2534         this.is_user = new_is_user;
2535
2536       /* If all the Personas have been removed, remove the Individual */
2537       if (this._persona_set.size < 1)
2538         {
2539           this.removed (replacement_individual);
2540           return;
2541         }
2542
2543       /* Update the ID. We choose the most interesting Persona in the
2544        * Individual and hash their UID. This is guaranteed to be globally
2545        * unique, and may not change (for one of the two Individuals) if we link
2546        * two Individuals together, which is nice though we can't rely on this
2547        * behaviour.
2548        *
2549        * This method of constructing an ID ensures that it'll be unique and
2550        * stable for a given Individual once the IndividualAggregator reaches
2551        * a quiescent state after startup. It guarantees that the ID will be
2552        * the same every time folks is used, until the Individual is linked
2553        * or unlinked to another Individual.
2554        *
2555        * We choose the most interesting Persona by ranking all the Personas
2556        * in the Individual by:
2557        *  1. store.is-primary-store
2558        *  2. store.trust-level
2559        *  3. store.id (alphabetically)
2560        *  4. persona.uid (alphabetically)
2561        *
2562        * Note that this heuristic shouldn't be changed without careful thought,
2563        * since stored references to IDs may be broken by the change.
2564        */
2565       if (this._persona_set.size > 0)
2566         {
2567           Persona? chosen_persona = null;
2568
2569           foreach (var persona in this._persona_set)
2570             {
2571               if (chosen_persona == null)
2572                 {
2573                   chosen_persona = persona;
2574                   continue;
2575                 }
2576
2577               var _chosen_persona = (!) chosen_persona;
2578
2579               if ((_chosen_persona.store.is_primary_store == false &&
2580                       persona.store.is_primary_store == true) ||
2581                   (_chosen_persona.store.is_primary_store ==
2582                           persona.store.is_primary_store &&
2583                       _chosen_persona.store.trust_level >
2584                           persona.store.trust_level) ||
2585                   (_chosen_persona.store.is_primary_store ==
2586                           persona.store.is_primary_store &&
2587                       _chosen_persona.store.trust_level ==
2588                           persona.store.trust_level &&
2589                       _chosen_persona.store.id > persona.store.id) ||
2590                   (_chosen_persona.store.is_primary_store ==
2591                           persona.store.is_primary_store &&
2592                       _chosen_persona.store.trust_level ==
2593                           persona.store.trust_level &&
2594                       _chosen_persona.store.id == persona.store.id &&
2595                       _chosen_persona.uid > persona.uid)
2596                  )
2597                {
2598                  chosen_persona = persona;
2599                }
2600             }
2601
2602           /* Hash the chosen persona's UID. We can guarantee chosen_persona is
2603            * non-null here because it's at least set to the first element of
2604            * this._persona_set, which we've checked is non-empty. */
2605           this.id = Checksum.compute_for_string (ChecksumType.SHA1,
2606               ((!) chosen_persona).uid);
2607         }
2608
2609       /* Update our aggregated fields and notify the changes */
2610       this._update_fields ();
2611     }
2612
2613   internal void replace (Individual replacement_individual)
2614     {
2615       this._set_personas (null, replacement_individual);
2616     }
2617
2618   /**
2619    * Anti-linked with a persona?
2620    *
2621    * Check whether this individual is anti-linked to {@link Persona} ``p`` at
2622    * all. If so, ``true`` will be returned — ``false`` will be returned
2623    * otherwise.
2624    *
2625    * Note that this will check for anti-links in either direction, since
2626    * anti-links are not necessarily symmetric.
2627    *
2628    * @param p persona to check for anti-links with
2629    * @return ``true`` if this individual is anti-linked with persona ``p``;
2630    * ``false``
2631    * otherwise
2632    * @since 0.7.3
2633    */
2634   public bool has_anti_link_with_persona (Persona p)
2635     {
2636       var al = p as AntiLinkable;
2637
2638       foreach (var persona in this._persona_set)
2639         {
2640           var pl = persona as AntiLinkable;
2641
2642           if ((al != null && ((!) al).has_anti_link_with_persona (persona)) ||
2643               (pl != null && ((!) pl).has_anti_link_with_persona (p)))
2644             {
2645               return true;
2646             }
2647         }
2648
2649       return false;
2650     }
2651
2652   /**
2653    * Anti-linked with an individual?
2654    *
2655    * Check whether this individual is anti-linked to any of the {@link Persona}s
2656    * in {@link Folks.Individual} ``i``. If so, ``true`` will be returned —
2657    * ``false`` will be returned otherwise.
2658    *
2659    * Note that this will check for anti-links in either direction, since
2660    * anti-links are not necessarily symmetric.
2661    *
2662    * @param i individual to check for anti-links with
2663    * @return ``true`` if this individual is anti-linked with individual ``i``;
2664    * ``false`` otherwise
2665    * @since 0.7.3
2666    */
2667   public bool has_anti_link_with_individual (Individual i)
2668     {
2669       foreach (var p in i.personas)
2670         {
2671           if (this.has_anti_link_with_persona (p) == true)
2672             {
2673               return true;
2674             }
2675         }
2676
2677       return false;
2678     }
2679 }