~ubuntu-branches/ubuntu/precise/folks/precise

« back to all changes in this revision

Viewing changes to backends/tracker/lib/trf-persona-store.vala

  • Committer: Bazaar Package Importer
  • Author(s): Ken VanDine
  • Date: 2011-06-10 11:28:11 UTC
  • mfrom: (1.2.11 upstream) (4.2.1 experimental)
  • Revision ID: james.westby@ubuntu.com-20110610112811-whyeodbo9mjezxfp
Tags: 0.5.2-1ubuntu1
* Merge with Debian experimental, remaining Ubuntu changes:
  - debian/control:
    + Add Vcs-Bzr link

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2011 Collabora Ltd.
 
3
 *
 
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.
 
8
 *
 
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.
 
13
 *
 
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/>.
 
16
 *
 
17
 * Authors:
 
18
 *         Travis Reitter <travis.reitter@collabora.co.uk>
 
19
 *         Philip Withnall <philip.withnall@collabora.co.uk>
 
20
 *         Marco Barisione <marco.barisione@collabora.co.uk>
 
21
 *         Raul Gutierrez Segales <raul.gutierrez.segales@collabora.co.uk>
 
22
 */
 
23
 
 
24
using Folks;
 
25
using Gee;
 
26
using GLib;
 
27
using Tracker;
 
28
using Tracker.Sparql;
 
29
 
 
30
extern const string BACKEND_NAME;
 
31
 
 
32
internal enum Trf.Fields
 
33
{
 
34
  TRACKER_ID,
 
35
  FULL_NAME,
 
36
  FAMILY_NAME,
 
37
  GIVEN_NAME,
 
38
  ADDITIONAL_NAMES,
 
39
  PREFIXES,
 
40
  SUFFIXES,
 
41
  ALIAS,
 
42
  BIRTHDAY,
 
43
  AVATAR_URL,
 
44
  IM_ADDRESSES,
 
45
  PHONES,
 
46
  EMAILS,
 
47
  URLS,
 
48
  FAVOURITE,
 
49
  CONTACT_URN,
 
50
  ROLES,
 
51
  NOTE,
 
52
  GENDER,
 
53
  POSTAL_ADDRESS,
 
54
  LOCAL_IDS_PROPERTY
 
55
}
 
56
 
 
57
internal enum Trf.AfflInfoFields
 
58
{
 
59
  IM_TRACKER_ID,
 
60
  IM_PROTOCOL,
 
61
  IM_ACCOUNT_ID,
 
62
  AFFL_TRACKER_ID,
 
63
  AFFL_ROLE,
 
64
  AFFL_ORG,
 
65
  AFFL_POBOX,
 
66
  AFFL_DISTRICT,
 
67
  AFFL_COUNTY,
 
68
  AFFL_LOCALITY,
 
69
  AFFL_POSTALCODE,
 
70
  AFFL_STREET_ADDRESS,
 
71
  AFFL_ADDRESS_LOCATION,
 
72
  AFFL_EXTENDED_ADDRESS,
 
73
  AFFL_COUNTRY,
 
74
  AFFL_REGION,
 
75
  AFFL_EMAIL,
 
76
  AFFL_PHONE,
 
77
  AFFL_WEBSITE,
 
78
  AFFL_BLOG,
 
79
  AFFL_URL,
 
80
  IM_NICKNAME
 
81
}
 
82
 
 
83
internal enum Trf.PostalAddressFields
 
84
{
 
85
  TRACKER_ID,
 
86
  POBOX,
 
87
  DISTRICT,
 
88
  COUNTY,
 
89
  LOCALITY,
 
90
  POSTALCODE,
 
91
  STREET_ADDRESS,
 
92
  ADDRESS_LOCATION,
 
93
  EXTENDED_ADDRESS,
 
94
  COUNTRY,
 
95
  REGION
 
96
}
 
97
 
 
98
internal enum Trf.UrlsFields
 
99
{
 
100
  TRACKER_ID,
 
101
  BLOG,
 
102
  WEBSITE,
 
103
  URL
 
104
}
 
105
 
 
106
internal enum Trf.RoleFields
 
107
{
 
108
  TRACKER_ID,
 
109
  ROLE,
 
110
  DEPARTMENT
 
111
}
 
112
 
 
113
internal enum Trf.IMFields
 
114
{
 
115
  TRACKER_ID,
 
116
  PROTO,
 
117
  ID,
 
118
  IM_NICKNAME
 
119
}
 
120
 
 
121
internal enum Trf.PhoneFields
 
122
{
 
123
  TRACKER_ID,
 
124
  PHONE
 
125
}
 
126
 
 
127
internal enum Trf.EmailFields
 
128
{
 
129
  TRACKER_ID,
 
130
  EMAIL
 
131
}
 
132
 
 
133
internal enum Trf.TagFields
 
134
{
 
135
  TRACKER_ID
 
136
}
 
137
 
 
138
private enum Trf.Attrib
 
139
{
 
140
  EMAILS,
 
141
  PHONES,
 
142
  URLS,
 
143
  IM_ADDRESSES,
 
144
  POSTAL_ADDRESSES
 
145
}
 
146
 
 
147
private const char _REMOVE_ALL_ATTRIBS = 0x01;
 
148
private const char _REMOVE_PHONES      = 0x02;
 
149
private const char _REMOVE_POSTALS     = 0x04;
 
150
private const char _REMOVE_IM_ADDRS    = 0x08;
 
151
private const char _REMOVE_EMAILS      = 0x10;
 
152
 
 
153
/**
 
154
 * A persona store.
 
155
 * It will create {@link Persona}s for each contacts on the main addressbook.
 
156
 */
 
157
public class Trf.PersonaStore : Folks.PersonaStore
 
158
{
 
159
  private const string _LOCAL_ID_PROPERTY_NAME = "folks-linking-ids";
 
160
  private const string _WSD_PROPERTY_NAME = "folks-linking-ws-addrs";
 
161
  private const string _OBJECT_NAME = "org.freedesktop.Tracker1";
 
162
  private const string _OBJECT_IFACE = "org.freedesktop.Tracker1.Resources";
 
163
  private const string _OBJECT_PATH = "/org/freedesktop/Tracker1/Resources";
 
164
  private HashMap<string, Persona> _personas;
 
165
  private Map<string, Persona> _personas_ro;
 
166
  private bool _is_prepared = false;
 
167
  private static const int _default_timeout = 100;
 
168
  private Resources _resources_object;
 
169
  private Tracker.Sparql.Connection _connection;
 
170
  private static Gee.TreeMap<string, string> _urn_prefix = null;
 
171
  private static Gee.TreeMap<string, int> _prefix_tracker_id = null;
 
172
  private static const string _INITIAL_QUERY =
 
173
    "SELECT " +
 
174
    "tracker:id(?_contact) " +
 
175
    "nco:fullname(?_contact) " +
 
176
    "nco:nameFamily(?_contact) " +
 
177
    "nco:nameGiven(?_contact) " +
 
178
    "nco:nameAdditional(?_contact) " +
 
179
    "nco:nameHonorificPrefix(?_contact) " +
 
180
    "nco:nameHonorificSuffix(?_contact) " +
 
181
    "nco:nickname(?_contact) " +
 
182
    "nco:birthDate(?_contact) " +
 
183
    "nie:url(nco:photo(?_contact)) " +
 
184
 
 
185
    /* keep synced with Trf.IMFields */
 
186
    "(SELECT " +
 
187
    "GROUP_CONCAT ( " +
 
188
    " fn:concat(tracker:id(?affl),'\t'," +
 
189
    " tracker:coalesce(nco:imProtocol(?a),''), " +
 
190
    "'\t', tracker:coalesce(nco:imID(?a),''), '\t'," +
 
191
    " tracker:coalesce(nco:imNickname(?a),'')), '\\n') " +
 
192
    "WHERE { ?_contact nco:hasAffiliation ?affl. " +
 
193
    " ?affl nco:hasIMAddress ?a } ) " +
 
194
 
 
195
    /* keep synced with Trf.PhoneFields */
 
196
    "(SELECT " +
 
197
    "GROUP_CONCAT " +
 
198
    " (fn:concat(tracker:id(?affl),'\t', " +
 
199
    " nco:phoneNumber(?aff_number)), " +
 
200
    "'\\n') " +
 
201
    "WHERE { ?_contact nco:hasAffiliation ?affl . " +
 
202
    " ?affl nco:hasPhoneNumber ?aff_number  } ) " +
 
203
 
 
204
    /* keep synced with Trf.EmailFields */
 
205
    "(SELECT " +
 
206
    "GROUP_CONCAT " +
 
207
    " (fn:concat(tracker:id(?affl), '\t', " +
 
208
    "  nco:emailAddress(?emailaddress)), " +
 
209
    "',') " +
 
210
    "WHERE { ?_contact nco:hasAffiliation ?affl . " +
 
211
    " ?affl nco:hasEmailAddress ?emailaddress }) " +
 
212
 
 
213
    /* keep synced with Trf.UrlsFields */
 
214
    " (SELECT " +
 
215
    "GROUP_CONCAT " +
 
216
    " (fn:concat(tracker:id(?affl), '\t'," +
 
217
    "  tracker:coalesce(nco:blogUrl(?affl),'')," +
 
218
    "  '\t'," +
 
219
    "  tracker:coalesce(nco:websiteUrl(?affl),'')" +
 
220
    "  , '\t'," +
 
221
    "  tracker:coalesce(nco:url(?affl),''))," +
 
222
    "  '\\n') " +
 
223
    "WHERE { ?_contact nco:hasAffiliation ?affl  } )" +
 
224
 
 
225
    /* keep synced with Trf.TagFields */
 
226
    "(SELECT " +
 
227
    "GROUP_CONCAT(tracker:id(?_tag), " +
 
228
    "',') " +
 
229
    "WHERE { ?_contact nao:hasTag " +
 
230
    "?_tag }) " +
 
231
 
 
232
    "?_contact " +
 
233
 
 
234
    /* keep synced with Trf.RoleFields */
 
235
    "(SELECT " +
 
236
    "GROUP_CONCAT " +
 
237
    " (fn:concat(tracker:id(?affl), '\t', " +
 
238
    "  tracker:coalesce(nco:role(?affl),''), '\t', " +
 
239
    "  tracker:coalesce(nco:department(?affl),'')),  " +
 
240
    "'\\n') " +
 
241
    "WHERE { ?_contact nco:hasAffiliation " +
 
242
    "?affl }) " +
 
243
 
 
244
    "nco:note(?_contact) " +
 
245
    "tracker:id(nco:gender(?_contact)) " +
 
246
 
 
247
    /* keep synced with Trf.PostalAddressFields*/
 
248
    "(SELECT " +
 
249
    "GROUP_CONCAT " +
 
250
    " (fn:concat(tracker:id(?affl), '\t', " +
 
251
    "  tracker:coalesce(nco:pobox(?postal),'')" +
 
252
    "  , '\t', " +
 
253
    "  tracker:coalesce(nco:district(?postal),'')" +
 
254
    "  , '\t', " +
 
255
    "  tracker:coalesce(nco:county(?postal),'')" +
 
256
    "  , '\t', " +
 
257
    "  tracker:coalesce(nco:locality(?postal),'')" +
 
258
    "  , '\t', " +
 
259
    "  tracker:coalesce(nco:postalcode(?postal),'')" +
 
260
    "  , '\t', " +
 
261
    "  tracker:coalesce(nco:streetAddress(?postal)" +
 
262
    "  ,''), '\t', " +
 
263
    "  tracker:coalesce(nco:addressLocation(?postal)" +
 
264
    "  ,''), '\t', " +
 
265
    "  tracker:coalesce(nco:extendedAddress(?postal)" +
 
266
    "  ,''), '\t', " +
 
267
    "  tracker:coalesce(nco:country(?postal),'')" +
 
268
    "  , '\t', " +
 
269
    "  tracker:coalesce(nco:region(?postal),'')),  " +
 
270
    "'\\n') " +
 
271
    "WHERE { ?_contact nco:hasAffiliation " +
 
272
    "?affl . ?affl nco:hasPostalAddress ?postal }) " +
 
273
 
 
274
    /* Linking between Trf.Personas */
 
275
    "(SELECT " +
 
276
    "GROUP_CONCAT " +
 
277
    " (?prop_value,  " +
 
278
    "',') " +
 
279
    "WHERE { ?_contact nao:hasProperty ?prop . " +
 
280
    " ?prop nao:propertyName ?prop_name . " +
 
281
    " ?prop nao:propertyValue ?prop_value . " +
 
282
    " FILTER (?prop_name = 'folks-linking-ids') } ) " +
 
283
 
 
284
    "{ ?_contact a nco:PersonContact . %s } " +
 
285
    "ORDER BY tracker:id(?_contact) ";
 
286
 
 
287
 
 
288
  /**
 
289
   * The type of persona store this is.
 
290
   *
 
291
   * See {@link Folks.PersonaStore.type_id}.
 
292
   */
 
293
  public override string type_id { get { return BACKEND_NAME; } }
 
294
 
 
295
  /**
 
296
   * Whether this PersonaStore can add {@link Folks.Persona}s.
 
297
   *
 
298
   * See {@link Folks.PersonaStore.can_add_personas}.
 
299
   *
 
300
   * @since 0.5.0
 
301
   */
 
302
  public override MaybeBool can_add_personas
 
303
    {
 
304
      get { return MaybeBool.TRUE; }
 
305
    }
 
306
 
 
307
  /**
 
308
   * Whether this PersonaStore can set the alias of {@link Folks.Persona}s.
 
309
   *
 
310
   * See {@link Folks.PersonaStore.can_alias_personas}.
 
311
   *
 
312
   * @since 0.5.0
 
313
   */
 
314
  public override MaybeBool can_alias_personas
 
315
    {
 
316
      get { return MaybeBool.FALSE; }
 
317
    }
 
318
 
 
319
  /**
 
320
   * Whether this PersonaStore can set the groups of {@link Folks.Persona}s.
 
321
   *
 
322
   * See {@link Folks.PersonaStore.can_group_personas}.
 
323
   *
 
324
   * @since 0.5.0
 
325
   */
 
326
  public override MaybeBool can_group_personas
 
327
    {
 
328
      get { return MaybeBool.FALSE; }
 
329
    }
 
330
 
 
331
  /**
 
332
   * Whether this PersonaStore can remove {@link Folks.Persona}s.
 
333
   *
 
334
   * See {@link Folks.PersonaStore.can_remove_personas}.
 
335
   *
 
336
   * @since 0.5.0
 
337
   */
 
338
  public override MaybeBool can_remove_personas
 
339
    {
 
340
      get { return MaybeBool.TRUE; }
 
341
    }
 
342
 
 
343
  /**
 
344
   * Whether this PersonaStore has been prepared.
 
345
   *
 
346
   * See {@link Folks.PersonaStore.is_prepared}.
 
347
   *
 
348
   * @since 0.5.0
 
349
   */
 
350
  public override bool is_prepared
 
351
    {
 
352
      get { return this._is_prepared; }
 
353
    }
 
354
 
 
355
  /**
 
356
   * The {@link Persona}s exposed by this PersonaStore.
 
357
   *
 
358
   * See {@link Folks.PersonaStore.personas}.
 
359
   */
 
360
  public override Map<string, Persona> personas
 
361
    {
 
362
      get { return this._personas_ro; }
 
363
    }
 
364
 
 
365
  /**
 
366
   * Create a new PersonaStore.
 
367
   *
 
368
   * Create a new persona store to store the {@link Persona}s for the contacts
 
369
   */
 
370
  public PersonaStore ()
 
371
    {
 
372
      Object (id: BACKEND_NAME, display_name: BACKEND_NAME);
 
373
      this._personas = new HashMap<string, Persona> ();
 
374
      this._personas_ro = this._personas.read_only_view;
 
375
      debug ("Initial query : \n%s\n", this._INITIAL_QUERY);
 
376
    }
 
377
 
 
378
  /**
 
379
   * Add a new {@link Persona} to the PersonaStore.
 
380
   *
 
381
   * Accepted keys for `details` are:
 
382
   * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
 
383
   * - PersonaStore.detail_key (PersonaDetail.ALIAS)
 
384
   * - PersonaStore.detail_key (PersonaDetail.FULL_NAME)
 
385
   * - PersonaStore.detail_key (PersonaDetail.IS_FAVOURITE)
 
386
   * - PersonaStore.detail_key (PersonaDetail.STRUCTURED_NAME)
 
387
   * - PersonaStore.detail_key (PersonaDetail.AVATAR)
 
388
   * - PersonaStore.detail_key (PersonaDetail.BIRTHDAY)
 
389
   * - PersonaStore.detail_key (PersonaDetail.GENDER)
 
390
   * - PersonaStore.detail_key (PersonaDetail.EMAIL_ADDRESSES)
 
391
   * - PersonaStore.detail_key (PersonaDetail.IM_ADDRESSES)
 
392
   * - PersonaStore.detail_key (PersonaDetail.NOTES)
 
393
   * - PersonaStore.detail_key (PersonaDetail.PHONE_NUMBERS)
 
394
   * - PersonaStore.detail_key (PersonaDetail.POSTAL_ADDRESSES)
 
395
   * - PersonaStore.detail_key (PersonaDetail.ROLES)
 
396
   * - PersonaStore.detail_key (PersonaDetail.URL)
 
397
   * - PersonaStore.detail_key (PersonaDetail.WEB_SERVICE_ADDRESSES)
 
398
   *
 
399
   * See {@link Folks.PersonaStore.add_persona_from_details}.
 
400
   */
 
401
  public override async Folks.Persona? add_persona_from_details (
 
402
      HashTable<string, Value?> details) throws Folks.PersonaStoreError
 
403
    {
 
404
      var builder = new Tracker.Sparql.Builder.update ();
 
405
      builder.insert_open (null);
 
406
      builder.subject ("_:p");
 
407
      builder.predicate ("a");
 
408
      builder.object ("nco:PersonContact");
 
409
 
 
410
      foreach (var k in details.get_keys ())
 
411
        {
 
412
          Value? v = details.lookup (k);
 
413
          if (k == Folks.PersonaStore.detail_key (PersonaDetail.ALIAS))
 
414
            {
 
415
              builder.subject ("_:p");
 
416
              builder.predicate (Trf.OntologyDefs.NCO_NICKNAME);
 
417
              builder.object_string (v.get_string ());
 
418
            }
 
419
          else if (k == Folks.PersonaStore.detail_key (
 
420
                PersonaDetail.FULL_NAME))
 
421
            {
 
422
              builder.subject ("_:p");
 
423
              builder.predicate (Trf.OntologyDefs.NCO_FULLNAME);
 
424
              builder.object_string (v.get_string ());
 
425
            }
 
426
          else if (k == Folks.PersonaStore.detail_key (
 
427
                PersonaDetail.STRUCTURED_NAME))
 
428
            {
 
429
              StructuredName sname = (StructuredName) v.get_object ();
 
430
              builder.subject ("_:p");
 
431
              builder.predicate (Trf.OntologyDefs.NCO_FAMILY);
 
432
              builder.object_string (sname.family_name);
 
433
              builder.predicate (Trf.OntologyDefs.NCO_GIVEN);
 
434
              builder.object_string (sname.given_name);
 
435
              builder.predicate (Trf.OntologyDefs.NCO_ADDITIONAL);
 
436
              builder.object_string (sname.additional_names);
 
437
              builder.predicate (Trf.OntologyDefs.NCO_SUFFIX);
 
438
              builder.object_string (sname.suffixes);
 
439
              builder.predicate (Trf.OntologyDefs.NCO_PREFIX);
 
440
              builder.object_string (sname.prefixes);
 
441
            }
 
442
          else if (k == Folks.PersonaStore.detail_key (
 
443
                PersonaDetail.IS_FAVOURITE))
 
444
            {
 
445
              if (v.get_boolean ())
 
446
                {
 
447
                  builder.subject ("_:p");
 
448
                  builder.predicate (Trf.OntologyDefs.NAO_TAG);
 
449
                  builder.object (Trf.OntologyDefs.NAO_FAVORITE);
 
450
                }
 
451
            }
 
452
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.AVATAR))
 
453
            {
 
454
              var avatar = (File) v.get_object ();
 
455
              builder.subject ("_:photo");
 
456
              builder.predicate ("a");
 
457
              builder.object ("nfo:Image, nie:DataObject");
 
458
              builder.predicate (Trf.OntologyDefs.NIE_URL);
 
459
              builder.object_string (avatar.get_uri ());
 
460
              builder.subject ("_:p");
 
461
              builder.predicate (Trf.OntologyDefs.NCO_PHOTO);
 
462
              builder.object ("_:photo");
 
463
            }
 
464
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.BIRTHDAY))
 
