/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 | } |