Coverage Report

Created: 2026-01-15 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tinysparql/src/libtinysparql/tracker-resource.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2016-2017, Sam Thursfield <sam@afuera.me.uk>
3
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (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 GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with this library; if not, write to the
16
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
 * Boston, MA  02110-1301, USA.
18
 */
19
20
#include "config.h"
21
22
#include <glib.h>
23
#include <json-glib/json-glib.h>
24
25
#include <string.h>
26
27
#include "tracker-deserializer-resource.h"
28
#include "tracker-uri.h"
29
#include "tracker-resource.h"
30
#include "tracker-ontologies.h"
31
32
/* For tracker_sparql_escape_string */
33
#include "tracker-utils.h"
34
35
/* For prefixed names parsing */
36
#include "core/tracker-sparql-grammar.h"
37
38
#include "tracker-private.h"
39
40
typedef struct {
41
  char *identifier;
42
  GHashTable *properties;
43
  GHashTable *overwrite;
44
} TrackerResourcePrivate;
45
46
0
G_DEFINE_TYPE_WITH_PRIVATE (TrackerResource, tracker_resource, G_TYPE_OBJECT)
47
0
#define GET_PRIVATE(object)  (tracker_resource_get_instance_private (object))
48
49
/**
50
 * TrackerResource:
51
 *
52
 * `TrackerResource` is an in-memory representation of RDF data about a given resource.
53
 *
54
 * This object keeps track of a set of properties for a given resource, and can
55
 * also link to other `TrackerResource` objects to form trees or graphs of RDF
56
 * data. See [method@Resource.set_relation] and [method@Resource.set_uri]
57
 * on how to link a `TrackerResource` to other RDF data.
58
 *
59
 * `TrackerResource` may also hold data about literal values, added through
60
 * the specialized [method@Resource.set_int64], [method@Resource.set_string],
61
 * etc family of functions, or the generic [method@Resource.set_gvalue] method.
62
 *
63
 * Since RDF properties may be multi-valued, for every `set` call there exists
64
 * another `add` call (e.g. [method@Resource.add_int64], [method@Resource.add_string]
65
 * and so on). The `set` methods do also reset any previously value the
66
 * property might hold for the given resource.
67
 *
68
 * Resources may have an IRI set at creation through [ctor@Resource.new],
69
 * or set afterwards through [method@Resource.set_identifier]. Resources
70
 * without a name will represent a blank node, and will be dealt with as such
71
 * during database insertions.
72
 *
73
 * `TrackerResource` performs no validation on the data being coherent as per
74
 * any ontology. Errors will be found out at the time of using the TrackerResource
75
 * for e.g. database updates.
76
 *
77
 * Once the RDF data is built in memory, the (tree of) `TrackerResource` may be
78
 * converted to a RDF format through [method@Resource.print_rdf], or
79
 * directly inserted into a database through [method@Batch.add_resource]
80
 * or [method@SparqlConnection.update_resource].
81
 */
82
83
static char *
84
generate_blank_node_identifier (void)
85
0
{
86
0
  static gint64 counter = 0;
87
88
0
  return g_strdup_printf("_:%" G_GINT64_FORMAT, counter++);
89
0
}
90
91
enum {
92
  PROP_0,
93
94
  PROP_IDENTIFIER,
95
};
96
97
static void dispose      (GObject *object);
98
static void finalize     (GObject *object);
99
static void get_property (GObject    *object,
100
                          guint       param_id,
101
                          GValue     *value,
102
                          GParamSpec *pspec);
103
static void set_property (GObject      *object,
104
                          guint         param_id,
105
                          const GValue *value,
106
                          GParamSpec   *pspec);
107
108
static char *
109
escape_iri (const gchar *str)
110
0
{
111
0
  GString *iri;
112
113
  /* Escapes IRI references according to IRIREF in SPARQL grammar definition,
114
   * further validation on IRI validity may happen deeper down.
115
   */
116
117
0
  if (!str)
118
0
    return NULL;
119
120
  /* Fast path, check whether there's no characters to escape */
121
0
  if (!strpbrk (str,
122
0
                "<>\"{}|^`\\"
123
0
                "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
124
0
                "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20")) {
125
0
    return g_strdup (str);
126
0
  }
127
128
0
  iri = g_string_new (NULL);
129
130
0
  while (*str != '\0') {
131
0
    gunichar unichar;
132
133
0
    unichar = g_utf8_get_char (str);
134
0
    str = g_utf8_next_char (str);
135
136
0
    if (unichar <= 0x20 ||
137
0
        unichar == '<' || unichar == '>' ||
138
0
        unichar == '"' || unichar == '{' ||
139
0
        unichar == '}' || unichar == '|' ||
140
0
        unichar == '^' || unichar == '`' ||
141
0
        unichar == '\\')
142
0
      g_string_append_printf (iri, "%%%X", unichar);
143
0
    else
144
0
      g_string_append_unichar (iri, unichar);
145
0
  }
146
147
0
  return g_string_free (iri, FALSE);
148
0
}
149
150
static void
151
tracker_resource_class_init (TrackerResourceClass *klass)
152
0
{
153
0
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
154
155
0
  object_class->dispose      = dispose;
156
0
  object_class->finalize     = finalize;
157
0
  object_class->get_property = get_property;
158
0
  object_class->set_property = set_property;
159
160
  /**
161
   * TrackerResource:identifier
162
   *
163
   * The URI identifier for this class, or %NULL for a
164
   * blank node.
165
   */
166
0
  g_object_class_install_property (object_class,
167
0
                                   PROP_IDENTIFIER,
168
0
                                   g_param_spec_string ("identifier",
169
0
                                                        "Identifier",
170
0
                                                        "Identifier",
171
0
                                                        NULL,
172
0
                                                        G_PARAM_READWRITE));
173
0
}
174
175
/* Destroy-notify function for the values stored in the hash table. */
176
static void
177
free_value (GValue *value)
178
0
{
179
0
  g_value_unset (value);
180
0
  g_slice_free (GValue, value);
181
0
}
182
183
static void
184
tracker_resource_init (TrackerResource *resource)
185
0
{
186
0
  TrackerResourcePrivate *priv = GET_PRIVATE(resource);
187
188
  /* Values of properties */
189
0
  priv->properties = g_hash_table_new_full (
190
0
          g_str_hash,
191
0
          g_str_equal,
192
0
          g_free,
193
0
          (GDestroyNotify) free_value);
194
195
  /* TRUE for any property where we should delete any existing values. */
196
0
  priv->overwrite = g_hash_table_new_full (
197
0
          g_str_hash,
198
0
          g_str_equal,
199
0
          g_free,
200
0
          NULL);
201
0
}
202
203
static void
204
dispose (GObject *object)
205
0
{
206
0
  TrackerResourcePrivate *priv;
207
208
0
  priv = GET_PRIVATE (TRACKER_RESOURCE (object));
209
210
0
  g_clear_pointer (&priv->overwrite, g_hash_table_unref);
211
0
  g_clear_pointer (&priv->properties, g_hash_table_unref);
212
213
0
  G_OBJECT_CLASS (tracker_resource_parent_class)->dispose (object);
214
0
}
215
216
static void
217
finalize (GObject *object)
218
0
{
219
0
  TrackerResourcePrivate *priv;
220
221
0
  priv = GET_PRIVATE (TRACKER_RESOURCE (object));
222
223
0
  g_clear_pointer (&priv->identifier, g_free);
224
225
0
  (G_OBJECT_CLASS (tracker_resource_parent_class)->finalize)(object);
226
0
}
227
228
static void
229
get_property (GObject    *object,
230
              guint       param_id,
231
              GValue     *value,
232
              GParamSpec *pspec)
233
0
{
234
0
  switch (param_id) {
235
0
  case PROP_IDENTIFIER:
236
0
    g_value_set_string (value, tracker_resource_get_identifier (TRACKER_RESOURCE (object)));
237
0
    break;
238
0
  default:
239
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
240
0
    break;
241
0
  }
242
0
}
243
244
static void
245
set_property (GObject      *object,
246
              guint         param_id,
247
              const GValue *value,
248
              GParamSpec   *pspec)
249
0
{
250
0
  switch (param_id) {
251
0
  case PROP_IDENTIFIER:
252
0
    tracker_resource_set_identifier (TRACKER_RESOURCE (object), g_value_get_string (value));
253
0
    break;
254
0
  default:
255
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
256
0
    break;
257
0
  }
258
0
}
259
260
/**
261
 * tracker_resource_new:
262
 * @identifier: (nullable): A string containing a URI, or %NULL.
263
 *
264
 * Creates a TrackerResource instance.
265
 *
266
 * Returns: a newly created `TrackerResource`.
267
 */
268
TrackerResource *
269
tracker_resource_new (const char *identifier)
270
0
{
271
0
  TrackerResource *resource;
272
273
0
  resource = g_object_new (TRACKER_TYPE_RESOURCE,
274
0
                           "identifier", identifier,
275
0
                           NULL);
276
277
0
  return resource;
278
0
}
279
280
/* Difference between 'set' and 'add': when generating a SPARQL update, the
281
 * setter will generate a corresponding DELETE, the adder will not. The setter
282
 * will also overwrite existing values in the Resource object, while the adder
283
 * will make a list.
284
 */
285
286
/**
287
 * tracker_resource_set_gvalue:
288
 * @self: the `TrackerResource`
289
 * @property_uri: a string identifying the property to set
290
 * @value: an initialised [struct@GObject.Value]
291
 *
292
 * Replace any previously existing value for @property_uri with @value.
293
 *
294
 * When serialising to SPARQL, any properties that were set with this function
295
 * will get a corresponding DELETE statement to remove any existing values in
296
 * the database.
297
 *
298
 * You can pass any kind of [struct@GObject.Value] for @value, but serialization functions will
299
 * normally only be able to serialize URIs/relationships and fundamental value
300
 * types (string, int, etc.).
301
 */
302
void
303
tracker_resource_set_gvalue (TrackerResource *self,
304
                             const char      *property_uri,
305
                             const GValue    *value)
306
0
{
307
0
  TrackerResourcePrivate *priv;
308
0
  GValue *our_value;
309
310
0
  g_return_if_fail (TRACKER_IS_RESOURCE (self));
311
0
  g_return_if_fail (property_uri != NULL);
312
0
  g_return_if_fail (G_IS_VALUE (value));
313
314
0
  priv = GET_PRIVATE (self);
315
316
0
  our_value = g_slice_new0 (GValue);
317
0
  g_value_init (our_value, G_VALUE_TYPE (value));
318
0
  g_value_copy (value, our_value);
319
320
0
  g_hash_table_insert (priv->properties, g_strdup (property_uri), our_value);
321
322
0
  g_hash_table_insert (priv->overwrite, g_strdup (property_uri), GINT_TO_POINTER (TRUE));
323
0
}
324
325
static gboolean
326
validate_boolean (gboolean    value,
327
0
                  const char *func_name) {
328
0
  return TRUE;
329
0
}
330
331
static gboolean
332
validate_double (double      value,
333
0
                 const char *func_name) {
334
0
  return TRUE;
335
0
}
336
337
static gboolean
338
validate_int (int         value,
339
0
              const char *func_name) {
340
0
  return TRUE;
341
0
}
342
343
static gboolean
344
validate_int64 (gint64      value,
345
0
                const char *func_name) {
346
0
  return TRUE;
347
0
}
348
349
static gboolean
350
validate_pointer (const void *pointer,
351
                  const char *func_name)
352
0
{
353
0
  if (pointer == NULL) {
354
0
    g_warning ("%s: NULL is not a valid value.", func_name);
355
0
    return FALSE;
356
0
  }
357
358
0
  return TRUE;
359
0
}
360
361
static void
362
value_set_uri (GValue      *value,
363
               const gchar *uri)
364
0
{
365
0
  g_value_take_string (value, escape_iri (uri));
366
0
}
367
368
#define SET_PROPERTY_FOR_GTYPE(name, ctype, gtype, set_function, validate_function) \
369
  void name (TrackerResource *self,                                           \
370
             const char *property_uri,                                        \
371
             ctype value)                                                     \
372
0
  {                                                                           \
373
0
    TrackerResourcePrivate *priv;                                       \
374
0
    GValue *our_value;                                                  \
375
0
                                                                                    \
376
0
    g_return_if_fail (TRACKER_IS_RESOURCE (self));                      \
377
0
    g_return_if_fail (property_uri != NULL);                            \
378
0
                                                                                    \
379
0
    priv = GET_PRIVATE (self);                                          \
380
0
                                                                                    \
381
0
    if (!validate_function (value, __func__)) {                         \
382
0
      return;                                                     \
383
0
    }                                                                   \
384
0
                                                                                    \
385
0
    our_value = g_slice_new0 (GValue);                                  \
386
0
    g_value_init (our_value, gtype);                                    \
387
0
    set_function (our_value, value);                                    \
388
0
                                                                                    \
389
0
    g_hash_table_insert (priv->properties,                              \
390
0
                         g_strdup (property_uri),                       \
391
0
                         our_value);                                    \
392
0
                                                                                    \
393
0
    g_hash_table_insert (priv->overwrite,                               \
394
0
                         g_strdup (property_uri),                       \
395
0
                         GINT_TO_POINTER (TRUE));                       \
396
0
  }
Unexecuted instantiation: tracker_resource_set_boolean
Unexecuted instantiation: tracker_resource_set_double
Unexecuted instantiation: tracker_resource_set_int
Unexecuted instantiation: tracker_resource_set_int64
Unexecuted instantiation: tracker_resource_set_relation
Unexecuted instantiation: tracker_resource_set_take_relation
Unexecuted instantiation: tracker_resource_set_string
Unexecuted instantiation: tracker_resource_set_uri
Unexecuted instantiation: tracker_resource_set_datetime
397
398
/**
399
 * tracker_resource_set_boolean:
400
 * @self: The `TrackerResource`
401
 * @property_uri: A string identifying the property to modify
402
 * @value: The property boolean value
403
 *
404
 * Sets a boolean property. Replaces any previous value.
405
 *
406
 * This method corresponds to [xsd:boolean](xsd-ontology.html#xsd:boolean).
407
 */
408
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_boolean, gboolean, G_TYPE_BOOLEAN, g_value_set_boolean, validate_boolean)
409
410
/**
411
 * tracker_resource_set_double:
412
 * @self: The `TrackerResource`
413
 * @property_uri: A string identifying the property to modify
414
 * @value: The property object
415
 *
416
 * Sets a numeric property with double precision. Replaces any previous value.
417
 *
418
 * This method corresponds to [xsd:double](xsd-ontology.html#xsd:double).
419
 */
