/* * Copyright (C) 2016-2017, Sam Thursfield * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include #include #include #include #include #include /* For tracker_sparql_escape_string */ #include "tracker-utils.h" #include typedef struct { char *identifier; GHashTable *properties; GHashTable *overwrite; } TrackerResourcePrivate; G_DEFINE_TYPE_WITH_PRIVATE (TrackerResource, tracker_resource, G_TYPE_OBJECT); #define GET_PRIVATE(object) (tracker_resource_get_instance_private (object)) /** * SECTION: tracker-resource * @short_description: Represents a single Tracker resource * @title: TrackerResource * @stability: Stable * @include: tracker-resource.h * * * #TrackerResource keeps track of a set of properties for a given resource. * The resulting data can be serialized in several ways. * */ static char * generate_blank_node_identifier (void) { static gint64 counter = 0; return g_strdup_printf("_:%" G_GINT64_FORMAT, counter++); } /** * TrackerResource: * * The TrackerResource object represents information * about a given resource. */ enum { PROP_0, PROP_IDENTIFIER, }; static void dispose (GObject *object); static void constructed (GObject *object); static void finalize (GObject *object); static void get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void tracker_resource_class_init (TrackerResourceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = dispose; object_class->constructed = constructed; object_class->finalize = finalize; object_class->get_property = get_property; object_class->set_property = set_property; /** * TrackerResource:identifier * * The URI identifier for this class, or %NULL for a * blank node. */ g_object_class_install_property (object_class, PROP_IDENTIFIER, g_param_spec_string ("identifier", "Identifier", "Identifier", NULL, G_PARAM_READWRITE)); } /* Destroy-notify function for the values stored in the hash table. */ static void free_value (GValue *value) { g_value_unset (value); g_slice_free (GValue, value); } static void tracker_resource_init (TrackerResource *resource) { TrackerResourcePrivate *priv = GET_PRIVATE(resource); /* Values of properties */ priv->properties = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) free_value); /* TRUE for any property where we should delete any existing values. */ priv->overwrite = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL); } static void dispose (GObject *object) { TrackerResourcePrivate *priv; priv = GET_PRIVATE (TRACKER_RESOURCE (object)); g_clear_pointer (&priv->overwrite, g_hash_table_unref); g_clear_pointer (&priv->properties, g_hash_table_unref); G_OBJECT_CLASS (tracker_resource_parent_class)->dispose (object); } static void constructed (GObject *object) { TrackerResourcePrivate *priv; priv = GET_PRIVATE (TRACKER_RESOURCE (object)); if (! priv->identifier) { priv->identifier = generate_blank_node_identifier (); } G_OBJECT_CLASS (tracker_resource_parent_class)->constructed (object); } static void finalize (GObject *object) { TrackerResourcePrivate *priv; priv = GET_PRIVATE (TRACKER_RESOURCE (object)); if (priv->identifier) { g_free (priv->identifier); } (G_OBJECT_CLASS (tracker_resource_parent_class)->finalize)(object); } static void get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { TrackerResourcePrivate *priv; priv = GET_PRIVATE (TRACKER_RESOURCE (object)); switch (param_id) { case PROP_IDENTIFIER: g_value_set_string (value, priv->identifier); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { switch (param_id) { case PROP_IDENTIFIER: tracker_resource_set_identifier (TRACKER_RESOURCE (object), g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } /** * tracker_resource_new: * @identifier: A string containing a URI * * Creates a TrackerResource instance. * * Returns: a newly created #TrackerResource. Free with g_object_unref() when done */ TrackerResource * tracker_resource_new (const char *identifier) { TrackerResource *resource; resource = g_object_new (TRACKER_TYPE_RESOURCE, "identifier", identifier, NULL); return resource; } /* Difference between 'set' and 'add': when generating a SPARQL update, the * setter will generate a corresponding DELETE, the adder will not. The setter * will also overwrite existing values in the Resource object, while the adder * will make a list. */ /** * tracker_resource_set_gvalue: * @self: the #TrackerResource * @property_uri: a string identifying the property to set * @value: an initialised #GValue * * State that the only value for the given property is 'value'. Any existing * values for 'property' will be removed. * * When serialising to SPARQL, any properties that were set with this function * will get a corresponding DELETE statement to remove any existing values in * the database. * * You can pass any kind of GValue for @value, but serialization functions will * normally only be able to serialize URIs/relationships and fundamental value * types (string, int, etc.). */ void tracker_resource_set_gvalue (TrackerResource *self, const char *property_uri, const GValue *value) { TrackerResourcePrivate *priv; GValue *our_value; g_return_if_fail (TRACKER_IS_RESOURCE (self)); g_return_if_fail (property_uri != NULL); g_return_if_fail (G_IS_VALUE (value)); priv = GET_PRIVATE (self); our_value = g_slice_new0 (GValue); g_value_init (our_value, G_VALUE_TYPE (value)); g_value_copy (value, our_value); g_hash_table_insert (priv->properties, g_strdup (property_uri), our_value); g_hash_table_insert (priv->overwrite, g_strdup (property_uri), GINT_TO_POINTER (TRUE)); }; static gboolean validate_boolean (gboolean value, const char *func_name) { return TRUE; } static gboolean validate_double (double value, const char *func_name) { return TRUE; } static gboolean validate_int (int value, const char *func_name) { return TRUE; } static gboolean validate_int64 (gint64 value, const char *func_name) { return TRUE; } static gboolean validate_pointer (const void *pointer, const char *func_name) { if (pointer == NULL) { g_warning ("%s: NULL is not a valid value.", func_name); return FALSE; } return TRUE; } #define SET_PROPERTY_FOR_GTYPE(name, ctype, gtype, set_function, validate_function) \ void name (TrackerResource *self, \ const char *property_uri, \ ctype value) \ { \ TrackerResourcePrivate *priv; \ GValue *our_value; \ \ g_return_if_fail (TRACKER_IS_RESOURCE (self)); \ g_return_if_fail (property_uri != NULL); \ \ priv = GET_PRIVATE (self); \ \ if (!validate_function (value, __func__)) { \ return; \ } \ \ our_value = g_slice_new0 (GValue); \ g_value_init (our_value, gtype); \ set_function (our_value, value); \ \ g_hash_table_insert (priv->properties, \ g_strdup (property_uri), \ our_value); \ \ g_hash_table_insert (priv->overwrite, \ g_strdup (property_uri), \ GINT_TO_POINTER (TRUE)); \ }; /** * tracker_resource_set_boolean: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Sets a single-valued boolean object. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_boolean, gboolean, G_TYPE_BOOLEAN, g_value_set_boolean, validate_boolean); /** * tracker_resource_set_double: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Sets a single-valued double object. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_double, double, G_TYPE_DOUBLE, g_value_set_double, validate_double); /** * tracker_resource_set_int: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Sets a single-valued integer object. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_int, int, G_TYPE_INT, g_value_set_int, validate_int); /** * tracker_resource_set_int64: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Sets a single-valued integer object. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_int64, gint64, G_TYPE_INT64, g_value_set_int64, validate_int64); /** * tracker_resource_set_relation: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @resource: the property object * * Sets a single-valued resource object as a #TrackerResource. This * function produces similar RDF to tracker_resource_set_uri(), * although in this function the URI will depend on the identifier * set on @resource. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_set_object, validate_pointer); /** * tracker_resource_set_take_relation: (skip) * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @resource: the property object * * Sets a single-valued resource object as a #TrackerResource. This * function produces similar RDF to tracker_resource_set_uri(), * although in this function the URI will depend on the identifier * set on @resource. This function takes ownership of @resource. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_take_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_take_object, validate_pointer); /** * tracker_resource_set_string: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Sets a single-valued string object. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_string, const char *, G_TYPE_STRING, g_value_set_string, validate_pointer); /** * tracker_resource_set_uri: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Sets a single-valued resource object as a string URI. This function * produces similar RDF to tracker_resource_set_relation(), although * it requires that the URI is previously known. */ SET_PROPERTY_FOR_GTYPE (tracker_resource_set_uri, const char *, TRACKER_TYPE_URI, g_value_set_string, validate_pointer); /** * tracker_resource_add_gvalue: * @self: the #TrackerResource * @property_uri: a string identifying the property to set * @value: an initialised #GValue * * Add 'value' to the list of values for given property. * * You can pass any kind of GValue for @value, but serialization functions will * normally only be able to serialize URIs/relationships and fundamental value * types (string, int, etc.). */ void tracker_resource_add_gvalue (TrackerResource *self, const char *property_uri, const GValue *value) { TrackerResourcePrivate *priv; GValue *existing_value, *array_holder, *our_value; GPtrArray *array; g_return_if_fail (TRACKER_IS_RESOURCE (self)); g_return_if_fail (property_uri != NULL); g_return_if_fail (G_IS_VALUE (value)); priv = GET_PRIVATE (self); existing_value = g_hash_table_lookup (priv->properties, property_uri); if (existing_value && G_VALUE_HOLDS (existing_value, G_TYPE_PTR_ARRAY)) { array = g_value_get_boxed (existing_value); array_holder = existing_value; } else { array = g_ptr_array_new_with_free_func ((GDestroyNotify)free_value); array_holder = g_slice_new0 (GValue); g_value_init (array_holder, G_TYPE_PTR_ARRAY); g_value_take_boxed (array_holder, array); if (existing_value) { /* The existing_value is owned by the hash table and will be freed * when we overwrite it with array_holder, so we need to allocate a * new value and give it to the ptrarray. */ our_value = g_slice_new0 (GValue); g_value_init (our_value, G_VALUE_TYPE (existing_value)); g_value_copy (existing_value, our_value); g_ptr_array_add (array, our_value); } } our_value = g_slice_new0 (GValue); g_value_init (our_value, G_VALUE_TYPE (value)); g_value_copy (value, our_value); g_ptr_array_add (array, our_value); if (array_holder != existing_value) { g_hash_table_insert (priv->properties, g_strdup (property_uri), array_holder); } }; #define ADD_PROPERTY_FOR_GTYPE(name, ctype, gtype, set_function, validate_function) \ void name (TrackerResource *self, \ const char *property_uri, \ ctype value) \ { \ TrackerResourcePrivate *priv; \ GValue *existing_value, *array_holder, *our_value; \ GPtrArray *array; \ \ g_return_if_fail (TRACKER_IS_RESOURCE (self)); \ g_return_if_fail (property_uri != NULL); \ \ priv = GET_PRIVATE (self); \ \ if (!validate_function (value, __func__)) { \ return; \ } \ \ existing_value = g_hash_table_lookup (priv->properties, \ property_uri); \ \ if (existing_value && G_VALUE_HOLDS (existing_value, \ G_TYPE_PTR_ARRAY)) { \ array = g_value_get_boxed (existing_value); \ array_holder = existing_value; \ } else { \ array = g_ptr_array_new_with_free_func ( \ (GDestroyNotify)free_value); \ array_holder = g_slice_new0 (GValue); \ g_value_init (array_holder, G_TYPE_PTR_ARRAY); \ g_value_take_boxed (array_holder, array); \ \ if (existing_value) { \ /* existing_value is owned by hash table */ \ our_value = g_slice_new0 (GValue); \ g_value_init (our_value, G_VALUE_TYPE(existing_value)); \ g_value_copy (existing_value, our_value); \ g_ptr_array_add (array, our_value); \ } \ } \ \ our_value = g_slice_new0 (GValue); \ g_value_init (our_value, gtype); \ set_function (our_value, value); \ \ g_ptr_array_add (array, our_value); \ \ if (array_holder != existing_value) { \ g_hash_table_insert (priv->properties, \ g_strdup (property_uri), array_holder); \ } \ }; /** * tracker_resource_add_boolean: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Adds a boolean object to a multi-valued property. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_boolean, gboolean, G_TYPE_BOOLEAN, g_value_set_boolean, validate_boolean); /** * tracker_resource_add_double: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Adds a double object to a multi-valued property. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_double, double, G_TYPE_DOUBLE, g_value_set_double, validate_double); /** * tracker_resource_add_int: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Adds an integer object to a multi-valued property. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_int, int, G_TYPE_INT, g_value_set_int, validate_int); /** * tracker_resource_add_int64: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Adds an integer object to a multi-valued property. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_int64, gint64, G_TYPE_INT64, g_value_set_int64, validate_int64); /** * tracker_resource_add_relation: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @resource: the property object * * Adds a resource object to a multi-valued property. This * function produces similar RDF to tracker_resource_add_uri(), * although in this function the URI will depend on the identifier * set on @resource. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_set_object, validate_pointer); /** * tracker_resource_add_take_relation: (skip) * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @resource: the property object * * Adds a resource object to a multi-valued property. This * function produces similar RDF to tracker_resource_add_uri(), * although in this function the URI will depend on the identifier * set on @resource. This function takes ownership of @resource. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_take_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_take_object, validate_pointer); /** * tracker_resource_add_string: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Adds a string object to a multi-valued property. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_string, const char *, G_TYPE_STRING, g_value_set_string, validate_pointer); /** * tracker_resource_add_uri: * @self: the #TrackerResource * @property_uri: a string identifying the property to modify * @value: the property object * * Adds a resource object to a multi-valued property. This function * produces similar RDF to tracker_resource_add_relation(), although * it requires that the URI is previously known. */ ADD_PROPERTY_FOR_GTYPE (tracker_resource_add_uri, const char *, TRACKER_TYPE_URI, g_value_set_string, validate_pointer); /** * tracker_resource_get_values: * @self: the #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the list of all known values of the given property. * * Returns: (transfer container) (element-type GValue): a #GList of #GValue * instances. The list should be freed with g_list_free() */ GList *tracker_resource_get_values (TrackerResource *self, const char *property_uri) { TrackerResourcePrivate *priv; GValue *value; g_return_val_if_fail (TRACKER_IS_RESOURCE (self), NULL); g_return_val_if_fail (property_uri, NULL); priv = GET_PRIVATE (self); value = g_hash_table_lookup (priv->properties, property_uri); if (value == NULL) { return NULL; } if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { GList *result = NULL; GPtrArray *array; int i; array = g_value_get_boxed (value); for (i = 0; i < array->len; i++) { value = g_ptr_array_index (array, i); result = g_list_prepend (result, value); }; return g_list_reverse (result); } else { return g_list_append (NULL, value); } } #define GET_PROPERTY_FOR_GTYPE(name, ctype, gtype, get_function, no_value) \ ctype name (TrackerResource *self, \ const char *property_uri) \ { \ TrackerResourcePrivate *priv; \ GValue *value; \ \ g_return_val_if_fail (TRACKER_IS_RESOURCE (self), no_value); \ g_return_val_if_fail (property_uri, no_value); \ \ priv = GET_PRIVATE (self); \ \ value = g_hash_table_lookup (priv->properties, property_uri); \ \ if (value == NULL) { \ return no_value; \ }; \ \ if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { \ GPtrArray *array; \ array = g_value_get_boxed (value); \ if (array->len == 0) { \ return no_value; \ } else { \ value = g_ptr_array_index (array, 0); \ } \ } \ \ return get_function (value); \ }; /** * tracker_resource_get_first_boolean: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first boolean object previously assigned to a property. * * Returns: the first boolean object */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_boolean, gboolean, G_TYPE_BOOLEAN, g_value_get_boolean, FALSE); /** * tracker_resource_get_first_double: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first double object previously assigned to a property. * * Returns: the first double object */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_double, double, G_TYPE_DOUBLE, g_value_get_double, 0.0); /** * tracker_resource_get_first_int: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first integer object previously assigned to a property. * * Returns: the first integer object */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_int, int, G_TYPE_INT, g_value_get_int, 0); /** * tracker_resource_get_first_int64: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first integer object previously assigned to a property. * * Returns: the first integer object */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_int64, gint64, G_TYPE_INT64, g_value_get_int64, 0); /** * tracker_resource_get_first_relation: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first resource object previously assigned to a property. * * Returns: (transfer none): the first resource object */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_relation, TrackerResource *, TRACKER_TYPE_RESOURCE, g_value_get_object, NULL); /** * tracker_resource_get_first_string: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first string object previously assigned to a property. * * Returns: the first string object */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_string, const char *, G_TYPE_STRING, g_value_get_string, NULL); /** * tracker_resource_get_first_uri: * @self: A #TrackerResource * @property_uri: a string identifying the property to look up * * Returns the first resource object previously assigned to a property. * * Returns: the first resource object as an URI. */ GET_PROPERTY_FOR_GTYPE (tracker_resource_get_first_uri, const char *, TRACKER_TYPE_URI, g_value_get_string, NULL); /** * tracker_resource_get_identifier: * @self: A #TrackerResource * * Returns the identifier of a resource. * * If the identifier was set to NULL, the identifier returned will be a unique * SPARQL blank node identifier, such as "_:123". * * Returns: a string owned by the resource */ const char * tracker_resource_get_identifier (TrackerResource *self) { TrackerResourcePrivate *priv; g_return_val_if_fail (TRACKER_IS_RESOURCE (self), NULL); priv = GET_PRIVATE (self); return priv->identifier; } /** * tracker_resource_set_identifier: * @self: a #TrackerResource * @identifier: (allow-none): a string identifying the resource * * Changes the identifier of a #TrackerResource. The identifier should be a * URI or compact URI, but this is not necessarily enforced. Invalid * identifiers may cause errors when serializing the resource or trying to * insert the results in a database. * * If the identifier is set to NULL, a SPARQL blank node identifier such as * "_:123" is assigned to the resource. */ void tracker_resource_set_identifier (TrackerResource *self, const char *identifier) { TrackerResourcePrivate *priv; g_return_if_fail (TRACKER_IS_RESOURCE (self)); priv = GET_PRIVATE (self); g_free (priv->identifier); if (identifier == NULL) { /* We take NULL to mean "this is a blank node", and generate a * unique blank node identifier right away. This is easier than * leaving it NULL and trying to generate a blank node ID at * serialization time, and it means that the serialization * output is stable when called multiple times. */ priv->identifier = generate_blank_node_identifier (); } else { priv->identifier = g_strdup (identifier); } } /** * tracker_resource_identifier_compare_func: * @resource: a #TrackerResource * @identifier: a string identifying the resource * * A helper function that compares a #TrackerResource by its identifier * string. * * Returns: an integer less than, equal to, or greater than zero, if the * resource identifier is <, == or > than @identifier **/ gint tracker_resource_identifier_compare_func (TrackerResource *resource, const char *identifier) { TrackerResourcePrivate *priv; g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), 0); g_return_val_if_fail (identifier != NULL, 0); priv = GET_PRIVATE (resource); return strcmp (priv->identifier, identifier); } /** * tracker_resource_compare: * @self: A #TrackerResource * * Compare the identifiers of two TrackerResource instances. The resources * are considered identical if they have the same identifier. * * Note that there can be false negatives with this simplistic approach: two * resources may have different identifiers that actually refer to the same * thing. * * Returns: 0 if the identifiers are the same, -1 or +1 otherwise */ gint tracker_resource_compare (TrackerResource *a, TrackerResource *b) { TrackerResourcePrivate *a_priv, *b_priv; g_return_val_if_fail (TRACKER_IS_RESOURCE (a), 0); g_return_val_if_fail (TRACKER_IS_RESOURCE (b), 0); a_priv = GET_PRIVATE (a); b_priv = GET_PRIVATE (b); return strcmp (a_priv->identifier, b_priv->identifier); }; /** * tracker_resource_get_properties: * @resource: a #TrackerResource * * Gets the list of properties defined in @resource * * Returns: (transfer container) (element-type utf8): The list of properties. * The list should be freed with g_list_free(). * * Since: 3.0 **/ GList * tracker_resource_get_properties (TrackerResource *resource) { TrackerResourcePrivate *priv; g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), NULL); priv = GET_PRIVATE (resource); return g_hash_table_get_keys (priv->properties); } /* Helper function for serialization code. This allows you to selectively * populate 'interned_namespaces' from 'all_namespaces' based on when a * particular prefix is actually used. This is quite inefficient compared * to just dumping all known namespaces, but it makes the serializated * output more readable. */ static void maybe_intern_prefix_of_compact_uri (TrackerNamespaceManager *all_namespaces, TrackerNamespaceManager *interned_namespaces, const char *uri) { /* The TrackerResource API doesn't distinguish between compact URIs and full * URIs. This is fine as long as users don't add prefixes that can be * confused with URIs. Both URIs and CURIEs can have anything following * the ':', so without hardcoding knowledge of well-known protocols here, * we can't really tell if the user has done something dumb like defining a * "urn" prefix. */ char *prefix = g_uri_parse_scheme (uri); if (prefix == NULL) { g_warning ("Invalid URI or compact URI: %s", uri); return; } if (tracker_namespace_manager_has_prefix (all_namespaces, prefix)) { if (!tracker_namespace_manager_has_prefix (interned_namespaces, prefix)) { const char *namespace = tracker_namespace_manager_lookup_prefix (all_namespaces, prefix); tracker_namespace_manager_add_prefix (interned_namespaces, prefix, namespace); } } g_free (prefix); } typedef struct { TrackerNamespaceManager *all_namespaces, *our_namespaces; GString *string; GList *done_list; } GenerateTurtleData; static void generate_turtle (TrackerResource *resource, GenerateTurtleData *data); static gboolean is_blank_node (const char *uri_or_curie_or_blank) { return (strncmp(uri_or_curie_or_blank, "_:", 2) == 0); } static gboolean is_builtin_class (const gchar *uri_or_curie, TrackerNamespaceManager *namespaces) { gchar *prefix = NULL; gboolean has_prefix; // blank nodes should be processed as nested resource // g_uri_parse_scheme returns NULL for blank nodes, i.e. _:1 if (is_blank_node (uri_or_curie)) return FALSE; prefix = g_uri_parse_scheme (uri_or_curie); if (!prefix) return TRUE; has_prefix = tracker_namespace_manager_has_prefix (namespaces, prefix); g_free (prefix); return has_prefix; } static void generate_nested_turtle_resource (TrackerResource *resource, GenerateTurtleData *data) { /* We don't need to produce turtle for builtin classes */ if (is_builtin_class (tracker_resource_get_identifier (resource), data->all_namespaces)) return; if (g_list_find (data->done_list, resource) == NULL) { data->done_list = g_list_prepend (data->done_list, resource); generate_turtle (resource, data); g_string_append (data->string, "\n"); } } static void generate_turtle_resources_foreach (gpointer key, gpointer value_ptr, gpointer user_data) { const GValue *value = value_ptr; GenerateTurtleData *data = user_data; TrackerResource *resource; int i; if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) { resource = g_value_get_object (value); generate_nested_turtle_resource (resource, data); } else if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { GPtrArray *array = g_value_get_boxed (value); const GValue *array_value; for (i = 0; i < array->len; i++) { array_value = g_ptr_array_index (array, i); if (G_VALUE_HOLDS (array_value, TRACKER_TYPE_RESOURCE)) { resource = g_value_get_object (array_value); generate_nested_turtle_resource (resource, data); } } } } static void generate_turtle_uri_value (const char *uri_or_curie_or_blank, GString *string, TrackerNamespaceManager *all_namespaces, TrackerNamespaceManager *our_namespaces) { /* The tracker_resource_set_uri() function accepts URIs * (such as http://example.com/) and compact URIs (such as nie:DataObject), * and blank node identifiers (_:0). The tracker_resource_set_identifier() * function works the same. * * We could expand all CURIEs, but the generated Turtle or SPARQL will be * clearer if we leave them be. We still need to attempt to expand them * internally in order to know whether they need <> brackets around them. */ if (is_blank_node (uri_or_curie_or_blank)) { g_string_append (string, uri_or_curie_or_blank); } else { char *prefix = g_uri_parse_scheme (uri_or_curie_or_blank); if (prefix && tracker_namespace_manager_has_prefix (all_namespaces, prefix)) { /* It's a compact URI and we know the prefix */ if (our_namespaces != NULL) { maybe_intern_prefix_of_compact_uri (all_namespaces, our_namespaces, uri_or_curie_or_blank); }; g_string_append (string, uri_or_curie_or_blank); } else { /* It's a full URI (or something invalid, but we can't really tell that here) */ g_string_append_printf (string, "<%s>", uri_or_curie_or_blank); } g_free (prefix); } } static void generate_turtle_value (const GValue *value, GString *string, TrackerNamespaceManager *all_namespaces, TrackerNamespaceManager *our_namespaces) { GType type = G_VALUE_TYPE (value); if (type == TRACKER_TYPE_URI) { generate_turtle_uri_value (g_value_get_string (value), string, all_namespaces, our_namespaces); } else if (type == TRACKER_TYPE_RESOURCE) { TrackerResource *relation = TRACKER_RESOURCE (g_value_get_object (value)); generate_turtle_uri_value (tracker_resource_get_identifier (relation), string, all_namespaces, our_namespaces); } else if (type == G_TYPE_STRING) { char *escaped = tracker_sparql_escape_string (g_value_get_string (value)); g_string_append_printf(string, "\"%s\"", escaped); g_free (escaped); } else if (type == G_TYPE_DATE) { char date_string[256]; g_date_strftime (date_string, 256, "\"%Y-%m-%d%z\"^^", g_value_get_boxed (value)); g_string_append (string, date_string); } else if (type == G_TYPE_DATE_TIME) { char *datetime_string; datetime_string = g_date_time_format (g_value_get_boxed (value), "\"%Y-%m-%dT%H:%M:%S%z\"^^"); g_string_append (string, datetime_string); g_free (datetime_string); } else if (type == G_TYPE_DOUBLE || type == G_TYPE_FLOAT) { /* We can't use GValue transformations here; they're locale-dependent. */ char buffer[256]; g_ascii_dtostr (buffer, 255, g_value_get_double (value)); g_string_append (string, buffer); } else { GValue str_value = G_VALUE_INIT; g_value_init (&str_value, G_TYPE_STRING); if (g_value_transform (value, &str_value)) { g_string_append (string, g_value_get_string (&str_value)); } else { g_warning ("Cannot serialize value of type %s to Turtle/SPARQL", G_VALUE_TYPE_NAME (value)); } g_value_unset (&str_value); } } void generate_turtle_property (const char *property, const GValue *value, GString *string, TrackerNamespaceManager *all_namespaces, TrackerNamespaceManager *our_namespaces) { if (strcmp (property, TRACKER_PREFIX_RDF "type") == 0 || strcmp (property, "rdf:type") == 0) { g_string_append (string, "a"); } else { g_string_append (string, property); } g_string_append (string, " "); if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { int i; GPtrArray *array = g_value_get_boxed (value); if (array->len > 0) { generate_turtle_value (g_ptr_array_index (array, 0), string, all_namespaces, our_namespaces); for (i = 1; i < array->len; i++) { g_string_append (string, " , "); generate_turtle_value (g_ptr_array_index (array, i), string, all_namespaces, our_namespaces); } } } else { generate_turtle_value (value, string, all_namespaces, our_namespaces); } } static void generate_turtle (TrackerResource *resource, GenerateTurtleData *data) { TrackerResourcePrivate *priv = GET_PRIVATE (resource); GHashTableIter iter; const char *property; const GValue *value; /* First we recurse to any relations that aren't already in the done list */ g_hash_table_foreach (priv->properties, generate_turtle_resources_foreach, data); generate_turtle_uri_value (tracker_resource_get_identifier(resource), data->string, data->all_namespaces, data->our_namespaces); g_string_append (data->string, " "); g_hash_table_iter_init (&iter, priv->properties); if (g_hash_table_iter_next (&iter, (gpointer *)&property, (gpointer *)&value)) { while (TRUE) { generate_turtle_property (property, value, data->string, data->all_namespaces, data->our_namespaces); maybe_intern_prefix_of_compact_uri (data->all_namespaces, data->our_namespaces, property); if (g_hash_table_iter_next (&iter, (gpointer *)&property, (gpointer *)&value)) { g_string_append (data->string, " ;\n "); } else { g_string_append (data->string, " .\n"); break; } } } } /** * tracker_resource_print_turtle: * @self: a #TrackerResource * @namespaces: (allow-none): a set of prefixed URLs, or %NULL to use the * default set * * Serialize all the information in @resource as a Turtle document. * * The generated Turtle should correspond to this standard: * * * The @namespaces object is used to expand any compact URI values. In most * cases you should pass %NULL, which means the set of namespaces returned by * tracker_namespace_manager_get_default() will be used. This defines the * usual prefixes for all of the ontologies that Tracker ships with by default. * * Returns: a newly-allocated string */ char * tracker_resource_print_turtle (TrackerResource *self, TrackerNamespaceManager *namespaces) { TrackerResourcePrivate *priv; GenerateTurtleData context; char *prefixes; g_return_val_if_fail (TRACKER_IS_RESOURCE (self), ""); priv = GET_PRIVATE (self); if (namespaces == NULL) { namespaces = tracker_namespace_manager_get_default (); } if (g_hash_table_size (priv->properties) == 0) { return g_strdup(""); } context.all_namespaces = namespaces; context.our_namespaces = tracker_namespace_manager_new (); context.string = g_string_new (""); context.done_list = g_list_prepend (NULL, self); maybe_intern_prefix_of_compact_uri (context.all_namespaces, context.our_namespaces, tracker_resource_get_identifier(self)); generate_turtle (self, &context); prefixes = tracker_namespace_manager_print_turtle (context.our_namespaces); g_string_prepend (context.string, "\n"); g_string_prepend (context.string, prefixes); g_object_unref (context.our_namespaces); g_free (prefixes); g_list_free (context.done_list); return g_string_free (context.string, FALSE); } typedef struct { TrackerNamespaceManager *namespaces; GString *string; char *graph_id; GList *done_list; } GenerateSparqlData; static void generate_sparql_deletes (TrackerResource *resource, GenerateSparqlData *data); static void generate_sparql_insert_pattern (TrackerResource *resource, GenerateSparqlData *data); static void generate_sparql_relation_deletes_foreach (gpointer key, gpointer value_ptr, gpointer user_data) { const GValue *value = value_ptr; GenerateSparqlData *data = user_data; int i; if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) { TrackerResource *relation = g_value_get_object (value); generate_sparql_deletes (relation, data); } else if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { GPtrArray *array = g_value_get_boxed (value); for (i = 0; i < array->len; i ++) { GValue *value = g_ptr_array_index (array, i); if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) { TrackerResource *relation = g_value_get_object (value); generate_sparql_deletes (relation, data); } } } } static void generate_sparql_relation_inserts_foreach (gpointer key, gpointer value_ptr, gpointer user_data) { const GValue *value = value_ptr; GenerateSparqlData *data = user_data; if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) { TrackerResource *relation = g_value_get_object (value); /* We don't need to produce inserts for builtin classes */ if (is_builtin_class (tracker_resource_get_identifier (relation), data->namespaces)) return; generate_sparql_insert_pattern (relation, data); } else if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { GPtrArray *array = g_value_get_boxed (value); const GValue *array_value; TrackerResource *relation; gint i; for (i = 0; i < array->len; i++) { array_value = g_ptr_array_index (array, i); if (!G_VALUE_HOLDS (array_value, TRACKER_TYPE_RESOURCE)) continue; relation = g_value_get_object (array_value); /* We don't need to produce inserts for builtin classes */ if (is_builtin_class (tracker_resource_get_identifier (relation), data->namespaces)) continue; generate_sparql_insert_pattern (relation, data); } } } static char * variable_name_for_property (const char *property) { return g_strcanon (g_strdup (property), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", '_'); } static void generate_sparql_delete_queries (TrackerResource *resource, GHashTable *overwrite_flags, GenerateSparqlData *data) { TrackerResourcePrivate *priv = GET_PRIVATE (resource); GHashTableIter iter; const char *property; const GValue *value; g_hash_table_iter_init (&iter, priv->properties); while (g_hash_table_iter_next (&iter, (gpointer *)&property, (gpointer *)&value)) { /* Whether to generate the DELETE is based on whether set_value was ever * called for this property. That's tracked in the overwrite_flags hash table. */ if (g_hash_table_lookup (overwrite_flags, property)) { char *variable_name = variable_name_for_property (property); g_string_append (data->string, "DELETE WHERE {\n"); if (data->graph_id) { g_string_append_printf (data->string, "GRAPH <%s> {\n", data->graph_id); } g_string_append (data->string, " "); generate_turtle_uri_value (priv->identifier, data->string, data->namespaces, NULL); g_string_append_printf (data->string, " %s ?%s }", property, variable_name); g_free (variable_name); if (data->graph_id) { g_string_append (data->string, " }"); } g_string_append (data->string, ";\n"); } } } void generate_sparql_deletes (TrackerResource *resource, GenerateSparqlData *data) { TrackerResourcePrivate *priv = GET_PRIVATE (resource); if (g_list_find (data->done_list, resource) != NULL) /* We already processed this resource. */ return; data->done_list = g_list_prepend (data->done_list, resource); if (! is_blank_node (priv->identifier) && g_hash_table_size (priv->overwrite) > 0) { generate_sparql_delete_queries (resource, priv->overwrite, data); } /* Now emit any sub-resources. */ g_hash_table_foreach (priv->properties, generate_sparql_relation_deletes_foreach, data); } static void generate_sparql_insert_pattern (TrackerResource *resource, GenerateSparqlData *data) { TrackerResourcePrivate *priv = GET_PRIVATE (resource); GHashTableIter iter; const char *property; char *full_property; const GValue *value; gboolean had_property = FALSE; if (g_list_find (data->done_list, resource) != NULL) /* We already processed this resource. */ return; data->done_list = g_list_prepend (data->done_list, resource); /* First, emit any sub-resources. */ g_hash_table_foreach (priv->properties, generate_sparql_relation_inserts_foreach, data); generate_turtle_uri_value (priv->identifier, data->string, data->namespaces, NULL); g_string_append_printf (data->string, " "); /* rdf:type needs to be first, otherwise you'll see 'subject x is not in domain y' * errors for the properties you try to set. */ value = g_hash_table_lookup (priv->properties, "rdf:type"); if (value != NULL) { generate_turtle_property ("a", value, data->string, data->namespaces, NULL); had_property = TRUE; } g_hash_table_iter_init (&iter, priv->properties); while (g_hash_table_iter_next (&iter, (gpointer *)&property, (gpointer *)&value)) { full_property = tracker_namespace_manager_expand_uri (data->namespaces, property); if (strcmp (full_property, TRACKER_PREFIX_RDF "type") != 0 && strcmp (property, "rdf:type") != 0) { if (had_property) { g_string_append (data->string, " ; \n "); } generate_turtle_property (property, value, data->string, data->namespaces, NULL); had_property = TRUE; } g_free (full_property); } g_string_append (data->string, " .\n"); } /** * tracker_resource_print_sparql_update: * @self: a #TrackerResource * @namespaces: (allow-none): a set of prefixed URLs, or %NULL to use the * default set * @graph_id: (allow-none): the URN of the graph the data should be added to, * or %NULL * * Generates a SPARQL command to update a database with the information * stored in @resource. * * The @namespaces object is used to expand any compact URI values. In most * cases you should pass %NULL, which means the set of namespaces returned by * tracker_namespace_manager_get_default() will be used. This defines the * usual prefixes for all of the ontologies that Tracker ships with by default. * * Returns: a newly-allocated string containing a SPARQL update command. */ char * tracker_resource_print_sparql_update (TrackerResource *resource, TrackerNamespaceManager *namespaces, const char *graph_id) { TrackerResourcePrivate *priv; GenerateSparqlData context = { 0, }; g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), ""); priv = GET_PRIVATE(resource); if (namespaces == NULL) { namespaces = tracker_namespace_manager_get_default (); } if (g_hash_table_size (priv->properties) == 0) { return g_strdup(""); } context.namespaces = namespaces; context.string = g_string_new (NULL); if (graph_id) context.graph_id = tracker_namespace_manager_expand_uri (namespaces, graph_id); /* Resources can be recursive, and may have repeated or even cyclic * relationships. This list keeps track of what we already processed. */ context.done_list = NULL; /* Delete the existing data. If we don't do this, we may get constraint * violations due to trying to add a second value to a single-valued * property, and we may get old metadata hanging around. */ generate_sparql_deletes (resource, &context); g_list_free (context.done_list); context.done_list = NULL; /* Finally insert the data */ g_string_append (context.string, "INSERT DATA {\n"); if (context.graph_id) { g_string_append_printf (context.string, "GRAPH <%s> {\n", context.graph_id); } generate_sparql_insert_pattern (resource, &context); if (context.graph_id) { g_string_append (context.string, "}\n"); } g_string_append (context.string, "};\n"); g_list_free (context.done_list); g_free (context.graph_id); context.done_list = NULL; return g_string_free (context.string, FALSE); } typedef struct { TrackerNamespaceManager *all_namespaces, *our_namespaces; JsonBuilder *builder; GList *done_list; } GenerateJsonldData; static void generate_jsonld_foreach (gpointer key, gpointer value_ptr, gpointer user_data); static void tracker_resource_generate_jsonld (TrackerResource *self, GenerateJsonldData *data) { TrackerResourcePrivate *priv = GET_PRIVATE (self); JsonBuilder *builder = data->builder; /* The JSON-LD spec says it is "important that nodes have an identifier", but * doesn't mandate one. I think it's better to omit the ID for blank nodes * (where the caller passed NULL as an identifier) than to emit something * SPARQL-specific like '_:123'. */ if (strncmp (priv->identifier, "_:", 2) != 0) { json_builder_set_member_name (builder, "@id"); json_builder_add_string_value (builder, priv->identifier); } g_hash_table_foreach (priv->properties, generate_jsonld_foreach, data); }; static void generate_jsonld_value (const GValue *value, GenerateJsonldData *data) { JsonNode *node; if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) { TrackerResource *resource; resource = TRACKER_RESOURCE (g_value_get_object (value)); if (g_list_find (data->done_list, resource) == NULL) { data->done_list = g_list_prepend (data->done_list, resource); json_builder_begin_object (data->builder); tracker_resource_generate_jsonld (resource, data); json_builder_end_object (data->builder); } else { json_builder_add_string_value (data->builder, tracker_resource_get_identifier(resource)); } } else if (G_VALUE_HOLDS (value, TRACKER_TYPE_URI)) { /* URIs can be treated the same as strings in JSON-LD provided the @context * sets the type of that property correctly. However, json_node_set_value() * will reject a GValue holding TRACKER_TYPE_URI, so we have to extract the * string manually here. */ const char *uri = g_value_get_string (value); maybe_intern_prefix_of_compact_uri (data->all_namespaces, data->our_namespaces, uri); node = json_node_new (JSON_NODE_VALUE); json_node_set_string (node, uri); json_builder_add_value (data->builder, node); } else if (G_VALUE_HOLDS (value, G_TYPE_UINT)) { node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_value_get_uint(value)); json_builder_add_value (data->builder, node); } else { node = json_node_new (JSON_NODE_VALUE); json_node_set_value (node, value); json_builder_add_value (data->builder, node); } } static void generate_jsonld_foreach (gpointer key, gpointer value_ptr, gpointer user_data) { const char *property = key; const GValue *value = value_ptr; GenerateJsonldData *data = user_data; JsonBuilder *builder = data->builder; if (strcmp (property, "rdf:type") == 0) { property = "@type"; } else { maybe_intern_prefix_of_compact_uri (data->all_namespaces, data->our_namespaces, property); } json_builder_set_member_name (builder, property); if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { json_builder_begin_array (builder); g_ptr_array_foreach (g_value_get_boxed (value), (GFunc) generate_jsonld_value, data); json_builder_end_array (builder); } else { generate_jsonld_value (value, data); } } static void generate_jsonld_namespace_mapping_foreach (gpointer key, gpointer value, gpointer user_data) { GenerateJsonldData *data = user_data; json_builder_set_member_name (data->builder, key); json_builder_add_string_value (data->builder, value); } /** * tracker_resource_print_jsonld: * @self: a #TrackerResource * @namespaces: (allow-none): a set of prefixed URLs, or %NULL to use the * default set * * Serialize all the information in @resource as a JSON-LD document. * * See for more information on the JSON-LD * serialization format. * * The @namespaces object is used to expand any compact URI values. In most * cases you should pass %NULL, which means the set of namespaces returned by * tracker_namespace_manager_get_default() will be used. This defines the * usual prefixes for all of the ontologies that Tracker ships with by default. * * Returns: a newly-allocated string containing JSON-LD data. */ char * tracker_resource_print_jsonld (TrackerResource *self, TrackerNamespaceManager *namespaces) { GenerateJsonldData context; JsonNode *json_root_node; JsonGenerator *generator; char *result; if (namespaces == NULL) { namespaces = tracker_namespace_manager_get_default (); } context.all_namespaces = namespaces; context.our_namespaces = tracker_namespace_manager_new (); context.builder = json_builder_new (); context.done_list = g_list_prepend (NULL, self); maybe_intern_prefix_of_compact_uri (context.all_namespaces, context.our_namespaces, tracker_resource_get_identifier (self)); json_builder_begin_object (context.builder); tracker_resource_generate_jsonld (self, &context); json_builder_set_member_name (context.builder, "@context"); json_builder_begin_object (context.builder); tracker_namespace_manager_foreach (context.our_namespaces, generate_jsonld_namespace_mapping_foreach, &context); json_builder_end_object (context.builder); json_builder_end_object (context.builder); json_root_node = json_builder_get_root (context.builder); generator = json_generator_new (); json_generator_set_root (generator, json_root_node); json_generator_set_pretty (generator, TRUE); result = json_generator_to_data (generator, NULL); g_list_free (context.done_list); json_node_free (json_root_node); g_object_unref (context.builder); g_object_unref (generator); return result; } static GVariant * tracker_serialize_single_value (TrackerResource *resource, const GValue *value) { if (G_VALUE_HOLDS_BOOLEAN (value)) { return g_variant_new_boolean (g_value_get_boolean (value)); } else if (G_VALUE_HOLDS_INT (value)) { return g_variant_new_int32 (g_value_get_int (value)); } else if (G_VALUE_HOLDS_INT64 (value)) { return g_variant_new_int64 (g_value_get_int64 (value)); } else if (G_VALUE_HOLDS_DOUBLE (value)) { return g_variant_new_double (g_value_get_double (value)); } else if (G_VALUE_HOLDS (value, TRACKER_TYPE_URI)) { /* Use bytestring for URIs, so they can be distinguised * from plain strings */ return g_variant_new_bytestring (g_value_get_string (value)); } else if (G_VALUE_HOLDS_STRING (value)) { return g_variant_new_string (g_value_get_string (value)); } else if (G_VALUE_HOLDS (value, TRACKER_TYPE_RESOURCE)) { return tracker_resource_serialize (g_value_get_object (value)); } g_warn_if_reached (); return NULL; } /** * tracker_resource_serialize: * @resource: A #TrackerResource * * Serializes a #TrackerResource to a #GVariant in a lossless way. * All child resources are subsequently serialized. It is implied * that both ends use a common #TrackerNamespaceManager. * * Returns: (transfer full): A variant describing the resource, * the reference is floating. **/ GVariant * tracker_resource_serialize (TrackerResource *resource) { TrackerResourcePrivate *priv = GET_PRIVATE (resource); GVariantBuilder builder; GHashTableIter iter; GList *properties, *l; const gchar *pred; GValue *value; g_return_val_if_fail (TRACKER_IS_RESOURCE (resource), NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); if (priv->identifier && strncmp (priv->identifier, "_:", 2) != 0) { g_variant_builder_add (&builder, "{sv}", "@id", g_variant_new_string (priv->identifier)); } g_hash_table_iter_init (&iter, priv->properties); /* Use a stable sort, so that GVariants are byte compatible */ properties = tracker_resource_get_properties (resource); properties = g_list_sort (properties, (GCompareFunc) g_strcmp0); for (l = properties; l; l = l->next) { pred = l->data; value = g_hash_table_lookup (priv->properties, pred); if (G_VALUE_HOLDS (value, G_TYPE_PTR_ARRAY)) { GPtrArray *array = g_value_get_boxed (value); GVariantBuilder array_builder; guint i; g_variant_builder_init (&array_builder, G_VARIANT_TYPE_ARRAY); for (i = 0; i < array->len; i++) { GValue *child = g_ptr_array_index (array, i); GVariant *variant; variant = tracker_serialize_single_value (resource, child); if (!variant) return NULL; g_variant_builder_add_value (&array_builder, variant); } g_variant_builder_add (&builder, "{sv}", pred, g_variant_builder_end (&array_builder)); } else { GVariant *variant; variant = tracker_serialize_single_value (resource, value); if (!variant) return NULL; g_variant_builder_add (&builder, "{sv}", pred, variant); } } g_list_free (properties); return g_variant_builder_end (&builder); } /** * tracker_resource_deserialize: * @variant: a #GVariant * * Deserializes a #TrackerResource previously serialized with * tracker_resource_serialize(). It is implied that both ends * use a common #TrackerNamespaceManager. * * Returns: (transfer full): A TrackerResource, or %NULL if * deserialization fails. **/ TrackerResource * tracker_resource_deserialize (GVariant *variant) { TrackerResource *resource; GVariantIter iter; GVariant *obj; gchar *pred; g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT), NULL); resource = tracker_resource_new (NULL); g_variant_iter_init (&iter, variant); while (g_variant_iter_next (&iter, "{sv}", &pred, &obj)) { /* Special case, "@id" for the resource identifier */ if (g_strcmp0 (pred, "@id") == 0 && g_variant_is_of_type (obj, G_VARIANT_TYPE_STRING)) { tracker_resource_set_identifier (resource, g_variant_get_string (obj, NULL)); continue; } if (g_variant_is_of_type (obj, G_VARIANT_TYPE_STRING)) { tracker_resource_set_string (resource, pred, g_variant_get_string (obj, NULL)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_BOOLEAN)) { tracker_resource_set_boolean (resource, pred, g_variant_get_boolean (obj)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_INT16)) { tracker_resource_set_int (resource, pred, (gint) g_variant_get_int16 (obj)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_INT32)) { tracker_resource_set_int (resource, pred, (gint) g_variant_get_int32 (obj)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_INT64)) { tracker_resource_set_int64 (resource, pred, (gint64) g_variant_get_int64 (obj)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_DOUBLE)) { tracker_resource_set_double (resource, pred, g_variant_get_double (obj)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_BYTESTRING)) { tracker_resource_set_uri (resource, pred, g_variant_get_bytestring (obj)); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_VARDICT)) { TrackerResource *child; child = tracker_resource_deserialize (obj); if (!child) { g_object_unref (resource); return NULL; } tracker_resource_set_relation (resource, pred, child); } else if (g_variant_is_of_type (obj, G_VARIANT_TYPE_ARRAY)) { GVariant *elem; GVariantIter iter2; g_variant_iter_init (&iter2, obj); /* Other arrays are multi-valued */ while ((elem = g_variant_iter_next_value (&iter2)) != NULL) { if (g_variant_is_of_type (elem, G_VARIANT_TYPE_STRING)) { tracker_resource_add_string (resource, pred, g_variant_get_string (elem, NULL)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_BOOLEAN)) { tracker_resource_add_boolean (resource, pred, g_variant_get_boolean (elem)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_INT16)) { tracker_resource_add_int (resource, pred, (gint) g_variant_get_int16 (elem)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_INT32)) { tracker_resource_add_int (resource, pred, (gint) g_variant_get_int32 (elem)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_INT64)) { tracker_resource_add_int64 (resource, pred, (gint64) g_variant_get_int64 (elem)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_DOUBLE)) { tracker_resource_add_double (resource, pred, g_variant_get_double (elem)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_BYTESTRING)) { tracker_resource_add_uri (resource, pred, g_variant_get_bytestring (elem)); } else if (g_variant_is_of_type (elem, G_VARIANT_TYPE_VARDICT)) { TrackerResource *child; child = tracker_resource_deserialize (elem); if (!child) { g_object_unref (resource); return NULL; } tracker_resource_add_relation (resource, pred, child); } else { g_warning ("Unhandled GVariant signature '%s'", g_variant_get_type_string (elem)); g_object_unref (resource); return NULL; } } } else { g_warning ("Unhandled GVariant signature '%s'", g_variant_get_type_string (obj)); g_object_unref (resource); return NULL; } } return resource; } /** * tracker_resource_get_property_overwrite: * @resource: a #TrackerResource * @property_uri: a string identifying the property to query * * Returns whether the prior values for this property would be deleted * in the SPARQL issued by @resource. * * Returns: #TRUE if the property would be overwritten * * Since: 3.1 **/ gboolean tracker_resource_get_property_overwrite (TrackerResource *resource, const gchar *property_uri) { TrackerResourcePrivate *priv = GET_PRIVATE (resource); return g_hash_table_contains (priv->overwrite, property_uri); }