465
            {
 
466
              var birthday = (DateTime) v.get_boxed ();
 
467
              builder.subject ("_:p");
 
468
              builder.predicate (Trf.OntologyDefs.NCO_BIRTHDAY);
 
469
              TimeVal tv;
 
470
              birthday.to_timeval (out tv);
 
471
              builder.object_string (tv.to_iso8601 ());
 
472
            }
 
473
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.GENDER))
 
474
            {
 
475
              var gender = (Gender) v.get_enum ();
 
476
              if (gender != Gender.UNSPECIFIED)
 
477
                {
 
478
                  builder.subject ("_:p");
 
479
                  builder.predicate (Trf.OntologyDefs.NCO_GENDER);
 
480
                  if (gender == Gender.MALE)
 
481
                    builder.object (Trf.OntologyDefs.NCO_MALE);
 
482
                  else
 
483
                    builder.object (Trf.OntologyDefs.NCO_FEMALE);
 
484
                }
 
485
            }
 
486
          else if (k == Folks.PersonaStore.detail_key (
 
487
                PersonaDetail.EMAIL_ADDRESSES))
 
488
            {
 
489
              Set<FieldDetails> email_addresses =
 
490
                (Set<FieldDetails>) v.get_object ();
 
491
              yield this._build_update_query_set (builder, email_addresses,
 
492
                  "_:p", Trf.Attrib.EMAILS);
 
493
            }
 
494
          else if (k == Folks.PersonaStore.detail_key (
 
495
                PersonaDetail.IM_ADDRESSES))
 
496
            {
 
497
              var im_addresses = (MultiMap<string, string>) v.get_object ();
 
498
 
 
499
              int im_cnt = 0;
 
500
              foreach (var proto in im_addresses.get_keys ())
 
501
                {
 
502
                  var addrs_a = im_addresses.get (proto);
 
503
 
 
504
                  foreach (var addr in addrs_a)
 
505
                    {
 
506
                      var im_affl = "_:im_affl%d".printf (im_cnt);
 
507
                      var im = "_:im%d".printf (im_cnt);
 
508
 
 
509
                      builder.subject (im);
 
510
                      builder.predicate ("a");
 
511
                      builder.object (Trf.OntologyDefs.NCO_IMADDRESS);
 
512
                      builder.predicate (Trf.OntologyDefs.NCO_IMID);
 
513
                      builder.object_string (addr);
 
514
                      builder.predicate (Trf.OntologyDefs.NCO_IMPROTOCOL);
 
515
                      builder.object_string (proto);
 
516
 
 
517
                      builder.subject (im_affl);
 
518
                      builder.predicate ("a");
 
519
                      builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
520
                      builder.predicate (Trf.OntologyDefs.NCO_HAS_IMADDRESS);
 
521
                      builder.object (im);
 
522
 
 
523
                      builder.subject ("_:p");
 
524
                      builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
525
                      builder.object (im_affl);
 
526
 
 
527
                      im_cnt++;
 
528
                    }
 
529
                }
 
530
            }
 
531
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.NOTES))
 
532
            {
 
533
              var notes = (Gee.HashSet<Note>) v.get_object ();
 
534
              foreach (var n in notes)
 
535
                {
 
536
                  builder.subject ("_:p");
 
537
                  builder.predicate (Trf.OntologyDefs.NCO_NOTE);
 
538
                  builder.object_string (n.content);
 
539
                }
 
540
            }
 
541
          else if (k == Folks.PersonaStore.detail_key (
 
542
                PersonaDetail.PHONE_NUMBERS))
 
543
            {
 
544
              Set<FieldDetails> phone_numbers =
 
545
                (Set<FieldDetails>) v.get_object ();
 
546
              yield this._build_update_query_set (builder, phone_numbers,
 
547
                "_:p", Trf.Attrib.PHONES);
 
548
            }
 
549
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.ROLES))
 
550
            {
 
551
              var roles = (Gee.HashSet<Role>) v.get_object ();
 
552
 
 
553
              int roles_cnt = 0;
 
554
              foreach (var r in roles)
 
555
                {
 
556
                  var role_affl = "_:role_affl%d".printf (roles_cnt);
 
557
 
 
558
                  builder.subject (role_affl);
 
559
                  builder.predicate ("a");
 
560
                  builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
561
                  builder.predicate (Trf.OntologyDefs.NCO_ROLE);
 
562
                  builder.object_string (r.title);
 
563
                  builder.predicate (Trf.OntologyDefs.NCO_ORG);
 
564
                  builder.object_string (r.organisation_name);
 
565
 
 
566
                  builder.subject ("_:p");
 
567
                  builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
568
                  builder.object (role_affl);
 
569
 
 
570
                  roles_cnt++;
 
571
                }
 
572
            }
 
573
          else if (k == Folks.PersonaStore.detail_key (
 
574
                PersonaDetail.POSTAL_ADDRESSES))
 
575
            {
 
576
              Set<PostalAddress> postal_addresses =
 
577
                (Set<PostalAddress>) v.get_object ();
 
578
 
 
579
              int postal_cnt = 0;
 
580
              foreach (var pa in postal_addresses)
 
581
                {
 
582
                  var postal_affl = "_:postal_affl%d".printf (postal_cnt);
 
583
                  var postal = "_:postal%d".printf (postal_cnt);
 
584
                  builder.subject (postal);
 
585
                  builder.predicate ("a");
 
586
                  builder.object (Trf.OntologyDefs.NCO_POSTAL_ADDRESS);
 
587
                  builder.predicate (Trf.OntologyDefs.NCO_POBOX);
 
588
                  builder.object_string (pa.po_box);
 
589
                  builder.predicate (Trf.OntologyDefs.NCO_LOCALITY);
 
590
                  builder.object_string (pa.locality);
 
591
                  builder.predicate (Trf.OntologyDefs.NCO_POSTALCODE);
 
592
                  builder.object_string (pa.postal_code);
 
593
                  builder.predicate (Trf.OntologyDefs.NCO_STREET_ADDRESS);
 
594
                  builder.object_string (pa.street);
 
595
                  builder.predicate (Trf.OntologyDefs.NCO_EXTENDED_ADDRESS);
 
596
                  builder.object_string (pa.extension);
 
597
                  builder.predicate (Trf.OntologyDefs.NCO_COUNTRY);
 
598
                  builder.object_string (pa.country);
 
599
                  builder.predicate (Trf.OntologyDefs.NCO_REGION);
 
600
                  builder.object_string (pa.region);
 
601
 
 
602
                  builder.subject (postal_affl);
 
603
                  builder.predicate ("a");
 
604
                  builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
605
                  builder.predicate (Trf.OntologyDefs.NCO_HAS_POSTAL_ADDRESS);
 
606
                  builder.object (postal);
 
607
 
 
608
                  builder.subject ("_:p");
 
609
                  builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
610
                  builder.object (postal_affl);
 
611
 
 
612
                  postal_cnt++;
 
613
                }
 
614
            }
 
615
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.URLS))
 
616
            {
 
617
              Set<FieldDetails> urls = (Set<FieldDetails>) v.get_object ();
 
618
 
 
619
              int url_cnt = 0;
 
620
              foreach (var u in urls)
 
621
                {
 
622
                  var url_affl = "_:url_affl%d".printf (url_cnt);
 
623
 
 
624
                  builder.subject (url_affl);
 
625
                  builder.predicate ("a");
 
626
                  builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
627
                  builder.predicate (Trf.OntologyDefs.NCO_URL);
 
628
                  builder.object_string (u.value);
 
629
 
 
630
                  builder.subject ("_:p");
 
631
                  builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
632
                  builder.object (url_affl);
 
633
 
 
634
                  url_cnt++;
 
635
                }
 
636
            }
 
637
          else if (k == Folks.PersonaStore.detail_key (PersonaDetail.LOCAL_IDS))
 