420
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_double, double, G_TYPE_DOUBLE, g_value_set_double, validate_double)
421
422
/**
423
 * tracker_resource_set_int:
424
 * @self: The `TrackerResource`
425
 * @property_uri: A string identifying the property to modify
426
 * @value: The property object
427
 *
428
 * Sets a numeric property with integer precision. Replaces any previous value.
429
 *
430
 * This method corresponds to [xsd:integer](xsd-ontology.html#xsd:integer).
431
 */
432
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_int, int, G_TYPE_INT, g_value_set_int, validate_int)
433
434
/**
435
 * tracker_resource_set_int64:
436
 * @self: the `TrackerResource`
437
 * @property_uri: a string identifying the property to modify
438
 * @value: the property object
439
 *
440
 * Sets a numeric property with 64-bit integer precision. Replaces any previous value.
441
 *
442
 * This method corresponds to [xsd:integer](xsd-ontology.html#xsd:integer).
443
 */
444
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_int64, gint64, G_TYPE_INT64, g_value_set_int64, validate_int64)
445
446
/**
447
 * tracker_resource_set_relation:
448
 * @self: the `TrackerResource`
449
 * @property_uri: a string identifying the property to modify
450
 * @resource: the property object
451
 *
452
 * Sets a resource property as a `TrackerResource`. Replaces any previous value.
453
 *
454
 * This method applies to properties with a [rdfs:range](rdf-ontology.html#rdfs:range)
455
 * that points to a non-literal class (i.e. a subclass of
456
 * [rdfs:Resource](rdf-ontology.html#rdfs:Resource)).
457
 *
458
 * This function produces similar RDF to [method@Resource.set_uri],
459
 * although in this function the URI will depend on the identifier
460
 * set on @resource.
461
 */
462
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_set_object, validate_pointer)
463
464
/**
465
 * tracker_resource_set_take_relation:
466
 * @self: the `TrackerResource`
467
 * @property_uri: a string identifying the property to modify
468
 * @resource: (transfer full): the property object
469
 *
470
 * Sets a resource property as a `TrackerResource`. Replaces any previous value.
471
 * Takes ownership on the given @resource.
472
 *
473
 * This method applies to properties with a [rdfs:range](rdf-ontology.html#rdfs:range)
474
 * that points to a non-literal class (i.e. a subclass of
475
 * [rdfs:Resource](rdf-ontology.html#rdfs:Resource)).
476
 *
477
 * This function produces similar RDF to [method@Resource.set_uri],
478
 * although in this function the URI will depend on the identifier
479
 * set on @resource.
480
 */
481
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_take_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_take_object, validate_pointer)
482
483
/**
484
 * tracker_resource_set_string:
485
 * @self: the `TrackerResource`
486
 * @property_uri: a string identifying the property to modify
487
 * @value: the property object
488
 *
489
 * Sets a string property. Replaces any previous value.
490
 *
491
 * This method corresponds to [xsd:string](xsd-ontology.html#xsd:string).
492
 */
493
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_string, const char *, G_TYPE_STRING, g_value_set_string, validate_pointer)
494
495
/**
496
 * tracker_resource_set_uri:
497
 * @self: the `TrackerResource`
498
 * @property_uri: a string identifying the property to modify
499
 * @value: the property object
500
 *
501
 * Sets a resource property as an URI string. Replaces any previous value.
502
 *
503
 * This method applies to properties with a [rdfs:range](rdf-ontology.html#rdfs:range)
504
 * that points to a non-literal class (i.e. a subclass of
505
 * [rdfs:Resource](rdf-ontology.html#rdfs:Resource)).
506
 *
507
 * This function produces similar RDF to [method@Resource.set_relation], although
508
 * it requires that the URI is previously known.
509
 */
510
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_uri, const char *, TRACKER_TYPE_URI, value_set_uri, validate_pointer)
511
512
/**
513
 * tracker_resource_set_datetime:
514
 * @self: the `TrackerResource`
515
 * @property_uri: a string identifying the property to modify
516
 * @value: the property object
517
 *
518
 * Sets a date property as a [type@GLib.DateTime]. Replaces any previous value.
519
 *
520
 * This method corresponds to [xsd:date](xsd-ontology.html#xsd:date) and
521
 * [xsd:dateTime](xsd-ontology.html#xsd:dateTime).
522
 *
523
 * Since: 3.2
524
 */
525
SET_PROPERTY_FOR_GTYPE (tracker_resource_set_datetime, GDateTime *, G_TYPE_DATE_TIME, g_value_set_boxed, validate_pointer)
526
527
/**
528
 * tracker_resource_add_gvalue:
529
 * @self: the `TrackerResource`
530
 * @property_uri: a string identifying the property to set
531
 * @value: an initialised [struct@GObject.Value]
532
 *
533
 * Add @value to the list of values for given property.
534
 *
535
 * You can pass any kind of [struct@GObject.Value] for @value, but serialization functions will
536
 * normally only be able to serialize URIs/relationships and fundamental value
537
 * types (string, int, etc.).
538
 */
539
void
540
tracker_resource_add_gvalue (TrackerResource *self,
541
                             const char      *property_uri,
542
                             const GValue    *value)
