edsf-persona.vala 52.1 KB
Newer Older
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
 * Copyright (C) 2011 Collabora Ltd.
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *       Travis Reitter <travis.reitter@collabora.co.uk>
 *       Marco Barisione <marco.barisione@collabora.co.uk>
 *       Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
 */

using E;
using Folks;
using Gee;
using GLib;
using Xml;

/**
 * A persona subclass which represents a single EDS contact.
 */
public class Edsf.Persona : Folks.Persona,
33
    AntiLinkable,
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
34
    AvatarDetails,
35
    BirthdayDetails,
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
36
    EmailDetails,
37
    FavouriteDetails,
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
38
    GenderDetails,
39
    GroupDetails,
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
40
41
42
43
44
    ImDetails,
    LocalIdDetails,
    NameDetails,
    NoteDetails,
    PhoneDetails,
45
    RoleDetails,
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
46
47
48
49
50
    UrlDetails,
    PostalAddressDetails,
    WebServiceDetails
{
  /* The following 4 definitions are used by the tests */
51
52
53
54
55
  /**
   * vCard field names for telephone numbers.
   *
   * @since 0.6.0
   */
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
56
57
58
59
60
  public static const string[] phone_fields = {
    "assistant_phone", "business_phone", "business_phone_2", "callback_phone",
    "car_phone", "company_phone", "home_phone", "home_phone_2", "isdn_phone",
    "mobile_phone", "other_phone", "primary_phone"
  };
61
62
63
64
65
  /**
   * vCard field names for postal addresses.
   *
   * @since 0.6.0
   */
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
66
67
68
  public static const string[] address_fields = {
    "address_home", "address_other", "address_work"
  };
69
70
71
72
73
  /**
   * vCard field names for e-mail addresses.
   *
   * @since 0.6.0
   */
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
74
75
76
  public static const string[] email_fields = {
    "email_1", "email_2", "email_3", "email_4"
  };
77
78

  [Deprecated]
79
80
81
82
83
  /**
   * vCard field names for miscellaneous URIs.
   *
   * @since 0.6.0
   */
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
84
85
86
  public static const string[] url_properties = {
    "blog_url", "fburl", "homepage_url", "video_url"
  };
87

88
89
90
91
92
93
94
95
96
97
98
99
  /* Some types of URLs are represented in EDS using custom vCard fields rather
   * than the X-URIS field. Here are mappings between the custom vCard field
   * names which EDS uses, and the TYPE values which folks uses which map to
   * them. */
  private struct UrlTypeMapping
    {
      string vcard_field_name;
      string folks_type;
    }

  internal static const UrlTypeMapping[] _url_properties =
    {
100
      { "homepage_url", UrlFieldDetails.PARAM_TYPE_HOME_PAGE },
101
102
103
104
105
      { "blog_url", UrlFieldDetails.PARAM_TYPE_BLOG },
      { "fburl", "x-free-busy" },
      { "video_url", "x-video" }
    };

106
107
108
109
  /**
   * The vCard attribute used to specify a Contact's gender
   *
   * Based on:
110
   * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
111
112
113
114
115
   *
   * Note that the above document is a draft and the gender property
   * is still considered experimental, hence the "X-" prefix in the
   * attribute name. So this might change.
   *
Travis Reitter's avatar
Travis Reitter committed
116
   * @since 0.6.0
117
118
119
120
121
122
123
124
   */
  public static const string gender_attribute_name = "X-GENDER";

  /**
   * The value used to define the male gender for the
   * X-GENDER vCard property.
   *
   * Based on:
125
   * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
126
   *
Travis Reitter's avatar
Travis Reitter committed
127
   * @since 0.6.0
128
129
130
131
132
133
134
135
   */
  public static const string gender_male = "M";

  /**
   * The value used to define the female gender for the
   * X-GENDER vCard property.
   *
   * Based on:
136
   * [[http://tools.ietf.org/html/draft-ietf-vcarddav-vcardrev-22]]
137
   *
Travis Reitter's avatar
Travis Reitter committed
138
   * @since 0.6.0
139
140
141
   */
  public static const string gender_female = "F";

Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
142
143
144
  private const string[] _linkable_properties = { "im-addresses",
                                                  "local-ids",
                                                  "web-service-addresses" };
145

146
147
  private HashSet<PhoneFieldDetails> _phone_numbers;
  private Set<PhoneFieldDetails> _phone_numbers_ro;
148
149
  private HashSet<EmailFieldDetails> _email_addresses;
  private Set<EmailFieldDetails> _email_addresses_ro;
150
151
  private HashSet<NoteFieldDetails> _notes;
  private Set<NoteFieldDetails> _notes_ro;
Philip Withnall's avatar
Philip Withnall committed
152
  private static HashTable<string, E.ContactField>? _im_eds_map = null;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
153

154
155
  private HashSet<PostalAddressFieldDetails> _postal_addresses;
  private Set<PostalAddressFieldDetails> _postal_addresses_ro;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
156
157
158
159

  private HashSet<string> _local_ids;
  private Set<string> _local_ids_ro;

160
  private HashMultiMap<string, WebServiceFieldDetails> _web_service_addresses;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
161

162
163
  private bool _is_favourite;

164
165
  private E.Contact _contact; /* should be set on construct */

Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
166
167
168
169
170
  /**
   * The e-d-s contact represented by this Persona
   */
  public E.Contact contact
    {
171
172
      get { return this._contact; }
      construct { this._contact = value; }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
173
174
175
176
177
    }

  /**
   * {@inheritDoc}
   */
178
  [CCode (notify = false)]
179
  public MultiMap<string, WebServiceFieldDetails> web_service_addresses
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
180
181
    {
      get { return this._web_service_addresses; }
182
183
184
185
186
187
      set { this.change_web_service_addresses.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
188
   * @since 0.6.2
189
190
191
192
193
194
195
   */
  public async void change_web_service_addresses (
      MultiMap<string, WebServiceFieldDetails> web_service_addresses)
          throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_web_service_addresses (this,
          web_service_addresses);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
196
197
198
199
200
    }

  /**
   * IDs used to link {@link Edsf.Persona}s.
   */
201
  [CCode (notify = false)]
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
202
203
204
205
  public Set<string> local_ids
    {
      get
        {
206
          if (this._local_ids.contains (this.iid) == false)
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
207
            {
208
              this._local_ids.add (this.iid);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
209
210
211
            }
          return this._local_ids_ro;
        }
212
213
214
215
216
217
      set { this.change_local_ids.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
218
   * @since 0.6.2
219
220
221
222
223
   */
  public async void change_local_ids (Set<string> local_ids)
      throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_local_ids (this, local_ids);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
224
225
226
227
228
229
230
    }

  /**
   * The postal addresses of the contact.
   *
   * A list of postal addresses associated to the contact.
   *
Travis Reitter's avatar
Travis Reitter committed
231
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
232
   */
233
  [CCode (notify = false)]
234
  public Set<PostalAddressFieldDetails> postal_addresses
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
235
236
    {
      get { return this._postal_addresses_ro; }
237
238
239
240
241
242
      set { this.change_postal_addresses.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
243
   * @since 0.6.2
244
245
246
247
248
249
   */
  public async void change_postal_addresses (
      Set<PostalAddressFieldDetails> postal_addresses) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_postal_addresses (this,
          postal_addresses);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
250
251
252
253
254
    }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
255
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
256
   */
257
  [CCode (notify = false)]
258
  public Set<PhoneFieldDetails> phone_numbers
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
259
260
    {
      get { return this._phone_numbers_ro; }
261
262
263
264
265
266
      set { this.change_phone_numbers.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
267
   * @since 0.6.2
268
269
270
271
272
   */
  public async void change_phone_numbers (
      Set<PhoneFieldDetails> phone_numbers) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_phones (this, phone_numbers);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
273
274
275
276
277
    }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
278
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
279
   */
280
  [CCode (notify = false)]
281
  public Set<EmailFieldDetails> email_addresses
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
282
283
    {
      get { return this._email_addresses_ro; }
284
285
286
287
288
289
      set { this.change_email_addresses.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
290
   * @since 0.6.2
291
292
293
294
295
296
   */
  public async void change_email_addresses (
      Set<EmailFieldDetails> email_addresses) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_emails (this,
          email_addresses);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
297
298
299
300
301
    }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
302
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
303
   */
304
  [CCode (notify = false)]
305
  public Set<NoteFieldDetails> notes
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
306
307
    {
      get { return this._notes_ro; }
308
309
310
311
312
313
      set { this.change_notes.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
314
   * @since 0.6.2
315
316
317
318
319
   */
  public async void change_notes (Set<NoteFieldDetails> notes)
      throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_notes (this, notes);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
320
321
322
323
324
    }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
325
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
326
327
328
329
330
331
   */
  public override string[] linkable_properties
    {
      get { return this._linkable_properties; }
    }

332
333
334
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
335
   * @since 0.6.0
336
337
338
   */
  public override string[] writeable_properties
    {
339
      get { return this.store.always_writeable_properties; }
340
341
    }

342
  private LoadableIcon? _avatar = null;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
343
344
345
  /**
   * An avatar for the Persona.
   *
346
   * See {@link Folks.AvatarDetails.avatar}.
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
347
   *
Travis Reitter's avatar
Travis Reitter committed
348
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
349
   */
350
  [CCode (notify = false)]
351
  public LoadableIcon? avatar
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
352
353
    {
      get { return this._avatar; }
354
355
356
357
358
359
      set { this.change_avatar.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
360
   * @since 0.6.2
361
362
363
   */
  public async void change_avatar (LoadableIcon? avatar) throws PropertyError
    {
364
      yield ((Edsf.PersonaStore) this.store)._set_avatar (this, avatar);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
365
366
    }

367
  private StructuredName? _structured_name = null;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
368
369
370
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
371
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
372
   */
373
  [CCode (notify = false)]
374
  public StructuredName? structured_name
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
375
376
    {
      get { return this._structured_name; }
377
378
379
380
381
382
      set { this.change_structured_name.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
383
   * @since 0.6.2
384
385
386
387
388
389
   */
  public async void change_structured_name (StructuredName? structured_name)
      throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_structured_name (this,
          structured_name);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
390
391
392
393
394
    }

  /**
   * The e-d-s contact uid
   *
Travis Reitter's avatar
Travis Reitter committed
395
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
396
   */
397
  public string contact_id { get; construct; }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
398

399
  private string _full_name = "";
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
400
401
402
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
403
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
404
   */
405
  [CCode (notify = false)]
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
406
407
408
  public string full_name
    {
      get { return this._full_name; }
409
      set { this.change_full_name.begin (value); }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
410
411
    }

412
413
414
  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
415
   * @since 0.6.2
416
417
418
419
420
421
422
   */
  public async void change_full_name (string full_name) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_full_name (this, full_name);
    }

  private string _nickname = "";
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
423
424
425
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
426
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
427
   */
428
  [CCode (notify = false)]
429
430
431
  public string nickname
    {
      get { return this._nickname; }
432
433
      set { this.change_nickname.begin (value); }
    }
434

435
436
437
  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
438
   * @since 0.6.2
439
440
441
442
   */
  public async void change_nickname (string nickname) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_nickname (this, nickname);
443
    }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
444

445
  private Gender _gender;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
446
447
448
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
449
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
450
   */
451
  [CCode (notify = false)]
452
453
454
  public Gender gender
    {
      get { return this._gender; }
455
456
457
458
459
460
      set { this.change_gender.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
461
   * @since 0.6.2
462
463
464
465
   */
  public async void change_gender (Gender gender) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_gender (this, gender);
466
    }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
467

468
469
  private HashSet<UrlFieldDetails> _urls;
  private Set<UrlFieldDetails> _urls_ro;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
470
471
472
  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
473
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
474
   */
475
  [CCode (notify = false)]
476
  public Set<UrlFieldDetails> urls
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
477
478
    {
      get { return this._urls_ro; }
479
480
481
482
483
484
      set { this.change_urls.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
485
   * @since 0.6.2
486
487
488
489
   */
  public async void change_urls (Set<UrlFieldDetails> urls) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_urls (this, urls);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
490
491
    }

492
493
  private HashMultiMap<string, ImFieldDetails> _im_addresses =
      new HashMultiMap<string, ImFieldDetails> (null, null,
Jeremy Whiting's avatar
Jeremy Whiting committed
494
495
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
496
497
498
499

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
500
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
501
   */
502
  [CCode (notify = false)]
503
  public MultiMap<string, ImFieldDetails> im_addresses
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
504
505
    {
      get { return this._im_addresses; }
506
507
508
509
510
511
      set { this.change_im_addresses.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
512
   * @since 0.6.2
513
514
515
516
517
   */
  public async void change_im_addresses (
      MultiMap<string, ImFieldDetails> im_addresses) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_im_fds (this, im_addresses);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
518
519
    }

520
521
522
523
524
525
  private HashSet<string> _groups;
  private Set<string> _groups_ro;

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
526
   * @since 0.6.0
527
   */
528
  [CCode (notify = false)]
529
530
531
  public Set<string> groups
    {
      get { return this._groups_ro; }
532
      set { this.change_groups.begin (value); }
533
534
535
536
537
    }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
538
   * @since 0.6.0
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
   */
  public async void change_group (string group, bool is_member)
      throws GLib.Error
    {
      /* Nothing to do? */
      if ((is_member == true && this._groups.contains (group) == true) ||
          (is_member == false && this._groups.contains (group) == false))
        {
          return;
        }

      /* Replace the current set of groups with a modified one. */
      var new_groups = new HashSet<string> ();
      foreach (var category_name in this._groups)
        {
          new_groups.add (category_name);
        }

      if (is_member == false)
        {
          new_groups.remove (group);
        }
      else
        {
          new_groups.add (group);
        }

566
567
568
569
570
571
      yield this.change_groups (new_groups);
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
572
   * @since 0.6.2
573
574
575
576
   */
  public async void change_groups (Set<string> groups) throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_groups (this, groups);
577
578
    }

579
580
581
582
583
  /**
   * {@inheritDoc}
   *
   * e-d-s has no equivalent field, so this is unsupported.
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
584
   * @since 0.6.2
585
586
587
588
589
590
591
592
   */
  [CCode (notify = false)]
  public string? calendar_event_id
    {
      get { return null; } /* unsupported */
      set { this.change_calendar_event_id.begin (value); } /* not writeable */
    }

593
594
595
596
  /* We cache the timezone we use for converting birthdays to UTC since creating
   * it requires mmapping /etc/localtime, which means lots of syscalls. */
  private static TimeZone _local_time_zone = new TimeZone.local ();

597
598
599
600
  private DateTime? _birthday = null;
  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
601
   * @since 0.6.2
602
603
604
605
606
607
608
609
610
611
612
   */
  [CCode (notify = false)]
  public DateTime? birthday
    {
      get { return this._birthday; }
      set { this.change_birthday.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
613
   * @since 0.6.2
614
615
616
617
618
619
620
621
   */
  public async void change_birthday (DateTime? bday)
      throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_birthday (this,
          bday);
    }

622
623
624
625
626
627
  private HashSet<RoleFieldDetails> _roles;
  private Set<RoleFieldDetails> _roles_ro;

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
628
   * @since 0.6.2
629
630
631
632
633
634
635
636
637
638
639
   */
  [CCode (notify = false)]
  public Set<RoleFieldDetails> roles
    {
      get { return this._roles_ro; }
      set { this.change_roles.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
640
   * @since 0.6.2
641
642
643
644
645
646
647
   */
  public async void change_roles (Set<RoleFieldDetails> roles)
      throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_roles (this, roles);
    }

648
649
650
  /**
   * Whether this contact is a user-defined favourite.
   *
Travis Reitter's avatar
Travis Reitter committed
651
   * @since 0.6.5
652
653
654
655
656
657
658
659
660
661
662
   */
  [CCode (notify = false)]
  public bool is_favourite
      {
        get { return this._is_favourite; }
        set { this.change_is_favourite.begin (value); }
      }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
663
   * @since 0.6.5
664
665
666
667
668
669
670
671
672
673
674
675
   */
  public async void change_is_favourite (bool is_favourite) throws PropertyError
    {
      if (this._is_favourite == is_favourite)
        {
          return;
        }

      yield ((Edsf.PersonaStore) this.store)._set_is_favourite (this,
          is_favourite);
    }

676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
  private HashSet<string> _anti_links;
  private Set<string> _anti_links_ro;

  /**
   * {@inheritDoc}
   *
   * @since UNRELEASED
   */
  [CCode (notify = false)]
  public Set<string> anti_links
    {
      get { return this._anti_links_ro; }
      set { this.change_anti_links.begin (value); }
    }

  /**
   * {@inheritDoc}
   *
   * @since UNRELEASED
   */
  public async void change_anti_links (Set<string> anti_links)
      throws PropertyError
    {
      yield ((Edsf.PersonaStore) this.store)._set_anti_links (this, anti_links);
    }

702
703
704
705
706
707
708
709
710
711
712
713
714
715
  private bool _in_google_personal_group;

  /**
   * Whether this contact is in the “My Contacts” section of the user’s address
   * book, rather than the “Other” section.
   *
   * @since UNRELEASED
   */
  [CCode (notify = false)]
  public bool in_google_personal_group
    {
      get { return this._in_google_personal_group; }
    }

Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
716
717
718
719
720
721
722
  /**
   * Build a IID.
   *
   * @param store_id the {@link PersonaStore.id}
   * @param contact the Contact
   * @return a valid IID
   *
Travis Reitter's avatar
Travis Reitter committed
723
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
724
725
726
727
728
   */
  internal static string build_iid_from_contact (string store_id,
      E.Contact contact)
    {
      var contact_id =
729
          Edsf.Persona._get_property_from_contact<string> (contact, "id");
Philip Withnall's avatar
Philip Withnall committed
730
      return Edsf.Persona.build_iid (store_id, (!) (contact_id ?? ""));
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
731
732
733
734
735
736
737
738
739
    }

  /**
   * Build a IID.
   *
   * @param store_id the {@link PersonaStore.id}
   * @param contact_id the id belonging to the Contact
   * @return a valid IID
   *
Travis Reitter's avatar
Travis Reitter committed
740
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
741
742
743
744
745
746
747
748
749
750
751
752
753
   */
  internal static string build_iid (string store_id, string contact_id)
    {
      return "%s:%s".printf (store_id, contact_id);
    }


  /**
   * Create a new persona.
   *
   * Create a new persona for the {@link PersonaStore} `store`, representing
   * the EDS contact given by `contact`.
   *
754
755
756
   * @param store the store which will contain the persona
   * @param contact the EDS contact being represented by the persona
   *
Travis Reitter's avatar
Travis Reitter committed
757
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
758
759
760
   */
  public Persona (PersonaStore store, E.Contact contact)
    {
Philip Withnall's avatar
Philip Withnall committed
761
      var _contact_id =
762
          Edsf.Persona._get_property_from_contact<string> (contact, "id");
Philip Withnall's avatar
Philip Withnall committed
763
764
      var contact_id = (!) (_contact_id ?? "");

Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
765
766
767
      var uid = this.build_uid (BACKEND_NAME, store.id, contact_id);
      var iid = Edsf.Persona.build_iid (store.id, contact_id);
      var is_user = BookClient.is_self (contact);
Philip Withnall's avatar
Philip Withnall committed
768
      var _full_name =
769
          Edsf.Persona._get_property_from_contact<string> (contact,
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
770
              "full_name");
Philip Withnall's avatar
Philip Withnall committed
771
      var full_name = (!) (_full_name ?? "");
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
772
773
774
775
776

      Object (display_id: full_name,
              uid: uid,
              iid: iid,
              store: store,
777
778
779
780
781
782
783
784
              is_user: is_user,
              contact_id: contact_id,
              contact: contact);
    }

  construct
    {
      debug ("Creating new Edsf.Persona with IID '%s'", this.iid);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
785

786
      this._gender = Gender.UNSPECIFIED;
787
      this._phone_numbers = new HashSet<PhoneFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
788
789
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
790
      this._phone_numbers_ro = this._phone_numbers.read_only_view;
791
      this._email_addresses = new HashSet<EmailFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
792
793
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
794
      this._email_addresses_ro = this._email_addresses.read_only_view;
795
      this._notes = new HashSet<NoteFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
796
797
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
798
      this._notes_ro = this._notes.read_only_view;
799
      this._urls = new HashSet<UrlFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
800
801
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
802
      this._urls_ro = this._urls.read_only_view;
803
      this._postal_addresses = new HashSet<PostalAddressFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
804
805
           AbstractFieldDetails<PostalAddress>.hash_static,
           AbstractFieldDetails<PostalAddress>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
806
807
808
      this._postal_addresses_ro = this._postal_addresses.read_only_view;
      this._local_ids = new HashSet<string> ();
      this._local_ids_ro = this._local_ids.read_only_view;
809
810
811
      this._web_service_addresses =
        new HashMultiMap<string, WebServiceFieldDetails> (
            null, null,
Jeremy Whiting's avatar
Jeremy Whiting committed
812
813
             AbstractFieldDetails<string>.hash_static,
             AbstractFieldDetails<string>.equal_static);
814
815
      this._groups = new HashSet<string> ();
      this._groups_ro = this._groups.read_only_view;
816
      this._roles = new HashSet<RoleFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
817
818
           AbstractFieldDetails<Role>.hash_static,
           AbstractFieldDetails<Role>.equal_static);
819
      this._roles_ro = this._roles.read_only_view;
820
821
      this._anti_links = new HashSet<string> ();
      this._anti_links_ro = this._anti_links.read_only_view;
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
822

823
      this._update (this._contact);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
824
825
826
827
828
    }

  /**
   * {@inheritDoc}
   *
Travis Reitter's avatar
Travis Reitter committed
829
   * @since 0.6.0
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
830
831
832
833
834
835
836
837
   */
  public override void linkable_property_to_links (string prop_name,
      Folks.Persona.LinkablePropertyCallback callback)
    {
      if (prop_name == "im-addresses")
        {
          foreach (var protocol in this._im_addresses.get_keys ())
            {
838
              var im_fds = this._im_addresses.get (protocol);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
839

840
841
              foreach (var im_fd in im_fds)
                  callback (protocol + ":" + im_fd.value);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
842
843
844
845
            }
        }
      else if (prop_name == "local-ids")
        {
846
847
848
849
850
          /* Note: we need to use this.local_ids and not this._local_ids,
           * otherwise this can have a different  behaviour depending
           * on the state of the current Persona depending on whether
           * this.local_ids was called before or not. */
          foreach (var id in this.local_ids)
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
851
852
853
854
855
856
857
858
859
860
861
            {
              callback (id);
            }
        }
      else if (prop_name == "web-service-addresses")
        {
          foreach (var web_service in this.web_service_addresses.get_keys ())
            {
              var web_service_addresses =
                  this._web_service_addresses.get (web_service);

862
863
              foreach (var ws_fd in web_service_addresses)
                  callback (web_service + ":" + ws_fd.value);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
            }
        }
      else
        {
          /* Chain up */
          base.linkable_property_to_links (prop_name, callback);
        }
    }

  ~Persona ()
    {
      debug ("Destroying Edsf.Persona '%s': %p", this.uid, this);
    }

  /**
   * Update attribs of the persona.
   */
881
  internal void _update (E.Contact updated_contact)
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
882
    {
883
884
      this.freeze_notify ();

885
886
887
888
889
      /* We get a new E.Contact instance from EDS containing all the updates,
       * so replace our existing contact with it. */
      this._contact = updated_contact;
      this.notify_property ("contact");

Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
890
891
892
893
894
895
      this._update_names ();
      this._update_avatar ();
      this._update_urls ();
      this._update_phones ();
      this._update_addresses ();
      this._update_emails ();
896
897
898
899
900

      /* Note: because we assume certain e-mail addresses
       * (@gmail, @msn, etc) to also be IM IDs we /must/
       * update the latter after we've taken care of the former.
       */
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
901
      this._update_im_addresses ();
902

903
      this._update_groups ();
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
904
905
906
      this._update_notes ();
      this._update_local_ids ();
      this._update_web_services_addresses ();
907
      this._update_gender ();
908
      this._update_birthday ();
909
      this._update_roles ();
910
      this._update_favourite ();
911
      this._update_anti_links ();
912
913

      this.thaw_notify ();
914
915
    }

916
917
918
919
920
921
922
923
  private void _update_params (AbstractFieldDetails details,
      E.VCardAttribute attr)
    {
      foreach (unowned E.VCardAttributeParam param in attr.get_params ())
        {
          string param_name = param.get_name ().down ();
          foreach (unowned string param_value in param.get_values ())
            {
924
925
926
927
928
929
930
931
              if (param_name == AbstractFieldDetails.PARAM_TYPE)
                {
                  details.add_parameter (param_name, param_value.down ());
                }
              else
                {
                  details.add_parameter (param_name, param_value);
                }
932
933
934
935
            }
        }
    }

936
937
938
939
940
941
942
943
  private void _update_gender ()
    {
      var gender = Gender.UNSPECIFIED;
      var gender_attr =
          this.contact.get_attribute (Edsf.Persona.gender_attribute_name);

      if (gender_attr != null)
        {
Philip Withnall's avatar
Philip Withnall committed
944
945
          var val = ((!) gender_attr).get_value ();
          if (val != null)
946
            {
Philip Withnall's avatar
Philip Withnall committed
947
948
949
950
951
952
953
954
955
956
957
958
              switch (((!) val).up ())
                {
                  case Edsf.Persona.gender_male:
                    gender = Gender.MALE;
                    break;
                  case Edsf.Persona.gender_female:
                    gender = Gender.FEMALE;
                    break;
                  default:
                    /* Unspecified, as above */
                    break;
                }
959
960
961
962
963
964
965
966
            }
        }

      if (this._gender != gender)
        {
          this._gender = gender;
          this.notify_property ("gender");
        }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
967
968
    }

969
970
  private void _update_birthday ()
    {
971
      var _bday = this._get_property<E.ContactDate> ("birth_date");
972

Philip Withnall's avatar
Philip Withnall committed
973
      if (_bday != null)
974
        {
Philip Withnall's avatar
Philip Withnall committed
975
976
          var bday = (!) _bday;

977
978
          /* Since e-d-s stores birthdays as a plain date, we take the
           * given date in local time and convert it to UTC as mandated
979
980
981
982
983
           * by the BirthdayDetails interface.
           * We cache the timezone since creating it requires mmapping
           * /etc/localtime, which means lots of syscalls. */
          var d = new DateTime (this._local_time_zone,
              (int) bday.year, (int) bday.month, (int) bday.day, 0, 0, 0.0);
984
985
          if (this._birthday == null ||
              (this._birthday != null &&
Philip Withnall's avatar
Philip Withnall committed
986
                  !((!) this._birthday).equal (d.to_utc ())))
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
            {
              this._birthday = d.to_utc ();
              this.notify_property ("birthday");
            }
        }
      else
        {
          if (this._birthday != null)
            {
              this._birthday = null;
              this.notify_property ("birthday");
            }
        }
    }

1002
1003
1004
  private void _update_roles ()
    {
      var new_roles = new HashSet<RoleFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
1005
1006
           AbstractFieldDetails<Role>.hash_static,
           AbstractFieldDetails<Role>.equal_static);
1007
1008
1009
1010

      var default_role_fd = this._get_default_role ();
      if (default_role_fd != null)
        {
Philip Withnall's avatar
Philip Withnall committed
1011
          new_roles.add ((!) default_role_fd);
1012
1013
1014
1015
1016
1017
1018
1019
        }

      var vcard = (E.VCard) this.contact;
      foreach (unowned E.VCardAttribute attr in vcard.get_attributes ())
        {
          if (attr.get_name () != "X-ROLES")
            continue;

1020
          var val = attr.get_value ();
Philip Withnall's avatar
Philip Withnall committed
1021
          if (val == null || (!) val == "")
1022
1023
1024
1025
             {
              continue;
            }

1026
          var role = new Role ("", "");
Philip Withnall's avatar
Philip Withnall committed
1027
          role.role = (!) val;
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
          var role_fd = new RoleFieldDetails (role);

          foreach (unowned E.VCardAttributeParam param in
              attr.get_params ())
            {
              string param_name = param.get_name ().down ();

              if (param_name == "organisation_name")
                {
                  foreach (unowned string param_value in
                      param.get_values ())
                    {
                      role.organisation_name = param_value;
                      break;
                    }
                }
              else if (param_name == "title")
                {
                  foreach (unowned string param_value in
                      param.get_values ())
                    {
                      role.title = param_value;
                      break;
                    }
                }
              else
                {
                  foreach (unowned string param_value in
                      param.get_values ())
                    {
                      role_fd.add_parameter (param_name, param_value);
                    }
                }
            }

            new_roles.add (role_fd);
        }

1066
      if (!Folks.Internal.equal_sets<RoleFieldDetails> (new_roles, this._roles))
1067
1068
1069
1070
1071
        {
          this._roles = new_roles;
          this._roles_ro = new_roles.read_only_view;
          this.notify_property ("roles");
        }
1072
1073
1074
1075
    }

  private RoleFieldDetails? _get_default_role ()
    {
Philip Withnall's avatar
Philip Withnall committed
1076
      RoleFieldDetails? _default_role = null;
1077

1078
1079
1080
1081
1082
1083
1084
      var org = this._get_property<string> ("org");
      var org_unit = this._get_property<string> ("org_unit");
      var office = this._get_property<string> ("office");
      var title = this._get_property<string> ("title");
      var role = this._get_property<string> ("role");
      var manager = this._get_property<string> ("manager");
      var assistant = this._get_property<string> ("assistant");
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094

      if (org != null ||
          org_unit != null ||
          office != null ||
          title != null ||
          role != null ||
          manager != null ||
          assistant != null)
        {
          var new_role = new Role (title, org);
Philip Withnall's avatar
Philip Withnall committed
1095
1096
          if (role != null && (!) role != "")
            new_role.role = (!) role;
1097

1098
1099
1100
          /* Check if it's non-empty. */
          if (!new_role.is_empty ())
            {
Philip Withnall's avatar
Philip Withnall committed
1101
              var default_role = new RoleFieldDetails (new_role);
1102

1103
              if (org_unit != null && org_unit != "")
Philip Withnall's avatar
Philip Withnall committed
1104
                default_role.set_parameter ("org_unit", (!) org_unit);
1105

1106
              if (office != null && office != "")
Philip Withnall's avatar
Philip Withnall committed
1107
                default_role.set_parameter ("office", (!) office);
1108

1109
              if (manager != null && manager != "")
Philip Withnall's avatar
Philip Withnall committed
1110
                default_role.set_parameter ("manager", (!) manager);
1111

1112
              if (assistant != null && manager != "")
Philip Withnall's avatar
Philip Withnall committed
1113
1114
1115
                default_role.set_parameter ("assistant", (!) assistant);

              _default_role = default_role;
1116
            }
1117
1118
        }

Philip Withnall's avatar
Philip Withnall committed
1119
      return _default_role;
1120
1121
    }

Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1122
1123
  private void _update_web_services_addresses ()
    {
1124
1125
      var new_services = new HashMultiMap<string, WebServiceFieldDetails> (
          null, null,
Jeremy Whiting's avatar
Jeremy Whiting committed
1126
1127
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1128
1129
1130
1131

      var services = this.contact.get_attribute ("X-FOLKS-WEB-SERVICES-IDS");
      if (services != null)
        {
Philip Withnall's avatar
Philip Withnall committed
1132
          foreach (var service in ((!) services).get_params ())
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1133
1134
1135
1136
            {
              var service_name = service.get_name ().down ();
              foreach (var service_id in service.get_values ())
                {
Philip Withnall's avatar
Philip Withnall committed
1137
                  if (service_id == "")
1138
1139
1140
                    {
                      continue;
                    }
1141

1142
                  new_services.set (service_name,
1143
                      new WebServiceFieldDetails (service_id));
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1144
1145
1146
1147
                }
            }
        }

1148
1149
1150
1151
1152
1153
      if (!Utils.multi_map_str_afd_equal (new_services,
              this._web_service_addresses))
        {
          this._web_service_addresses = new_services;
          this.notify_property ("web-service-addresses");
        }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1154
1155
1156
1157
    }

  private void _update_emails ()
    {
1158
      var new_email_addresses = new HashSet<EmailFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
1159
1160
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1161

1162
      var attrs = this.contact.get_attributes (E.ContactField.EMAIL);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1163
1164
      foreach (var attr in attrs)
        {
1165
          var val = attr.get_value ();
Philip Withnall's avatar
Philip Withnall committed
1166
          if (val == null || (!) val == "")
1167
1168
1169
1170
            {
              continue;
            }

Philip Withnall's avatar
Philip Withnall committed
1171
          var email_fd = new EmailFieldDetails ((!) val);
1172
          this._update_params (email_fd, attr);
1173
          new_email_addresses.add (email_fd);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1174
1175
        }

1176
      if (!Folks.Internal.equal_sets<EmailFieldDetails> (new_email_addresses,
1177
              this._email_addresses))
1178
1179
1180
1181
1182
        {
         this._email_addresses = new_email_addresses;
         this._email_addresses_ro = new_email_addresses.read_only_view;
         this.notify_property ("email-addresses");
       }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1183
1184
1185
1186
    }

  private void _update_notes ()
    {
1187
      var new_notes = new HashSet<NoteFieldDetails> (
Jeremy Whiting's avatar
Jeremy Whiting committed
1188
1189
           AbstractFieldDetails<string>.hash_static,
           AbstractFieldDetails<string>.equal_static);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1190

1191
      var n = this._get_property<string> ("note");
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1192
1193
      if (n != null && n != "")
        {
Philip Withnall's avatar
Philip Withnall committed
1194
          var note = new NoteFieldDetails ((!) n);
1195
          new_notes.add (note);
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1196
1197
        }

1198
      if (!Folks.Internal.equal_sets<NoteFieldDetails> (new_notes, this._notes))
1199
1200
1201
1202
1203
        {
          this._notes = new_notes;
          this._notes_ro = this._notes.read_only_view;
          this.notify_property ("notes");
        }
Raul Gutierrez Segales's avatar
Raul Gutierrez Segales committed
1204
1205
1206
1207
    }

  private void _update_names ()
    {
1208
      var _full_name = this._get_property<string> ("full_name");
1209

Philip Withnall's avatar
Philip Withnall committed
1210
      if (_full_name == null)
1211
        {
Philip Withnall's avatar
Philip Withnall committed
1212
          _full_name = "";