638
            {
 
639
              var local_ids = (Gee.HashSet<string>) v.get_object ();
 
640
              string ids = Trf.PersonaStore.serialize_local_ids (local_ids);
 
641
 
 
642
              builder.subject ("_:folks_ids");
 
643
              builder.predicate ("a");
 
644
              builder.object (Trf.OntologyDefs.NAO_PROPERTY);
 
645
              builder.predicate (Trf.OntologyDefs.NAO_PROPERTY_NAME);
 
646
              builder.object_string (this._LOCAL_ID_PROPERTY_NAME);
 
647
              builder.predicate (Trf.OntologyDefs.NAO_PROPERTY_VALUE);
 
648
              builder.object_string (ids);
 
649
 
 
650
              builder.subject ("_:p");
 
651
              builder.predicate (Trf.OntologyDefs.NAO_HAS_PROPERTY);
 
652
              builder.object ("_:folks_ids");
 
653
            }
 
654
          else if (k ==
 
655
              Folks.PersonaStore.detail_key (
 
656
                  PersonaDetail.WEB_SERVICE_ADDRESSES))
 
657
            {
 
658
              MultiMap<string, string> ws_obj =
 
659
                (MultiMap<string, string>) v.get_object ();
 
660
 
 
661
              var ws_addrs = Trf.PersonaStore.serialize_web_services (ws_obj);
 
662
 
 
663
              builder.subject ("_:folks_ws_ids");
 
664
              builder.predicate ("a");
 
665
              builder.object (Trf.OntologyDefs.NAO_PROPERTY);
 
666
              builder.predicate (Trf.OntologyDefs.NAO_PROPERTY_NAME);
 
667
              builder.object_string (this._WSD_PROPERTY_NAME);
 
668
              builder.predicate (Trf.OntologyDefs.NAO_PROPERTY_VALUE);
 
669
              builder.object_string (ws_addrs);
 
670
 
 
671
              builder.subject ("_:p");
 
672
              builder.predicate (Trf.OntologyDefs.NAO_HAS_PROPERTY);
 
673
              builder.object ("_:folks_ws_ids");
 
674
            }
 
675
          else
 
676
            {
 
677
              throw new PersonaStoreError.INVALID_ARGUMENT (
 
678
                  /* Translators: the first parameter identifies the
 
679
                   * persona store and the second the unknown key
 
680
                   * that was received with the details params. */
 
681
                _("Unrecognized paramter %s by the  %s PersonaStore:\n')"),
 
682
                this.type_id, k);
 
683
            }
 
684
        }
 
685
      builder.insert_close ();
 
686
 
 
687
      Trf.Persona ret = null;
 
688
      lock (this._personas)
 
689
        {
 
690
          string? contact_urn = yield this._insert_persona (builder.result,
 
691
              "p");
 
692
          if (contact_urn != null)
 
693
            {
 
694
              string filter = " FILTER(?_contact = <%s>) ".printf (contact_urn);
 
695
              string query = this._INITIAL_QUERY.printf (filter);
 
696
              var ret_personas = yield this._do_add_contacts (query);
 
697
 
 
698
              /* Return the first persona we find in the set */
 
699
              foreach (var p in ret_personas)
 
700
                {
 
701
                  ret = p;
 
702
                  break;
 
703
                }
 
704
            }
 
705
          else
 
706
            {
 
707
              debug ("Failed to inserting the new persona  into Tracker.");
 
708
            }
 
709
        }
 
710
 
 
711
      return ret;
 
712
    }
 
713
 
 
714
 /**
 
715
   * Returns "service1:addr1,addr2;service2:addr3,.."
 
716
   *
 
717
   * @since 0.5.1
 
718
   */
 
719
  public static string serialize_web_services (
 
720
      MultiMap<string, string> ws_obj)
 
721
    {
 
722
      var str = "";
 
723
 
 
724
      foreach (var service in ws_obj.get_keys ())
 
725
        {
 
726
          if (str != "")
 
727
            {
 
728
              str += ";";
 
729
            }
 
730
 
 
731
          str += service + ":";
 
732
 
 
733
          var addrs = ws_obj.get (service);
 
734
          bool first = true;
 
735
          foreach (var addr in addrs)
 
736
            {
 
737
              if (first == false)
 
738
                {
 
739
                  str += ",";
 
740
                }
 
741
 
 
742
              str += addr;
 
743
              first = false;
 
744
            }
 
745
        }
 
746
 
 
747
      return str;
 
748
    }
 
749
 
 
750
 /**
 
751
   * Transforms "service1:addr1,addr2;service2:addr3,.." to
 
752
   *   --->  HashMultiMap<string, string>
 
753
   *
 
754
   * @since 0.5.1
 
755
   */
 
756
  public static MultiMap<string, string> unserialize_web_services
 
757
      (string ws_addrs)
 
758
    {
 
759
      var ret = new HashMultiMap<string, string> ();
 
760
 
 
761
      var services = ws_addrs.split (";");
 
762
      foreach (var service_line in services)
 
763
          {
 
764
            var service_t = service_line.split (":");
 
765
            var service_name = service_t[0];
 
766
            var addrs = service_t[1].split (",");
 
767
 
 
768
            foreach (var a in addrs)
 
769
              {
 
770
                ret.set (service_name, a);
 
771
              }
 
772
          }
 
773
 
 
774
      return ret;
 
775
    }
 
776
 
 
777
 /**
 
778
   * Transform HashSet<string> to "id1,id2,.."
 
779
   *
 
780
   * @since 0.5.1
 
781
   */
 
782
  public static string serialize_local_ids (Set<string> local_ids)
 
783
    {
 
784
      var str = "";
 
785
 
 
786
      foreach (var id in local_ids)
 
787
        {
 
788
          if (str != "")
 
789
            {
 
790
              str += ",";
 
791
            }
 
792
          str += id;
 
793
        }
 
794
 
 
795
      return str;
 
796
    }
 
797
 
 
798
  /**
 
799
   * Transform from id1,id2,.. to HashSet<string>
 
800
   *
 
801
   * @since 0.5.1
 
802
   */
 
803
  public static Set<string> unserialize_local_ids (string local_ids)
 
804
    {
 
805
      var ids = new HashSet<string> ();
 
806
 
 
807
      if (local_ids != "")
 
808
        {
 
809
          string[] ids_a = local_ids.split (",");
 
810
          foreach (var id in ids_a)
 
811
            {
 
812
              ids.add (id);
 
813
            }
 
814
        }
 
815
 
 
816
      return ids;
 
817
    }
 
818
 
 
819
  /**
 
820
   * Remove a {@link Persona} from the PersonaStore.
 
821
   *
 
822
   * See {@link Folks.PersonaStore.remove_persona}.
 
823
   *
 
824
   */
 
825
  public override async void remove_persona (Folks.Persona persona)
 
826
      throws Folks.PersonaStoreError
 
827
    {
 
828
      var urn = yield this._remove_attributes_from_persona (persona,
 
829
          _REMOVE_ALL_ATTRIBS);
 
830
 
 
831
      /* Finally: remove literal properties */
 
832
      var q = " DELETE { " +
 
833
        " %s ?p ?o " +
 
834
        "} " +
 
835
        "WHERE { " +
 
836
        " %s ?p ?o " +
 
837
        "} ";
 
838
      yield this._tracker_update (q.printf (urn, urn), "remove_persona");
 
839
    }
 
840
 
 
841
  private async string _remove_attributes_from_persona (Folks.Persona persona,
 
842
      char remove_flag)
 
843
    {
 
844
      var urn = yield this._urn_from_persona (persona);
 
845
      yield this._remove_attributes (urn, remove_flag);
 
846
      return urn;
 
847
    }
 
848
 
 
849
  private async void _build_update_query_set (
 
850
      Tracker.Sparql.Builder builder,
 
851
      Set<FieldDetails> properties,
 
852
      string contact_var,
 
853
      Trf.Attrib attrib)
 
854
    {
 
855
      string? affl_var = null;
 
856
      string? obj_var = null;
 
857
      unowned string? related_attrib = null;
 
858
      unowned string? related_prop = null;
 
859
      unowned string? related_connection = null;
 
860
 
 
861
      switch (attrib)
 
862
        {
 
863
          case Trf.Attrib.PHONES:
 
864
            related_attrib = Trf.OntologyDefs.NCO_PHONE;
 
865
            related_prop = Trf.OntologyDefs.NCO_PHONE_PROP;
 
866
            related_connection = Trf.OntologyDefs.NCO_HAS_PHONE;
 
867
            affl_var = "_:phone_affl%d";
 
868
            obj_var = "_:phone%d";
 
869
            break;
 
870
          case Trf.Attrib.EMAILS:
 
871
            related_attrib = Trf.OntologyDefs.NCO_EMAIL;
 
872
            related_prop = Trf.OntologyDefs.NCO_EMAIL_PROP;
 
873
            related_connection = Trf.OntologyDefs.NCO_HAS_EMAIL;
 
874
            affl_var = "_:email_affl%d";
 
875
            obj_var = "_:email%d";
 
876
            break;
 
877
        }
 
878
 
 
879
      int cnt = 0;
 
880
      foreach (var p in properties)
 
881
        {
 
882
          var affl = affl_var.printf (cnt);
 
883
          var obj = yield this._urn_from_property (
 
884
              related_attrib, related_prop, p.value);
 
885
 
 
886
          if (obj == "")
 
887
            {
 
888
              obj = obj_var.printf (cnt);
 
889
              builder.subject (obj);
 
890
              builder.predicate ("a");
 
891
              builder.object (related_attrib);
 
892
              builder.predicate (related_prop);
 
893
              builder.object_string (p.value);
 
894
            }
 
895
 
 
896
          builder.subject (affl);
 
897
          builder.predicate ("a");
 
898
          builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
899
          builder.predicate (related_connection);
 
900
          builder.object (obj);
 
901
 
 
902
          builder.subject (contact_var);
 
903
          builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
904
          builder.object (affl);
 
905
 
 
906
          cnt++;
 
907
        }
 
908
    }
 
909
 
 
910
  /*
 
911
   * Garbage collecting related resources:
 
912
   *  - for each related resource we (recursively)
 
913
   *    check to if the deleted nco:Person
 
914
   *    is the only one holding a link, if so we
 
915
   *    remove the resource.
 
916
   */
 
917
  private async void _remove_attributes (string urn, char remove_flag)
 
918
    {
 
919
      Gee.HashSet<string> affiliations =
 
920
       yield this._affiliations_from_persona (urn);
 
921
 
 
922
     foreach (var affl in affiliations)
 
923
       {
 
924
         bool got_attrib = false;
 
925
 
 
926
         if ((remove_flag & _REMOVE_ALL_ATTRIBS) ==
 
927
             _REMOVE_ALL_ATTRIBS ||
 
928
             (remove_flag & _REMOVE_PHONES) == _REMOVE_PHONES)
 
929
           {
 
930
             Gee.HashSet<string> phones =
 
931
               yield this._phones_from_affiliation (affl);
 
932
 
 
933
             foreach (var phone in phones)
 
934
               {
 
935
                 got_attrib = true;
 
936
                 yield this._delete_resource (phone);
 
937
               }
 
938
           }
 
939
 
 
940
         if ((remove_flag & _REMOVE_ALL_ATTRIBS) ==
 
941
             _REMOVE_ALL_ATTRIBS ||
 
942
             (remove_flag & _REMOVE_POSTALS) == _REMOVE_POSTALS)
 
943
           {
 
944
             Gee.HashSet<string> postals =
 
945
               yield this._postals_from_affiliation (affl);
 
946
             foreach (var postal in postals)
 
947
               {
 
948
                 got_attrib = true;
 
949
                 yield this._delete_resource (postal);
 
950
               }
 
951
           }
 
952
 
 
953
         if ((remove_flag & _REMOVE_ALL_ATTRIBS) ==
 
954
             _REMOVE_ALL_ATTRIBS ||
 
955
             (remove_flag & _REMOVE_IM_ADDRS) == _REMOVE_IM_ADDRS)
 
956
           {
 
957
             Gee.HashSet<string> im_addrs =
 
958
               yield this._imaddrs_from_affiliation (affl);
 
959
             foreach (var im_addr in im_addrs)
 
960
               {
 
961
                 got_attrib = true;
 
962
                 yield this._delete_resource (im_addr);
 
963
               }
 
964
           }
 
965
 
 
966
         if ((remove_flag & _REMOVE_ALL_ATTRIBS) ==
 
967
             _REMOVE_ALL_ATTRIBS ||
 
968
             (remove_flag & _REMOVE_EMAILS) == _REMOVE_EMAILS)
 
969
           {
 
970
             Gee.HashSet<string> emails =
 
971
               yield this._emails_from_affiliation (affl);
 
972
               foreach (var email in emails)
 
973
                 {
 
974
                   got_attrib = true;
 
975
                   yield yield this._delete_resource (email);
 
976
                 }
 
977
           }
 
978
 
 
979
         if (got_attrib ||
 
980
             (remove_flag & _REMOVE_ALL_ATTRIBS) == _REMOVE_ALL_ATTRIBS)
 
981
           yield this._delete_resource (affl);
 
982
       }
 
983
   }
 
984
 
 
985
  /**
 
986
   * Prepare the PersonaStore for use.
 
987
   *
 
988
   * TODO: we should throw different errors dependening on what went wrong
 
989
   *       when we were trying to setup the PersonaStore.
 
990
   *
 
991
   * See {@link Folks.PersonaStore.prepare}.
 
992
   */
 
993
  public override async void prepare () throws GLib.Error
 