543
0
{
544
0
  TrackerResourcePrivate *priv;
545
0
  GValue *existing_value, *array_holder, *our_value;
546
0
  GPtrArray *array;
547
548
0
  g_return_if_fail (TRACKER_IS_RESOURCE (self));
549
0
  g_return_if_fail (property_uri != NULL);
550
0
  g_return_if_fail (G_IS_VALUE (value));
551
552
0
  priv = GET_PRIVATE (self);
553
554
0
  existing_value = g_hash_table_lookup (priv->properties, property_uri);
555
556
0
  if (existing_value && G_VALUE_HOLDS (existing_value, G_TYPE_PTR_ARRAY)) {
557
0
    array = g_value_get_boxed (existing_value);
558
0
    array_holder = existing_value;
559
0
  } else {
560
0
    array = g_ptr_array_new_with_free_func ((GDestroyNotify)free_value);
561
0
    array_holder = g_slice_new0 (GValue);
562
0
    g_value_init (array_holder, G_TYPE_PTR_ARRAY);
563
0
    g_value_take_boxed (array_holder, array);
564
565
0
    if (existing_value) {
566
      /* The existing_value is owned by the hash table and will be freed
567
       * when we overwrite it with array_holder, so we need to allocate a
568
       * new value and give it to the ptrarray.
569
       */
570
0
      our_value = g_slice_new0 (GValue);
571
0
      g_value_init (our_value, G_VALUE_TYPE (existing_value));
572
0
      g_value_copy (existing_value, our_value);
573
0
      g_ptr_array_add (array, our_value);
574
0
    }
575
0
  }
576
577
0
  our_value = g_slice_new0 (GValue);
578
0
  g_value_init (our_value, G_VALUE_TYPE (value));
579
0
  g_value_copy (value, our_value);
580
581
0
  g_ptr_array_add (array, our_value);
582
583
0
  if (array_holder != existing_value) {
584
0
    g_hash_table_insert (priv->properties, g_strdup (property_uri), array_holder);
585
0
  }
586
0
}
587
588
#define ADD_PROPERTY_FOR_GTYPE(name, ctype, gtype, set_function, validate_function)     \
589
  void name (TrackerResource *self,                                               \
590
             const char *property_uri,                                            \
591
             ctype value)                                                         \
592
0
  {                                                                               \
593
0
    TrackerResourcePrivate *priv;                                           \
594
0
    GValue *existing_value, *array_holder, *our_value;                      \
595
0
    GPtrArray *array;                                                       \
596
0
                                                                                        \
597
0
    g_return_if_fail (TRACKER_IS_RESOURCE (self));                          \
598
0
    g_return_if_fail (property_uri != NULL);                                \
599
0
                                                                                        \
600
0
    priv = GET_PRIVATE (self);                                              \
601
0
                                                                                        \
602
0
    if (!validate_function (value, __func__)) {                             \
603
0
      return;                                                         \
604
0
    }                                                                       \
605
0
                                                                                        \
606
0
    existing_value = g_hash_table_lookup (priv->properties,                 \
607
0
                                          property_uri);                    \
608
0
                                                                                        \
609
0
    if (existing_value && G_VALUE_HOLDS (existing_value,                    \
610
0
                                         G_TYPE_PTR_ARRAY)) {               \
611
0
      array = g_value_get_boxed (existing_value);                     \
612
0
      array_holder = existing_value;                                  \
613
0
    } else {                                                                \
614
0
      array = g_ptr_array_new_with_free_func (                        \
615
0
              (GDestroyNotify)free_value);                            \
616
0
      array_holder = g_slice_new0 (GValue);                           \
617
0
      g_value_init (array_holder, G_TYPE_PTR_ARRAY);                  \
618
0
      g_value_take_boxed (array_holder, array);                       \
619
0
                                                                                        \
620
0
      if (existing_value) {                                           \
621
0
        /* existing_value is owned by hash table */             \
622
0
        our_value = g_slice_new0 (GValue);                      \
623
0
        g_value_init (our_value, G_VALUE_TYPE(existing_value)); \
624
0
        g_value_copy (existing_value, our_value);               \
625
0
        g_ptr_array_add (array, our_value);                     \
626
0
      }                                                               \
627
0
    }                                                                       \
628
0
                                                                                        \
629
0
    our_value = g_slice_new0 (GValue);                                      \
630
0
    g_value_init (our_value, gtype);                                        \
631
0
    set_function (our_value, value);                                        \
632
0
                                                                                        \
633
0
    g_ptr_array_add (array, our_value);                                     \
634
0
                                                                                        \
635
0
    if (array_holder != existing_value) {                                   \
636
0
      g_hash_table_insert (priv->properties,                          \
637
0
                           g_strdup (property_uri), array_holder);    \
638
0
    }                                                                       \
639
0
  }
Unexecuted instantiation: tracker_resource_add_boolean
Unexecuted instantiation: tracker_resource_add_double
Unexecuted instantiation: tracker_resource_add_int
Unexecuted instantiation: tracker_resource_add_int64
Unexecuted instantiation: tracker_resource_add_relation
Unexecuted instantiation: tracker_resource_add_take_relation
Unexecuted instantiation: tracker_resource_add_string
Unexecuted instantiation: tracker_resource_add_uri
Unexecuted instantiation: tracker_resource_add_datetime
640
641
/**
642
 * tracker_resource_add_boolean:
643
 * @self: The `TrackerResource`
644
 * @property_uri: A string identifying the property to modify
645
 * @value: The property boolean value
646
 *
647
 * Adds a boolean property. Previous values for the same property are kept.
648
 *
649
 * This method is meant for RDF properties allowing multiple values, see
650
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
651
 *
652
 * This method corresponds to [xsd:boolean](xsd-ontology.html#xsd:boolean).
653
 */
654
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_boolean, gboolean, G_TYPE_BOOLEAN, g_value_set_boolean, validate_boolean)
655
656
/**
657
 * tracker_resource_add_double:
658
 * @self: the `TrackerResource`
659
 * @property_uri: a string identifying the property to modify
660
 * @value: the property object
661
 *
662
 * Adds a numeric property with double precision. Previous values for the same property are kept.
663
 *
664
 * This method is meant for RDF properties allowing multiple values, see
665
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
666
 *
667
 * This method corresponds to [xsd:double](xsd-ontology.html#xsd:double).
668
 */
669
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_double, double, G_TYPE_DOUBLE, g_value_set_double, validate_double)
670
671
/**
672
 * tracker_resource_add_int:
673
 * @self: the `TrackerResource`
674
 * @property_uri: a string identifying the property to modify
675
 * @value: the property object
676
 *
677
 * Adds a numeric property with integer precision. Previous values for the same property are kept.
678
 *
679
 * This method is meant for RDF properties allowing multiple values, see
680
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
681
 *
682
 * This method corresponds to [xsd:integer](xsd-ontology.html#xsd:integer).
683
 */
684
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_int, int, G_TYPE_INT, g_value_set_int, validate_int)
685
686
/**
687
 * tracker_resource_add_int64:
688
 * @self: the `TrackerResource`
689
 * @property_uri: a string identifying the property to modify
690
 * @value: the property object
691
 *
692
 * Adds a numeric property with 64-bit integer precision. Previous values for the same property are kept.
693
 *
694
 * This method is meant for RDF properties allowing multiple values, see
695
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
696
 *
697
 * This method corresponds to [xsd:integer](xsd-ontology.html#xsd:integer).
698
 */
699
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_int64, gint64, G_TYPE_INT64, g_value_set_int64, validate_int64)
700
701
/**
702
 * tracker_resource_add_relation:
703
 * @self: the `TrackerResource`
704
 * @property_uri: a string identifying the property to modify
705
 * @resource: the property object
706
 *
707
 * Adds a resource property as a `TrackerResource`. Previous values for the same property are kept.
708
 *
709
 * This method is meant for RDF properties allowing multiple values, see
710
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
711
 *
712
 * This method applies to properties with a [rdfs:range](rdf-ontology.html#rdfs:range)
713
 * that points to a non-literal class (i.e. a subclass of
714
 * [rdfs:Resource](rdf-ontology.html#rdfs:Resource)).
715
 *
716
 * This method produces similar RDF to [method@Resource.add_uri],
717
 * although in this function the URI will depend on the identifier
718
 * set on @resource.
719
 */
720
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_set_object, validate_pointer)
721
722
/**
723
 * tracker_resource_add_take_relation:
724
 * @self: the `TrackerResource`
725
 * @property_uri: a string identifying the property to modify
726
 * @resource: (transfer full): the property object
727
 *
728
 * Adds a resource property as a `TrackerResource`. Previous values for the same property are kept.
729
 * Takes ownership on the given @resource.
730
 *
731
 * This method is meant to RDF properties allowing multiple values, see
732
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
733
 *
734
 * This method applies to properties with a [rdfs:range](rdf-ontology.html#rdfs:range)
735
 * that points to a non-literal class (i.e. a subclass of
736
 * [rdfs:Resource](rdf-ontology.html#rdfs:Resource)).
737
 *
738
 * This function produces similar RDF to [method@Resource.add_uri],
739
 * although in this function the URI will depend on the identifier
740
 * set on @resource. This function takes ownership of @resource.
741
 */
742
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_take_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_take_object, validate_pointer)
743
744
745
/**
746
 * tracker_resource_add_string:
747
 * @self: the `TrackerResource`
748
 * @property_uri: a string identifying the property to modify
749
 * @value: the property object
750
 *
751
 * Adds a string property. Previous values for the same property are kept.
752
 *
753
 * This method is meant for RDF properties allowing multiple values, see
754
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
755
 *
756
 * This method corresponds to [xsd:string](xsd-ontology.html#xsd:string).
757
 */
