2
* Copyright (C) 2011 Collabora Ltd.
4
* This library is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser General Public License as published by
6
* the Free Software Foundation, either version 2.1 of the License, or
7
* (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public License
15
* along with this library. If not, see <http://www.gnu.org/licenses/>.
18
* Travis Reitter <travis.reitter@collabora.co.uk>
19
* Marco Barisione <marco.barisione@collabora.co.uk>
20
* Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
30
* A persona subclass which represents a single nco:Contact.
32
public class Trf.Persona : Folks.Persona,
49
private string _alias;
50
private bool _is_favourite;
51
private const string[] _linkable_properties =
52
{"im-addresses", "local-ids", "web-service-addresses"};
53
private HashSet<FieldDetails> _phone_numbers;
54
private Set<FieldDetails> _phone_numbers_ro;
55
private HashSet<FieldDetails> _email_addresses;
56
private Set<FieldDetails> _email_addresses_ro;
57
private weak Sparql.Cursor _cursor;
58
private string _tracker_id;
61
* An alias for the Persona.
63
* See {@link Folks.AliasDetails.alias}.
67
get { return this._alias; }
71
if (this._alias == value)
74
this.notify_property ("alias");
75
((Trf.PersonaStore) this.store)._set_alias (this, value);
82
public Set<FieldDetails> phone_numbers
84
get { return this._phone_numbers_ro; }
87
((Trf.PersonaStore) this.store)._set_phones (this, value);
94
public Set<FieldDetails> email_addresses
96
get { return this._email_addresses_ro; }
99
((Trf.PersonaStore) this.store)._set_emails (this, value);
106
public override string[] linkable_properties
108
get { return this._linkable_properties; }
111
private File _avatar;
113
* An avatar for the Persona.
115
* See {@link Folks.Avatar.avatar}.
119
get { return this._avatar; }
122
((Trf.PersonaStore) this.store)._set_avatar (this, value);
126
private StructuredName _structured_name;
130
public StructuredName structured_name
132
get { return this._structured_name; }
135
((Trf.PersonaStore) this.store)._set_structured_name (this, value);
139
private string _full_name;
143
public string full_name
145
get { return this._full_name; }
148
((Trf.PersonaStore) this.store)._set_full_name (this, value);
152
private string _nickname;
156
public string nickname { get { return this._nickname; } }
158
private Gender _gender;
164
get { return this._gender; }
167
((Trf.PersonaStore) this.store)._set_gender (this, value);
171
private DateTime _birthday;
175
public DateTime birthday
177
get { return this._birthday; }
180
((Trf.PersonaStore) this.store)._set_birthday (this, value);
184
public string calendar_event_id { get; set; }
186
private HashSet<Role> _roles;
187
private Set<Role> _roles_ro;
192
public Set<Role> roles
194
get { return this._roles_ro; }
197
((Trf.PersonaStore) this.store)._set_roles (this, value);
201
private HashSet<Note> _notes;
202
private Set<Note> _notes_ro;
207
public Set<Note> notes
209
get { return this._notes_ro; }
212
((Trf.PersonaStore) this.store)._set_notes (this, value);
216
private HashSet<FieldDetails> _urls;
217
private Set<FieldDetails> _urls_ro;
222
public Set<FieldDetails> urls
224
get { return this._urls_ro; }
227
((Trf.PersonaStore) this.store)._set_urls (this, value);
231
private HashSet<PostalAddress> _postal_addresses;
232
private Set<PostalAddress> _postal_addresses_ro;
237
public Set<PostalAddress> postal_addresses
239
get { return this._postal_addresses_ro; }
242
((Trf.PersonaStore) this.store)._set_postal_addresses (this, value);
246
private HashTable<string, HashTable<string, string>> _tracker_ids_ims =
247
new HashTable<string, HashTable<string, string>> (str_hash, str_equal);
249
private HashMultiMap<string, string> _im_addresses =
250
new HashMultiMap<string, string> ();
255
public MultiMap<string, string> im_addresses
257
get { return this._im_addresses; }
260
((Trf.PersonaStore) this.store)._set_im_addresses (this,
266
* Whether this contact is a user-defined favourite.
268
public bool is_favourite
270
get { return this._is_favourite; }
274
if (this._is_favourite == value)
278
* this property will be set (and notified)
279
* once we receive a notification event from Tracker
281
((Trf.PersonaStore) this.store)._set_is_favourite (this, value);
285
private HashSet<string> _local_ids;
286
private Set<string> _local_ids_ro;
289
* IDs used to link {@link Trf.Persona}s.
291
public Set<string> local_ids
295
if (this._local_ids.contains (this.uid) == false)
297
this._local_ids.add (this.uid);
299
return this._local_ids_ro;
303
if (value.contains (this.uid) == false)
305
value.add (this.uid);
307
((Trf.PersonaStore) this.store)._set_local_ids (this, value);
311
private HashMultiMap<string, string> _web_service_addresses =
312
new HashMultiMap<string, string> ();
317
public MultiMap<string, string> web_service_addresses
319
get { return this._web_service_addresses; }
322
((Trf.PersonaStore) this.store)._set_web_service_addrs (this, value);
329
* @param store_id the {@link PersonaStore.id}
330
* @param tracker_id the tracker id belonging to nco:PersonContact
331
* @return a valid IID
335
internal static string build_iid (string store_id, string tracker_id)
337
return "%s:%s".printf (store_id, tracker_id);
341
* Create a new persona.
343
* Create a new persona for the {@link PersonaStore} `store`, representing
344
* the nco:Contact whose details are stored in the cursor.
346
public Persona (PersonaStore store, string tracker_id,
347
Sparql.Cursor? cursor = null)
349
string uid = this.build_uid (BACKEND_NAME, store.id, tracker_id);
350
string iid = this.build_iid (store.id, tracker_id);
351
bool is_user = false;
352
string fullname = "";
356
fullname = cursor.get_string (Trf.Fields.FULL_NAME).dup ();
357
var contact_urn = cursor.get_string (Trf.Fields.CONTACT_URN);
358
if (contact_urn == Trf.OntologyDefs.DEFAULT_CONTACT_URN)
364
debug ("Creating new Trf.Persona with iid '%s'", iid);
366
Object (display_id: fullname,
372
this._gender = Gender.UNSPECIFIED;
373
this._full_name = fullname;
374
this._tracker_id = tracker_id;
375
this._structured_name = new StructuredName (null, null, null, null, null);
376
this._phone_numbers = new HashSet<FieldDetails> ();
377
this._phone_numbers_ro = this._phone_numbers.read_only_view;
378
this._email_addresses = new HashSet<FieldDetails> ();
379
this._email_addresses_ro = this._email_addresses.read_only_view;
380
this._roles = new HashSet<Role> ((GLib.HashFunc) Role.hash,
381
(GLib.EqualFunc) Role.equal);
382
this._roles_ro = this._roles.read_only_view;
383
this._notes = new HashSet<Note> ((GLib.HashFunc) Note.hash,
384
(GLib.EqualFunc) Note.equal);
385
this._notes_ro = this._notes.read_only_view;
386
this._urls = new HashSet<FieldDetails> ();
387
this._urls_ro = this._urls.read_only_view;
388
this._postal_addresses = new HashSet<PostalAddress> ();
389
this._postal_addresses_ro = this._postal_addresses.read_only_view;
390
this._local_ids = new HashSet<string> ();
391
this._local_ids_ro = this._local_ids.read_only_view;
395
this._cursor = cursor;
400
internal string tracker_id ()
402
return this._tracker_id;
408
public override void linkable_property_to_links (string prop_name,
409
Folks.Persona.LinkablePropertyCallback callback)
411
if (prop_name == "im-addresses")
413
foreach (var protocol in this._im_addresses.get_keys ())
415
var im_addresses = this._im_addresses.get (protocol);
417
foreach (string address in im_addresses)
418
callback (protocol + ":" + address);
421
else if (prop_name == "local-ids")
423
foreach (var id in this._local_ids)
428
else if (prop_name == "web-service-addresses")
430
foreach (var web_service in this._web_service_addresses.get_keys ())
432
var web_service_addresses =
433
this._web_service_addresses.get (web_service);
435
foreach (string address in web_service_addresses)
436
callback (web_service + ":" + address);
442
base.linkable_property_to_links (prop_name, callback);
448
debug ("Destroying Trf.Persona '%s': %p", this.uid, this);
451
internal void _update_full_name (string? fn)
453
if (fn != null && this.full_name != fn)
455
this._full_name = fn;
456
this.notify_property ("full-name");
460
internal void _update_nickname (string? nickname)
462
if (nickname != null && this._nickname != nickname)
464
this._nickname = nickname;
465
this.notify_property ("nickname");
469
internal void _update_alias (string? alias)
471
if (alias != null && this._alias != alias)
474
this.notify_property ("alias");
478
internal void _update_family_name (string? family_name)
480
if (family_name != null)
482
this._structured_name.family_name = family_name;
483
this.notify_property ("structured-name");
487
internal void _update_given_name (string? given_name)
489
if (given_name != null)
491
this._structured_name.given_name = given_name;
492
this.notify_property ("structured-name");
496
internal void _update_additional_names (string? additional_names)
498
if (additional_names != null)
500
this._structured_name.additional_names = additional_names;
501
this.notify_property ("structured-name");
505
internal void _update_prefixes (string? prefixes)
507
if (prefixes != null)
509
this._structured_name.prefixes = prefixes;
510
this.notify_property ("structured-name");
514
internal void _update_suffixes (string? suffixes)
516
if (suffixes != null)
518
this._structured_name.suffixes = suffixes;
519
this.notify_property ("structured-name");
523
internal void _update ()
525
this._update_names ();
526
this._update_avatar ();
527
this._update_im_addresses ();
528
this._update_phones ();
529
this._update_email_addresses ();
530
this._update_urls ();
531
this._update_favourite ();
532
this._update_roles ();
533
this._update_bday ();
534
this._update_note ();
535
this._update_gender ();
536
this._update_postal_addresses ();
537
this._update_local_ids ();
540
private void _update_postal_addresses ()
542
string postal_field = this._cursor.get_string
543
(Trf.Fields.POSTAL_ADDRESS).dup ();
545
if (postal_field == null)
550
var postal_addresses = new HashSet<PostalAddress> ();
552
string[] addresses_a = postal_field.split ("\n");
554
foreach (var a in addresses_a)
556
bool address_empty = true;
557
string[] a_info = a.split ("\t");
558
for (int i = 0; i < a_info.length; i++)
560
if (a_info[i] != null && a_info[i] != "")
562
address_empty = false;
570
var types = new HashSet<string> ();
572
var pa = new PostalAddress (a_info[Trf.PostalAddressFields.POBOX],
573
a_info[Trf.PostalAddressFields.EXTENDED_ADDRESS],
574
a_info[Trf.PostalAddressFields.STREET_ADDRESS],
575
a_info[Trf.PostalAddressFields.LOCALITY],
576
a_info[Trf.PostalAddressFields.REGION],
577
a_info[Trf.PostalAddressFields.POSTALCODE],
578
a_info[Trf.PostalAddressFields.COUNTRY],
580
a_info[Trf.PostalAddressFields.TRACKER_ID]);
582
postal_addresses.add (pa);
585
this._postal_addresses = postal_addresses;
586
this._postal_addresses_ro = this._postal_addresses.read_only_view;
589
private void _update_local_ids ()
591
string local_ids = this._cursor.get_string
592
(Trf.Fields.LOCAL_IDS_PROPERTY).dup ();
594
if (local_ids == null)
599
this._set_local_ids (local_ids);
602
internal bool _add_postal_address (PostalAddress postal_address)
604
foreach (var pa in this._postal_addresses)
606
if (postal_address.equal (pa))
612
this._postal_addresses.add (postal_address);
613
this.notify_property ("postal-addresses");
617
internal bool _remove_postal_address (string tracker_id)
619
foreach (var pa in this._postal_addresses)
621
if (pa.uid == tracker_id)
623
this._postal_addresses.remove (pa);
624
this.notify_property ("postal-addresses");
631
private void _update_gender ()
633
string gender = this._cursor.get_string (Trf.Fields.GENDER).dup ();
638
gender_id = int.parse (gender);
641
this._set_gender (gender_id);
644
internal void _set_gender (int gender_id)
648
this._gender = Gender.UNSPECIFIED;
652
var trf_store = (Trf.PersonaStore) this.store;
654
if (gender_id == trf_store.get_gender_male_id ())
656
this._gender = Gender.MALE;
658
else if (gender_id == trf_store.get_gender_female_id ())
660
this._gender = Gender.FEMALE;
664
this.notify_property ("gender");
667
private void _update_note ()
669
string note = this._cursor.get_string (Trf.Fields.NOTE).dup ();
670
this._set_note (note);
673
internal void _set_note (string? note_content)
675
if (note_content != null)
677
var note = new Note (note_content);
678
this._notes.add ((owned) note);
682
this._notes.clear ();
684
this.notify_property ("notes");
687
private void _update_bday ()
689
string bday = this._cursor.get_string (Trf.Fields.BIRTHDAY).dup ();
690
this._set_birthday (bday);
693
internal void _set_birthday (string? birthday)
695
if (birthday != null && birthday != "")
697
TimeVal t = TimeVal ();
698
t.from_iso8601 (birthday);
699
this._birthday = new DateTime.from_timeval_utc (t);
700
this.notify_property ("birthday");
704
if (this._birthday != null)
706
this._birthday = null;
707
this.notify_property ("birthday");
712
private void _update_roles ()
714
string roles_field = this._cursor.get_string (
715
Trf.Fields.ROLES).dup ();
717
if (roles_field == null)
722
HashSet<Role> roles = new HashSet<Role> (
723
(GLib.HashFunc) Role.hash,
724
(GLib.EqualFunc) Role.equal);
726
string[] roles_a = roles_field.split ("\n");
728
foreach (var r in roles_a)
730
string[] r_info = r.split ("\t");
731
var tracker_id = r_info[Trf.RoleFields.TRACKER_ID];
732
var title = r_info[Trf.RoleFields.ROLE];
733
var organisation = r_info[Trf.RoleFields.DEPARTMENT];
735
var role = new Role (title, organisation, tracker_id);
740
this._roles_ro = this._roles.read_only_view;
743
internal bool _add_role (string tracker_id, string? title, string? org)
745
var role = new Role (title, org, tracker_id);
746
if (this._roles.add (role))
748
this.notify_property ("roles");
754
internal bool _remove_role (string tracker_id)
756
foreach (var r in this._roles)
758
if (r.uid == tracker_id)
760
this._roles.remove (r);
761
this.notify_property ("roles");
769
private void _update_names ()
771
string fullname = this._cursor.get_string (Trf.Fields.FULL_NAME).dup ();
772
this._update_full_name (fullname);
774
string alias = this._cursor.get_string (Trf.Fields.ALIAS).dup ();
775
this._update_alias (alias);
777
string family_name = this._cursor.get_string (
778
Trf.Fields.FAMILY_NAME).dup ();
779
this._update_family_name (family_name);
781
string given_name = this._cursor.get_string (
782
Trf.Fields.GIVEN_NAME).dup ();
783
this._update_given_name (given_name);
785
string additional_names = this._cursor.get_string (
786
Trf.Fields.ADDITIONAL_NAMES).dup ();
787
this._update_additional_names (additional_names);
789
string prefixes = this._cursor.get_string (Trf.Fields.PREFIXES).dup ();
790
this._update_prefixes (prefixes);
792
string suffixes = this._cursor.get_string (Trf.Fields.SUFFIXES).dup ();
793
this._update_suffixes (suffixes);
796
private void _update_avatar ()
798
string avatar_url = this._cursor.get_string (
799
Trf.Fields.AVATAR_URL).dup ();
800
this._set_avatar (avatar_url);
803
internal bool _set_avatar (string? avatar_url)
806
if (avatar_url != null && avatar_url != "")
808
_avatar = File.new_for_uri (avatar_url);
810
this._avatar = _avatar;
811
this.notify_property ("avatar");
815
internal bool _set_local_ids (string local_ids)
818
(HashSet<string>) Trf.PersonaStore.unserialize_local_ids (local_ids);
819
this._local_ids_ro = this._local_ids.read_only_view;
820
this.notify_property ("local-ids");
824
internal bool _set_web_service_addrs (string ws_addrs)
826
this._web_service_addresses =
827
(HashMultiMap<string, string>)
828
Trf.PersonaStore.unserialize_web_services (ws_addrs);
829
this.notify_property ("web-service-addresses");
833
private void _update_im_addresses ()
835
string addresses = this._cursor.get_string (
836
Trf.Fields.IM_ADDRESSES).dup ();
838
if (addresses == null)
843
this._im_addresses.clear ();
845
string[] addresses_a = addresses.split ("\n");
847
foreach (var addr in addresses_a)
849
string[] addr_info = addr.split ("\t");
850
var tracker_id = addr_info[Trf.IMFields.TRACKER_ID];
851
var proto = addr_info[Trf.IMFields.PROTO];
852
var account_id = addr_info[Trf.IMFields.ID];
853
var nickname = addr_info[Trf.IMFields.IM_NICKNAME];
855
this._update_nickname (nickname);
856
this._add_im_address (tracker_id, proto, account_id, false);
859
this.notify_property ("im-addresses");
862
internal bool _add_im_address (string tracker_id, string im_proto,
863
string account_id, bool notify = true)
867
var account_id_copy = account_id.dup ();
868
var normalised_addr = (owned) normalise_im_address
869
((owned) account_id_copy, im_proto);
871
this._im_addresses.set (im_proto, normalised_addr);
873
var im_proto_hash = new HashTable<string, string> (str_hash,
875
var proto_copy_2 = im_proto.dup ();
876
var account_id_copy_2 = account_id.dup ();
877
im_proto_hash.insert ((owned) proto_copy_2,
878
(owned) account_id_copy_2);
879
var tracker_id_copy = tracker_id.dup ();
880
this._tracker_ids_ims.insert ((owned) tracker_id_copy,
881
(owned) im_proto_hash);
885
this.notify_property ("im-addresses");
888
catch (Folks.ImDetailsError e)
891
"Problem when trying to normalise address: %s\n",
898
internal bool _remove_im_address (string tracker_id, bool notify = true)
900
var proto_im = this._tracker_ids_ims.lookup (tracker_id);
902
if (proto_im == null)
905
var proto = proto_im.get_keys ().nth_data (0);
906
var im_addr = proto_im.lookup (proto);
908
if (this._im_addresses.remove (proto, im_addr))
910
this._tracker_ids_ims.remove (tracker_id);
913
this.notify_property ("im-addresses");
922
private void _update_phones ()
924
string phones_field = this._cursor.get_string (Trf.Fields.PHONES).dup ();
926
if (phones_field == null)
931
var phones = new HashSet<FieldDetails> ();
932
string[] phones_a = phones_field.split ("\n");
934
foreach (var p in phones_a)
936
if (p != null && p != "")
938
string[] p_info = p.split ("\t");
939
var fd = new FieldDetails (p_info[Trf.PhoneFields.PHONE]);
940
fd.set_parameter ("tracker_id",
941
p_info[Trf.PhoneFields.TRACKER_ID]);
946
this._phone_numbers = phones;
947
this._phone_numbers_ro = this._phone_numbers.read_only_view;
950
internal bool _add_phone (string phone, string tracker_id)
954
foreach (var p in this._phone_numbers)
956
if (p.get_parameter_values ("tracker_id").contains (tracker_id))
965
var fd = new FieldDetails (phone);
966
fd.set_parameter ("tracker_id", tracker_id);
967
this._phone_numbers.add (fd);
968
this.notify_property ("phone-numbers");
974
internal bool _remove_phone (string tracker_id)
978
foreach (var p in this._phone_numbers)
980
if (p.get_parameter_values ("tracker_id").contains (tracker_id))
982
this._phone_numbers.remove (p);
990
this.notify_property ("phone-numbers");
996
internal bool _add_email (string addr, string tracker_id)
1000
foreach (var e in this._email_addresses)
1002
if (e.get_parameter_values ("tracker_id").contains (tracker_id))
1011
var fd = new FieldDetails (addr);
1012
fd.set_parameter ("tracker_id", tracker_id);
1013
this._email_addresses.add (fd);
1014
this.notify_property ("email-addresses");
1020
internal bool _remove_email (string tracker_id)
1024
foreach (var e in this._email_addresses)
1026
if (e.get_parameter_values ("tracker_id").contains (tracker_id))
1028
this._email_addresses.remove (e);
1036
this.notify_property ("email-addresses");
1042
private void _update_email_addresses ()
1044
string emails_field = this._cursor.get_string (Trf.Fields.EMAILS).dup ();
1046
if (emails_field == null)
1051
var email_addresses = new HashSet<FieldDetails> ();
1052
string[] emails_a = emails_field.split (",");
1054
foreach (var e in emails_a)
1056
if (e != null && e != "")
1058
string[] id_addr = e.split ("\t");
1059
var fd = new FieldDetails (id_addr[Trf.EmailFields.EMAIL]);
1060
fd.set_parameter ("tracker_id",
1061
id_addr[Trf.EmailFields.TRACKER_ID]);
1062
email_addresses.add (fd);
1066
this._email_addresses = email_addresses;
1067
this._email_addresses_ro = this._email_addresses.read_only_view;
1070
private void _update_urls ()
1072
var urls = new HashSet<FieldDetails> ();
1073
var _urls_field = this._cursor.get_string (Trf.Fields.URLS).dup ();
1075
if (_urls_field == null)
1078
string[] urls_table = _urls_field.split ("\n");
1080
foreach (var row in urls_table)
1082
string[] u = row.split ("\t");
1083
var tracker_id = u[Trf.UrlsFields.TRACKER_ID];
1085
for (int i=1; i< u.length; i++)
1087
if (u[i] == null || u[i] == "")
1093
case Trf.UrlsFields.BLOG:
1096
case Trf.UrlsFields.WEBSITE:
1099
case Trf.UrlsFields.URL:
1104
var fd = new FieldDetails (u[i]);
1105
fd.set_parameter ("tracker_id", tracker_id);
1106
fd.set_parameter ("type", type);
1112
this._urls_ro = this._urls.read_only_view;
1115
internal bool _add_url (string url, string tracker_id, string type = "")
1119
foreach (var p in this._urls)
1121
if (p.get_parameter_values ("tracker_id").contains (tracker_id))
1130
var fd = new FieldDetails (url);
1131
fd.set_parameter ("tracker_id", tracker_id);
1132
fd.set_parameter ("type", type);
1133
this._urls.add (fd);
1134
this.notify_property ("urls");
1140
internal bool _remove_url (string tracker_id)
1144
foreach (var u in this._urls)
1146
if (u.get_parameter_values ("tracker_id").contains (tracker_id))
1148
this._urls.remove (u);
1154
this.notify_property ("urls");
1159
private void _update_favourite ()
1161
var favourite = this._cursor.get_string (Trf.Fields.FAVOURITE).dup ();
1163
this._is_favourite = false;
1165
if (favourite != null)
1167
var trf_store = (Trf.PersonaStore) this.store;
1168
int favorite_tracker_id = trf_store.get_favorite_id ();
1169
foreach (var tag in favourite.split (","))
1171
if (int.parse (tag) == favorite_tracker_id)
1173
this._is_favourite = true;
1180
* This method sets the is_favourite attribute internally.
1181
* That is, it should be used as a result of an event fired by
1182
* Tracker since this method doesn't propagate changes back
1185
internal void _set_favourite (bool is_fav)
1187
this._is_favourite = is_fav;
1188
this.notify_property ("is-favourite");