994
    {
 
995
      lock (this._is_prepared)
 
996
        {
 
997
          if (!this._is_prepared)
 
998
            {
 
999
              try
 
1000
                {
 
1001
                  this._connection =
 
1002
                    yield Tracker.Sparql.Connection.get_async ();
 
1003
 
 
1004
                  yield this._build_predicates_table ();
 
1005
                  yield this._do_add_contacts (this._INITIAL_QUERY.printf (""));
 
1006
 
 
1007
                  /* Don't add a match rule for all signals from Tracker but
 
1008
                   * only for GraphUpdated with the specific class we need. We
 
1009
                   * don't want to be woken up for irrelevent updates on the
 
1010
                   * graph.
 
1011
                   */
 
1012
                  this._resources_object = yield GLib.Bus.get_proxy<Resources> (
 
1013
                      BusType.SESSION,
 
1014
                      this._OBJECT_NAME,
 
1015
                      this._OBJECT_PATH,
 
1016
                      DBusProxyFlags.DO_NOT_CONNECT_SIGNALS |
 
1017
                        DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
 
1018
                  this._resources_object.g_connection.signal_subscribe
 
1019
                      (this._OBJECT_NAME, this._OBJECT_IFACE,
 
1020
                      "GraphUpdated", this._OBJECT_PATH,
 
1021
                      Trf.OntologyDefs.PERSON_CLASS, GLib.DBusSignalFlags.NONE,
 
1022
                      this._graph_updated_cb);
 
1023
 
 
1024
                  this._is_prepared = true;
 
1025
                  this.notify_property ("is-prepared");
 
1026
                }
 
1027
              catch (GLib.IOError e1)
 
1028
                {
 
1029
                  warning ("Could not connect to D-Bus service: %s",
 
1030
                           e1.message);
 
1031
                  throw new PersonaStoreError.INVALID_ARGUMENT (e1.message);
 
1032
                }
 
1033
              catch (Tracker.Sparql.Error e2)
 
1034
                {
 
1035
                  warning ("Error fetching SPARQL connection handler: %s",
 
1036
                           e2.message);
 
1037
                  throw new PersonaStoreError.INVALID_ARGUMENT (e2.message);
 
1038
                }
 
1039
              catch (GLib.DBusError e3)
 
1040
                {
 
1041
                  warning ("Could not connect to D-Bus service: %s",
 
1042
                           e3.message);
 
1043
                  throw new PersonaStoreError.INVALID_ARGUMENT (e3.message);
 
1044
                }
 
1045
            }
 
1046
        }
 
1047
    }
 
1048
 
 
1049
  public int get_favorite_id ()
 
1050
    {
 
1051
      return this._prefix_tracker_id.get
 
1052
          (Trf.OntologyDefs.NAO_FAVORITE);
 
1053
    }
 
1054
 
 
1055
  public int get_gender_male_id ()
 
1056
    {
 
1057
      return this._prefix_tracker_id.get
 
1058
          (Trf.OntologyDefs.NCO_MALE);
 
1059
    }
 
1060
 
 
1061
  public int get_gender_female_id ()
 
1062
    {
 
1063
      return this._prefix_tracker_id.get
 
1064
          (Trf.OntologyDefs.NCO_FEMALE);
 
1065
    }
 
1066
 
 
1067
  private async void _build_predicates_table ()
 
1068
    {
 
1069
      if (this._prefix_tracker_id != null)
 
1070
        {
 
1071
          return;
 
1072
        }
 
1073
 
 
1074
      yield this._build_urn_prefix_table ();
 
1075
 
 
1076
      this._prefix_tracker_id = new Gee.TreeMap<string, int> ();
 
1077
 
 
1078
      string query = "SELECT  ";
 
1079
      foreach (var urn_t in this._urn_prefix.keys)
 
1080
        {
 
1081
          query += " tracker:id(" + urn_t + ")";
 
1082
        }
 
1083
      query += " WHERE {} ";
 
1084
 
 
1085
      try
 
1086
        {
 
1087
          Sparql.Cursor cursor = yield this._connection.query_async (query);
 
1088
 
 
1089
          while (cursor.next ())
 
1090
            {
 
1091
              int i=0;
 
1092
              foreach (var urn in this._urn_prefix.keys)
 
1093
                {
 
1094
                  var tracker_id = (int) cursor.get_integer (i);
 
1095
                  var prefix = this._urn_prefix.get (urn).dup ();
 
1096
                  this._prefix_tracker_id.set (prefix, tracker_id);
 
1097
                  i++;
 
1098
                }
 
1099
            }
 
1100
        }
 
1101
      catch (Tracker.Sparql.Error e1)
 
1102
        {
 
1103
          warning ("Couldn't build predicates table: %s %s", query, e1.message);
 
1104
        }
 
1105
      catch (GLib.Error e2)
 
1106
        {
 
1107
          warning ("Couldn't build predicates table: %s %s", query, e2.message);
 
1108
        }
 
1109
    }
 
1110
 
 
1111
  private async void _build_urn_prefix_table ()
 
1112
    {
 
1113
      if (this._urn_prefix != null)
 
1114
        {
 
1115
          return;
 
1116
        }
 
1117
      this._urn_prefix = new Gee.TreeMap<string, string> ();
 
1118
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#fullname>",
 
1119
          Trf.OntologyDefs.NCO_FULLNAME);
 
1120
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameFamily>",
 
1121
          Trf.OntologyDefs.NCO_FAMILY);
 
1122
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameGiven>",
 
1123
          Trf.OntologyDefs.NCO_GIVEN);
 
1124
      this._urn_prefix.set (
 
1125
          Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameAdditional>",
 
1126
          Trf.OntologyDefs.NCO_ADDITIONAL);
 
1127
      this._urn_prefix.set (
 
1128
          Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameHonorificSuffix>",
 
1129
          Trf.OntologyDefs.NCO_SUFFIX);
 
1130
      this._urn_prefix.set (
 
1131
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nameHonorificPrefix>",
 
1132
         Trf.OntologyDefs.NCO_PREFIX);
 
1133
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#nickname>",
 
1134
         Trf.OntologyDefs.NCO_NICKNAME);
 
1135
      this._urn_prefix.set (
 
1136
         Trf.OntologyDefs.RDF_URL_PREFIX + "22-rdf-syntax-ns#type>",
 
1137
         Trf.OntologyDefs.RDF_TYPE);
 
1138
      this._urn_prefix.set (
 
1139
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#PersonContact>",
 
1140
         Trf.OntologyDefs.NCO_PERSON);
 
1141
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#websiteUrl>",
 
1142
         Trf.OntologyDefs.NCO_WEBSITE);
 
1143
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#blogUrl>",
 
1144
         Trf.OntologyDefs.NCO_BLOG);
 
1145
      this._urn_prefix.set (
 
1146
         Trf.OntologyDefs.NAO_URL_PREFIX + "nao#predefined-tag-favorite>",
 
1147
         Trf.OntologyDefs.NAO_FAVORITE);
 
1148
      this._urn_prefix.set (Trf.OntologyDefs.NAO_URL_PREFIX + "nao#hasTag>",
 
1149
         Trf.OntologyDefs.NAO_TAG);
 
1150
      this._urn_prefix.set (
 
1151
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#hasEmailAddress>",
 
1152
         Trf.OntologyDefs.NCO_HAS_EMAIL);
 
1153
      this._urn_prefix.set (
 
1154
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#hasPhoneNumber>",
 
1155
         Trf.OntologyDefs.NCO_HAS_PHONE);
 
1156
      this._urn_prefix.set (
 
1157
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#hasAffiliation>",
 
1158
         Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
1159
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#birthDate>",
 
1160
         Trf.OntologyDefs.NCO_BIRTHDAY);
 
1161
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#note>",
 
1162
         Trf.OntologyDefs.NCO_NOTE);
 
1163
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender>",
 
1164
         Trf.OntologyDefs.NCO_GENDER);
 
1165
      this._urn_prefix.set (
 
1166
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender-male>",
 
1167
         Trf.OntologyDefs.NCO_MALE);
 
1168
      this._urn_prefix.set (
 
1169
         Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender-female>",
 
1170
         Trf.OntologyDefs.NCO_FEMALE);
 
1171
      this._urn_prefix.set (Trf.OntologyDefs.NCO_URL_PREFIX + "nco#photo>",
 
1172
         Trf.OntologyDefs.NCO_PHOTO);
 
1173
      this._urn_prefix.set (
 
1174
         Trf.OntologyDefs.NAO_URL_PREFIX + "nao#hasProperty>",
 
1175
         Trf.OntologyDefs.NAO_PROPERTY);
 
1176
    }
 
1177
 
 
1178
  private void _graph_updated_cb (DBusConnection connection,
 
1179
      string sender_name, string object_path, string interface_name,
 
1180
      string signal_name, Variant parameters)
 
1181
    {
 
1182
      string class_name = "";
 
1183
      VariantIter iter_del = null;
 
1184
      VariantIter iter_ins = null;
 
1185
 
 
1186
      parameters.get("(sa(iiii)a(iiii))", &class_name, &iter_del, &iter_ins);
 
1187
 
 
1188
      if (class_name != Trf.OntologyDefs.PERSON_CLASS)
 
1189
        {
 
1190
          return;
 
1191
        }
 
1192
 
 
1193
      this._handle_events ((owned) iter_del, (owned) iter_ins);
 
1194
    }
 
1195
 
 
1196
  private async void _handle_events
 
1197
      (owned VariantIter iter_del, owned VariantIter iter_ins)
 
1198
    {
 
1199
      yield this._handle_delete_events ((owned) iter_del);
 
1200
      yield this._handle_insert_events ((owned) iter_ins);
 
1201
    }
 
1202
 
 
1203
  private async void _handle_delete_events (owned VariantIter iter_del)
 
1204
    {
 
1205
      var removed_personas = new HashSet<Persona> ();
 
1206
      var nco_person_id =
 
1207
          this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_PERSON);
 
1208
      var rdf_type_id = this._prefix_tracker_id.get (Trf.OntologyDefs.RDF_TYPE);
 
1209
      Event e = Event ();
 
1210
 
 
1211
      while (iter_del.next
 
1212
          ("(iiii)", &e.graph_id, &e.subject_id, &e.pred_id, &e.object_id))
 
1213
        {
 
1214
          var p_id = Trf.Persona.build_iid (this.id, e.subject_id.to_string ());
 
1215
          if (e.pred_id == rdf_type_id &&
 
1216
              e.object_id == nco_person_id)
 
1217
            {
 
1218
              lock (this._personas)
 
1219
                {
 
1220
                  var removed_p = this._personas.get (p_id);
 
1221
                  if (removed_p != null)
 
1222
                    {
 
1223
                      removed_personas.add (removed_p);
 
1224
                      _personas.unset (removed_p.iid);
 
1225
                    }
 
1226
                }
 
1227
            }
 
1228
          else
 
1229
            {
 
1230
              var persona = this._personas.get (p_id);
 
1231
              if (persona != null)
 
1232
                {
 
1233
                  yield this._do_update (persona, e, false);
 
1234
                }
 
1235
            }
 
1236
        }
 
1237
 
 
1238
      if (removed_personas.size > 0)
 
1239
        {
 
1240
          this._emit_personas_changed (null, removed_personas);
 
1241
        }
 
1242
    }
 
1243
 
 
1244
  private async void _handle_insert_events (owned VariantIter iter_ins)
 
1245
    {
 
1246
      var added_personas = new HashSet<Persona> ();
 
1247
      Event e = Event ();
 
1248
 
 
1249
      while (iter_ins.next
 
1250
          ("(iiii)", &e.graph_id, &e.subject_id, &e.pred_id, &e.object_id))
 
1251
        {
 
1252
          var subject_tracker_id = e.subject_id.to_string ();
 
1253
          var p_id = Trf.Persona.build_iid (this.id, subject_tracker_id);
 
1254
          Trf.Persona persona;
 
1255
          lock (this._personas)
 
1256
            {
 
1257
              persona = this._personas.get (p_id);
 
1258
              if (persona == null)
 
1259
                {
 
1260
                  persona = new Trf.Persona (this, subject_tracker_id);
 
1261
                  this._personas.set (persona.iid, persona);
 
1262
                  added_personas.add (persona);
 
1263
                }
 
1264
            }
 
1265
          yield this._do_update (persona, e);
 
1266
        }
 
1267
 
 
1268
      if (added_personas.size > 0)
 
1269
        {
 
1270
          this._emit_personas_changed (added_personas, null);
 
1271
        }
 
1272
    }
 
1273
 
 
1274
  private async HashSet<Persona> _do_add_contacts (string query)
 
1275
    {
 
1276
      var added_personas = new HashSet<Persona> ();
 
1277
 
 
1278
      try {
 
1279
        Sparql.Cursor cursor = yield this._connection.query_async (query);
 
1280
 
 
1281
        while (cursor.next ())
 
1282
          {
 
1283
            int tracker_id = (int) cursor.get_integer (Trf.Fields.TRACKER_ID);
 
1284
            var p_id = Trf.Persona.build_iid (this.id, tracker_id.to_string ());
 
1285
            if (this._personas.get (p_id) == null)
 
1286
              {
 
1287
                var persona = new Trf.Persona (this,
 
1288
                    tracker_id.to_string (), cursor);
 
1289
                this._personas.set (persona.iid, persona);
 
1290
                added_personas.add (persona);
 
1291
              }
 
1292
          }
 
1293
 
 
1294
        if (added_personas.size > 0)
 
1295
          {
 
1296
            this._emit_personas_changed (added_personas, null);
 
1297
          }
 
1298
      } catch (GLib.Error e) {
 
1299
        warning ("Couldn't perform queries: %s %s", query, e.message);
 
1300
      }
 
1301
 
 
1302
      return added_personas;
 
1303
    }
 
1304
 
 
1305
  private async void _do_update (Persona p, Event e, bool adding = true)
 
1306
    {
 
1307
      if (e.pred_id ==
 
1308
          this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_FULLNAME))
 
1309
        {
 
1310
          string fullname = "";
 
1311
          if (adding)
 
1312
            {
 
1313
              fullname =
 
1314
                yield this._get_property (e.subject_id,
 
1315
                    Trf.OntologyDefs.NCO_FULLNAME);
 
1316
            }
 
1317
          p._update_full_name (fullname);
 
1318
        }
 
1319
      else if (e.pred_id ==
 
1320
               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_NICKNAME))
 
1321
        {
 
1322
          string alias = "";
 
1323
          if (adding)
 
1324
            {
 
1325
              alias =
 
1326
                yield this._get_property (
 
1327
                    e.subject_id, Trf.OntologyDefs.NCO_NICKNAME);
 
1328
            }
 
1329
          p._update_alias (alias);
 
1330
        }
 
1331
      else if (e.pred_id ==
 
1332
               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_FAMILY))
 
1333
        {
 
1334
          string family_name = "";
 
1335
          if (adding)
 
1336
            {
 
1337
              family_name = yield this._get_property (e.subject_id,
 
1338
                  Trf.OntologyDefs.NCO_FAMILY);
 
1339
            }
 
1340
          p._update_family_name (family_name);
 
1341
        }
 
1342
      else if (e.pred_id ==
 
1343
               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_GIVEN))
 
1344
        {
 
1345
          string given_name = "";
 
1346
          if (adding)
 
1347
            {
 
1348
              given_name = yield this._get_property (
 
1349
                  e.subject_id, Trf.OntologyDefs.NCO_GIVEN);
 
1350
            }
 
1351
          p._update_given_name (given_name);
 
1352
        }
 