758
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_string, const char *, G_TYPE_STRING, g_value_set_string, validate_pointer)
759
760
/**
761
 * tracker_resource_add_uri:
762
 * @self: the `TrackerResource`
763
 * @property_uri: a string identifying the property to modify
764
 * @value: the property object
765
 *
766
 * Adds a resource property as an URI string. Previous values for the same property are kept.
767
 *
768
 * This method applies to properties with a [rdfs:range](rdf-ontology.html#rdfs:range)
769
 * that points to a non-literal class (i.e. a subclass of
770
 * [rdfs:Resource](rdf-ontology.html#rdfs:Resource)).
771
 *
772
 * This method is meant for RDF properties allowing multiple values, see
773
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
774
 *
775
 * This function produces similar RDF to [method@Resource.add_relation], although
776
 * it requires that the URI is previously known.
777
 */
778
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_uri, const char *, TRACKER_TYPE_URI, value_set_uri, validate_pointer)
779
780
/**
781
 * tracker_resource_add_datetime:
782
 * @self: the `TrackerResource`
783
 * @property_uri: a string identifying the property to modify
784
 * @value: the property object
785
 *
786
 * Adds a date property as a [type@GLib.DateTime]. Previous values for the
787
 * same property are kept.
788
 *
789
 * This method is meant for RDF properties allowing multiple values, see
790
 * [nrl:maxCardinality](nrl-ontology.html#nrl:maxCardinality).
791
 *
792
 * This method corresponds to [xsd:date](xsd-ontology.html#xsd:date) and
793
 * [xsd:dateTime](xsd-ontology.html#xsd:dateTime).
794
 *
795
 * Since: 3.2
796
 */
797
ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_datetime, GDateTime *, G_TYPE_DATE_TIME, g_value_set_boxed, validate_pointer)
798
799
/**
800
 * tracker_resource_get_values:
801
 * @self: the `TrackerResource`
802
 * @property_uri: a string identifying the property to look up
803
 *
804
 * Returns the list of all known values of the given property.
805
 *
806
 * Returns: (transfer container) (element-type GValue) (nullable): a [struct@GLib.List] of
807
 *   [struct@GObject.Value] instances. The list should be freed with [func@GLib.List.free]
808
 */
809
GList *tracker_resource_get_values (TrackerResource *self,
810
                                    const char      *property_uri)
811
0
{
812
0
  TrackerResourcePrivate *priv;
813
0
  GValue *value;
814
815
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (self), NULL);
816
0
  g_return_val_if_fail (property_uri, NULL);
817
818
0
  priv = GET_PRIVATE (self);
819
820
0
  value = g_hash_table_lookup (priv->properties, property_uri);
821
822
0
  if (value == NULL) {
823
0
    return NULL;
824
0
  }
825
826
0
  if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) {
827
0
    GList *result = NULL;
828
0
    GPtrArray *array;
829
0
    guint i;
830
831
0
    array = g_value_get_boxed (value);
832
833
0
    for (i = 0; i < array->len; i++) {
834
0
      value = g_ptr_array_index (array, i);
835
0
      result = g_list_prepend (result, value);
836
0
    };
837
838
0
    return g_list_reverse (result);
839
0
  } else {
840
0
    return g_list_append (NULL, value);
841
0
  }
842
0
}
843
844
#define GET_PROPERTY_FOR_GTYPE(name, ctype, gtype, get_function, no_value)    \
845
  ctype name (TrackerResource *self,                                    \
846
              const char *property_uri)                                 \
847
0
  {                                                                     \
848
0
    TrackerResourcePrivate *priv;                                 \
849
0
    GValue *value;                                                \
850
0
                                                                              \
851
0
    g_return_val_if_fail (TRACKER_IS_RESOURCE (self), no_value);  \
852
0
    g_return_val_if_fail (property_uri, no_value);                \
853
0
                                                                              \
854
0
    priv = GET_PRIVATE (self);                                    \
855
0
                                                                              \
856
0
    value = g_hash_table_lookup (priv->properties, property_uri); \
857
0
                                                                              \
858
0
    if (value == NULL) {                                          \
859
0
      return no_value;                                      \
860
0
    };                                                            \
861
0
                                                                              \
862
0
    if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) {                \
863
0
      GPtrArray *array;                                     \
864
0
      array = g_value_get_boxed (value);                    \
865
0
      if (array->len == 0) {                                \
866
0
        return no_value;                              \
867
0
      } else {                                              \
868
0
        value = g_ptr_array_index (array, 0);         \
869
0
      }                                                     \
870
0
    }                                                             \
871
0
                                                                              \
872
0
    return get_function (value);                                  \
873
0
  }
874
875
/**
876
 * tracker_resource_get_first_boolean:
877
 * @self: A `TrackerResource`
878
 * @property_uri: a string identifying the property to look up
879
 *
880
 * Returns the first boolean object previously assigned to a property.
881
 *
882
 * Returns: the first boolean object
883
 */
884
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_boolean, gboolean, G_TYPE_BOOLEAN, g_value_get_boolean, FALSE)
885
886
/**
887
 * tracker_resource_get_first_double:
888
 * @self: A `TrackerResource`
889
 * @property_uri: a string identifying the property to look up
890
 *
891
 * Returns the first double object previously assigned to a property.
892
 *
893
 * Returns: the first double object
894
 */
895
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_double, double, G_TYPE_DOUBLE, g_value_get_double, 0.0)
896
897
/**
898
 * tracker_resource_get_first_int:
899
 * @self: A `TrackerResource`
900
 * @property_uri: a string identifying the property to look up
901
 *
902
 * Returns the first integer object previously assigned to a property.
903
 *
904
 * Returns: the first integer object
905
 */
906
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_int, int, G_TYPE_INT, g_value_get_int, 0)
907
908
/**
909
 * tracker_resource_get_first_int64:
910
 * @self: A `TrackerResource`
911
 * @property_uri: a string identifying the property to look up
912
 *
913
 * Returns the first integer object previously assigned to a property.
914
 *
915
 * Returns: the first integer object
916
 */
917
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_int64, gint64, G_TYPE_INT64, g_value_get_int64, 0)
918
919
/**
920
 * tracker_resource_get_first_relation:
921
 * @self: A `TrackerResource`
922
 * @property_uri: a string identifying the property to look up
923
 *
924
 * Returns the first resource object previously assigned to a property.
925
 *
926
 * Returns: (transfer none) (nullable): the first resource object
927
 */
928
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_get_object, NULL)
929
930
/**
931
 * tracker_resource_get_first_string:
932
 * @self: A `TrackerResource`
933
 * @property_uri: a string identifying the property to look up
934
 *
935
 * Returns the first string object previously assigned to a property.
936
 *
937
 * Returns: (nullable): the first string object
938
 */
939
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_string, const char *, G_TYPE_STRING, g_value_get_string, NULL)
940
941
/**
942
 * tracker_resource_get_first_uri:
943
 * @self: A `TrackerResource`
944
 * @property_uri: a string identifying the property to look up
945
 *
946
 * Returns the first resource object previously assigned to a property.
947
 *
948
 * Returns: (nullable): the first resource object as an URI.
949
 */
950
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_uri, const char *, TRACKER_TYPE_URI, g_value_get_string, NULL)
951
952
/**
953
 * tracker_resource_get_first_datetime:
954
 * @self: A `TrackerResource`
955
 * @property_uri: a string identifying the property to look up
956
 *
957
 * Returns the first [type@GLib.DateTime] previously assigned to a property.
958
 *
959
 * Returns: (transfer none) (nullable): the first GDateTime object
960
 * Since: 3.2
961
 */
962
0
GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_datetime, GDateTime *, G_TYPE_DATE_TIME, g_value_get_boxed, NULL)
963
964
/**
965
 * tracker_resource_get_identifier:
966
 * @self: A `TrackerResource`
967
 *
968
 * Returns the identifier of a resource.
969
 *
970
 * If the identifier was set to NULL, the identifier returned will be a locally
971
 * unique SPARQL blank node identifier, such as `_:123`.
972
 *
973
 * Returns: (nullable): a string owned by the resource
974
 */
975
const char *
976
tracker_resource_get_identifier (TrackerResource *self)
977
0
{
978
0
  TrackerResourcePrivate *priv;
979
980
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (self), NULL);
981
982
0
  priv = GET_PRIVATE (self);
983
984
0
  if (!priv->identifier)
985
0
    priv->identifier = generate_blank_node_identifier ();
986
987
0
  return priv->identifier;
988
0
}
989
990
/**
991
 * tracker_resource_set_identifier:
992
 * @self: A `TrackerResource`
993
 * @identifier: (nullable): a string identifying the resource
994
 *
995
 * Changes the identifier of a `TrackerResource`. The identifier should be a
996
 * URI or compact URI, but this is not necessarily enforced. Invalid
997
 * identifiers may cause errors when serializing the resource or trying to
998
 * insert the results in a database.
999
 *
1000
 * If the identifier is set to %NULL, a SPARQL blank node identifier such as
1001
 * `_:123` is assigned to the resource.
1002
 */
1003
void
1004
tracker_resource_set_identifier (TrackerResource *self,
1005
                                 const char      *identifier)
1006
0
{
1007
0
  TrackerResourcePrivate *priv;
1008
1009
0
  g_return_if_fail (TRACKER_IS_RESOURCE (self));
1010
1011
0
  priv = GET_PRIVATE (self);
1012
1013
0
  g_clear_pointer (&priv->identifier, g_free);
1014
0
  priv->identifier = escape_iri (identifier);
1015
0
}
1016
1017
/**
1018
 * tracker_resource_identifier_compare_func:
1019
 * @resource: a `TrackerResource`
1020
 * @identifier: a string identifying the resource
1021
 *
1022
 * A helper function that compares a `TrackerResource` by its identifier
1023
 * string.
1024
 *
1025
 * Returns: an integer less than, equal to, or greater than zero, if the
1026
 *          resource identifier is <, == or > than @identifier
1027
 **/
1028
gint
1029
tracker_resource_identifier_compare_func (TrackerResource *resource,
1030
                                          const char      *identifier)
1031
0
{
1032
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), 0);
1033
0
  g_return_val_if_fail (identifier != NULL, 0);
1034
1035
0
  return strcmp (tracker_resource_get_identifier (resource), identifier);
