2 * Copyright (C) 2011 Collabora Ltd.
3 * Copyright (C) 2013 Philip Withnall
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.
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.
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/>.
19 * Travis Reitter <travis.reitter@collabora.co.uk>
20 * Marco Barisione <marco.barisione@collabora.co.uk>
21 * Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
31 * A persona subclass which represents a single EDS contact.
33 * Each {@link Edsf.Persona} instance represents a single EDS {@link E.Contact}.
34 * When the contact is modified (either by this folks client, or a different
35 * client), the {@link Edsf.Persona} remains the same, but is assigned a new
36 * {@link E.Contact}. It then updates its properties from this new contact.
38 public class Edsf.Persona : Folks.Persona,
56 /* The following 4 definitions are used by the tests */
58 * vCard field names for telephone numbers.
62 public static const string[] phone_fields = {
63 "assistant_phone", "business_phone", "business_phone_2", "callback_phone",
64 "car_phone", "company_phone", "home_phone", "home_phone_2", "isdn_phone",
65 "mobile_phone", "other_phone", "primary_phone"
68 * vCard field names for postal addresses.
72 public static const string[] address_fields = {
73 "address_home", "address_other", "address_work"
76 * vCard field names for e-mail addresses.
80 public static const string[] email_fields = {
81 "email_1", "email_2", "email_3", "email_4"
85 * vCard field names for miscellaneous URIs.
89 [Deprecated (since = "0.6.3",
90 replacement = "Folks.UrlFieldDetails.PARAM_TYPE_BLOG")]
91 public static const string[] url_properties = {
92 "blog_url", "fburl", "homepage_url", "video_url"
95 /* Some types of URLs are represented in EDS using custom vCard fields rather
96 * than the X-URIS field. Here are mappings between the custom vCard field
97 * names which EDS uses, and the TYPE values which folks uses which map to
99 private struct UrlTypeMapping
101 string vcard_field_name;
105 internal static const UrlTypeMapping[] _url_properties =
107 { "homepage_url", UrlFieldDetails.PARAM_TYPE_HOME_PAGE },
108 { "blog_url", UrlFieldDetails.PARAM_TYPE_BLOG },
109 { "fburl", "x-free-busy" },
110 { "video_url", "x-video" }
114 * The vCard attribute used to specify a Contact's gender
117 * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
119 * Note that the above document is a draft and the gender property
120 * is still considered experimental, hence the "X-" prefix in the
121 * attribute name. So this might change.
125 public static const string gender_attribute_name = "X-GENDER";
128 * The value used to define the male gender for the
129 * X-GENDER vCard property.
132 * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
136 public static const string gender_male = "M";
139 * The value used to define the female gender for the
140 * X-GENDER vCard property.
143 * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
147 public static const string gender_female = "F";
149 private const string[] _linkable_properties = { "im-addresses",
152 "web-service-addresses" };
154 private static GLib.HashTable<string, E.ContactField>? _im_eds_map = null;
156 private E.Contact _contact; /* should be set on construct */
159 * The e-d-s contact represented by this Persona
161 public E.Contact contact
163 get { return this._contact; }
164 construct { this._contact = value; }
167 /* NOTE: Other properties support lazy initialisation, but
168 * web-service-addresses doesn't as it's a linkable property, so always has to
169 * be loaded anyway. */
170 private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
175 [CCode (notify = false)]
176 public MultiMap<string, WebServiceFieldDetails> web_service_addresses
178 get { return this._web_service_addresses; }
179 set { this.change_web_service_addresses.begin (value); }
187 public async void change_web_service_addresses (
188 MultiMap<string, WebServiceFieldDetails> web_service_addresses)
191 yield ((Edsf.PersonaStore) this.store)._set_web_service_addresses (this,
192 web_service_addresses);
195 /* NOTE: Other properties support lazy initialisation, but local-ids doesn't
196 * as it's a linkable property, so always has to be loaded anyway. */
197 private HashSet<string> _local_ids = new HashSet<string> ();
198 private Set<string> _local_ids_ro;
201 * IDs used to link {@link Edsf.Persona}s.
203 [CCode (notify = false)]
204 public Set<string> local_ids
208 if (this._local_ids.contains (this.iid) == false)
210 this._local_ids.add (this.iid);
212 return this._local_ids_ro;
214 set { this.change_local_ids.begin (value); }
222 public async void change_local_ids (Set<string> local_ids)
225 yield ((Edsf.PersonaStore) this.store)._set_local_ids (this, local_ids);
228 private HashSet<PostalAddressFieldDetails>? _postal_addresses = null;
229 private Set<PostalAddressFieldDetails>? _postal_addresses_ro = null;
232 * The postal addresses of the contact.
234 * A list of postal addresses associated to the contact.
238 [CCode (notify = false)]
239 public Set<PostalAddressFieldDetails> postal_addresses
243 this._update_addresses (true, false);
244 return this._postal_addresses_ro;
246 set { this.change_postal_addresses.begin (value); }
254 public async void change_postal_addresses (
255 Set<PostalAddressFieldDetails> postal_addresses) throws PropertyError
257 yield ((Edsf.PersonaStore) this.store)._set_postal_addresses (this,
261 private HashSet<PhoneFieldDetails>? _phone_numbers = null;
262 private Set<PhoneFieldDetails>? _phone_numbers_ro = null;
269 [CCode (notify = false)]
270 public Set<PhoneFieldDetails> phone_numbers
274 this._update_phones (true, false);
275 return this._phone_numbers_ro;
277 set { this.change_phone_numbers.begin (value); }
285 public async void change_phone_numbers (
286 Set<PhoneFieldDetails> phone_numbers) throws PropertyError
288 yield ((Edsf.PersonaStore) this.store)._set_phones (this, phone_numbers);
291 private HashSet<EmailFieldDetails>? _email_addresses =
292 new HashSet<EmailFieldDetails> (
293 AbstractFieldDetails<string>.hash_static,
294 AbstractFieldDetails<string>.equal_static);
295 private Set<EmailFieldDetails> _email_addresses_ro;
302 [CCode (notify = false)]
303 public Set<EmailFieldDetails> email_addresses
305 get { return this._email_addresses_ro; }
306 set { this.change_email_addresses.begin (value); }
314 public async void change_email_addresses (
315 Set<EmailFieldDetails> email_addresses) throws PropertyError
317 yield ((Edsf.PersonaStore) this.store)._set_emails (this,
321 private HashSet<NoteFieldDetails>? _notes = null;
322 private Set<NoteFieldDetails>? _notes_ro = null;
329 [CCode (notify = false)]
330 public Set<NoteFieldDetails> notes
334 this._update_notes (true, false);
335 return this._notes_ro;
337 set { this.change_notes.begin (value); }
345 public async void change_notes (Set<NoteFieldDetails> notes)
348 yield ((Edsf.PersonaStore) this.store)._set_notes (this, notes);
356 public override string[] linkable_properties
358 get { return Persona._linkable_properties; }
366 public override string[] writeable_properties
368 get { return this.store.always_writeable_properties; }
371 private LoadableIcon? _avatar = null;
373 * An avatar for the Persona.
375 * See {@link Folks.AvatarDetails.avatar}.
379 [CCode (notify = false)]
380 public LoadableIcon? avatar
382 get { return this._avatar; }
383 set { this.change_avatar.begin (value); }
391 public async void change_avatar (LoadableIcon? avatar) throws PropertyError
393 yield ((Edsf.PersonaStore) this.store)._set_avatar (this, avatar);
396 private StructuredName? _structured_name = null;
402 [CCode (notify = false)]
403 public StructuredName? structured_name
405 get { return this._structured_name; }
406 set { this.change_structured_name.begin (value); }
414 public async void change_structured_name (StructuredName? structured_name)
417 yield ((Edsf.PersonaStore) this.store)._set_structured_name (this,
422 * The e-d-s contact uid
426 public string contact_id { get; construct; }
428 private string _full_name = "";
434 [CCode (notify = false)]
435 public string full_name
437 get { return this._full_name; }
438 set { this.change_full_name.begin (value); }
446 public async void change_full_name (string full_name) throws PropertyError
448 yield ((Edsf.PersonaStore) this.store)._set_full_name (this, full_name);
451 private string _nickname = "";
457 [CCode (notify = false)]
458 public string nickname
460 get { return this._nickname; }
461 set { this.change_nickname.begin (value); }
469 public async void change_nickname (string nickname) throws PropertyError
471 yield ((Edsf.PersonaStore) this.store)._set_nickname (this, nickname);
474 private Gender _gender;
480 [CCode (notify = false)]
483 get { return this._gender; }
484 set { this.change_gender.begin (value); }
492 public async void change_gender (Gender gender) throws PropertyError
494 yield ((Edsf.PersonaStore) this.store)._set_gender (this, gender);
497 private HashSet<UrlFieldDetails>? _urls = null;
498 private Set<UrlFieldDetails>? _urls_ro = null;
504 [CCode (notify = false)]
505 public Set<UrlFieldDetails> urls
509 this._update_urls (true, false);
510 return this._urls_ro;
512 set { this.change_urls.begin (value); }
520 public async void change_urls (Set<UrlFieldDetails> urls) throws PropertyError
522 yield ((Edsf.PersonaStore) this.store)._set_urls (this, urls);
525 /* NOTE: Other properties support lazy initialisation, but im-addresses
526 * doesn't as it's a linkable property, so always has to be loaded anyway. */
527 private HashMultiMap<string, ImFieldDetails> _im_addresses =
528 new HashMultiMap<string, ImFieldDetails> (null, null,
529 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
530 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
537 [CCode (notify = false)]
538 public MultiMap<string, ImFieldDetails> im_addresses
540 get { return this._im_addresses; }
541 set { this.change_im_addresses.begin (value); }
549 public async void change_im_addresses (
550 MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
552 yield ((Edsf.PersonaStore) this.store)._set_im_fds (this, im_addresses);
555 private HashSet<string>? _groups = null;
556 private Set<string>? _groups_ro = null;
563 [CCode (notify = false)]
564 public Set<string> groups
568 this._update_groups (true, false);
569 return this._groups_ro;
571 set { this.change_groups.begin (value); }
579 public async void change_group (string group, bool is_member)
582 /* NOTE: This method specifically accesses this.groups rather than
583 * this._groups, so that lazy loading is guaranteed to happen if
586 if ((is_member == true && this.groups.contains (group) == true) ||
587 (is_member == false && this.groups.contains (group) == false))
592 /* Replace the current set of groups with a modified one. */
593 var new_groups = new HashSet<string> ();
594 foreach (var category_name in this.groups)
596 new_groups.add (category_name);
599 if (is_member == false)
601 new_groups.remove (group);
605 new_groups.add (group);
608 yield this.change_groups (new_groups);
616 public async void change_groups (Set<string> groups) throws PropertyError
618 yield ((Edsf.PersonaStore) this.store)._set_groups (this, groups);
622 * Change the contact's system groups.
624 * The system groups are a property exposed by Google Contacts address books,
625 * and can include any combination of the following identifier:
631 * Setting the system groups will also change the group membership to include
632 * the localized version of those groups, and may change the value of
633 * {@link Edsf.Persona.in_google_personal_group}.
635 * Attempting to call this method on a persona beloging to a PersonaStore which
636 * is not Google will throw a PropertyError.
638 * It's preferred to call this rather than setting {@link Persona.system_groups}
639 * directly, as this method gives error notification and will only return once
640 * the groups have been written to the relevant backing store (or the
641 * operation's failed).
643 * @param system_groups the complete set of system group ids the contact should be a member of
644 * @throws PropertyError if setting the groups failed
647 public async void change_system_groups (Set<string> system_groups) throws PropertyError
649 yield ((Edsf.PersonaStore) this.store)._set_system_groups (this, system_groups);
652 private static const string GOOGLE_PERSONAL_GROUP_NAME = "Contacts";
655 * Change whether this contact belongs to the personal group or not.
657 * The personal contact group is a concept that exists only in Google
658 * address books. Other backends will throw a PropertyError.
660 * It's preferred to call this rather than setting {@link Persona.in_google_personal_group}
661 * directly, as this method gives error notification and will only return once
662 * the membership has been written to the relevant backing store (or the
663 * operation's failed).
665 * @param in_personal Whether to add or remove the personal group membership
666 * @throws PropertyError if the address book is not Google, or if setting the property failed
669 public async void change_in_google_personal_group (bool in_personal) throws PropertyError
671 if (in_personal == this._in_google_personal_group)
676 HashSet<string> new_system_groups = new HashSet<string> ();
677 foreach (var sg in this._system_groups)
679 if (sg == GOOGLE_PERSONAL_GROUP_NAME && !in_personal)
684 new_system_groups.add (sg);
689 new_system_groups.add (GOOGLE_PERSONAL_GROUP_NAME);
692 yield ((Edsf.PersonaStore) this.store)._set_system_groups (this, new_system_groups);
698 * e-d-s has no equivalent field, so this is unsupported.
702 [CCode (notify = false)]
703 public string? calendar_event_id
705 get { return null; } /* unsupported */
706 set { this.change_calendar_event_id.begin (value); } /* not writeable */
709 /* We cache the timezone we use for converting birthdays to UTC since creating
710 * it requires mmapping /etc/localtime, which means lots of syscalls. */
711 private static TimeZone _local_time_zone = new TimeZone.local ();
713 private DateTime? _birthday = null;
719 [CCode (notify = false)]
720 public DateTime? birthday
722 get { return this._birthday; }
723 set { this.change_birthday.begin (value); }
731 public async void change_birthday (DateTime? bday)
734 yield ((Edsf.PersonaStore) this.store)._set_birthday (this,
738 private HashSet<RoleFieldDetails>? _roles = null;
739 private Set<RoleFieldDetails>? _roles_ro = null;
746 [CCode (notify = false)]
747 public Set<RoleFieldDetails> roles
751 this._update_roles (true, false);
752 return this._roles_ro;
754 set { this.change_roles.begin (value); }
762 public async void change_roles (Set<RoleFieldDetails> roles)
765 yield ((Edsf.PersonaStore) this.store)._set_roles (this, roles);
768 private bool _is_favourite = false;
771 * Whether this contact is a user-defined favourite.
775 [CCode (notify = false)]
776 public bool is_favourite
780 this._update_groups (true, false); /* also checks for favourites */
781 return this._is_favourite;
783 set { this.change_is_favourite.begin (value); }
791 public async void change_is_favourite (bool is_favourite) throws PropertyError
793 if (this._is_favourite == is_favourite)
798 yield ((Edsf.PersonaStore) this.store)._set_is_favourite (this,
802 private HashSet<string> _anti_links;
803 private Set<string> _anti_links_ro;
810 [CCode (notify = false)]
811 public Set<string> anti_links
813 get { return this._anti_links_ro; }
814 set { this.change_anti_links.begin (value); }
822 public async void change_anti_links (Set<string> anti_links)
825 yield ((Edsf.PersonaStore) this.store)._set_anti_links (this, anti_links);
828 private HashSet<string>? _system_groups = null;
829 private Set<string>? _system_groups_ro = null;
830 private bool _in_google_personal_group;
833 * The complete set of system group identifiers the contact belongs to.
834 * See {@link Persona.change_system_groups} for details.
838 [CCode (notify = false)]
839 public Set<string>? system_groups
843 this._update_groups (true);
844 return this._system_groups_ro;
847 set { this.change_system_groups.begin (value); }
851 * Whether this contact is in the “My Contacts” section of the user’s address
852 * book, rather than the “Other” section.
856 [CCode (notify = false)]
857 public bool in_google_personal_group
861 this._update_groups (true); /* also checks for the personal group */
862 return this._in_google_personal_group;
865 set { this.change_in_google_personal_group.begin (value); }
871 * @param store_id the {@link PersonaStore.id}
872 * @param contact the Contact
873 * @return a valid IID
877 internal static string build_iid_from_contact (string store_id,
881 Edsf.Persona._get_property_from_contact<string> (contact, "id");
882 return Edsf.Persona.build_iid (store_id, (!) (contact_id ?? ""));
888 * @param store_id the {@link PersonaStore.id}
889 * @param contact_id the id belonging to the Contact
890 * @return a valid IID
894 internal static string build_iid (string store_id, string contact_id)
896 return "%s:%s".printf (store_id, contact_id);
901 * Create a new persona.
903 * Create a new persona for the {@link PersonaStore} ``store``, representing
904 * the EDS contact given by ``contact``.
906 * @param store the store which will contain the persona
907 * @param contact the EDS contact being represented by the persona
911 public Persona (PersonaStore store, E.Contact contact)
914 Edsf.Persona._get_property_from_contact<string> (contact, "id");
915 var contact_id = (!) (_contact_id ?? "");
917 var uid = Folks.Persona.build_uid (BACKEND_NAME, store.id, contact_id);
918 var iid = Edsf.Persona.build_iid (store.id, contact_id);
919 var is_user = BookClient.is_self (contact);
921 Edsf.Persona._get_property_from_contact<string> (contact,
923 var full_name = (!) (_full_name ?? "");
925 Object (display_id: full_name,
930 contact_id: contact_id,
936 debug ("Creating new Edsf.Persona with IID '%s'", this.iid);
938 this._gender = Gender.UNSPECIFIED;
939 this._phone_numbers = new HashSet<PhoneFieldDetails> (
940 AbstractFieldDetails<string>.hash_static,
941 AbstractFieldDetails<string>.equal_static);
942 this._phone_numbers_ro = this._phone_numbers.read_only_view;
943 this._email_addresses = new HashSet<EmailFieldDetails> (
944 AbstractFieldDetails<string>.hash_static,
945 AbstractFieldDetails<string>.equal_static);
946 this._email_addresses_ro = this._email_addresses.read_only_view;
947 this._notes = new HashSet<NoteFieldDetails> (
948 AbstractFieldDetails<string>.hash_static,
949 AbstractFieldDetails<string>.equal_static);
950 this._notes_ro = this._notes.read_only_view;
951 this._urls = new HashSet<UrlFieldDetails> (
952 AbstractFieldDetails<string>.hash_static,
953 AbstractFieldDetails<string>.equal_static);
954 this._urls_ro = this._urls.read_only_view;
955 this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
956 AbstractFieldDetails<PostalAddress>.hash_static,
957 AbstractFieldDetails<PostalAddress>.equal_static);
958 this._postal_addresses_ro = this._postal_addresses.read_only_view;
959 this._local_ids = new HashSet<string> ();
960 this._local_ids_ro = this._local_ids.read_only_view;
961 this._web_service_addresses =
962 new HashMultiMap<string, WebServiceFieldDetails> (
964 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
965 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
966 this._email_addresses_ro = this._email_addresses.read_only_view;
967 this._groups = new HashSet<string> ();
968 this._groups_ro = this._groups.read_only_view;
969 this._roles = new HashSet<RoleFieldDetails> (
970 AbstractFieldDetails<Role>.hash_static,
971 AbstractFieldDetails<Role>.equal_static);
972 this._roles_ro = this._roles.read_only_view;
973 this._anti_links = new HashSet<string> ();
974 this._anti_links_ro = this._anti_links.read_only_view;
976 this._update (this._contact);
984 public override void linkable_property_to_links (string prop_name,
985 Folks.Persona.LinkablePropertyCallback callback)
987 if (prop_name == "im-addresses")
989 foreach (var protocol in this._im_addresses.get_keys ())
991 var im_fds = this._im_addresses.get (protocol);
993 foreach (var im_fd in im_fds)
994 callback (protocol + ":" + im_fd.value);
997 else if (prop_name == "local-ids")
999 /* Note: we need to use this.local_ids and not this._local_ids,
1000 * otherwise this can have a different behaviour depending
1001 * on the state of the current Persona depending on whether
1002 * this.local_ids was called before or not. */
1003 foreach (var id in this.local_ids)
1008 else if (prop_name == "web-service-addresses")
1010 foreach (var web_service in this.web_service_addresses.get_keys ())
1012 var web_service_addresses =
1013 this._web_service_addresses.get (web_service);
1015 foreach (var ws_fd in web_service_addresses)
1016 callback (web_service + ":" + ws_fd.value);
1019 else if (prop_name == "email-addresses")
1021 foreach (var email in this._email_addresses)
1022 callback (email.value);
1027 base.linkable_property_to_links (prop_name, callback);
1033 debug ("Destroying Edsf.Persona '%s': %p", this.uid, this);
1037 * Update attribs of the persona.
1039 internal void _update (E.Contact updated_contact)
1041 this.freeze_notify ();
1043 /* We get a new E.Contact instance from EDS containing all the updates,
1044 * so replace our existing contact with it. */
1045 this._contact = updated_contact;
1046 this.notify_property ("contact");
1048 this._update_names ();
1049 this._update_avatar ();
1050 this._update_urls (false);
1051 this._update_phones (false);
1052 this._update_addresses (false);
1053 this._update_emails ();
1055 /* Note: because we assume certain e-mail addresses
1056 * (@gmail, @msn, etc) to also be IM IDs we /must/
1057 * update the latter after we've taken care of the former.
1059 this._update_im_addresses ();
1061 this._update_groups (false);
1062 this._update_notes (false);
1063 this._update_local_ids ();
1064 this._update_web_services_addresses ();
1065 this._update_gender ();
1066 this._update_birthday ();
1067 this._update_roles (false);
1068 this._update_favourite ();
1069 this._update_anti_links ();
1071 this.thaw_notify ();
1074 private void _update_params (AbstractFieldDetails details,
1075 E.VCardAttribute attr)
1077 foreach (unowned E.VCardAttributeParam param in attr.get_params ())
1079 string param_name = param.get_name ().down ();
1080 foreach (unowned string param_value in param.get_values ())
1082 if (param_name == AbstractFieldDetails.PARAM_TYPE)
1084 details.add_parameter (param_name, param_value.down ());
1088 details.add_parameter (param_name, param_value);
1094 private void _update_gender ()
1096 var gender = Gender.UNSPECIFIED;
1098 this.contact.get_attribute (Edsf.Persona.gender_attribute_name);
1100 if (gender_attr != null)
1102 var val = ((!) gender_attr).get_value ();
1105 switch (((!) val).up ())
1107 case Edsf.Persona.gender_male:
1108 gender = Gender.MALE;
1110 case Edsf.Persona.gender_female:
1111 gender = Gender.FEMALE;
1114 /* Unspecified, as above */
1120 if (this._gender != gender)
1122 this._gender = gender;
1123 this.notify_property ("gender");
1127 private void _update_birthday ()
1129 var _bday = this._get_property<E.ContactDate> ("birth_date");
1133 var bday = (!) _bday;
1135 /* Since e-d-s stores birthdays as a plain date, we take the
1136 * given date in local time and convert it to UTC as mandated
1137 * by the BirthdayDetails interface.
1138 * We cache the timezone since creating it requires mmapping
1139 * /etc/localtime, which means lots of syscalls. */
1140 var d = new DateTime (Persona._local_time_zone,
1141 (int) bday.year, (int) bday.month, (int) bday.day, 0, 0, 0.0);
1142 if (this._birthday == null ||
1143 (this._birthday != null &&
1144 !((!) this._birthday).equal (d.to_utc ())))
1146 this._birthday = d.to_utc ();
1147 this.notify_property ("birthday");
1152 if (this._birthday != null)
1154 this._birthday = null;
1155 this.notify_property ("birthday");
1160 private void _update_roles (bool create_if_not_exist, bool emit_notification = true)
1162 /* See the comments in Folks.Individual about the lazy instantiation
1163 * strategy for roles. */
1164 if (this._roles == null && create_if_not_exist == false)
1166 if (emit_notification)
1168 this.notify_property ("roles");
1172 else if (this._roles == null)
1174 this._roles = new HashSet<RoleFieldDetails> (
1175 AbstractFieldDetails<Role>.hash_static,
1176 AbstractFieldDetails<Role>.equal_static);
1177 this._roles_ro = this._roles.read_only_view;
1180 var new_roles = new HashSet<RoleFieldDetails> (
1181 AbstractFieldDetails<Role>.hash_static,
1182 AbstractFieldDetails<Role>.equal_static);
1184 var default_role_fd = this._get_default_role ();
1185 if (default_role_fd != null)
1187 new_roles.add ((!) default_role_fd);
1190 var vcard = (E.VCard) this.contact;
1191 foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
1193 if (attr.get_name () != "X-ROLES")
1196 var val = attr.get_value ();
1197 if (val == null || (!) val == "")
1202 var role = new Role ("", "");
1203 role.role = (!) val;
1204 var role_fd = new RoleFieldDetails (role);
1206 foreach (unowned E.VCardAttributeParam param in
1209 string param_name = param.get_name ().down ();
1211 if (param_name == "organisation_name")
1213 foreach (unowned string param_value in
1214 param.get_values ())
1216 role.organisation_name = param_value;
1220 else if (param_name == "title")
1222 foreach (unowned string param_value in
1223 param.get_values ())
1225 role.title = param_value;
1231 foreach (unowned string param_value in
1232 param.get_values ())
1234 role_fd.add_parameter (param_name, param_value);
1239 new_roles.add (role_fd);
1242 if (!Folks.Internal.equal_sets<RoleFieldDetails> (new_roles, this._roles))
1244 this._roles = new_roles;
1245 this._roles_ro = new_roles.read_only_view;
1246 if (emit_notification)
1248 this.notify_property ("roles");
1253 private RoleFieldDetails? _get_default_role ()
1255 RoleFieldDetails? _default_role = null;
1257 var org = this._get_property<string> ("org");
1258 var org_unit = this._get_property<string> ("org_unit");
1259 var office = this._get_property<string> ("office");
1260 var title = this._get_property<string> ("title");
1261 var role = this._get_property<string> ("role");
1262 var manager = this._get_property<string> ("manager");
1263 var assistant = this._get_property<string> ("assistant");
1273 var new_role = new Role (title, org);
1274 if (role != null && (!) role != "")
1275 new_role.role = (!) role;
1277 /* Check if it's non-empty. */
1278 if (!new_role.is_empty ())
1280 var default_role = new RoleFieldDetails (new_role);
1282 if (org_unit != null && org_unit != "")
1283 default_role.set_parameter ("org_unit", (!) org_unit);
1285 if (office != null && office != "")
1286 default_role.set_parameter ("office", (!) office);
1288 if (manager != null && manager != "")
1289 default_role.set_parameter ("manager", (!) manager);
1291 if (assistant != null && manager != "")
1292 default_role.set_parameter ("assistant", (!) assistant);
1294 _default_role = default_role;
1298 return _default_role;
1301 private void _update_web_services_addresses ()
1303 var new_services = new HashMultiMap<string, WebServiceFieldDetails> (
1305 (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
1306 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
1308 var services = this.contact.get_attribute ("X-FOLKS-WEB-SERVICES-IDS");
1309 if (services != null)
1311 foreach (var service in ((!) services).get_params ())
1313 var service_name = service.get_name ().down ();
1314 foreach (var service_id in service.get_values ())
1316 if (service_id == "")
1321 new_services.set (service_name,
1322 new WebServiceFieldDetails (service_id));
1327 if (!Utils.multi_map_str_afd_equal (new_services,
1328 this._web_service_addresses))
1330 this._web_service_addresses = new_services;
1331 this.notify_property ("web-service-addresses");
1335 private void _update_emails (bool emit_notification = true)
1337 var new_email_addresses = new HashSet<EmailFieldDetails> (
1338 AbstractFieldDetails<string>.hash_static,
1339 AbstractFieldDetails<string>.equal_static);
1341 var attrs = this.contact.get_attributes (E.ContactField.EMAIL);
1342 foreach (var attr in attrs)
1344 var val = attr.get_value ();
1345 if (val == null || (!) val == "")
1350 var email_fd = new EmailFieldDetails ((!) val);
1351 this._update_params (email_fd, attr);
1352 new_email_addresses.add (email_fd);
1355 if (!Folks.Internal.equal_sets<EmailFieldDetails> (new_email_addresses,
1356 this._email_addresses))
1358 this._email_addresses = new_email_addresses;
1359 this._email_addresses_ro = new_email_addresses.read_only_view;
1360 if (emit_notification)
1362 this.notify_property ("email-addresses");
1367 private void _update_notes (bool create_if_not_exist, bool emit_notification = true)
1369 /* See the comments in Folks.Individual about the lazy instantiation
1370 * strategy for notes. */
1371 if (this._notes == null && create_if_not_exist == false)
1373 if (emit_notification)
1375 this.notify_property ("notes");
1379 else if (this._notes == null)
1381 this._notes = new HashSet<NoteFieldDetails> (
1382 AbstractFieldDetails<string>.hash_static,
1383 AbstractFieldDetails<string>.equal_static);
1384 this._notes_ro = this._notes.read_only_view;
1387 var new_notes = new HashSet<NoteFieldDetails> (
1388 AbstractFieldDetails<string>.hash_static,
1389 AbstractFieldDetails<string>.equal_static);
1391 var n = this._get_property<string> ("note");
1392 if (n != null && n != "")
1394 var note = new NoteFieldDetails ((!) n);
1395 new_notes.add (note);
1398 if (!Folks.Internal.equal_sets<NoteFieldDetails> (new_notes, this._notes))
1400 this._notes = new_notes;
1401 this._notes_ro = this._notes.read_only_view;
1402 if (emit_notification)
1404 this.notify_property ("notes");
1409 private void _update_names ()
1411 var _full_name = this._get_property<string> ("full_name");
1413 if (_full_name == null)
1418 var full_name = (!) _full_name;
1420 if (this._full_name != full_name)
1422 this._full_name = full_name;
1423 this.notify_property ("full-name");
1426 var _nickname = this._get_property<string> ("nickname");
1428 if (_nickname == null)
1433 var nickname = (!) _nickname;
1435 if (this._nickname != nickname)
1437 this._nickname = nickname;
1438 this.notify_property ("nickname");
1441 StructuredName? structured_name = null;
1442 var _cn = this._get_property<E.ContactName> ("name");
1447 string family_name = cn.family;
1448 string given_name = cn.given;
1449 string additional_names = cn.additional;
1450 string prefixes = cn.prefixes;
1451 string suffixes = cn.suffixes;
1452 structured_name = new StructuredName (family_name, given_name,
1453 additional_names, prefixes,
1457 if (structured_name != null && !((!) structured_name).is_empty ())
1459 this._structured_name = (!) structured_name;
1460 this.notify_property ("structured-name");
1462 else if (this._structured_name != null)
1464 this._structured_name = null;
1465 this.notify_property ("structured-name");
1469 private LoadableIcon? _contact_photo_to_loadable_icon (ContactPhoto? _p)
1480 case ContactPhotoType.URI:
1481 var uri = p.get_uri ();
1487 return new FileIcon (File.new_for_uri ((!) uri));
1488 case ContactPhotoType.INLINED:
1489 var data = p.get_inlined ();
1490 var mime_type = p.get_mime_type ();
1491 if (data == null || mime_type == null)
1496 return new Edsf.MemoryIcon ((!) mime_type, (!) data);
1502 private void _update_avatar ()
1504 var p = this._get_property<E.ContactPhoto> ("photo");
1506 var cache = AvatarCache.dup ();
1508 // Convert the ContactPhoto to a LoadableIcon and store or update it.
1509 var new_avatar = this._contact_photo_to_loadable_icon (p);
1511 if (this._avatar != null && new_avatar == null)
1513 // Remove the old cached avatar, ignoring errors.
1514 cache.remove_avatar.begin (this.uid, (obj, res) =>
1518 cache.remove_avatar.end (res);
1520 catch (GLib.Error e1) {}
1522 this._avatar = null;
1523 this.notify_property ("avatar");
1526 else if ((this._avatar == null && new_avatar != null) ||
1527 (this._avatar != null && new_avatar != null &&
1528 ((!) this._avatar).equal (new_avatar) == false))
1530 /* Store the new avatar in the cache. new_avatar is guaranteed to be
1532 cache.store_avatar.begin (this.uid, (!) new_avatar, (obj, res) =>
1536 cache.store_avatar.end (res);
1537 this._avatar = new_avatar;
1538 this.notify_property ("avatar");
1540 catch (GLib.Error e2)
1542 warning ("Couldn't cache avatar for Edsf.Persona '%s': %s",
1543 this.uid, e2.message);
1544 new_avatar = null; /* failure */
1550 private void _update_urls (bool create_if_not_exist, bool emit_notification = true)
1552 /* See the comments in Folks.Individual about the lazy instantiation
1553 * strategy for URIs. */
1554 if (this._urls == null && create_if_not_exist == false)
1556 if (emit_notification)
1558 this.notify_property ("urls");
1562 else if (this._urls == null)
1564 this._urls = new HashSet<UrlFieldDetails> (
1565 AbstractFieldDetails<string>.hash_static,
1566 AbstractFieldDetails<string>.equal_static);
1567 this._urls_ro = this._urls.read_only_view;
1570 var new_urls = new HashSet<UrlFieldDetails> (
1571 AbstractFieldDetails<string>.hash_static,
1572 AbstractFieldDetails<string>.equal_static);
1574 /* First we get the standard Evo urls.. */
1575 foreach (var mapping in Persona._url_properties)
1577 var url_property = mapping.vcard_field_name;
1578 var folks_type = mapping.folks_type;
1580 var u = this._get_property<string> (url_property);
1581 if (u != null && u != "")
1583 var fd_u = new UrlFieldDetails ((!) u);
1584 fd_u.set_parameter (AbstractFieldDetails.PARAM_TYPE, folks_type);
1585 new_urls.add (fd_u);
1589 /* Now we go for extra URLs */
1590 var vcard = (E.VCard) this.contact;
1591 foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
1593 if (attr.get_name () == "X-URIS")
1595 var val = attr.get_value ();
1596 if (val == null || (!) val == "")
1601 var url_fd = new UrlFieldDetails ((!) val);
1602 this._update_params (url_fd, attr);
1603 new_urls.add (url_fd);
1607 if (!Utils.set_afd_equal (new_urls, this._urls))
1609 this._urls = new_urls;
1610 this._urls_ro = new_urls.read_only_view;
1611 if (emit_notification)
1613 this.notify_property ("urls");
1618 private void _update_im_addresses ()
1620 var im_eds_map = Persona._get_im_eds_map ();
1621 var new_im_addresses = new HashMultiMap<string, ImFieldDetails> (null,
1622 null, (Gee.HashDataFunc) AbstractFieldDetails<string>.hash_static,
1623 (Gee.EqualDataFunc) AbstractFieldDetails<string>.equal_static);
1625 foreach (var im_proto in im_eds_map.get_keys ())
1627 var addresses = this.contact.get_attributes (
1628 im_eds_map.lookup (im_proto));
1629 foreach (var attr in addresses)
1633 var addr = attr.get_value ();
1634 if (addr == null || (!) addr == "")
1639 string normalised_addr =
1640 (owned) ImDetails.normalise_im_address ((!) addr, im_proto);
1642 if (normalised_addr == "")
1647 var im_fd = new ImFieldDetails (normalised_addr);
1648 new_im_addresses.set (im_proto, im_fd);
1650 catch (Folks.ImDetailsError e)
1653 "Problem when trying to normalise address: %s\n",
1659 /* We consider some e-mail addresses to be IM IDs too. This
1660 * is pretty much a hack to make sure e-d-s contacts are
1661 * automatically linked with their corresponding Telepathy
1662 * Persona. As an undesired side effect we might end up having
1663 * IM addresses that aren't actually used as such (i.e.: people
1664 * who don't actually use GMail or MSN addresses for IM).
1668 foreach (var email in this._email_addresses)
1670 var _proto = this._im_proto_from_addr (email.value);
1673 var proto = (!) _proto;
1675 /* Has this already been added? */
1677 Collection<ImFieldDetails>? current_im_addrs =
1678 new_im_addresses.get (proto);
1679 if (current_im_addrs != null)
1681 foreach (var cur_im in (!) current_im_addrs)
1683 if (cur_im.value == email.value)
1696 string normalised_addr =
1697 (owned) ImDetails.normalise_im_address (email.value, proto);
1698 var im_fd = new ImFieldDetails (normalised_addr);
1699 new_im_addresses.set (proto, im_fd);
1701 catch (Folks.ImDetailsError e)
1704 "Problem when trying to normalise address: %s\n",
1710 if (!Utils.multi_map_str_afd_equal (new_im_addresses,
1711 this._im_addresses))
1713 this._im_addresses = new_im_addresses;
1714 this.notify_property ("im-addresses");
1718 private void _update_groups (bool create_if_not_exist, bool emit_notification = true)
1720 /* See the comments in Folks.Individual about the lazy instantiation
1721 * strategy for groups. */
1722 if (this._groups == null && create_if_not_exist == false)
1724 if (emit_notification)
1726 this.notify_property ("groups");
1731 var category_names =
1732 this._contact.get<GLib.List<string>> (E.ContactField.CATEGORY_LIST);
1733 var new_categories = new HashSet<string> ();
1734 bool any_added_categories = false;
1736 foreach (var category_name in category_names)
1738 /* Skip the “Starred in Android” group for Google personas; we handle
1740 if (((Edsf.PersonaStore) store)._is_google_contacts_address_book () &&
1741 category_name == Edsf.PersonaStore.android_favourite_group_name)
1746 new_categories.add (category_name);
1748 /* Is this a new category? */
1749 if (this._groups == null || !this._groups.contains (category_name))
1751 any_added_categories = true;
1755 /* Work out if categories have been removed. */
1756 bool any_removed_categories = false;
1758 if (this._groups != null)
1760 foreach (var category_name in this._groups)
1762 /* Skip the “Starred in Android” group for Google personas; we handle
1764 if (((Edsf.PersonaStore) store)._is_google_contacts_address_book () &&
1765 category_name == Edsf.PersonaStore.android_favourite_group_name)
1770 if (!new_categories.contains (category_name))
1772 any_removed_categories = true;
1777 this._groups = new_categories;
1778 this._groups_ro = this._groups.read_only_view;
1780 /* Check our new set of system groups if this is a Google address book. */
1781 var store = (Edsf.PersonaStore) this.store;
1782 var in_google_personal_group = false;
1783 var should_notify_sysgroups = false;
1785 if (store._is_google_contacts_address_book ())
1787 var vcard = (E.VCard) this.contact;
1788 unowned E.VCardAttribute? attr =
1789 vcard.get_attribute ("X-GOOGLE-SYSTEM-GROUP-IDS");
1792 unowned GLib.List<string> system_group_ids = attr.get_values();
1793 var new_sysgroups = new HashSet<string> ();
1794 bool any_added_sysgroups = false;
1796 foreach (var system_group_id in system_group_ids)
1798 new_sysgroups.add (system_group_id);
1800 if (this._system_groups == null ||
1801 !this._system_groups.contains (system_group_id))
1803 any_added_sysgroups = true;
1807 /* Work out if categories have been removed. */
1808 bool any_removed_sysgroups = false;
1810 if (this._system_groups != null)
1812 foreach (var system_group_id in this._system_groups)
1814 if (!new_sysgroups.contains (system_group_id))
1816 any_removed_sysgroups = true;
1821 /* If we're in the GDATA_CONTACTS_GROUP_CONTACTS group, then
1822 * we're in the user's "My Contacts" address book, as opposed
1823 * to their "Other" address book. */
1824 if (new_sysgroups.contains (GOOGLE_PERSONAL_GROUP_NAME))
1826 in_google_personal_group = true;
1829 this._system_groups = new_sysgroups;
1830 this._system_groups_ro = new_sysgroups.read_only_view;
1832 if (any_added_sysgroups || any_removed_sysgroups)
1833 should_notify_sysgroups = true;
1837 this._system_groups = new HashSet<string>();
1838 this._system_groups_ro = this._system_groups.read_only_view;
1842 /* Check whether our favourite status needs updating. */
1843 var old_is_favourite = this._is_favourite;
1845 if (store._is_google_contacts_address_book ())
1847 this._is_favourite = false;
1849 foreach (var category_name in category_names)
1851 /* We link the “Starred in Android” group to Google Contacts
1852 * address books. See: bgo#661490. */
1853 if (category_name ==
1854 Edsf.PersonaStore.android_favourite_group_name)
1856 this._is_favourite = true;
1861 /* Notify if anything's changed. */
1862 this.freeze_notify ();
1864 if ((any_added_categories || any_removed_categories) &&
1867 this.notify_property ("groups");
1869 if (should_notify_sysgroups && emit_notification)
1871 this.notify_property ("system-groups");
1873 if (this._is_favourite != old_is_favourite && emit_notification)
1875 this.notify_property ("is-favourite");
1877 if (in_google_personal_group != this._in_google_personal_group)
1879 this._in_google_personal_group = in_google_personal_group;
1880 if (emit_notification)
1882 this.notify_property ("in-google-personal-group");
1886 this.thaw_notify ();
1890 * build a table of im protocols / im protocol aliases
1892 internal static GLib.HashTable<string, E.ContactField> _get_im_eds_map ()
1894 GLib.HashTable<string, E.ContactField> retval;
1896 lock (Edsf.Persona._im_eds_map)
1898 if (Edsf.Persona._im_eds_map == null)
1901 new GLib.HashTable<string, E.ContactField> (str_hash,
1904 table.insert ("aim", ContactField.IM_AIM);
1905 table.insert ("yahoo", ContactField.IM_YAHOO);
1906 table.insert ("groupwise", ContactField.IM_GROUPWISE);
1907 table.insert ("jabber", ContactField.IM_JABBER);
1908 table.insert ("msn", ContactField.IM_MSN);
1909 table.insert ("icq", ContactField.IM_ICQ);
1910 table.insert ("gadugadu", ContactField.IM_GADUGADU);
1911 table.insert ("skype", ContactField.IM_SKYPE);
1913 Edsf.Persona._im_eds_map = table;
1916 retval = (!) Edsf.Persona._im_eds_map;
1922 private void _update_phones (bool create_if_not_exist, bool emit_notification = true)
1924 /* See the comments in Folks.Individual about the lazy instantiation
1925 * strategy for phone numbers. */
1926 if (this._phone_numbers == null && create_if_not_exist == false)
1928 this.notify_property ("phone-numbers");
1931 else if (this._phone_numbers == null)
1933 this._phone_numbers = new HashSet<PhoneFieldDetails> (
1934 AbstractFieldDetails<string>.hash_static,
1935 AbstractFieldDetails<string>.equal_static);
1936 this._phone_numbers_ro = this._phone_numbers.read_only_view;
1939 var new_phone_numbers = new HashSet<PhoneFieldDetails> (
1940 AbstractFieldDetails<string>.hash_static,
1941 AbstractFieldDetails<string>.equal_static);
1943 var attrs = this.contact.get_attributes (E.ContactField.TEL);
1944 foreach (var attr in attrs)
1946 var val = attr.get_value ();
1947 if (val == null || (!) val == "")
1952 var phone_fd = new PhoneFieldDetails ((!) val);
1953 this._update_params (phone_fd, attr);
1954 new_phone_numbers.add (phone_fd);
1957 if (!Folks.Internal.equal_sets<PhoneFieldDetails> (new_phone_numbers,
1958 this._phone_numbers))
1960 this._phone_numbers = new_phone_numbers;
1961 this._phone_numbers_ro = new_phone_numbers.read_only_view;
1962 if (emit_notification)
1964 this.notify_property ("phone-numbers");
1969 private PostalAddress _postal_address_from_attribute (E.VCardAttribute attr)
1971 unowned GLib.List<string>? values = attr.get_values();
1972 unowned GLib.List<string>? l = values;
1974 var address_format = "";
1980 var postal_code = "";
1985 po_box = ((!) l).data;
1990 extension = ((!) l).data;
1995 street = ((!) l).data;
2000 locality = ((!) l).data;
2005 region = ((!) l).data;
2010 postal_code = ((!) l).data;
2015 country = ((!) l).data;
2019 return new PostalAddress (po_box, extension, street,
2020 locality, region, postal_code, country,
2021 address_format, null);
2025 * TODO: we should check if addresses corresponding to different types
2026 * are the same and if so instantiate only one PostalAddress
2027 * (with the given types).
2029 private void _update_addresses (bool create_if_not_exist, bool emit_notification = true)
2031 /* See the comments in Folks.Individual about the lazy instantiation
2032 * strategy for addresses. */
2033 if (this._postal_addresses == null && create_if_not_exist == false)
2035 if (emit_notification)
2037 this.notify_property ("postal-addresses");
2041 else if (this._postal_addresses == null)
2043 this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
2044 AbstractFieldDetails<PostalAddress>.hash_static,
2045 AbstractFieldDetails<PostalAddress>.equal_static);
2046 this._postal_addresses_ro = this._postal_addresses.read_only_view;
2049 var new_postal_addresses = new HashSet<PostalAddressFieldDetails> (
2050 AbstractFieldDetails<PostalAddress>.hash_static,
2051 AbstractFieldDetails<PostalAddress>.equal_static);
2053 var attrs = this.contact.get_attributes (E.ContactField.ADDRESS);
2054 foreach (unowned E.VCardAttribute attr in attrs)
2056 var address = this._postal_address_from_attribute (attr);
2057 if (address.is_empty ())
2062 var pa_fd = new PostalAddressFieldDetails (address);
2063 this._update_params (pa_fd, attr);
2064 new_postal_addresses.add (pa_fd);
2067 if (!Folks.Internal.equal_sets<PostalAddressFieldDetails> (
2068 new_postal_addresses,
2069 this._postal_addresses))
2071 this._postal_addresses = new_postal_addresses;
2072 this._postal_addresses_ro = new_postal_addresses.read_only_view;
2073 if (emit_notification)
2075 this.notify_property ("postal-addresses");
2080 private void _update_local_ids ()
2082 var new_local_ids = new HashSet<string> ();
2084 var ids = this.contact.get_attribute ("X-FOLKS-CONTACTS-IDS");
2087 unowned GLib.List<string> ids_v = ((!) ids).get_values ();
2089 foreach (var local_id in ids_v)
2093 new_local_ids.add (local_id);
2098 /* Make sure it includes our local id */
2099 new_local_ids.add (this.iid);
2101 if (!Folks.Internal.equal_sets<string> (new_local_ids, this.local_ids))
2103 this._local_ids = new_local_ids;
2104 this._local_ids_ro = this._local_ids.read_only_view;
2105 this.notify_property ("local-ids");
2109 private void _update_favourite ()
2111 bool is_fav = false;
2113 var fav = this.contact.get_attribute ("X-FOLKS-FAVOURITE");
2116 var val = ((!) fav).get_value ();
2117 if (val != null && ((!) val).down () == "true")
2123 if (is_fav != this._is_favourite)
2125 this._is_favourite = is_fav;
2126 this.notify_property ("is-favourite");
2130 private void _update_anti_links ()
2132 var new_anti_links = new HashSet<string> ();
2134 var vcard = (E.VCard) this.contact;
2135 foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
2137 if (attr.get_name () != Edsf.PersonaStore.anti_links_attribute_name)
2142 var val = attr.get_value ();
2143 if (val == null || (!) val == "")
2148 new_anti_links.add ((!) val);
2151 if (!Folks.Internal.equal_sets<string> (new_anti_links, this._anti_links))
2153 this._anti_links = new_anti_links;
2154 this._anti_links_ro = new_anti_links.read_only_view;
2155 this.notify_property ("anti-links");
2159 internal static T? _get_property_from_contact<T> (E.Contact contact,
2162 T? prop_value = null;
2163 prop_value = contact.get<T> (E.Contact.field_id (prop_name));
2167 private T? _get_property<T> (string prop_name)
2169 return Edsf.Persona._get_property_from_contact<T> (this.contact,
2173 private string? _im_proto_from_addr (string addr)
2175 if (addr.index_of ("@") == -1)
2178 var tokens = addr.split ("@", 2);
2180 if (tokens.length != 2)
2183 var domain = tokens[1];
2184 if (domain.index_of (".") == -1)
2187 tokens = domain.split (".", 2);
2189 if (tokens.length != 2)
2194 if (domain == "msn" ||
2195 domain == "hotmail" ||
2198 else if (domain == "gmail" ||
2199 domain == "googlemail")
2201 else if (domain == "yahoo")