1353
      else if (e.pred_id ==
 
1354
               this._prefix_tracker_id.get (Trf.OntologyDefs.NCO_ADDITIONAL))
 
1355
        {
 
1356
          string additional_name = "";
 
1357
          if (adding)
 
1358
            {
 
1359
              additional_name = yield this._get_property
 
1360
                  (e.subject_id, Trf.OntologyDefs.NCO_ADDITIONAL);
 
1361
            }
 
1362
          p._update_additional_names (additional_name);
 
1363
        }
 
1364
      else if (e.pred_id == this._prefix_tracker_id.get
 
1365
          (Trf.OntologyDefs.NCO_SUFFIX))
 
1366
        {
 
1367
          string suffix_name = "";
 
1368
          if (adding)
 
1369
            {
 
1370
              suffix_name = yield this._get_property
 
1371
                  (e.subject_id, Trf.OntologyDefs.NCO_SUFFIX);
 
1372
            }
 
1373
          p._update_suffixes (suffix_name);
 
1374
        }
 
1375
      else if (e.pred_id == this._prefix_tracker_id.get
 
1376
          (Trf.OntologyDefs.NCO_PREFIX))
 
1377
        {
 
1378
          string prefix_name = "";
 
1379
          if (adding)
 
1380
            {
 
1381
              prefix_name = yield this._get_property
 
1382
                  (e.subject_id, Trf.OntologyDefs.NCO_PREFIX);
 
1383
            }
 
1384
          p._update_prefixes (prefix_name);
 
1385
        }
 
1386
      else if (e.pred_id == this._prefix_tracker_id.get
 
1387
          (Trf.OntologyDefs.NAO_TAG))
 
1388
        {
 
1389
          if (e.object_id == this.get_favorite_id ())
 
1390
            {
 
1391
              if (adding)
 
1392
                {
 
1393
                  p._set_favourite (true);
 
1394
                }
 
1395
              else
 
1396
                {
 
1397
                  p._set_favourite (false);
 
1398
                }
 
1399
            }
 
1400
        }
 
1401
      else if (e.pred_id == this._prefix_tracker_id.get
 
1402
          (Trf.OntologyDefs.NCO_HAS_EMAIL))
 
1403
        {
 
1404
          if (adding)
 
1405
            {
 
1406
              var email = yield this._get_property (
 
1407
                  e.object_id,
 
1408
                  Trf.OntologyDefs.NCO_EMAIL_PROP,
 
1409
                  Trf.OntologyDefs.NCO_EMAIL);
 
1410
              p._add_email (email, e.object_id.to_string ());
 
1411
            }
 
1412
          else
 
1413
            {
 
1414
              p._remove_email (e.object_id.to_string ());
 
1415
            }
 
1416
        }
 
1417
      else if (e.pred_id == this._prefix_tracker_id.get
 
1418
          (Trf.OntologyDefs.NCO_HAS_PHONE))
 
1419
        {
 
1420
          if (adding)
 
1421
            {
 
1422
              var phone = yield this._get_property (
 
1423
                  e.object_id, Trf.OntologyDefs.NCO_PHONE_PROP,
 
1424
                  Trf.OntologyDefs.NCO_PHONE);
 
1425
              p._add_phone (phone, e.object_id.to_string ());
 
1426
            }
 
1427
          else
 
1428
            {
 
1429
              p._remove_phone (e.object_id.to_string ());
 
1430
            }
 
1431
        }
 
1432
      else if (e.pred_id == this._prefix_tracker_id.get
 
1433
          (Trf.OntologyDefs.NCO_HAS_AFFILIATION))
 
1434
        {
 
1435
          if (adding)
 
1436
            {
 
1437
              var affl_info =
 
1438
                yield this._get_affl_info (e.subject_id.to_string (),
 
1439
                  e.object_id.to_string ());
 
1440
 
 
1441
              debug ("affl_info : %s", affl_info.to_string ());
 
1442
 
 
1443
              if (affl_info.im_tracker_id != null)
 
1444
                {
 
1445
                  p._update_nickname (affl_info.im_nickname);
 
1446
                  if (affl_info.im_proto != null)
 
1447
                    p._add_im_address (affl_info.affl_tracker_id,
 
1448
                        affl_info.im_proto, affl_info.im_account_id);
 
1449
                }
 
1450
 
 
1451
              if (affl_info.affl_tracker_id != null)
 
1452
                {
 
1453
                  if (affl_info.title != null ||
 
1454
                      affl_info.org != null)
 
1455
                    {
 
1456
                      p._add_role (affl_info.affl_tracker_id, affl_info.title,
 
1457
                          affl_info.org);
 
1458
                    }
 
1459
                }
 
1460
 
 
1461
              if (affl_info.postal_address != null)
 
1462
                p._add_postal_address (affl_info.postal_address);
 
1463
 
 
1464
              if (affl_info.phone != null)
 
1465
                p._add_phone (affl_info.phone, e.object_id.to_string ());
 
1466
 
 
1467
              if (affl_info.email != null)
 
1468
                p._add_email (affl_info.email, e.object_id.to_string ());
 
1469
 
 
1470
              if (affl_info.website != null)
 
1471
                p._add_url (affl_info.website,
 
1472
                    affl_info.affl_tracker_id, "website");
 
1473
 
 
1474
              if (affl_info.blog != null)
 
1475
                p._add_url (affl_info.blog,
 
1476
                    affl_info.affl_tracker_id, "blog");
 
1477
 
 
1478
              if (affl_info.url != null)
 
1479
                p._add_url (affl_info.url,
 
1480
                    affl_info.affl_tracker_id, "url");
 
1481
            }
 
1482
          else
 
1483
            {
 
1484
              p._remove_im_address (e.object_id.to_string ());
 
1485
              p._remove_role (e.object_id.to_string ());
 
1486
              p._remove_postal_address (e.object_id.to_string ());
 
1487
              p._remove_phone (e.object_id.to_string ());
 
1488
              p._remove_email (e.object_id.to_string ());
 
1489
              p._remove_url (e.object_id.to_string ());
 
1490
            }
 
1491
        }
 
1492
      else if (e.pred_id == this._prefix_tracker_id.get
 
1493
          (Trf.OntologyDefs.NCO_BIRTHDAY))
 
1494
        {
 
1495
          string bday = "";
 
1496
          if (adding)
 
1497
            {
 
1498
              bday = yield this._get_property (
 
1499
                  e.subject_id, Trf.OntologyDefs.NCO_BIRTHDAY);
 
1500
            }
 
1501
          p._set_birthday (bday);
 
1502
        }
 
1503
      else if (e.pred_id == this._prefix_tracker_id.get
 
1504
          (Trf.OntologyDefs.NCO_NOTE))
 
1505
        {
 
1506
          string note = "";
 
1507
          if (adding)
 
1508
            {
 
1509
              note = yield this._get_property (
 
1510
                  e.subject_id, Trf.OntologyDefs.NCO_NOTE);
 
1511
            }
 
1512
          p._set_note (note);
 
1513
        }
 
1514
      else if (e.pred_id == this._prefix_tracker_id.get
 
1515
          (Trf.OntologyDefs.NCO_GENDER))
 
1516
        {
 
1517
          if (adding)
 
1518
            {
 
1519
              p._set_gender (e.object_id);
 
1520
            }
 
1521
          else
 
1522
            {
 
1523
              p._set_gender (0);
 
1524
            }
 
1525
        }
 
1526
      else if (e.pred_id == this._prefix_tracker_id.get
 
1527
          (Trf.OntologyDefs.NCO_PHOTO))
 
1528
        {
 
1529
          string avatar_url = "";
 
1530
          if (adding)
 
1531
            {
 
1532
              avatar_url = yield this._get_property (e.object_id,
 
1533
                  Trf.OntologyDefs.NIE_URL, Trf.OntologyDefs.NFO_IMAGE);
 
1534
            }
 
1535
          p._set_avatar (avatar_url);
 
1536
        }
 
1537
      else if (e.pred_id == this._prefix_tracker_id.get
 
1538
          (Trf.OntologyDefs.NAO_PROPERTY))
 
1539
        {
 
1540
          /* WARNING:
 
1541
           *  nao:Properties shouldn't be abused since we have to reset
 
1542
           *  them all when there is a DELETE. Plus, the Tracker devs
 
1543
           *  say nao:Property is by nature slow. */
 
1544
          if (adding)
 
1545
            {
 
1546
              string[] prop_info =  yield this._get_nao_property_by_prop_id (
 
1547
                  e.object_id);
 
1548
              if (prop_info[0] == this._LOCAL_ID_PROPERTY_NAME)
 
1549
                {
 
1550
                  p._set_local_ids (prop_info[1]);
 
1551
                }
 
1552
              else if (prop_info[0] == this._WSD_PROPERTY_NAME)
 
1553
                {
 
1554
                  p._set_web_service_addrs (prop_info[1]);
 
1555
                }
 
1556
            }
 
1557
          else
 
1558
            {
 
1559
              string local_ids = yield this._get_nao_property_by_person_id (
 
1560
                  e.subject_id,
 
1561
                  this._LOCAL_ID_PROPERTY_NAME);
 
1562
              string ws_addrs = yield this._get_nao_property_by_person_id (
 
1563
                  e.subject_id,
 
1564
                  this._WSD_PROPERTY_NAME);
 
1565
 
 
1566
              p._set_local_ids (local_ids);
 
1567
              p._set_web_service_addrs (ws_addrs);
 
1568
            }
 
1569
        }
 
1570
    }
 
1571
 
 
1572
  private async string _get_property
 
1573
      (int subject_tracker_id, string property,
 
1574
       string subject_type = Trf.OntologyDefs.NCO_PERSON)
 
1575
    {
 
1576
      const string query_template =
 
1577
        "SELECT ?property WHERE" +
 
1578
        " { ?p a %s ; " +
 
1579
        "   %s ?property " +
 
1580
        " . FILTER(tracker:id(?p) = %d ) }";
 
1581
 
 
1582
      string query = query_template.printf (subject_type,
 
1583
          property, subject_tracker_id);
 
1584
      return yield this._single_value_query (query);
 
1585
    }
 
1586
 
 
1587
  private async string _get_nao_property_by_person_id (int nco_person_id,
 
1588
      string prop_name)
 
1589
    {
 
1590
      const string query_t = "SELECT " +
 
1591
        " ?prop_value " +
 
1592
        "WHERE { " +
 
1593
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " ; " +
 
1594
        Trf.OntologyDefs.NAO_HAS_PROPERTY + " ?prop . " +
 
1595
        " ?prop " + Trf.OntologyDefs.NAO_PROPERTY_NAME + " ?prop_name . " +
 
1596
        " ?prop " + Trf.OntologyDefs.NAO_PROPERTY_VALUE + " ?prop_value . " +
 
1597
        " FILTER (tracker:id(?p) = %d && ?prop_name = '%s') } ";
 
1598
 
 
1599
      string query = query_t.printf(nco_person_id, prop_name);
 
1600
      return yield this._single_value_query (query);
 
1601
    }
 
1602
 
 
1603
  private async string[] _get_nao_property_by_prop_id (int nao_prop_id)
 
1604
    {
 
1605
      const string query_t = "SELECT " +
 
1606
        " fn:concat(?prop_name, '\t', ?prop_value)" +
 
1607
        "WHERE { " +
 
1608
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " ; " +
 
1609
        Trf.OntologyDefs.NAO_HAS_PROPERTY + " ?prop . " +
 
1610
        " ?prop " + Trf.OntologyDefs.NAO_PROPERTY_NAME + " ?prop_name . " +
 
1611
        " ?prop " + Trf.OntologyDefs.NAO_PROPERTY_VALUE + " ?prop_value . " +
 
1612
        " FILTER (tracker:id(?prop) = %d) } ";
 
1613
 
 
1614
      string query = query_t.printf(nao_prop_id);
 
1615
      var ret = yield this._single_value_query (query);
 
1616
      return ret.split ("\t");
 
1617
    }
 
1618
 
 
1619
  /*
 
1620
   * This should be kept in sync with Trf.AfflInfoFields
 
1621
   */
 
1622
  private async Trf.AfflInfo _get_affl_info (
 
1623
      string person_id, string affiliation_id)
 