1036
0
}
1037
1038
/**
1039
 * tracker_resource_compare:
1040
 * @a: A `TrackerResource`
1041
 * @b: A second `TrackerResource` to compare
1042
 *
1043
 * Compare the identifiers of two TrackerResource instances. The resources
1044
 * are considered identical if they have the same identifier.
1045
 *
1046
 * Note that there can be false negatives with this simplistic approach: two
1047
 * resources may have different identifiers that actually refer to the same
1048
 * thing.
1049
 *
1050
 * Returns: 0 if the identifiers are the same, -1 or +1 otherwise
1051
 */
1052
gint
1053
tracker_resource_compare (TrackerResource *a,
1054
                          TrackerResource *b)
1055
0
{
1056
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (a), 0);
1057
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (b), 0);
1058
1059
0
  return strcmp (tracker_resource_get_identifier (a),
1060
0
           tracker_resource_get_identifier (b));
1061
0
}
1062
1063
/**
1064
 * tracker_resource_get_properties:
1065
 * @resource: a `TrackerResource`
1066
 *
1067
 * Gets the list of properties defined in @resource
1068
 *
1069
 * Returns: (transfer container) (element-type utf8): The list of properties.
1070
 **/
1071
GList *
1072
tracker_resource_get_properties (TrackerResource *resource)
1073
0
{
1074
0
  TrackerResourcePrivate *priv;
1075
1076
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), NULL);
1077
1078
0
  priv = GET_PRIVATE (resource);
1079
1080
0
  return g_hash_table_get_keys (priv->properties);
1081
0
}
1082
1083
static gchar *
1084
parse_prefix (const gchar *prefixed_name)
1085
0
{
1086
0
  const gchar *end, *token_end;
1087
1088
0
  end = &prefixed_name[strlen(prefixed_name)];
1089
1090
0
  if (!terminal_PNAME_NS (prefixed_name, end, &token_end))
1091
0
    return NULL;
1092
1093
0
  g_assert (token_end != NULL);
1094
1095
  /* We have read the ':', take a step back */
1096
0
  if (token_end > prefixed_name)
1097
0
    token_end--;
1098
1099
0
  if (*token_end != ':')
1100
0
    return NULL;
1101
1102
0
  return g_strndup (prefixed_name, token_end - prefixed_name);
1103
0
}
1104
1105
static gboolean
1106
is_blank_node (const char *uri_or_curie_or_blank)
1107
0
{
1108
0
  return (strncmp(uri_or_curie_or_blank, "_:", 2) == 0);
1109
0
}
1110
1111
static gboolean
1112
is_builtin_class (const gchar             *uri_or_curie,
1113
                  TrackerNamespaceManager *namespaces)
1114
0
{
1115
0
  gchar *prefix = NULL;
1116
0
  gboolean has_prefix;
1117
1118
  /* blank nodes should be processed as nested resource
1119
   * parse_prefix returns NULL for blank nodes, i.e. _:1
1120
   */
1121
0
  if (is_blank_node (uri_or_curie))
1122
0
    return FALSE;
1123
1124
0
  prefix = parse_prefix (uri_or_curie);
1125
1126
0
  if (!prefix)
1127
0
    return TRUE;
1128
1129
0
  has_prefix = tracker_namespace_manager_has_prefix (namespaces, prefix);
1130
0
  g_free (prefix);
1131
1132
0
  return has_prefix;
1133
0
}
1134
1135
static void
1136
generate_turtle_uri_value (const char              *uri_or_curie_or_blank,
1137
                           GString                 *string,
1138
                           TrackerNamespaceManager *all_namespaces)
1139
0
{
1140
  /* The tracker_resource_set_uri() function accepts URIs
1141
   * (such as http://example.com/) and compact URIs (such as nie:DataObject),
1142
   * and blank node identifiers (_:0). The tracker_resource_set_identifier()
1143
   * function works the same.
1144
   *
1145
   * We could expand all CURIEs, but the generated Turtle or SPARQL will be
1146
   * clearer if we leave them be. We still need to attempt to expand them
1147
   * internally in order to know whether they need <> brackets around them.
1148
   */
1149
0
  if (is_blank_node (uri_or_curie_or_blank)) {
1150
0
    g_string_append (string, uri_or_curie_or_blank);
1151
0
  } else {
1152
0
    char *prefix = parse_prefix (uri_or_curie_or_blank);
1153
1154
0
    if (prefix && tracker_namespace_manager_has_prefix (all_namespaces, prefix)) {
1155
0
      g_string_append (string, uri_or_curie_or_blank);
1156
0
    } else {
1157
      /* It's a full URI (or something invalid, but we can't really tell that here) */
1158
0
      g_string_append_printf (string, "<%s>", uri_or_curie_or_blank);
1159
0
    }
1160
1161
0
    g_free (prefix);
1162
0
  }
1163
0
}
1164
1165
static void
1166
generate_turtle_value (const GValue            *value,
1167
                       GString                 *string,
1168
                       TrackerNamespaceManager *all_namespaces)
1169
0
{
1170
0
  GType type = G_VALUE_TYPE (value);
1171
0
  if (type == TRACKER_TYPE_URI) {
1172
0
    generate_turtle_uri_value (g_value_get_string (value),
1173
0
                               string,
1174
0
                               all_namespaces);
1175
0
  } else if (type == TRACKER_TYPE_RESOURCE) {
1176
0
    TrackerResource *relation = TRACKER_RESOURCE (g_value_get_object (value));
1177
0
    generate_turtle_uri_value (tracker_resource_get_identifier (relation),
1178
0
                               string,
1179
0
                               all_namespaces);
1180
0
  } else if (type == G_TYPE_STRING) {
1181
0
    char *escaped = tracker_sparql_escape_string (g_value_get_string (value));
1182
0
    g_string_append_printf(string, "\"%s\"", escaped);
1183
0
    g_free (escaped);
1184
0
  } else if (type == G_TYPE_DATE) {
1185
0
    char date_string[256];
1186
0
    g_date_strftime (date_string, 256,
1187
0
                     "\"%Y-%m-%d%z\"^^<http://www.w3.org/2001/XMLSchema#date>",
1188
0
                     g_value_get_boxed (value));
1189
0
    g_string_append (string, date_string);
1190
0
  } else if (type == G_TYPE_DATE_TIME) {
1191
0
    char *datetime_string;
1192
0
    datetime_string = g_date_time_format (g_value_get_boxed (value),
1193
0
                                          "\"%Y-%m-%dT%H:%M:%S%z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>");
1194
0
    g_string_append (string, datetime_string);
1195
0
    g_free (datetime_string);
1196
0
  } else if (type == G_TYPE_DOUBLE || type == G_TYPE_FLOAT) {
1197
    /* We can't use GValue transformations here; they're locale-dependent. */
1198
0
    char buffer[256];
1199
0
    g_ascii_dtostr (buffer, 255, g_value_get_double (value));
1200
0
    g_string_append (string, buffer);
1201
0
  } else {
1202
0
    GValue str_value = G_VALUE_INIT;
1203
0
    g_value_init (&str_value, G_TYPE_STRING);
1204
0
    if (g_value_transform (value, &str_value)) {
1205
0
      g_string_append (string, g_value_get_string (&str_value));
1206
0
    } else {
1207
0
      g_warning ("Cannot serialize value of type %s to Turtle/SPARQL",
1208
0
                 G_VALUE_TYPE_NAME (value));
1209
0
    }
1210
0
    g_value_unset (&str_value);
1211
0
  }
1212
0
}
1213
1214
void
1215
generate_turtle_property (const char              *property,
1216
                          const GValue            *value,
1217
                          GString                 *string,
1218
                          TrackerNamespaceManager *all_namespaces)
1219
0
{
1220
0
  if (strcmp (property, TRACKER_PREFIX_RDF "type") == 0 || strcmp (property, "rdf:type") == 0) {
1221
0
    g_string_append (string, "a");
1222
0
  } else {
1223
0
    g_string_append (string, property);
1224
0
  }
1225
1226
0
  g_string_append (string, " ");
1227
0
  if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) {
1228
0
    guint i;
1229
0
    GPtrArray *array = g_value_get_boxed (value);
1230
0
    if (array->len > 0) {
1231
0
      generate_turtle_value (g_ptr_array_index (array, 0),
1232
0
                             string,
1233
0
                             all_namespaces);
1234
0
      for (i = 1; i < array->len; i++) {
1235
0
        g_string_append (string, " , ");
1236
0
        generate_turtle_value (g_ptr_array_index (array, i),
1237
0
                               string,
1238
0
                               all_namespaces);
1239
0
      }
1240
0
    }
1241
0
  } else {
1242
0
    generate_turtle_value (value, string, all_namespaces);
1243
0
  }
1244
0
}
1245
1246
/**
1247
 * tracker_resource_print_turtle:
1248
 * @self: a `TrackerResource`
1249
 * @namespaces: (allow-none): a set of prefixed URLs, or %NULL to use the
1250
 *     Nepomuk set
1251
 *
1252
 * Serialize all the information in @resource as a Turtle document.
1253
 *
1254
 * The generated Turtle should correspond to this standard:
1255
 * <https://www.w3.org/TR/2014/REC-turtle-20140225/>
1256
 *
1257
 * The @namespaces object is used to expand any compact URI values. In most
1258
 * cases you should pass the one returned by [method@SparqlConnection.get_namespace_manager]
1259
 * from the connection that is the intended recipient of this data.
1260
 *
1261
 * Returns: a newly-allocated string
1262
 *
1263
 * Deprecated: 3.4: Use [method@Resource.print_rdf] instead.
1264
 */
1265
char *
1266
tracker_resource_print_turtle (TrackerResource         *self,
1267
                               TrackerNamespaceManager *namespaces)
1268
0
{
1269
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (self), "");
1270
1271
0
  if (namespaces == NULL) {
1272
0
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1273
0
    namespaces = tracker_namespace_manager_get_default ();
1274
0
    G_GNUC_END_IGNORE_DEPRECATIONS
1275
0
  }
