summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garnacho <carlosg@gnome.org>2020-05-22 16:01:34 +0200
committerJean Felder <jean.felder@gmail.com>2020-08-21 15:05:10 +0000
commitd6002a4a550afba4817631954f9c7b09c38dc6a1 (patch)
treeb7f41d8ae9c422377beefde8906274d96e54070b
parente3eaa4ec1fbb9f677ce26cbd00e7d2d0ff14c87a (diff)
downloadgrilo-plugins-d6002a4a550afba4817631954f9c7b09c38dc6a1.tar.gz
tracker3: Add infrastructure to cache TrackerSparqlStatements
-rw-r--r--src/tracker3/grl-tracker-source-priv.h1
-rw-r--r--src/tracker3/grl-tracker-source-statements.c491
-rw-r--r--src/tracker3/grl-tracker-source-statements.h47
-rw-r--r--src/tracker3/meson.build2
4 files changed, 541 insertions, 0 deletions
diff --git a/src/tracker3/grl-tracker-source-priv.h b/src/tracker3/grl-tracker-source-priv.h
index eeaad96..1d32482 100644
--- a/src/tracker3/grl-tracker-source-priv.h
+++ b/src/tracker3/grl-tracker-source-priv.h
@@ -67,6 +67,7 @@ struct _GrlTrackerSourcePriv {
GHashTable *operations;
GrlTrackerSourceNotify *notifier;
+ GList *cached_statements;
gboolean notify_changes;
diff --git a/src/tracker3/grl-tracker-source-statements.c b/src/tracker3/grl-tracker-source-statements.c
new file mode 100644
index 0000000..404b444
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-statements.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ * Copyright (C) 2020 Red Hat Inc.
+ *
+ * Contact: Iago Toral Quiroga <itoral@igalia.com>
+ *
+ * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
+ * Juan A. Suarez Romero <jasuarez@igalia.com>
+ * Carlos Garnacho <carlosg@gnome.org>
+ *
+ * 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; 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gio/gio.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "grl-tracker.h"
+#include "grl-tracker-source-priv.h"
+#include "grl-tracker-source-statements.h"
+#include "grl-tracker-utils.h"
+
+#define MAX_N_CACHED_STATEMENTS 10
+#define MINER_FS_BUS_NAME "org.freedesktop.Tracker3.Miner.Files"
+
+typedef struct _CachedStatement CachedStatement;
+
+struct _CachedStatement
+{
+ GrlTrackerQueryType type;
+ GrlOperationOptions *options;
+ GList *keys;
+ TrackerSparqlStatement *stmt;
+ gchar *extra_sparql;
+};
+
+static const gchar *query_bases[GRL_TRACKER_QUERY_N_QUERIES] = {
+};
+
+static void
+append_query_variables (GString *str, GList *keys, GrlTypeFilter filter)
+{
+ GList *l;
+
+ for (l = keys; l; l = l->next) {
+ const gchar *var_name;
+
+ if (!grl_tracker_key_get_sparql_statement (GRLPOINTER_TO_KEYID (l->data), filter))
+ continue;
+ var_name = grl_tracker_key_get_variable_name (GRLPOINTER_TO_KEYID (l->data));
+ if (!var_name)
+ continue;
+ g_string_append_printf (str, "?%s ", var_name);
+ }
+}
+
+static void
+append_subselect_bindings (GString *str, GList *keys, GrlTypeFilter filter)
+{
+ GList *l;
+
+ for (l = keys; l; l = l->next) {
+ const gchar *property_statement, *name;
+
+ property_statement = grl_tracker_key_get_sparql_statement (GRLPOINTER_TO_KEYID (l->data), filter);
+ if (!property_statement)
+ continue;
+
+ name = grl_tracker_key_get_variable_name (GRLPOINTER_TO_KEYID (l->data));
+ g_string_append_printf (str, "(%s AS ?%s) ", property_statement, name);
+ }
+}
+
+static void
+append_filters (GString *str, GrlOperationOptions *options)
+{
+ GList *filters, *ranges, *l;
+ const gchar *name;
+
+ if (!options)
+ return;
+
+ filters = grl_operation_options_get_key_filter_list (options);
+ ranges = grl_operation_options_get_key_range_filter_list (options);
+
+ if (!filters && !ranges)
+ return;
+
+ g_string_append (str, "FILTER (true ");
+
+ for (l = filters; l; l = l->next) {
+ name = grl_tracker_key_get_variable_name (GRLPOINTER_TO_KEYID (l->data));
+ g_string_append_printf (str, "&& ?%s = ~%s", name, name);
+ }
+
+ for (l = ranges; l; l = l->next) {
+ GValue *min, *max;
+
+ name = grl_tracker_key_get_variable_name (GRLPOINTER_TO_KEYID (l->data));
+ grl_operation_options_get_key_range_filter (options,
+ GRLPOINTER_TO_KEYID (l->data),
+ &min, &max);
+ if (min)
+ g_string_append_printf (str, "&& ?%s >= ~min_%s ", name, name);
+ if (max)
+ g_string_append_printf (str, "&& ?%s <= ~max_%s ", name, name);
+ }
+
+ g_string_append (str, ")");
+ g_list_free (filters);
+ g_list_free (ranges);
+}
+
+static int
+key_compare (gconstpointer a,
+ gconstpointer b)
+{
+ GrlKeyID key_a = GRLPOINTER_TO_KEYID (a);
+ GrlKeyID key_b = GRLPOINTER_TO_KEYID (b);
+
+ return key_a - key_b;
+}
+
+static gboolean
+key_equal (gconstpointer a,
+ gconstpointer b)
+{
+ GrlKeyID key_a = GRLPOINTER_TO_KEYID (a);
+ GrlKeyID key_b = GRLPOINTER_TO_KEYID (b);
+
+ return key_a == key_b;
+}
+
+static GList *
+merge_list (GList *target, GList *list)
+{
+ GList *result = target;
+ GList *l;
+
+ for (l = list; l; l = l->next) {
+ if (!g_list_find (result, l->data))
+ result = g_list_insert_sorted (result, l->data, key_compare);
+ }
+
+ return result;
+}
+
+static GList *
+get_all_keys (GList *keys, GrlOperationOptions *options)
+{
+ GList *copy, *filters = NULL, *ranges = NULL;
+
+ copy = g_list_copy (keys);
+
+ /* We sort over last modification date, ensure this property is there */
+ if (!g_list_find (copy, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_MODIFICATION_DATE))) {
+ copy = g_list_insert_sorted (copy, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_MODIFICATION_DATE),
+ key_compare);
+ }
+
+ if (!options)
+ return copy;
+
+ filters = grl_operation_options_get_key_filter_list (options);
+ ranges = grl_operation_options_get_key_range_filter_list (options);
+
+ copy = merge_list (copy, filters);
+ g_list_free (filters);
+
+ copy = merge_list (copy, ranges);
+ g_list_free (ranges);
+
+ return copy;
+}
+
+static gchar *
+create_query_string (GrlTrackerQueryType type,
+ GrlOperationOptions *options,
+ GList *keys,
+ const gchar *extra_sparql)
+{
+ GrlTypeFilter filter;
+ GString *str;
+ GList *merged_list;
+
+ if (options)
+ filter = grl_operation_options_get_type_filter (options);
+ else
+ filter = GRL_TYPE_FILTER_ALL;
+
+ str = g_string_new ("SELECT ?resourceType ?urn ");
+ append_query_variables (str, keys, filter);
+ g_string_append (str, "WHERE { ");
+
+ merged_list = get_all_keys (keys, options);
+
+ /* Remote miner-fs bits */
+ g_string_append (str, "SERVICE <dbus:" MINER_FS_BUS_NAME "> { ");
+
+ /* Make a subquery so we can apply limit and offset */
+ g_string_append (str, "SELECT ?mediaType ?urn ");
+ append_query_variables (str, keys, filter);
+ g_string_append (str, "{ ");
+
+ if (filter & GRL_TYPE_FILTER_AUDIO) {
+ g_string_append (str, "{ GRAPH tracker:Audio { SELECT (rdf:type(?urn) AS ?resourceType) ?urn ");
+ append_subselect_bindings (str, merged_list, GRL_TYPE_FILTER_AUDIO);
+ g_string_append_printf (str, "{ ?urn a nfo:Audio . %s } } } ",
+ query_bases[type]);
+
+ if (filter & (GRL_TYPE_FILTER_VIDEO | GRL_TYPE_FILTER_IMAGE))
+ g_string_append (str, "UNION ");
+ }
+
+ if (filter & GRL_TYPE_FILTER_VIDEO) {
+ g_string_append (str, "{ GRAPH tracker:Video { SELECT (rdf:type(?urn) AS ?resourceType) ?urn ");
+ append_subselect_bindings (str, merged_list, GRL_TYPE_FILTER_VIDEO);
+ g_string_append_printf (str, "{ ?urn a nfo:Video . %s } } } ",
+ query_bases[type]);
+
+ if (filter & GRL_TYPE_FILTER_IMAGE)
+ g_string_append (str, "UNION ");
+ }
+
+ if (filter & GRL_TYPE_FILTER_IMAGE) {
+ g_string_append (str, "{ GRAPH tracker:Pictures { SELECT (rdf:type(?urn) AS ?resourceType) ?urn ");
+ append_subselect_bindings (str, merged_list, GRL_TYPE_FILTER_IMAGE);
+ g_string_append_printf (str, "{ ?urn a nfo:Image . %s } } } ",
+ query_bases[type]);
+ }
+
+ append_filters (str, options);
+
+ g_string_append_printf (str,
+ "} LIMIT ~limit OFFSET ~offset"
+ "} %s } "
+ "ORDER BY ?lastModified ",
+ extra_sparql ? extra_sparql : "");
+
+ g_list_free (merged_list);
+
+ return g_string_free (str, FALSE);
+}
+
+static gboolean
+compare_lists (GList *a, GList *b, GEqualFunc equal)
+{
+ GList *l1 = a, *l2 = b;
+
+ while (l1 && l2)
+ {
+ if (!equal (l1->data, l2->data))
+ return FALSE;
+
+ l1 = l1->next;
+ l2 = l2->next;
+ }
+
+ if ((l1 == NULL) != (l2 == NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+cached_statement_matches (CachedStatement *stmt,
+ GrlTrackerQueryType type,
+ GrlOperationOptions *options,
+ GList *keys,
+ const gchar *extra_sparql)
+{
+ if (stmt->type != type)
+ return FALSE;
+ if (g_strcmp0 (stmt->extra_sparql, extra_sparql) != 0)
+ return FALSE;
+ if (!compare_lists (stmt->keys, keys, key_equal))
+ return FALSE;
+ if ((stmt->options == NULL) != (options == NULL))
+ return FALSE;
+
+ if (stmt->options && options) {
+ GList *list_a, *list_b;
+ gboolean equal;
+
+ if (grl_operation_options_get_type_filter (stmt->options) !=
+ grl_operation_options_get_type_filter (options))
+ return FALSE;
+
+ list_a = g_list_sort (grl_operation_options_get_key_filter_list (stmt->options), key_compare);
+ list_b = g_list_sort (grl_operation_options_get_key_filter_list (options), key_compare);
+ equal = compare_lists (list_a, list_b, key_equal);
+ g_list_free (list_a);
+ g_list_free (list_b);
+
+ if (!equal)
+ return FALSE;
+
+ list_a = g_list_sort (grl_operation_options_get_key_range_filter_list (stmt->options), key_compare);
+ list_b = g_list_sort (grl_operation_options_get_key_range_filter_list (options), key_compare);
+ equal = compare_lists (list_a, list_b, key_equal);
+ g_list_free (list_a);
+ g_list_free (list_b);
+
+ if (!equal)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GList *
+find_cached_statement_link (GrlTrackerSource *source,
+ GrlTrackerQueryType type,
+ GrlOperationOptions *options,
+ GList *keys,
+ const gchar *extra_sparql)
+{
+ GList *l;
+
+ for (l = source->priv->cached_statements; l; l = l->next) {
+ if (cached_statement_matches (l->data, type, options, keys, extra_sparql))
+ return l;
+ }
+
+ return NULL;
+}
+
+static void
+cached_statement_free (CachedStatement *cached)
+{
+ g_clear_object (&cached->stmt);
+ g_clear_object (&cached->options);
+ g_list_free (cached->keys);
+ g_free (cached->extra_sparql);
+ g_free (cached);
+}
+
+static void
+bind_gvalue (TrackerSparqlStatement *stmt, const gchar *name, GValue *value)
+{
+ if (G_VALUE_HOLDS_STRING (value))
+ tracker_sparql_statement_bind_string (stmt, name, g_value_get_string (value));
+ else if (G_VALUE_HOLDS_INT (value))
+ tracker_sparql_statement_bind_int (stmt, name, g_value_get_int (value));
+ else if (G_VALUE_HOLDS_DOUBLE (value))
+ tracker_sparql_statement_bind_double (stmt, name, g_value_get_double (value));
+ else if (G_VALUE_HOLDS_FLOAT (value))
+ tracker_sparql_statement_bind_double (stmt, name, g_value_get_float (value));
+ else if (G_VALUE_HOLDS_BOOLEAN (value))
+ tracker_sparql_statement_bind_boolean (stmt, name, g_value_get_boolean (value));
+ else if (G_VALUE_HOLDS (value, G_TYPE_DATE_TIME)) {
+ GDateTime *time;
+ gchar *time_str;
+
+ time = g_value_get_boxed (value);
+ time_str = g_date_time_format_iso8601 (time);
+ tracker_sparql_statement_bind_string (stmt, name, time_str);
+ g_free (time_str);
+ }
+}
+
+static void
+bind_options (TrackerSparqlStatement *stmt,
+ GrlOperationOptions *options)
+{
+ GList *filters, *ranges, *l;
+ const gchar *name;
+
+ if (!options)
+ {
+ tracker_sparql_statement_bind_int (stmt, "limit", G_MAXUINT);
+ tracker_sparql_statement_bind_int (stmt, "offset", 0);
+ return;
+ }
+
+ tracker_sparql_statement_bind_int (stmt, "limit",
+ grl_operation_options_get_count (options));
+ tracker_sparql_statement_bind_int (stmt, "offset",
+ grl_operation_options_get_skip (options));
+
+ filters = grl_operation_options_get_key_filter_list (options);
+ ranges = grl_operation_options_get_key_range_filter_list (options);
+
+ for (l = filters; l; l = l->next) {
+ GValue *value;
+
+ name = grl_tracker_key_get_variable_name (GRLPOINTER_TO_KEYID (l->data));
+ value = grl_operation_options_get_key_filter (options, GRLPOINTER_TO_KEYID (l->data));
+ bind_gvalue (stmt, name, value);
+ }
+
+ for (l = ranges; l; l = l->next) {
+ GValue *min, *max;
+ gchar *prop;
+
+ name = grl_tracker_key_get_variable_name (GRLPOINTER_TO_KEYID (l->data));
+ grl_operation_options_get_key_range_filter (options,
+ GRLPOINTER_TO_KEYID (l->data),
+ &min, &max);
+ if (min) {
+ prop = g_strdup_printf ("min_%s", name);
+ bind_gvalue (stmt, prop, min);
+ g_free (prop);
+ }
+
+ if (max) {
+ prop = g_strdup_printf ("max_%s", name);
+ bind_gvalue (stmt, prop, max);
+ g_free (prop);
+ }
+ }
+
+ g_list_free (filters);
+ g_list_free (ranges);
+}
+
+TrackerSparqlStatement *
+grl_tracker_source_create_statement (GrlTrackerSource *source,
+ GrlTrackerQueryType type,
+ GrlOperationOptions *options,
+ GList *keys,
+ const gchar *extra_sparql,
+ GError **error)
+{
+ GrlTrackerSourcePriv *priv = source->priv;
+ CachedStatement *cache;
+ GError *tracker_error = NULL;
+ GList *link, *keys_copy;
+ gchar *query_str;
+
+ keys_copy = g_list_sort (g_list_copy (keys), key_compare);
+ link = find_cached_statement_link (source, type, options, keys_copy, extra_sparql);
+
+ if (link) {
+ cache = link->data;
+ priv->cached_statements = g_list_remove_link (priv->cached_statements, link);
+ priv->cached_statements = g_list_concat (priv->cached_statements, link);
+ g_list_free (keys_copy);
+ } else {
+ cache = g_new0 (CachedStatement, 1);
+ cache->type = type;
+ cache->options = options ? grl_operation_options_copy (options) : NULL;
+ cache->keys = keys_copy;
+ cache->extra_sparql = g_strdup (extra_sparql);
+
+ query_str = create_query_string (type, options, keys, extra_sparql);
+ cache->stmt = tracker_sparql_connection_query_statement (priv->tracker_connection,
+ query_str,
+ NULL,
+ &tracker_error);
+ g_free (query_str);
+
+ if (!cache->stmt) {
+ g_propagate_error (error, tracker_error);
+ cached_statement_free (cache);
+ return NULL;
+ }
+
+ priv->cached_statements = g_list_prepend (priv->cached_statements, cache);
+
+ /* Limit the number of cached statements */
+ if (g_list_length (priv->cached_statements) > MAX_N_CACHED_STATEMENTS) {
+ CachedStatement *deleted = priv->cached_statements->data;
+
+ priv->cached_statements = g_list_remove (priv->cached_statements, deleted);
+ cached_statement_free (deleted);
+ }
+ }
+
+ bind_options (cache->stmt, options);
+
+ return g_object_ref (cache->stmt);
+}
diff --git a/src/tracker3/grl-tracker-source-statements.h b/src/tracker3/grl-tracker-source-statements.h
new file mode 100644
index 0000000..3c4bbb9
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-statements.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 202011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ * Copyright (C) 2020 Red Hat Inc
+ *
+ * Contact: Iago Toral Quiroga <itoral@igalia.com>
+ *
+ * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
+ * Juan A. Suarez Romero <jasuarez@igalia.com>
+ * Carlos Garnacho <carlosg@gnome.org>
+ *
+ * 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; 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_SOURCE_STATEMENTS_H_
+#define _GRL_TRACKER_SOURCE_STATEMENTS_H_
+
+#include "grl-tracker-source.h"
+
+/**/
+
+typedef enum {
+ GRL_TRACKER_QUERY_N_QUERIES,
+} GrlTrackerQueryType;
+
+TrackerSparqlStatement *grl_tracker_source_create_statement (GrlTrackerSource *source,
+ GrlTrackerQueryType type,
+ GrlOperationOptions *options,
+ GList *keys,
+ const gchar *extra_sparql,
+ GError **error);
+
+#endif /* _GRL_TRACKER_SOURCE_STATEMENTS_H_ */
diff --git a/src/tracker3/meson.build b/src/tracker3/meson.build
index 81b346a..a8a8a63 100644
--- a/src/tracker3/meson.build
+++ b/src/tracker3/meson.build
@@ -13,6 +13,8 @@ tracker_sources = [
'grl-tracker-source-notif.c',
'grl-tracker-source-notif.h',
'grl-tracker-source-priv.h',
+ 'grl-tracker-source-statements.c',
+ 'grl-tracker-source-statements.h',
'grl-tracker-source.c',
'grl-tracker-source.h',
'grl-tracker-utils.c',