1624
    {
 
1625
      Trf.AfflInfo affl_info = new Trf.AfflInfo ();
 
1626
      const string query_template =
 
1627
        "SELECT " +
 
1628
        "tracker:id(?i) " +
 
1629
        Trf.OntologyDefs.NCO_IMPROTOCOL  + "(?i) " +
 
1630
        Trf.OntologyDefs.NCO_IMID + "(?i) " +
 
1631
        "tracker:id(?a) " +
 
1632
        Trf.OntologyDefs.NCO_ROLE + "(?a) " +
 
1633
        Trf.OntologyDefs.NCO_ORG + "(?a) " +
 
1634
        Trf.OntologyDefs.NCO_POBOX + "(?postal) " +
 
1635
        Trf.OntologyDefs.NCO_DISTRICT + "(?postal) " +
 
1636
        Trf.OntologyDefs.NCO_COUNTY + "(?postal) " +
 
1637
        Trf.OntologyDefs.NCO_LOCALITY + "(?postal) " +
 
1638
        Trf.OntologyDefs.NCO_POSTALCODE + "(?postal) " +
 
1639
        Trf.OntologyDefs.NCO_STREET_ADDRESS + "(?postal) " +
 
1640
        Trf.OntologyDefs.NCO_ADDRESS_LOCATION + "(?postal) " +
 
1641
        Trf.OntologyDefs.NCO_EXTENDED_ADDRESS + "(?postal) " +
 
1642
        Trf.OntologyDefs.NCO_COUNTRY + "(?postal) " +
 
1643
        Trf.OntologyDefs.NCO_REGION + "(?postal) " +
 
1644
        Trf.OntologyDefs.NCO_EMAIL_PROP + "(?e) " +
 
1645
        Trf.OntologyDefs.NCO_PHONE_PROP + "(?number) " +
 
1646
        Trf.OntologyDefs.NCO_WEBSITE + "(?a) " +
 
1647
        Trf.OntologyDefs.NCO_BLOG + "(?a) " +
 
1648
        Trf.OntologyDefs.NCO_URL + "(?a) " +
 
1649
        Trf.OntologyDefs.NCO_IM_NICKNAME + "(?i) " +
 
1650
        "WHERE { "+
 
1651
        " ?p a " + Trf.OntologyDefs.NCO_PERSON  + " ; " +
 
1652
        Trf.OntologyDefs.NCO_HAS_AFFILIATION + " ?a . " +
 
1653
        " OPTIONAL { ?a " + Trf.OntologyDefs.NCO_HAS_IMADDRESS + " ?i } . " +
 
1654
        " OPTIONAL { ?a " + Trf.OntologyDefs.NCO_HAS_POSTAL_ADDRESS +
 
1655
        "               ?postal } . " +
 
1656
        " OPTIONAL { ?a " + Trf.OntologyDefs.NCO_HAS_EMAIL + " ?e } . " +
 
1657
        " OPTIONAL { ?a " + Trf.OntologyDefs.NCO_HAS_PHONE + " ?number }  " +
 
1658
        " FILTER(tracker:id(?p) = %s" +
 
1659
        " && tracker:id(?a) = %s" +
 
1660
        " ) } ";
 
1661
 
 
1662
      string query = query_template.printf (person_id, affiliation_id);
 
1663
 
 
1664
      debug ("_get_affl_info: %s", query);
 
1665
 
 
1666
      try
 
1667
        {
 
1668
          Sparql.Cursor cursor = yield this._connection.query_async (query);
 
1669
          while (yield cursor.next_async ())
 
1670
            {
 
1671
              affl_info.im_tracker_id = cursor.get_string
 
1672
                  (Trf.AfflInfoFields.IM_TRACKER_ID).dup ();
 
1673
              affl_info.im_proto = cursor.get_string
 
1674
                  (Trf.AfflInfoFields.IM_PROTOCOL).dup ();
 
1675
              affl_info.im_account_id = cursor.get_string
 
1676
                  (Trf.AfflInfoFields.IM_ACCOUNT_ID).dup ();
 
1677
              affl_info.im_nickname = cursor.get_string
 
1678
                  (Trf.AfflInfoFields.IM_NICKNAME).dup ();
 
1679
 
 
1680
              affl_info.affl_tracker_id = cursor.get_string
 
1681
                  (Trf.AfflInfoFields.AFFL_TRACKER_ID).dup ();
 
1682
              affl_info.title = cursor.get_string
 
1683
                  (Trf.AfflInfoFields.AFFL_ROLE).dup ();
 
1684
              affl_info.org = cursor.get_string
 
1685
                  (Trf.AfflInfoFields.AFFL_ORG).dup ();
 
1686
 
 
1687
              var po_box = cursor.get_string
 
1688
                  (Trf.AfflInfoFields.AFFL_POBOX).dup ();
 
1689
              var extension = cursor.get_string
 
1690
                  (Trf.AfflInfoFields.AFFL_EXTENDED_ADDRESS).dup ();
 
1691
              var street = cursor.get_string
 
1692
                  (Trf.AfflInfoFields.AFFL_STREET_ADDRESS).dup ();
 
1693
              var locality = cursor.get_string
 
1694
                  (Trf.AfflInfoFields.AFFL_LOCALITY).dup ();
 
1695
              var region = cursor.get_string
 
1696
                  (Trf.AfflInfoFields.AFFL_REGION).dup ();
 
1697
              var postal_code = cursor.get_string
 
1698
                  (Trf.AfflInfoFields.AFFL_POSTALCODE).dup ();
 
1699
              var country = cursor.get_string
 
1700
                  (Trf.AfflInfoFields.AFFL_COUNTRY).dup ();
 
1701
 
 
1702
              var types = new HashSet<string> ();
 
1703
 
 
1704
              affl_info.postal_address = new Folks.PostalAddress (
 
1705
                  po_box, extension, street, locality, region, postal_code,
 
1706
                  country, null, types, affl_info.affl_tracker_id);
 
1707
 
 
1708
              affl_info.email = cursor.get_string
 
1709
                  (Trf.AfflInfoFields.AFFL_EMAIL).dup ();
 
1710
              affl_info.phone = cursor.get_string
 
1711
                  (Trf.AfflInfoFields.AFFL_PHONE).dup ();
 
1712
 
 
1713
              affl_info.website = cursor.get_string
 
1714
                  (Trf.AfflInfoFields.AFFL_WEBSITE).dup ();
 
1715
              affl_info.blog = cursor.get_string
 
1716
                  (Trf.AfflInfoFields.AFFL_BLOG).dup ();
 
1717
              affl_info.url = cursor.get_string
 
1718
                  (Trf.AfflInfoFields.AFFL_URL).dup ();
 
1719
            }
 
1720
        }
 
1721
      catch (Tracker.Sparql.Error e1)
 
1722
        {
 
1723
          warning ("Couldn't fetch affiliation info: %s %s",
 
1724
              query, e1.message);
 
1725
        }
 
1726
      catch (GLib.Error e2)
 
1727
        {
 
1728
          warning ("Couldn't fetch affiliation info: %s %s",
 
1729
              query, e2.message);
 
1730
        }
 
1731
 
 
1732
      return affl_info;
 
1733
    }
 
1734
 
 
1735
  private async string? _insert_persona (string query, string persona_var)
 
1736
    {
 
1737
      GLib.Variant variant;
 
1738
      string contact_urn = null;
 
1739
 
 
1740
      try
 
1741
        {
 
1742
          debug ("_insert_persona: %s", query);
 
1743
          variant = yield this._connection.update_blank_async (query);
 
1744
 
 
1745
          VariantIter iter1, iter2, iter3;
 
1746
          string anon_var = null;
 
1747
          iter1 = variant.iterator ();
 
1748
 
 
1749
          while (iter1.next ("aa{ss}", out iter2))
 
1750
            {
 
1751
              if (iter2 == null)
 
1752
                continue;
 
1753
 
 
1754
              while (iter2.next ("a{ss}", out iter3))
 
1755
                {
 
1756
                  if (iter3 == null)
 
1757
                    continue;
 
1758
 
 
1759
                  while (iter3.next ("{ss}", out anon_var, out contact_urn))
 
1760
                    {
 
1761
                      /* The dictionary mapping blank node names to
 
1762
                       * IRIs doesn't have a fixed order so we need
 
1763
                       * check for the anon var corresponding to
 
1764
                       * nco:PersonContact.
 
1765
                       */
 
1766
                      if (anon_var == persona_var)
 
1767
                        return contact_urn;
 
1768
                    }
 
1769
                }
 
1770
            }
 
1771
        }
 
1772
      catch (GLib.Error e)
 
1773
        {
 
1774
          contact_urn = null;
 
1775
          warning ("Couldn't insert nco:PersonContact: %s", e.message);
 
1776
        }
 
1777
 
 
1778
      return null;
 
1779
    }
 
1780
 
 
1781
  private async string _single_value_query (string query)
 
1782
    {
 
1783
      Gee.HashSet<string> rows = yield this._multi_value_query (query);
 
1784
      foreach (var r in rows)
 
1785
        {
 
1786
          return r;
 
1787
        }
 
1788
      return "";
 
1789
    }
 
1790
 
 
1791
  private async Gee.HashSet<string> _multi_value_query (string query)
 
1792
    {
 
1793
      Gee.HashSet<string> ret = new Gee.HashSet<string> ();
 
1794
 
 
1795
      debug ("[_multi_value_query] %s", query);
 
1796
 
 
1797
      try
 
1798
        {
 
1799
          Sparql.Cursor cursor = yield this._connection.query_async (query);
 
1800
          while (cursor.next ())
 
1801
            {
 
1802
              var prop = cursor.get_string (0);
 
1803
              if (prop != null)
 
1804
                ret.add (prop);
 
1805
            }
 
1806
        }
 
1807
      catch (Tracker.Sparql.Error e1)
 
1808
        {
 
1809
          warning ("Couldn't run query: %s %s", query, e1.message);
 
1810
        }
 
1811
      catch (GLib.Error e2)
 
1812
        {
 
1813
          warning ("Couldn't run query: %s %s", query, e2.message);
 
1814
        }
 
1815
 
 
1816
      return ret;
 
1817
    }
 
1818
 
 
1819
  private async string _urn_from_tracker_id (string tracker_id)
 
1820
    {
 
1821
      const string query = "SELECT fn:concat('<', tracker:uri(%s), '>') " +
 
1822
        "WHERE {}";
 
1823
      return yield this._single_value_query (query.printf (tracker_id));
 
1824
    }
 
1825
 
 
1826
  internal async void _set_alias (Trf.Persona persona, string alias)
 
1827
    {
 
1828
      const string query_t = "DELETE { "+
 
1829
        " ?p " + Trf.OntologyDefs.NCO_NICKNAME + " ?n  " +
 
1830
        "} " +
 
1831
        "WHERE { " +
 
1832
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " ; " +
 
1833
        Trf.OntologyDefs.NCO_NICKNAME + " ?n . " +
 
1834
        " FILTER(tracker:id(?p) = %s) " +
 
1835
        "} " +
 
1836
        "INSERT { " +
 
1837
        " ?p " + Trf.OntologyDefs.NCO_NICKNAME + " '%s' " +
 
1838
        "} " +
 
1839
        "WHERE { "+
 
1840
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " . " +
 
1841
        "FILTER (tracker:id(?p) = %s) " +
 
1842
        "} ";
 
1843
 
 
1844
      string query = query_t.printf (persona.tracker_id (), alias,
 
1845
          persona.tracker_id ());
 
1846
 
 
1847
      yield this._tracker_update (query, "change_alias");
 
1848
    }
 
1849
 
 
1850
  internal async void _set_local_ids (Trf.Persona persona,
 
1851
      Set<string> local_ids)
 
1852
    {
 
1853
      string ids = Trf.PersonaStore.serialize_local_ids (local_ids);
 
1854
      yield this._set_tracker_property (persona,
 
1855
          this._LOCAL_ID_PROPERTY_NAME, ids,
 
1856
          "_set_local_ids");
 
1857
    }
 
1858
 
 
1859
  internal async void _set_web_service_addrs (Trf.Persona persona,
 
1860
      MultiMap<string, string> ws_obj)
 
1861
    {
 
1862
      var ws_addrs = Trf.PersonaStore.serialize_web_services (ws_obj);
 
1863
      yield this._set_tracker_property (persona,
 
1864
          this._WSD_PROPERTY_NAME, ws_addrs,
 
1865
          "_set_web_service_addrs");
 
1866
    }
 
1867
 
 
1868
  private async void _set_tracker_property(Trf.Persona persona,
 
1869
      string prop_name, string prop_value, string callers_name)
 
1870
    {
 
1871
      const string query_t = "DELETE " +
 
1872
      " { ?p " + Trf.OntologyDefs.NAO_HAS_PROPERTY + " ?prop } " +
 
1873
      "WHERE { " +
 
1874
      " ?p a " + Trf.OntologyDefs.NCO_PERSON + " ; " +
 
1875
      Trf.OntologyDefs.NAO_HAS_PROPERTY + " ?prop . " +
 
1876
      " ?prop " + Trf.OntologyDefs.NAO_PROPERTY_NAME + " ?prop_name . " +
 
1877
      " FILTER (tracker:id(?p) = %s && ?name = '%s' ) } " +
 
1878
      "INSERT { " +
 
1879
      " _:prop a " + Trf.OntologyDefs.NAO_PROPERTY + " ; " +
 
1880
      Trf.OntologyDefs.NAO_PROPERTY_NAME +
 
1881
      " '%s' ; " +
 
1882
      Trf.OntologyDefs.NAO_PROPERTY_VALUE + " '%s' . " +
 
1883
      " ?p " + Trf.OntologyDefs.NAO_HAS_PROPERTY + " _:prop " +
 
1884
      "} " +
 
1885
      "WHERE { " +
 
1886
      " ?p a nco:PersonContact . " +
 
1887
      "FILTER (tracker:id(?p) = %s) } ";
 
1888
 
 
1889
      string query = query_t.printf (persona.tracker_id (), prop_name,
 
1890
          prop_name, prop_value, persona.tracker_id ());
 
1891
      yield this._tracker_update (query, callers_name);
 
1892
    }
 
1893
 
 
1894
  internal async void _set_is_favourite (Folks.Persona persona,
 
1895
      bool is_favourite)
 
1896
    {
 
1897
      const string ins_q = "INSERT { " +
 
1898
        " ?p " + Trf.OntologyDefs.NAO_TAG + " " +
 
1899
        Trf.OntologyDefs.NAO_FAVORITE +
 
1900
        "} " +
 
1901
        "WHERE { " +
 
1902
        " ?p a " + Trf.OntologyDefs.NCO_PERSON +
 
1903
        " FILTER (tracker:id(?p) = %s) " +
 
1904
        "} ";
 
1905
      const string del_q = "DELETE { " +
 
1906
        " ?p " + Trf.OntologyDefs.NAO_TAG + " " +
 
1907
        Trf.OntologyDefs.NAO_FAVORITE + " " +
 
1908
       "} " +
 
1909
        "WHERE { " +
 
1910
        " ?p a " + Trf.OntologyDefs.NCO_PERSON +
 
1911
        " FILTER (tracker:id(?p) = %s) " +
 
1912
        "} ";
 
1913
      string query;
 
1914
 
 
1915
      if (is_favourite)
 
1916
        {
 
1917
          query = ins_q.printf (((Trf.Persona) persona).tracker_id ());
 
1918
        }
 
1919
      else
 
1920
        {
 
1921
          query = del_q.printf (((Trf.Persona) persona).tracker_id ());
 
1922
        }
 
1923
 
 
1924
      yield this._tracker_update (query, "change_is_favourite");
 
1925
    }
 
1926
 
 
1927
  internal async void _set_emails (Folks.Persona persona,
 
1928
      Set<FieldDetails> emails)
 
1929
    {
 
1930
      yield this._set_unique_attrib_set (persona, emails,
 
1931
          Trf.Attrib.EMAILS);
 
1932
    }
 
1933
 
 
1934
  internal async void _set_phones (Folks.Persona persona,
 
1935
      Set<FieldDetails> phone_numbers)
 
1936
    {
 
1937
      yield this._set_unique_attrib_set (persona, phone_numbers,
 
1938
          Trf.Attrib.PHONES);
 
1939
    }
 
1940
 
 
1941
  internal async void _set_unique_attrib_set (Folks.Persona persona,
 
1942
      Set<FieldDetails> properties, Trf.Attrib attrib)
 