1276
1277
0
  return tracker_resource_print_rdf (self, namespaces, TRACKER_RDF_FORMAT_TURTLE, NULL);
1278
0
}
1279
1280
typedef struct {
1281
  TrackerNamespaceManager *namespaces;
1282
  GString *string;
1283
  char *graph_id;
1284
  GList *done_list;
1285
} GenerateSparqlData;
1286
1287
static void generate_sparql_deletes (TrackerResource *resource, GenerateSparqlData *data);
1288
static void generate_sparql_insert_pattern (TrackerResource *resource, GenerateSparqlData *data);
1289
1290
static void
1291
generate_sparql_relation_deletes_foreach (gpointer key,
1292
                                          gpointer value_ptr,
1293
                                          gpointer user_data)
1294
0
{
1295
0
  const GValue *value = value_ptr;
1296
0
  GenerateSparqlData *data = user_data;
1297
0
  guint i;
1298
1299
0
  if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) {
1300
0
    TrackerResource *relation = g_value_get_object (value);
1301
1302
0
    generate_sparql_deletes (relation, data);
1303
0
  } else if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) {
1304
0
    GPtrArray *array = g_value_get_boxed (value);
1305
1306
0
    for (i = 0; i < array->len; i ++) {
1307
0
      GValue *value = g_ptr_array_index (array, i);
1308
1309
0
      if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) {
1310
0
        TrackerResource *relation = g_value_get_object (value);
1311
1312
0
        generate_sparql_deletes (relation, data);
1313
0
      }
1314
0
    }
1315
0
  }
1316
0
}
1317
1318
static void
1319
generate_sparql_relation_inserts_foreach (gpointer key,
1320
                                          gpointer value_ptr,
1321
                                          gpointer user_data)
1322
0
{
1323
0
  const GValue *value = value_ptr;
1324
0
  GenerateSparqlData *data = user_data;
1325
1326
0
  if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) {
1327
0
    TrackerResource *relation = g_value_get_object (value);
1328
1329
    /* We don't need to produce inserts for builtin classes */
1330
0
    if (is_builtin_class (tracker_resource_get_identifier (relation),
1331
0
                          data->namespaces))
1332
0
      return;
1333
1334
0
    generate_sparql_insert_pattern (relation, data);
1335
0
  } else if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) {
1336
0
    GPtrArray *array = g_value_get_boxed (value);
1337
0
    const GValue *array_value;
1338
0
    TrackerResource *relation;
1339
0
    guint i;
1340
1341
0
    for (i = 0; i < array->len; i++) {
1342
0
      array_value = g_ptr_array_index (array, i);
1343
1344
0
      if (!G_VALUE_HOLDS (array_value, TRACKER_TYPE_RESOURCE))
1345
0
        continue;
1346
1347
0
      relation = g_value_get_object (array_value);
1348
1349
      /* We don't need to produce inserts for builtin classes */
1350
0
      if (is_builtin_class (tracker_resource_get_identifier (relation),
1351
0
                data->namespaces))
1352
0
        continue;
1353
1354
0
      generate_sparql_insert_pattern (relation, data);
1355
0
    }
1356
0
  }
1357
0
}
1358
1359
static char *
1360
0
variable_name_for_property (const char *property) {
1361
0
  return g_strcanon (g_strdup (property),
1362
0
                     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
1363
0
                     '_');
1364
0
}
1365
1366
static void
1367
generate_sparql_delete_queries (TrackerResource     *resource,
1368
                                GHashTable          *overwrite_flags,
1369
                                GenerateSparqlData  *data)
1370
0
{
1371
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1372
0
  GHashTableIter iter;
1373
0
  const char *property;
1374
0
  const GValue *value;
1375
1376
0
  g_hash_table_iter_init (&iter, priv->properties);
1377
0
  while (g_hash_table_iter_next (&iter, (gpointer *)&property, (gpointer *)&value)) {
1378
    /* Whether to generate the DELETE is based on whether set_value was ever
1379
    * called for this property. That's tracked in the overwrite_flags hash table.
1380
    */
1381
0
    if (g_hash_table_lookup (overwrite_flags, property)) {
1382
0
      char *variable_name = variable_name_for_property (property);
1383
1384
0
      g_string_append (data->string, "DELETE WHERE {\n");
1385
1386
0
      if (data->graph_id) {
1387
0
        g_string_append_printf (data->string, "GRAPH <%s> {\n", data->graph_id);
1388
0
      }
1389
1390
0
      g_string_append (data->string, "  ");
1391
0
      generate_turtle_uri_value (tracker_resource_get_identifier (resource),
1392
0
                                 data->string, data->namespaces);
1393
0
      g_string_append_printf (data->string, " %s ?%s }", property, variable_name);
1394
0
      g_free (variable_name);
1395
1396
0
      if (data->graph_id) {
1397
0
        g_string_append (data->string, " }");
1398
0
      }
1399
1400
0
      g_string_append (data->string, ";\n");
1401
0
    }
1402
0
  }
1403
0
}
1404
1405
void
1406
generate_sparql_deletes (TrackerResource    *resource,
1407
                         GenerateSparqlData *data)
1408
0
{
1409
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1410
1411
0
  if (g_list_find (data->done_list, resource) != NULL)
1412
    /* We already processed this resource. */
1413
0
    return;
1414
1415
0
  data->done_list = g_list_prepend (data->done_list, resource);
1416
1417
0
  if (!tracker_resource_is_blank_node (resource) && g_hash_table_size (priv->overwrite) > 0) {
1418
0
    generate_sparql_delete_queries (resource, priv->overwrite, data);
1419
0
  }
1420
1421
  /* Now emit any sub-resources. */
1422
0
  g_hash_table_foreach (priv->properties, generate_sparql_relation_deletes_foreach, data);
1423
0
}
1424
1425
static void
1426
generate_sparql_insert_pattern (TrackerResource    *resource,
1427
                                GenerateSparqlData *data)
1428
0
{
1429
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1430
0
  GHashTableIter iter;
1431
0
  const char *property;
1432
0
  char *full_property;
1433
0
  const GValue *value;
1434
0
  gboolean had_property = FALSE;
1435
1436
0
  if (g_list_find (data->done_list, resource) != NULL)
1437
    /* We already processed this resource. */
1438
0
    return;
1439
1440
0
  data->done_list = g_list_prepend (data->done_list, resource);
1441
1442
  /* First, emit any sub-resources. */
1443
0
  g_hash_table_foreach (priv->properties, generate_sparql_relation_inserts_foreach, data);
1444
1445
0
  generate_turtle_uri_value (tracker_resource_get_identifier (resource),
1446
0
                             data->string, data->namespaces);
1447
0
  g_string_append_printf (data->string, " ");
1448
1449
  /* rdf:type needs to be first, otherwise you'll see 'subject x is not in domain y'
1450
   * errors for the properties you try to set.
1451
   */
1452
0
  value = g_hash_table_lookup (priv->properties, "rdf:type");
1453
0
  if (value != NULL) {
1454
0
    generate_turtle_property ("a", value, data->string, data->namespaces);
1455
0
    had_property = TRUE;
1456
0
  }
1457
1458
0
  g_hash_table_iter_init (&iter, priv->properties);
1459
0
  while (g_hash_table_iter_next (&iter, (gpointer *)&property, (gpointer *)&value)) {
1460
0
    full_property = tracker_namespace_manager_expand_uri (data->namespaces, property);
1461
1462
0
    if (strcmp (full_property, TRACKER_PREFIX_RDF "type") != 0 && strcmp (property, "rdf:type") != 0) {
1463
0
      if (had_property) {
1464
0
        g_string_append (data->string, " ; \n  ");
1465
0
      }
1466
1467
0
      generate_turtle_property (property, value, data->string, data->namespaces);
1468
1469
0
      had_property = TRUE;
1470
0
    }
1471
1472
0
    g_free (full_property);
1473
0
  }
1474
1475
0
  g_string_append (data->string, " .\n");
1476
0
}
1477
1478
/**
1479
 * tracker_resource_print_sparql_update:
1480
 * @self: a `TrackerResource`
1481
 * @namespaces: (allow-none): a set of prefixed URLs, or %NULL to use the
1482
 *     Nepomuk set
1483
 * @graph_id: (allow-none): the URN of the graph the data should be added to,
1484
 *     or %NULL
1485
 *
1486
 * Generates a SPARQL command to update a database with the information
1487
 * stored in @resource.
1488
 *
1489
 * The @namespaces object is used to expand any compact URI values. In most
1490
 * cases you should pass the one returned by [method@SparqlConnection.get_namespace_manager]
1491
 * from the connection that is the intended recipient of this data.
1492
 *
1493
 * Returns: a newly-allocated string containing a SPARQL update command.
1494
 */
1495
char *
1496
tracker_resource_print_sparql_update (TrackerResource         *resource,
1497
                                      TrackerNamespaceManager *namespaces,
1498
                                      const char              *graph_id)
1499
0
{
1500
0
  TrackerResourcePrivate *priv;
1501
0
  GenerateSparqlData context = { 0, };
1502
1503
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), "");
1504
1505
0
  priv = GET_PRIVATE(resource);
1506
1507
0
  if (namespaces == NULL) {
1508
0
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1509
0
    namespaces = tracker_namespace_manager_get_default ();
1510
0
    G_GNUC_END_IGNORE_DEPRECATIONS
1511
0
  }
1512
1513
0
  if (g_hash_table_size (priv->properties) == 0) {
1514
0
    return g_strdup("");
1515
0
  }
1516
1517
0
  context.namespaces = namespaces;
1518
0
  context.string = g_string_new (NULL);
1519
1520
0
  if (graph_id)
1521
0
    context.graph_id = tracker_namespace_manager_expand_uri (namespaces, graph_id);
1522
1523
  /* Resources can be recursive, and may have repeated or even cyclic
1524
   * relationships. This list keeps track of what we already processed.
1525
   */
1526
0
  context.done_list = NULL;
1527
1528
  /* Delete the existing data. If we don't do this, we may get constraint
1529
   * violations due to trying to add a second value to a single-valued
1530
   * property, and we may get old metadata hanging around.
1531
   */
1532
0
  generate_sparql_deletes (resource, &context);
1533
1534
0
  g_list_free (context.done_list);
1535
0
  context.done_list = NULL;
1536
1537
  /* Finally insert the data */
1538
0
  g_string_append (context.string, "INSERT DATA {\n");
1539
0
  if (context.graph_id) {
1540
0
    g_string_append_printf (context.string, "GRAPH <%s> {\n", context.graph_id);
1541
0
  }
1542
1543
0
  generate_sparql_insert_pattern (resource, &context);
1544
1545
0
  if (context.graph_id) {
1546
0
    g_string_append (context.string, "}\n");
1547
0
  }
1548
0
  g_string_append (context.string, "};\n");
1549
1550
0
  g_list_free (context.done_list);
1551
0
  g_free (context.graph_id);
1552
0
  context.done_list = NULL;
1553
1554
0
  return g_string_free (context.string, FALSE);
1555
0
}
1556
1557
/**
1558
 * tracker_resource_print_jsonld:
1559
 * @self: a `TrackerResource`
1560
 * @namespaces: (nullable): a set of prefixed URLs, or %NULL to use the
1561
 *     Nepomuk set
1562
 *
1563
 * Serialize all the information in @resource as a JSON-LD document.
1564
 *
1565
 * See <http://www.jsonld.org/> for more information on the JSON-LD
1566
 * serialization format.
1567
 *
1568
 * The @namespaces object is used to expand any compact URI values. In most
1569
 * cases you should pass the one returned by [method@SparqlConnection.get_namespace_manager]
1570
 * from the connection that is the intended recipient of this data.
1571
 *
1572
 * Returns: a newly-allocated string containing JSON-LD data.
1573
 *
1574
 * Deprecated: 3.5: Use [method@Resource.print_rdf] instead.
1575
 */
1576
char *
1577
tracker_resource_print_jsonld (TrackerResource         *self,
1578
                               TrackerNamespaceManager *namespaces)
1579
0
{
1580
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (self), "");
1581
1582
0
  if (namespaces == NULL) {
1583
0
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1584
0
    namespaces = tracker_namespace_manager_get_default ();
1585
0
    G_GNUC_END_IGNORE_DEPRECATIONS
1586
0
  }
1587
1588
0
  return tracker_resource_print_rdf (self, namespaces, TRACKER_RDF_FORMAT_JSON_LD, NULL);
1589
0
}
1590
1591
static TrackerSerializerFormat
1592
convert_format (TrackerRdfFormat format)
1593
0
{
1594
0
  switch (format) {
1595
0
  case TRACKER_RDF_FORMAT_TURTLE:
1596
0
    return TRACKER_SERIALIZER_FORMAT_TTL;
1597
0
  case TRACKER_RDF_FORMAT_TRIG:
1598
0
    return TRACKER_SERIALIZER_FORMAT_TRIG;
1599
0
  case TRACKER_RDF_FORMAT_JSON_LD:
1600
0
    return TRACKER_SERIALIZER_FORMAT_JSON_LD;
1601
0
  case TRACKER_RDF_FORMAT_LAST:
1602
0
    g_assert_not_reached ();
1603
0
  }
1604
1605
0
  return -1;
1606
0
}
1607
1608
/**
1609
 * tracker_resource_print_rdf:
1610
 * @self: a `TrackerResource`
1611
 * @namespaces: a set of prefixed URLs
1612
 * @format: RDF format of the printed string
1613
 * @graph: (nullable): target graph of the resource RDF, or %NULL for the
1614
 * default graph
1615
 *
1616
 * Serialize all the information in @resource into the selected RDF format.
1617
 *
1618
 * The @namespaces object is used to expand any compact URI values. In most
1619
 * cases you should pass the one returned by [method@SparqlConnection.get_namespace_manager]
1620
 * from the connection that is the intended recipient of this data.
1621
 *
1622
 * Returns: a newly-allocated string containing RDF data in the requested format.
1623
 *
1624
 * Since: 3.4
1625
 **/
1626
char *
1627
tracker_resource_print_rdf (TrackerResource         *self,
1628
                            TrackerNamespaceManager *namespaces,
1629
                            TrackerRdfFormat         format,
1630
                            const gchar             *graph)
1631
0
{
1632
0
  TrackerSparqlCursor *deserializer;
1633
0
  GInputStream *serializer;
1634
0
  GString *str;
1635
1636
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (self), NULL);
1637
0
  g_return_val_if_fail (TRACKER_IS_NAMESPACE_MANAGER (namespaces), NULL);
1638
0
  g_return_val_if_fail (format < TRACKER_N_RDF_FORMATS, NULL);
1639
1640
0
  deserializer = tracker_deserializer_resource_new (self, namespaces, graph);
1641
0
  serializer = tracker_serializer_new (TRACKER_SPARQL_CURSOR (deserializer),
1642
0
                                       namespaces,
1643
0
                                       convert_format (format));
1644
0
  g_object_unref (deserializer);
1645
1646
0
  str = g_string_new (NULL);
1647
1648
0
  if (format == TRACKER_RDF_FORMAT_JSON_LD) {
1649
0
    JsonParser *parser;
1650
0
    JsonGenerator *generator;
1651
0
    JsonNode *root;
1652
1653
    /* Special case, ensure that json is pretty printed */
1654
0
    parser = json_parser_new ();
1655
1656
0
    if (!json_parser_load_from_stream (parser,
1657
0
                                       serializer,
1658
0
                                       NULL,
1659
0
                                       NULL)) {
1660
0
      g_object_unref (parser);
1661
0
      return g_string_free (str, FALSE);
1662
0
    }
1663
1664
0
    generator = json_generator_new ();
1665
0
    root = json_parser_get_root (parser);
1666
0
    json_generator_set_root (generator, root);
1667
0
    json_generator_set_pretty (generator, TRUE);
1668
0
    json_generator_to_gstring (generator, str);
1669
0
    g_object_unref (generator);
1670
0
    g_object_unref (parser);
1671
1672
0
    return g_string_free (str, FALSE);
1673
0
  }
1674
1675
0
#define BUF_SIZE 4096
1676
0
  while (TRUE) {
1677
0
    GBytes *bytes;
1678
1679
0
    bytes = g_input_stream_read_bytes (serializer, BUF_SIZE, NULL, NULL);
1680
0
    if (!bytes) {
1681
0
      g_string_free (str, TRUE);
1682
0
      return NULL;
1683
0
    }
1684
1685
0
    if (g_bytes_get_size (bytes) == 0) {
1686
0
      g_bytes_unref (bytes);
1687
0
      break;
1688
0
    }
1689
1690
0
    g_string_append_len (str,
1691
0
                         g_bytes_get_data (bytes, NULL),
1692
0
                         g_bytes_get_size (bytes));
1693
0
    g_bytes_unref (bytes);
1694
0
  }
1695
0
#undef BUF_SIZE
1696
1697
0
  g_object_unref (serializer);
1698
1699
0
  return g_string_free (str, FALSE);
1700
0
}
1701
1702
static GVariant *
1703
tracker_serialize_single_value (TrackerResource         *resource,
1704
                                const GValue            *value)
1705
0
{
1706
0
  if (G_VALUE_HOLDS_BOOLEAN (value)) {
1707
0
    return g_variant_new_boolean (g_value_get_boolean (value));
1708
0
  } else if (G_VALUE_HOLDS_INT (value)) {
1709
0
    return g_variant_new_int32 (g_value_get_int (value));
1710
0
  } else if (G_VALUE_HOLDS_INT64 (value)) {
1711
0
    return g_variant_new_int64 (g_value_get_int64 (value));
1712
0
  } else if (G_VALUE_HOLDS_DOUBLE (value)) {
1713
0
    return g_variant_new_double (g_value_get_double (value));
1714
0
  } else if (G_VALUE_HOLDS (value, TRACKER_TYPE_URI)) {
1715
    /* Use bytestring for URIs, so they can be distinguised
1716
     * from plain strings
1717
     */
1718
0
    return g_variant_new_bytestring (g_value_get_string (value));
1719
0
  } else if (G_VALUE_HOLDS_STRING (value)) {
1720
0
    return g_variant_new_string (g_value_get_string (value));
1721
0
  } else if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) {
1722
0
    return tracker_resource_serialize (g_value_get_object (value));
1723
0
  }
1724
1725
0
  g_warn_if_reached ();
1726
1727
0
  return NULL;
1728
0
}
1729
1730
/**
1731
 * tracker_resource_serialize:
1732
 * @resource: A `TrackerResource`
1733
 *
1734
 * Serializes a `TrackerResource` to a [type@GLib.Variant] in a lossless way.
1735
 * All child resources are subsequently serialized. It is implied
1736
 * that both ends use a common [class@NamespaceManager].
1737
 *
1738
 * Returns: (transfer floating) (nullable): A variant describing the resource,
1739
 *          the reference is floating.
1740
 **/
1741
GVariant *
1742
tracker_resource_serialize (TrackerResource *resource)
1743
0
{
1744
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1745
0
  GVariantBuilder builder;
1746
0
  GHashTableIter iter;
1747
0
  GList *properties, *l;
1748
0
  const gchar *pred;
1749
0
  GValue *value;
1750
1751
0
  g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), NULL);
1752
1753
0
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
1754
1755
0
  if (!tracker_resource_is_blank_node (resource)) {
1756
0
    g_variant_builder_add (&builder, "{sv}", "@id",
1757
0
                           g_variant_new_string (priv->identifier));
1758
0
  }
1759
1760
0
  g_hash_table_iter_init (&iter, priv->properties);
1761
1762
  /* Use a stable sort, so that GVariants are byte compatible */
1763
0
  properties = tracker_resource_get_properties (resource);