1943
    {
 
1944
      string? query_name = null;
 
1945
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
1946
      var builder = new Tracker.Sparql.Builder.update ();
 
1947
      builder.insert_open (null);
 
1948
 
 
1949
      switch (attrib)
 
1950
        {
 
1951
          case Trf.Attrib.PHONES:
 
1952
            query_name = "_set_phones";
 
1953
            yield this._remove_attributes_from_persona (persona,
 
1954
                _REMOVE_PHONES);
 
1955
            yield this._build_update_query_set (builder, properties,
 
1956
                "?contact", Trf.Attrib.PHONES);
 
1957
            break;
 
1958
          case Trf.Attrib.EMAILS:
 
1959
            query_name = "_set_emailss";
 
1960
            yield this._remove_attributes_from_persona (persona,
 
1961
                _REMOVE_EMAILS);
 
1962
            yield this._build_update_query_set (builder, properties,
 
1963
                "?contact", Trf.Attrib.EMAILS);
 
1964
            break;
 
1965
        }
 
1966
      builder.insert_close ();
 
1967
      builder.where_open ();
 
1968
      builder.subject ("?contact");
 
1969
      builder.predicate ("a");
 
1970
      builder.object (Trf.OntologyDefs.NCO_PERSON);
 
1971
      string filter = " FILTER(tracker:id(?contact) = %s) ".printf (p_id);
 
1972
      builder.append (filter);
 
1973
      builder.where_close ();
 
1974
 
 
1975
      yield this._tracker_update (builder.result, query_name);
 
1976
    }
 
1977
 
 
1978
  internal async void _set_urls (Folks.Persona persona,
 
1979
      Set<FieldDetails> urls)
 
1980
    {
 
1981
       yield this._set_attrib_set (persona, urls,
 
1982
          Trf.Attrib.URLS);
 
1983
    }
 
1984
 
 
1985
  internal async void _set_im_addresses (Folks.Persona persona,
 
1986
      MultiMap<string, string> im_addresses)
 
1987
    {
 
1988
      /* FIXME:
 
1989
       * - this conversion should go away once we've switched to use the
 
1990
       *   same data structure for each property that is a list of something.
 
1991
       *   See: https://bugzilla.gnome.org/show_bug.cgi?id=646079 */
 
1992
      var ims = new HashSet<FieldDetails> ();
 
1993
      foreach (var proto in im_addresses.get_keys ())
 
1994
        {
 
1995
          var addrs = im_addresses.get (proto);
 
1996
          foreach (var a in addrs)
 
1997
            {
 
1998
              var fd = new FieldDetails (a);
 
1999
              fd.set_parameter ("proto", proto);
 
2000
              ims.add (fd);
 
2001
            }
 
2002
        }
 
2003
 
 
2004
       yield this._set_attrib_set (persona, ims, Trf.Attrib.IM_ADDRESSES);
 
2005
    }
 
2006
 
 
2007
  internal async void _set_postal_addresses (Folks.Persona persona,
 
2008
      Set<PostalAddress> postal_addresses)
 
2009
    {
 
2010
       yield this._set_attrib_set (persona, postal_addresses,
 
2011
          Trf.Attrib.POSTAL_ADDRESSES);
 
2012
    }
 
2013
 
 
2014
  internal async void _set_roles (Folks.Persona persona,
 
2015
      Set<Role> roles)
 
2016
    {
 
2017
      const string del_t = "DELETE { " +
 
2018
        " ?p " + Trf.OntologyDefs.NCO_HAS_AFFILIATION + " ?a " +
 
2019
        "} " +
 
2020
        "WHERE { " +
 
2021
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + "; " +
 
2022
        " " + Trf.OntologyDefs.NCO_HAS_AFFILIATION + " ?a . " +
 
2023
        " OPTIONAL { ?a " + Trf.OntologyDefs.NCO_ORG +  " ?o } . " +
 
2024
        " OPTIONAL { ?a " + Trf.OntologyDefs.NCO_ROLE + " ?r } . " +
 
2025
        " FILTER(tracker:id(?p) = %s) " +
 
2026
        "} ";
 
2027
 
 
2028
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2029
      string del_q = del_t.printf (p_id);
 
2030
 
 
2031
      var builder = new Tracker.Sparql.Builder.update ();
 
2032
      builder.insert_open (null);
 
2033
 
 
2034
      int i = 0;
 
2035
      foreach (var r in roles)
 
2036
        {
 
2037
          string affl = "_:a%d".printf (i);
 
2038
 
 
2039
          builder.subject (affl);
 
2040
          builder.predicate ("a");
 
2041
          builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
2042
          builder.predicate (Trf.OntologyDefs.NCO_ROLE);
 
2043
          builder.object_string (r.title);
 
2044
          builder.predicate (Trf.OntologyDefs.NCO_ORG);
 
2045
          builder.object_string (r.organisation_name);
 
2046
          builder.subject ("?contact");
 
2047
          builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
2048
          builder.object (affl);
 
2049
        }
 
2050
 
 
2051
      builder.insert_close ();
 
2052
      builder.where_open ();
 
2053
      builder.subject ("?contact");
 
2054
      builder.predicate ("a");
 
2055
      builder.object (Trf.OntologyDefs.NCO_PERSON);
 
2056
      string filter = " FILTER(tracker:id(?contact) = %s) ".printf (p_id);
 
2057
      builder.append (filter);
 
2058
      builder.where_close ();
 
2059
 
 
2060
      yield this._tracker_update (del_q + builder.result, "_set_roles");
 
2061
   }
 
2062
 
 
2063
  internal async void _set_notes (Folks.Persona persona,
 
2064
      Set<Note> notes)
 
2065
    {
 
2066
      const string del_t = "DELETE { " +
 
2067
        "?p " + Trf.OntologyDefs.NCO_NOTE  + " ?n " +
 
2068
        "} " +
 
2069
        "WHERE {" +
 
2070
        " ?p a nco:PersonContact ; " +
 
2071
        Trf.OntologyDefs.NCO_NOTE + " ?n . " +
 
2072
        " FILTER(tracker:id(?p) = %s)" +
 
2073
        "}";
 
2074
 
 
2075
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2076
      string del_q = del_t.printf (p_id);
 
2077
 
 
2078
      var builder = new Tracker.Sparql.Builder.update ();
 
2079
      builder.insert_open (null);
 
2080
 
 
2081
      foreach (var n in notes)
 
2082
        {
 
2083
          builder.subject ("?contact");
 
2084
          builder.predicate (Trf.OntologyDefs.NCO_NOTE);
 
2085
          builder.object_string (n.content);
 
2086
        }
 
2087
 
 
2088
      builder.insert_close ();
 
2089
      builder.where_open ();
 
2090
      builder.subject ("?contact");
 
2091
      builder.predicate ("a");
 
2092
      builder.object (Trf.OntologyDefs.NCO_PERSON);
 
2093
      string filter = " FILTER(tracker:id(?contact) = %s) ".printf (p_id);
 
2094
      builder.append (filter);
 
2095
      builder.where_close ();
 
2096
 
 
2097
      yield this._tracker_update (del_q + builder.result, "_set_notes");
 
2098
    }
 
2099
 
 
2100
  internal async void _set_birthday (Folks.Persona persona,
 
2101
      owned DateTime bday)
 
2102
    {
 
2103
      const string q_t = "DELETE { " +
 
2104
         " ?p " + Trf.OntologyDefs.NCO_BIRTHDAY + " ?b " +
 
2105
         "} " +
 
2106
         "WHERE { " +
 
2107
         " ?p a " + Trf.OntologyDefs.NCO_PERSON + "; " +
 
2108
         Trf.OntologyDefs.NCO_BIRTHDAY + " ?b . " +
 
2109
         " FILTER (tracker:id(?p) = %s ) " +
 
2110
         "} " +
 
2111
         "INSERT { " +
 
2112
         " ?p " + Trf.OntologyDefs.NCO_BIRTHDAY + " '%s' " +
 
2113
         "} " +
 
2114
         "WHERE { " +
 
2115
         " ?p a " + Trf.OntologyDefs.NCO_PERSON + " . " +
 
2116
         " FILTER (tracker:id(?p) = %s) " +
 
2117
         "} ";
 
2118
 
 
2119
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2120
      TimeVal tv;
 
2121
      bday.to_timeval (out tv);
 
2122
      string query = q_t.printf (p_id, tv.to_iso8601 (), p_id);
 
2123
 
 
2124
      yield this._tracker_update (query, "_set_birthday");
 
2125
    }
 
2126
 
 
2127
  internal async void _set_gender (Folks.Persona persona,
 
2128
      owned Gender gender)
 
2129
    {
 
2130
      const string del_t = "DELETE { " +
 
2131
        " ?p " + Trf.OntologyDefs.NCO_GENDER + " ?g " +
 
2132
        "} " +
 
2133
        "WHERE { " +
 
2134
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " ; " +
 
2135
        Trf.OntologyDefs.NCO_GENDER + " ?g . " +
 
2136
        " FILTER (tracker:id(?p) = %s) " +
 
2137
        "} ";
 
2138
      const string ins_t = "INSERT { " +
 
2139
        " ?p " + Trf.OntologyDefs.NCO_GENDER + " %s " +
 
2140
        "} " +
 
2141
        "WHERE { " +
 
2142
        " ?p a " + Trf.OntologyDefs.NCO_PERSON +  " . " +
 
2143
        " FILTER (tracker:id(?p) = %s) " +
 
2144
        "} ";
 
2145
 
 
2146
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2147
      string query;
 
2148
 
 
2149
      if (gender == Gender.UNSPECIFIED)
 
2150
        {
 
2151
          query = del_t.printf (p_id);
 
2152
        }
 
2153
      else
 
2154
        {
 
2155
          string gender_urn;
 
2156
 
 
2157
          if (gender == Gender.MALE)
 
2158
            gender_urn = Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender-male>";
 
2159
          else
 
2160
            gender_urn = Trf.OntologyDefs.NCO_URL_PREFIX + "nco#gender-female>";
 
2161
 
 
2162
          query = del_t.printf (p_id) + ins_t.printf (gender_urn, p_id);
 
2163
        }
 
2164
 
 
2165
      yield this._tracker_update (query, "_set_gender");
 
2166
    }
 
2167
 
 
2168
  internal async void _set_avatar (Folks.Persona persona,
 
2169
      File? avatar)
 
2170
    {
 
2171
      const string query_d = "DELETE {" +
 
2172
        " ?c " + Trf.OntologyDefs.NCO_PHOTO  + " ?p " +
 
2173
        " } " +
 
2174
        "WHERE { " +
 
2175
        " ?c a " + Trf.OntologyDefs.NCO_PERSON  + " ; " +
 
2176
        Trf.OntologyDefs.NCO_PHOTO + " ?p . " +
 
2177
        " FILTER(tracker:id(?c) = %s) " +
 
2178
        "} ";
 
2179
 
 
2180
      const string query_i = "INSERT { " +
 
2181
        " _:i a " + Trf.OntologyDefs.NFO_IMAGE  + ", " +
 
2182
        Trf.OntologyDefs.NIE_DATAOBJECT + " ; " +
 
2183
        Trf.OntologyDefs.NIE_URL + " '%s' . " +
 
2184
        " ?c " + Trf.OntologyDefs.NCO_PHOTO + " _:i " +
 
2185
        "} " +
 
2186
        "WHERE { " +
 
2187
        " ?c a nco:PersonContact . " +
 
2188
        " FILTER(tracker:id(?c) = %s) " +
 
2189
        "}";
 
2190
 
 
2191
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2192
 
 
2193
      var image_urn = yield this._get_property (int.parse (p_id),
 
2194
          Trf.OntologyDefs.NCO_PHOTO);
 
2195
      if (image_urn != "")
 
2196
        this._delete_resource ("<%s>".printf (image_urn));
 
2197
 
 
2198
      string query = query_d.printf (p_id);
 
2199
      if (avatar != null)
 
2200
        {
 
2201
          query += query_i.printf (avatar.get_uri (), p_id);
 
2202
        }
 
2203
      yield this._tracker_update (query, "_set_avatar");
 
2204
    }
 
2205
 
 
2206
  internal async void _set_structured_name (Folks.Persona persona,
 
2207
      StructuredName sname)
 
2208
    {
 
2209
      const string query_t = "DELETE { " +
 
2210
        " ?p " + Trf.OntologyDefs.NCO_FAMILY + " ?family . " +
 
2211
        " ?p " + Trf.OntologyDefs.NCO_GIVEN + " ?given . " +
 
2212
        " ?p " + Trf.OntologyDefs.NCO_ADDITIONAL + " ?adi . " +
 
2213
        " ?p " + Trf.OntologyDefs.NCO_PREFIX + " ?prefix . " +
 
2214
        " ?p " + Trf.OntologyDefs.NCO_SUFFIX + " ?suffix " +
 
2215
        "} " +
 
2216
        "WHERE { " +
 
2217
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " .  " +
 
2218
        " OPTIONAL { ?p " + Trf.OntologyDefs.NCO_FAMILY + " ?family } . " +
 
2219
        " OPTIONAL { ?p " + Trf.OntologyDefs.NCO_GIVEN + " ?given } . " +
 
2220
        " OPTIONAL { ?p " + Trf.OntologyDefs.NCO_ADDITIONAL + " ?adi } . " +
 
2221
        " OPTIONAL { ?p " + Trf.OntologyDefs.NCO_PREFIX + " ?prefix } . " +
 
2222
        " OPTIONAL { ?p " + Trf.OntologyDefs.NCO_SUFFIX + " ?suffix } . " +
 
2223
        " FILTER (tracker:id(?p) = %s) " +
 
2224
        "} " +
 
2225
        "INSERT { " +
 
2226
        " ?p " + Trf.OntologyDefs.NCO_FAMILY + " '%s'; " +
 
2227
        " " + Trf.OntologyDefs.NCO_GIVEN + " '%s'; " +
 
2228
        " " + Trf.OntologyDefs.NCO_ADDITIONAL + " '%s'; " +
 
2229
        " " + Trf.OntologyDefs.NCO_PREFIX + " '%s'; " +
 
2230
        " " + Trf.OntologyDefs.NCO_SUFFIX + " '%s' " +
 
2231
        " } " +
 
2232
        "WHERE { " +
 
2233
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " . " +
 
2234
        " FILTER (tracker:id(?p) = %s) " +
 
2235
        "} ";
 
2236
 
 
2237
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2238
      string query = query_t.printf (p_id, sname.family_name, sname.given_name,
 
2239
          sname.additional_names, sname.prefixes, sname.suffixes, p_id);
 
2240
      yield this._tracker_update (query, "_set_structured_name");
 
2241
    }
 
2242
 
 
2243
  internal async void _set_full_name  (Folks.Persona persona,
 
2244
      string full_name)
 
2245
    {
 
2246
      const string query_t = "DELETE { " +
 
2247
        " ?p " + Trf.OntologyDefs.NCO_FULLNAME + " ?fn " +
 
2248
        "} " +
 
2249
        "WHERE { " +
 
2250
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " .  " +
 
2251
        " OPTIONAL { ?p " + Trf.OntologyDefs.NCO_FULLNAME + " ?fn } . " +
 
2252
        " FILTER (tracker:id(?p) = %s) " +
 
2253
        "} " +
 
2254
        "INSERT { " +
 
2255
        " ?p " + Trf.OntologyDefs.NCO_FULLNAME + " '%s' " +
 
2256
        "} " +
 
2257
        "WHERE { " +
 
2258
        " ?p a " + Trf.OntologyDefs.NCO_PERSON + " . " +
 
2259
        " FILTER (tracker:id(?p) = %s) " +
 
2260
        "} ";
 
2261
 
 
2262
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2263
      string query = query_t.printf (p_id, full_name, p_id);
 
2264
      yield this._tracker_update (query, "_set_full_name");
 
2265
    }
 
2266
 
 
2267
  /* NOTE:
 
2268
   * - first we nuke old attribs
 
2269
   * - we create new affls with the new attribs
 
2270
   */
 
2271
  private async void _set_attrib_set (Folks.Persona persona,
 
2272
      Set<Object> attribs, Trf.Attrib what)
 
2273
    {
 
2274
      var p_id = ((Trf.Persona) persona).tracker_id ();
 
2275
 
 
2276
      unowned string? related_attrib = null;
 
2277
      unowned string? related_prop = null;
 
2278
      unowned string? related_prop_2 = null;
 
2279
      unowned string? related_connection = null;
 
2280
 
 
2281
      switch (what)
 
2282
        {
 
2283
          case Trf.Attrib.IM_ADDRESSES:
 
2284
            related_attrib = Trf.OntologyDefs.NCO_IMADDRESS;
 
2285
            related_prop = Trf.OntologyDefs.NCO_IMID;
 
2286
            related_prop_2 = Trf.OntologyDefs.NCO_IMPROTOCOL;
 
2287
            related_connection = Trf.OntologyDefs.NCO_HAS_IMADDRESS;
 
2288
            yield this._remove_attributes_from_persona (persona,
 
2289
                _REMOVE_IM_ADDRS);
 
2290
            break;
 
2291
          case Trf.Attrib.POSTAL_ADDRESSES:
 
2292
            related_attrib = Trf.OntologyDefs.NCO_POSTAL_ADDRESS;
 
2293
            related_connection = Trf.OntologyDefs.NCO_HAS_POSTAL_ADDRESS;
 
2294
            yield this._remove_attributes_from_persona (persona,
 
2295
                _REMOVE_POSTALS);
 
2296
            break;
 
2297
          case Trf.Attrib.URLS:
 
2298
            related_attrib = Trf.OntologyDefs.NCO_URL;
 
2299
            related_connection = Trf.OntologyDefs.NCO_URL;
 
2300
            break;
 
2301
        }
 
2302
 
 
2303
      var builder = new Tracker.Sparql.Builder.update ();
 
2304
      builder.insert_open (null);
 
2305
      int i = 0;
 
2306
      foreach (var p in attribs)
 
2307
        {
 
2308
          FieldDetails fd = null;
 
2309
          PostalAddress pa = null;
 
2310
 
 
2311
          string affl = "_:a%d".printf (i);
 
2312
          string attr = null;
 
2313
 
 
2314
          switch (what)
 
2315
            {
 
2316
              case Trf.Attrib.POSTAL_ADDRESSES:
 
2317
                pa = (PostalAddress) p;
 
2318
                attr = "_:p%d".printf (i);
 
2319
                builder.subject (attr);
 
2320
                builder.predicate ("a");
 
2321
                builder.object (related_attrib);
 
2322
                builder.predicate (Trf.OntologyDefs.NCO_POBOX);
 
2323
                builder.object_string (pa.po_box);
 
2324
                builder.predicate (Trf.OntologyDefs.NCO_LOCALITY);
 
2325
                builder.object_string (pa.locality);
 
2326
                builder.predicate (Trf.OntologyDefs.NCO_POSTALCODE);
 
2327
                builder.object_string (pa.postal_code);
 
2328
                builder.predicate (Trf.OntologyDefs.NCO_STREET_ADDRESS);
 
2329
                builder.object_string (pa.street);
 
2330
                builder.predicate (Trf.OntologyDefs.NCO_EXTENDED_ADDRESS);
 
2331
                builder.object_string (pa.extension);
 
2332
                builder.predicate (Trf.OntologyDefs.NCO_COUNTRY);
 
2333
                builder.object_string (pa.country);
 
2334
                builder.predicate (Trf.OntologyDefs.NCO_REGION);
 
2335
                builder.object_string (pa.region);
 
2336
                break;
 
2337
              case Trf.Attrib.URLS:
 
2338
                fd = (FieldDetails) p;
 
2339
                var type_p = fd.get_parameter_values ("type");
 
2340
                if (type_p.contains ("blog"))
 
2341
                  {
 
2342
                    related_connection = Trf.OntologyDefs.NCO_BLOG;
 
2343
                  }
 
2344
                else if (type_p.contains ("website"))
 
2345
                  {
 
2346
                    related_connection = Trf.OntologyDefs.NCO_WEBSITE;
 
2347
                  }
 
2348
                attr = "'%s'".printf (fd.value);
 
2349
                break;
 
2350
              case Trf.Attrib.IM_ADDRESSES:
 
2351
              default:
 
2352
                fd = (FieldDetails) p;
 
2353
                attr = "_:p%d".printf (i);
 
2354
                builder.subject (attr);
 
2355
                builder.predicate ("a");
 
2356
                builder.object (related_attrib);
 
2357
                builder.predicate (related_prop);
 
2358
                builder.object_string (fd.value);
 
2359
 
 
2360
                if (what == Trf.Attrib.IM_ADDRESSES)
 
2361
                  {
 
2362
                    builder.predicate (related_prop_2);
 
2363
                    var im_params =
 
2364
                        fd.get_parameter_values ("proto").to_array ();
 
2365
                    builder.object_string (im_params[0]);
 
2366
                  }
 
2367
 
 
2368
                break;
 
2369
            }
 
2370
 
 
2371
          builder.subject (affl);
 
2372
          builder.predicate ("a");
 
2373
          builder.object (Trf.OntologyDefs.NCO_AFFILIATION);
 
2374
          builder.predicate (related_connection);
 
2375
          builder.object (attr);
 
2376
          builder.subject ("?contact");
 
2377
          builder.predicate (Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
2378
          builder.object (affl);
 
2379
 
 
2380
          i++;
 
2381
        }
 
2382
      builder.insert_close ();
 
2383
      builder.where_open ();
 
2384
      builder.subject ("?contact");
 
2385
      builder.predicate ("a");
 
2386
      builder.object (Trf.OntologyDefs.NCO_PERSON);
 
2387
      string filter = " FILTER(tracker:id(?contact) = %s) ".printf (p_id);
 
2388
      builder.append (filter);
 
2389
      builder.where_close ();
 
2390
 
 
2391
      yield this._tracker_update (builder.result, "set_attrib");
 
2392
    }
 
2393
 
 
2394
  private async bool _tracker_update (string query, string caller)
 
2395
    {
 
2396
      bool ret = false;
 
2397
 
 
2398
      debug ("%s: %s", caller, query);
 
2399
 
 
2400
      try
 
2401
        {
 
2402
          yield this._connection.update_async (query);
 
2403
          ret = true;
 
2404
        }
 
2405
      catch (Tracker.Sparql.Error e1)
 
2406
        {
 
2407
          warning ("[%s] SPARQL syntax error: %s. Query: %s",
 
2408
              caller, e1.message, query);
 
2409
        }
 
2410
      catch (GLib.IOError e2)
 
2411
        {
 
2412
          warning ("[%s] IO error: %s",
 
2413
              caller, e2.message);
 
2414
        }
 
2415
      catch (GLib.DBusError e3)
 
2416
        {
 
2417
          warning ("[%s] DBus error: %s",
 
2418
              caller, e3.message);
 
2419
        }
 
2420
 
 
2421
      return ret;
 
2422
    }
 
2423
 
 
2424
  private async Gee.HashSet<string> _affiliations_from_persona (string urn)
 
2425
    {
 
2426
      return yield this._linked_resources (urn, Trf.OntologyDefs.NCO_PERSON,
 
2427
          Trf.OntologyDefs.NCO_HAS_AFFILIATION);
 
2428
    }
 
2429
 
 
2430
  private async Gee.HashSet<string> _phones_from_affiliation (string affl)
 
2431
    {
 
2432
      return yield this._linked_resources (affl,
 
2433
          Trf.OntologyDefs.NCO_AFFILIATION,
 
2434
          Trf.OntologyDefs.NCO_HAS_PHONE);
 
2435
    }
 
2436
 
 
2437
  private async Gee.HashSet<string>  _postals_from_affiliation (string affl)
 
2438
    {
 
2439
      return yield this._linked_resources (affl,
 
2440
          Trf.OntologyDefs.NCO_AFFILIATION,
 
2441
          Trf.OntologyDefs.NCO_HAS_POSTAL_ADDRESS);
 
2442
    }
 
2443
 
 
2444
  private async Gee.HashSet<string> _imaddrs_from_affiliation  (string affl)
 
2445
    {
 
2446
      return yield this._linked_resources (affl,
 
2447
          Trf.OntologyDefs.NCO_AFFILIATION,
 
2448
          Trf.OntologyDefs.NCO_HAS_IMADDRESS);
 
2449
    }
 
2450
 
 
2451
  private async Gee.HashSet<string> _emails_from_affiliation (string affl)
 
2452
    {
 
2453
      return yield this._linked_resources (affl,
 
2454
          Trf.OntologyDefs.NCO_AFFILIATION,
 
2455
          Trf.OntologyDefs.NCO_HAS_EMAIL);
 
2456
    }
 
2457
 
 
2458
  /**
 
2459
   * Retrieve the list of linked resources of a given subject
 
2460
   *
 
2461
   * @param resource          the urn of the resource in <urn> format
 
2462
   * @return number of resources linking to this resource
 
2463
   */
 
2464
  private async int _resource_usage_count (string resource)
 
2465
    {
 
2466
      const string query_t = "SELECT " +
 
2467
        " count(?s) " +
 
2468
        "WHERE { " +
 
2469
        " %s a rdfs:Resource . " +
 
2470
        " ?s ?p %s } ";
 
2471
 
 
2472
      var query = query_t.printf (resource, resource);
 
2473
      var result = yield this._single_value_query (query);
 
2474
      return int.parse (result);
 
2475
    }
 
2476
 
 
2477
  /*
 
2478
   * NOTE:
 
2479
   *
 
2480
   * We asume that the caller is holding a link to the resource,
 
2481
   * so if _resource_usage_count () == 1 it means no one else
 
2482
   * (beside the caller) is linking to the resource.
 
2483
   *
 
2484
   * This means that _delete_resource shold be called before
 
2485
   * removing the resources that hold a link to it (which also
 
2486
   * makes sense from the signaling perspective).
 
2487
   */
 
2488
  private async bool _delete_resource (string resource_urn,
 
2489
      bool check_count = true)
 
2490
    {
 
2491
      bool deleted = false;
 
2492
      var query_t = " DELETE { " +
 
2493
        " %s a rdfs:Resource " +
 
2494
        "} " +
 
2495
        "WHERE { " +
 
2496
        " %s a rdfs:Resource " +
 
2497
        "} ";
 
2498
 
 
2499
      var query = query_t.printf (resource_urn, resource_urn);
 
2500
      if (check_count)
 
2501
        {
 
2502
          int count = yield this._resource_usage_count (resource_urn);
 
2503
          if (count == 1)
 
2504
            {
 
2505
              deleted = yield this._tracker_update (query, "_delete_resource");
 
2506
            }
 
2507
        }
 
2508
      else
 
2509
        {
 
2510
          deleted = yield this._tracker_update (query, "_delete_resource");
 
2511
        }
 
2512
 
 
2513
      return deleted;
 
2514
    }
 
2515
 
 
2516
  /**
 
2517
   * Retrieve the list of linked resources of a given subject
 
2518
   *
 
2519
   * @param urn               the urn of the subject in <urn> format
 
2520
   * @param subject_type      i.e: nco:Person, nco:Affiliation, etc
 
2521
   * @param linking_predicate i.e.: nco:hasAffiliation
 
2522
   * @return a list of linked resources (in <urn> format)
 
2523
   */
 
2524
  private async Gee.HashSet<string> _linked_resources (string urn,
 
2525
      string subject_type, string linking_predicate)
 
2526
    {
 
2527
      string query_t = "SELECT " +
 
2528
        " fn:concat('<',?linkedr,'>')  " +
 
2529
        "WHERE { " +
 
2530
        " %s a %s; " +
 
2531
        " %s ?linkedr " +
 
2532
        "} ";
 
2533
 
 
2534
      var query = query_t.printf (urn, subject_type, linking_predicate);
 
2535
      return yield this._multi_value_query (query);
 
2536
    }
 
2537
 
 
2538
  private async string _urn_from_persona (Folks.Persona persona)
 
2539
    {
 
2540
      var id = ((Trf.Persona) persona).tracker_id ();
 
2541
      return yield this._urn_from_tracker_id (id);
 
2542
    }
 
2543
 
 
2544
  /**
 
2545
   * Helper method to figure out if a constrained property
 
2546
   * already exists.
 
2547
   */
 
2548
  private async string _urn_from_property (string class_name,
 
2549
      string property_name,
 
2550
      string property_value)
 
2551
    {
 
2552
      const string query_template = "SELECT " +
 
2553
        " fn:concat('<', ?o, '>') " +
 
2554
        "WHERE { " +
 
2555
        " ?o a %s ; " +
 
2556
        " %s ?prop_val . " +
 
2557
        "FILTER (?prop_val = '%s') " +
 
2558
        "}";
 
2559
 
 
2560
      string query = query_template.printf (class_name,
 
2561
          property_name, property_value);
 
2562
      return yield this._single_value_query (query);
 
2563
    }
 
2564
}