1764
0
  properties = g_list_sort (properties, (GCompareFunc) g_strcmp0);
1765
1766
0
  for (l = properties; l; l = l->next) {
1767
0
    pred = l->data;
1768
0
    value = g_hash_table_lookup (priv->properties, pred);
1769
1770
0
    if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) {
1771
0
      GPtrArray *array = g_value_get_boxed (value);
1772
0
      GVariantBuilder array_builder;
1773
0
      guint i;
1774
1775
0
      g_variant_builder_init (&array_builder, G_VARIANT_TYPE_ARRAY);
1776
1777
0
      for (i = 0; i < array->len; i++) {
1778
0
        GValue *child = g_ptr_array_index (array, i);
1779
0
        GVariant *variant;
1780
1781
0
        variant = tracker_serialize_single_value (resource, child);
1782
0
        if (!variant)
1783
0
          return NULL;
1784
1785
0
        g_variant_builder_add_value (&array_builder, variant);
1786
0
      }
1787
1788
0
      g_variant_builder_add (&builder, "{sv}", pred,
1789
0
                             g_variant_builder_end (&array_builder));
1790
0
    } else {
1791
0
      GVariant *variant;
1792
1793
0
      variant = tracker_serialize_single_value (resource, value);
1794
0
      if (!variant)
1795
0
        return NULL;
1796
1797
0
      g_variant_builder_add (&builder, "{sv}", pred, variant);
1798
0
    }
1799
0
  }
1800
1801
0
  g_list_free (properties);
1802
1803
0
  return g_variant_builder_end (&builder);
1804
0
}
1805
1806
/**
1807
 * tracker_resource_deserialize:
1808
 * @variant: a [type@GLib.Variant]
1809
 *
1810
 * Deserializes a `TrackerResource` previously serialized with
1811
 * [method@Resource.serialize]. It is implied that both ends
1812
 * use a common [class@NamespaceManager].
1813
 *
1814
 * Returns: (transfer full) (nullable): A TrackerResource, or %NULL if
1815
 *          deserialization fails.
1816
 **/
1817
TrackerResource *
1818
tracker_resource_deserialize (GVariant *variant)
1819
0
{
1820
0
  TrackerResource *resource;
1821
0
  GVariantIter iter;
1822
0
  GVariant *obj;
1823
0
  gchar *pred;
1824
1825
0
  g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT), NULL);
1826
1827
0
  resource = tracker_resource_new (NULL);
1828
1829
0
  g_variant_iter_init (&iter, variant);
1830
1831
0
  while (g_variant_iter_next (&iter, "{sv}", &pred, &obj)) {
1832
    /* Special case, "@id" for the resource identifier */
1833
0
    if (g_strcmp0 (pred, "@id") == 0 &&
1834
0
        g_variant_is_of_type (obj, G_VARIANT_TYPE_STRING)) {
1835
0
      tracker_resource_set_identifier (resource, g_variant_get_string (obj, NULL));
1836
0
      continue;
1837
0
    }
1838
1839
0
    if (g_variant_is_of_type (obj, G_VARIANT_TYPE_STRING)) {
1840
0
      tracker_resource_set_string (resource, pred,
1841
0
                                   g_variant_get_string (obj, NULL));
1842
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_BOOLEAN)) {
1843
0
      tracker_resource_set_boolean (resource, pred,
1844
0
                                    g_variant_get_boolean (obj));
1845
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_INT16)) {
1846
0
      tracker_resource_set_int (resource, pred,
1847
0
              (gint) g_variant_get_int16 (obj));
1848
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_INT32)) {
1849
0
      tracker_resource_set_int (resource, pred,
1850
0
              (gint) g_variant_get_int32 (obj));
1851
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_INT64)) {
1852
0
      tracker_resource_set_int64 (resource, pred,
1853
0
                                  (gint64) g_variant_get_int64 (obj));
1854
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_DOUBLE)) {
1855
0
      tracker_resource_set_double (resource, pred,
1856
0
                                   g_variant_get_double (obj));
1857
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_BYTESTRING)) {
1858
0
      tracker_resource_set_uri (resource, pred,
1859
0
                                g_variant_get_bytestring (obj));
1860
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_VARDICT)) {
1861
0
      TrackerResource *child;
1862
1863
0
      child = tracker_resource_deserialize (obj);
1864
0
      if (!child) {
1865
0
        g_object_unref (resource);
1866
0
        return NULL;
1867
0
      }
1868
1869
0
      tracker_resource_set_relation (resource, pred, child);
1870
0
    } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_ARRAY)) {
1871
0
      GVariant *elem;
1872
0
      GVariantIter iter2;
1873
1874
0
      g_variant_iter_init (&iter2, obj);
1875
1876
      /* Other arrays are multi-valued */
1877
0
      while ((elem = g_variant_iter_next_value (&iter2)) != NULL) {
1878
0
        if (g_variant_is_of_type (elem, G_VARIANT_TYPE_STRING)) {
1879
0
          tracker_resource_add_string (resource, pred,
1880
0
                                       g_variant_get_string (elem, NULL));
1881
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_BOOLEAN)) {
1882
0
          tracker_resource_add_boolean (resource, pred,
1883
0
                                        g_variant_get_boolean (elem));
1884
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_INT16)) {
1885
0
          tracker_resource_add_int (resource, pred,
1886
0
                  (gint) g_variant_get_int16 (elem));
1887
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_INT32)) {
1888
0
          tracker_resource_add_int (resource, pred,
1889
0
                  (gint) g_variant_get_int32 (elem));
1890
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_INT64)) {
1891
0
          tracker_resource_add_int64 (resource, pred,
1892
0
                                      (gint64) g_variant_get_int64 (elem));
1893
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_DOUBLE)) {
1894
0
          tracker_resource_add_double (resource, pred,
1895
0
                                       g_variant_get_double (elem));
1896
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_BYTESTRING)) {
1897
0
          tracker_resource_add_uri (resource, pred,
1898
0
                                    g_variant_get_bytestring (elem));
1899
0
        } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_VARDICT)) {
1900
0
          TrackerResource *child;
1901
1902
0
          child = tracker_resource_deserialize (elem);
1903
0
          if (!child) {
1904
0
            g_object_unref (resource);
1905
0
            return NULL;
1906
0
          }
1907
1908
0
          tracker_resource_add_relation (resource, pred, child);
1909
0
        } else {
1910
0
          g_warning ("Unhandled GVariant signature '%s'",
1911
0
                     g_variant_get_type_string (elem));
1912
0
          g_object_unref (resource);
1913
0
          return NULL;
1914
0
        }
1915
0
      }
1916
0
    } else {
1917
0
      g_warning ("Unhandled GVariant signature '%s'",
1918
0
                 g_variant_get_type_string (obj));
1919
0
      g_object_unref (resource);
1920
0
      return NULL;
1921
0
    }
1922
0
  }
1923
1924
0
  return resource;
1925
0
}
1926
1927
/**
1928
 * tracker_resource_get_property_overwrite:
1929
 * @resource: a `TrackerResource`
1930
 * @property_uri: a string identifying the property to query
1931
 *
1932
 * Returns whether the prior values for this property would be deleted
1933
 * in the SPARQL issued by @resource.
1934
 *
1935
 * Returns: #TRUE if the property would be overwritten
1936
 *
1937
 * Since: 3.1
1938
 **/
1939
gboolean
1940
tracker_resource_get_property_overwrite (TrackerResource *resource,
1941
                                         const gchar     *property_uri)
1942
0
{
1943
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1944
1945
0
  return g_hash_table_contains (priv->overwrite, property_uri);
1946
0
}
1947
1948
void
1949
tracker_resource_iterator_init (TrackerResourceIterator *iter,
1950
                                TrackerResource         *resource)
1951
0
{
1952
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1953
1954
0
  bzero (iter, sizeof (TrackerResourceIterator));
1955
0
  g_hash_table_iter_init (&iter->prop_iter, priv->properties);
1956
0
}
1957
1958
gboolean
1959
tracker_resource_iterator_next (TrackerResourceIterator  *iter,
1960
                                const gchar             **property,
1961
                                const GValue            **value)
1962
0
{
1963
0
  gpointer key, val;
1964
1965
0
  if (iter->cur_values && iter->cur_prop) {
1966
0
    iter->idx++;
1967
1968
0
    if (iter->idx < iter->cur_values->len) {
1969
0
      *property = iter->cur_prop;
1970
0
      *value = g_ptr_array_index (iter->cur_values, iter->idx);
1971
0
      return TRUE;
1972
0
    } else {
1973
0
      iter->cur_values = NULL;
1974
0
      iter->cur_prop = NULL;
1975
0
    }
1976
0
  }
1977
1978
0
  if (!g_hash_table_iter_next (&iter->prop_iter, &key, &val))
1979
0
    return FALSE;
1980
1981
0
  if (G_VALUE_HOLDS (val, G_TYPE_PTR_ARRAY)) {
1982
0
    iter->cur_prop = key;
1983
0
    iter->cur_values = g_value_get_boxed (val);
1984
0
    iter->idx = 0;
1985
0
    *property = iter->cur_prop;
1986
0
    *value = g_ptr_array_index (iter->cur_values, iter->idx);
1987
0
    return TRUE;
1988
0
  }
1989
1990
0
  *property = key;
1991
0
  *value = val;
1992
0
  return TRUE;
1993
0
}
1994
1995
const gchar *
1996
tracker_resource_get_identifier_internal (TrackerResource *resource)
1997
0
{
1998
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
1999
2000
0
  return priv->identifier;
2001
0
}
2002
2003
gboolean
2004
tracker_resource_is_blank_node (TrackerResource *resource)
2005
0
{
2006
0
  TrackerResourcePrivate *priv = GET_PRIVATE (resource);
2007
2008
0
  if (!priv->identifier)
2009
0
    return TRUE;
2010
2011
0
  return strncmp (priv->identifier, "_:", 2) == 0;
2012
0
}