diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2016-10-08 14:44:58 +0200 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2016-11-20 17:52:08 +0100 |
commit | 8152d6d432578371b779c63dbbee4a1d9d780a93 (patch) | |
tree | e5386d5e238c57888f10caf70a56c253bb3d6f5d /src | |
parent | e1936b68709621302a627d3703bdd7e06db26fcf (diff) | |
download | tracker-8152d6d432578371b779c63dbbee4a1d9d780a93.tar.gz |
libtracker-sparql: Add TrackerNotifier to subscribe to change notifications
This object abstracts GraphUpdated, and is now the recommended method
to receive notifications upon changes in Tracker data. After creation,
the caller may connect to the ::events signal. This signal packs all
events received in one go, so as to avoid signal emission overhead on
mass changes.
The TrackerNotifier behavior can be slightly modified through the
flags passed during instantiation. Eg. it can be requested to query
some data (urn, location) upfront, although the API is tracker:id centric
otherwise.
https://bugzilla.gnome.org/show_bug.cgi?id=773028
Diffstat (limited to 'src')
-rw-r--r-- | src/libtracker-sparql-backend/Makefile.am | 2 | ||||
-rw-r--r-- | src/libtracker-sparql/Makefile.am | 20 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-notifier.c | 988 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-notifier.h | 90 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-sparql-enum-types.c.template | 44 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-sparql-enum-types.h.template | 26 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-sparql.h | 1 |
7 files changed, 1168 insertions, 3 deletions
diff --git a/src/libtracker-sparql-backend/Makefile.am b/src/libtracker-sparql-backend/Makefile.am index 8d7934d21..2f06e0973 100644 --- a/src/libtracker-sparql-backend/Makefile.am +++ b/src/libtracker-sparql-backend/Makefile.am @@ -29,5 +29,5 @@ libtracker_sparql_@TRACKER_API_VERSION@_la_LIBADD = \ libtracker_sparql_@TRACKER_API_VERSION@_la_LDFLAGS = \ -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \ - -export-symbols-regex '^(tracker_sparql_(connection|cursor|builder|escape|error|value|get)_.*|tracker_namespace_manager_.*|tracker_resource_.*)' + -export-symbols-regex '^(tracker_sparql_(connection|cursor|builder|escape|error|value|get)_.*|tracker_namespace_manager_.*|tracker_resource_.*|tracker_notifier_.*)' diff --git a/src/libtracker-sparql/Makefile.am b/src/libtracker-sparql/Makefile.am index 48410bb48..42122c960 100644 --- a/src/libtracker-sparql/Makefile.am +++ b/src/libtracker-sparql/Makefile.am @@ -17,13 +17,26 @@ AM_CPPFLAGS = \ libtracker_sparqlincludedir = $(includedir)/tracker-$(TRACKER_API_VERSION)/libtracker-sparql +tracker-sparql-enum-types.h: tracker-sparql-enum-types.h.template tracker-notifier.h + $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template tracker-sparql-enum-types.h.template tracker-notifier.h) > $@ + +tracker-sparql-enum-types.c: tracker-sparql-enum-types.c.template tracker-sparql-enum-types.h + $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template tracker-sparql-enum-types.c.template tracker-notifier.h) > $@ + +ENUM_SOURCES = \ + tracker-sparql-enum-types.c \ + tracker-sparql-enum-types.h + libtracker_sparql_la_SOURCES = \ + $(ENUM_SOURCES) \ tracker-namespace.vala \ tracker-builder.vala \ tracker-connection.vala \ tracker-cursor.vala \ tracker-namespace-manager.c \ tracker-namespace-manager.h \ + tracker-notifier.c \ + tracker-notifier.h \ tracker-resource.c \ tracker-resource.h \ tracker-utils.vala \ @@ -54,7 +67,8 @@ tracker-sparql-$(TRACKER_API_VERSION).vapi: tracker-sparql.vapi libtracker_sparqlinclude_HEADERS = \ $(vala_header) \ tracker-namespace-manager.h \ - tracker-ontologies.h \ + tracker-notifier.h \ + tracker-ontologies.h \ tracker-resource.h \ tracker-sparql.h \ tracker-version.h @@ -69,7 +83,9 @@ pkgconfig_DATA = tracker-sparql-$(TRACKER_API_VERSION).pc tracker-sparql-$(TRACKER_API_VERSION).pc: tracker-sparql.pc cp $< $@ -CLEANFILES = tracker-sparql-$(TRACKER_API_VERSION).pc +CLEANFILES = \ + $(ENUM_SOURCES) \ + tracker-sparql-$(TRACKER_API_VERSION).pc # Vala bindings vapidir = $(datadir)/vala/vapi diff --git a/src/libtracker-sparql/tracker-notifier.c b/src/libtracker-sparql/tracker-notifier.c new file mode 100644 index 000000000..d9094c9b8 --- /dev/null +++ b/src/libtracker-sparql/tracker-notifier.c @@ -0,0 +1,988 @@ +/* + * Copyright (C) 2016 Red Hat Inc. + * + * 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. + */ + +/** + * SECTION: tracker-notifier + * @short_description: Listen to changes in the Tracker database + * @include: libtracker-sparql/tracker-sparql.h + * + * #TrackerNotifier is an object that receives notifications about + * changes to the Tracker database. A #TrackerNotifier is created + * through tracker_notifier_new(), passing the RDF types that are + * relevant to the caller, and possible different #TrackerNotifierFlags + * to change #TrackerNotifier behavior. After the notifier is created, + * events can be listened for by connecting to the #TrackerNotifier::events + * signal. This object was added in Tracker 1.12. + * + * #TrackerNotifier by default coalesces events so the user is only + * notified of changes after all metadata is extracted and inserted + * in the database, so the user is guaranteed that all relevant data + * will be available at the time of notification. If this is not desired, + * or when dealing with data not dealt with by tracker-extract, the + * %TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED flag can be passed. + * + * If the %TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED flag is passed, + * created/updated elements may be notified in two steps, first a + * CREATED/UPDATED event will be emitted, and then a second UPDATED + * event might appear after further metadata is extracted. + * + * #TrackerNotifier is tracker:id centric, the ID can be + * obtained from every event through tracker_notifier_event_get_id(). + * The expected way to retrieving metadata is a query of the form: + * |[<!-- language="SPARQL" --> + * SELECT ?urn … + * WHERE { + * ?urn a rdfs:Resource . + * … + * FILTER (tracker:id(?urn) = …) + * } + * ]| + * + * If the flags %TRACKER_NOTIFIER_FLAG_QUERY_URN or + * %TRACKER_NOTIFIER_FLAG_QUERY_LOCATION are passed, the extra metadata + * will be available through tracker_notifier_event_get_urn() and/or + * tracker_notifier_event_get_location(). Note that this metadata can't + * be obtained for every element and situation, most notably during + * %TRACKER_NOTIFIER_EVENT_DELETE events. + * + * # Known caveats # {#trackernotifier-caveats} + * + * * If a resource is deleted, the %TRACKER_NOTIFIER_EVENT_DELETE event + * emitted will have a %NULL location, even if the notifier was created + * with the %TRACKER_NOTIFIER_FLAG_QUERY_LOCATION flag enabled. At the + * time of notifying, the resource does not exist anymore, so no + * meatadata can be retrieved. + * * Notifications of files being moved across indexed folders will + * appear as %TRACKER_NOTIFIER_EVENT_UPDATE events, containing + * the new location (if requested). The older location is no longer + * known to Tracker, this may make tracking of elements in specific + * folders hard using solely the #TrackerNotifier/Tracker data + * available at event notification time. + * + * The recommendation to fix those is making the caller aware + * of tracker:ids, querying those in the application SPARQL + * queries so the client can search the formerly queried data for + * matching IDs when #TrackerNotifier events happen. URNs are just + * as effective as a matching mechanism, but more costly. + */ + +#include "config.h" + +#include "tracker-notifier.h" +#include "tracker-sparql-enum-types.h" +#include "tracker-sparql.h" + +typedef struct _TrackerNotifierPrivate TrackerNotifierPrivate; +typedef struct _TrackerNotifierEventCache TrackerNotifierEventCache; + +struct _TrackerNotifierPrivate { + TrackerSparqlConnection *connection; + GDBusConnection *dbus_connection; + TrackerNotifierFlags flags; + GHashTable *cached_ids; /* gchar -> gint64* */ + GHashTable *cached_events; /* gchar -> GSequence */ + gchar **expanded_classes; + gchar **classes; + guint graph_updated_signal_id; + guint has_arg0_filter : 1; +}; + +struct _TrackerNotifierEventCache { + gchar *class; + GSequence *sequence; +}; + +struct _TrackerNotifierEvent { + gint8 type; + guint delayed : 1; + gint64 id; + const gchar *rdf_type; /* Belongs to cache */ + gchar *urn; + gchar *location; + guint ref_count; +}; + +enum { + PROP_0, + PROP_CLASSES, + PROP_FLAGS, + N_PROPS +}; + +enum { + EVENTS, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0 }; + +static void tracker_notifier_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (TrackerNotifier, tracker_notifier, G_TYPE_OBJECT, + G_ADD_PRIVATE (TrackerNotifier) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + tracker_notifier_initable_iface_init)) + +static void +tracker_notifier_cache_id (TrackerNotifier *notifier, + const gchar *urn) +{ + TrackerSparqlCursor *cursor; + TrackerNotifierPrivate *priv; + gchar *sparql; + + priv = tracker_notifier_get_instance_private (notifier); + sparql = g_strdup_printf ("SELECT tracker:id(%s) {}", urn); + cursor = tracker_sparql_connection_query (priv->connection, sparql, + NULL, NULL); + g_free (sparql); + + if (!cursor) + return; + + if (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + gint64 id = tracker_sparql_cursor_get_integer (cursor, 0); + gint64 *ptr = g_memdup (&id, sizeof (gint64)); + + g_hash_table_insert (priv->cached_ids, g_strdup (urn), ptr); + } + + g_object_unref (cursor); +} + +static gboolean +tracker_notifier_id_matches (TrackerNotifier *notifier, + gint64 id, + const gchar *urn) +{ + TrackerNotifierPrivate *priv; + gint64 *cached_id; + + priv = tracker_notifier_get_instance_private (notifier); + cached_id = g_hash_table_lookup (priv->cached_ids, urn); + + return (cached_id && *cached_id == id); +} + +static TrackerNotifierEvent * +tracker_notifier_event_new (gint64 id, + const gchar *rdf_type) +{ + TrackerNotifierEvent *event; + + event = g_new0 (TrackerNotifierEvent, 1); + event->type = -1; + /* The type string belongs to the cache, and lives longer */ + event->rdf_type = rdf_type; + event->id = id; + event->ref_count = 1; + return event; +} + +static TrackerNotifierEvent * +tracker_notifier_event_ref (TrackerNotifierEvent *event) +{ + g_atomic_int_inc (&event->ref_count); + return event; +} + +static void +tracker_notifier_event_unref (TrackerNotifierEvent *event) +{ + if (g_atomic_int_dec_and_test (&event->ref_count)) { + g_free (event->urn); + g_free (event->location); + g_free (event); + } +} + +static gint +compare_event_cb (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + const TrackerNotifierEvent *event1 = a, *event2 = b; + return event1->id - event2->id; +} + +static TrackerNotifierEventCache * +tracker_notifier_event_cache_new (const gchar *rdf_class) +{ + TrackerNotifierEventCache *event_cache; + + event_cache = g_new0 (TrackerNotifierEventCache, 1); + event_cache->class = g_strdup (rdf_class); + event_cache->sequence = g_sequence_new ((GDestroyNotify) tracker_notifier_event_unref); + + return event_cache; +} + +static void +tracker_notifier_event_cache_free (TrackerNotifierEventCache *event_cache) +{ + g_sequence_free (event_cache->sequence); + g_free (event_cache->class); + g_free (event_cache); +} + +static TrackerNotifierEventCache * +tracker_notifier_get_event_cache (TrackerNotifier *notifier, + const gchar *rdf_class) +{ + TrackerNotifierPrivate *priv; + TrackerNotifierEventCache *event_cache; + + priv = tracker_notifier_get_instance_private (notifier); + event_cache = g_hash_table_lookup (priv->cached_events, rdf_class); + + if (!event_cache) { + event_cache = tracker_notifier_event_cache_new (rdf_class); + g_hash_table_insert (priv->cached_events, + event_cache->class, event_cache); + } + + return event_cache; +} + +/* This is always meant to return a pointer */ +static TrackerNotifierEvent * +tracker_notifier_event_cache_get_event (TrackerNotifierEventCache *cache, + gint64 id) +{ + TrackerNotifierEvent *event = NULL, search; + GSequenceIter *iter, *prev; + + search.id = id; + iter = g_sequence_search (cache->sequence, &search, + compare_event_cb, NULL); + + if (!g_sequence_iter_is_begin (iter)) { + prev = g_sequence_iter_prev (iter); + event = g_sequence_get (prev); + if (event->id == id) + return event; + } else if (!g_sequence_iter_is_end (iter)) { + event = g_sequence_get (iter); + if (event->id == id) + return event; + } + + event = tracker_notifier_event_new (id, cache->class); + g_sequence_insert_before (iter, event); + + return event; +} + +static void +handle_deletes (TrackerNotifier *notifier, + TrackerNotifierEventCache *cache, + GVariantIter *iter) +{ + gint graph, subject, predicate, object; + TrackerNotifierPrivate *priv; + + priv = tracker_notifier_get_instance_private (notifier); + + while (g_variant_iter_loop (iter, "(iiii)", + &graph, &subject, &predicate, &object)) { + TrackerNotifierEvent *event; + + event = tracker_notifier_event_cache_get_event (cache, subject); + + if (tracker_notifier_id_matches (notifier, predicate, "rdf:type")) { + if (event->delayed && + event->type == TRACKER_NOTIFIER_EVENT_CREATE) { + /* This rdf:type was created and dropped, + * restore type to its original unset state so + * it is ignored and freed afterwards. + */ + event->type = -1; + } else { + event->type = TRACKER_NOTIFIER_EVENT_DELETE; + } + } else if (event->type != TRACKER_NOTIFIER_EVENT_DELETE && + (priv->flags & TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED) == 0 && + tracker_notifier_id_matches (notifier, predicate, "nie:dataSource") && + tracker_notifier_id_matches (notifier, object, "tracker:extractor-data-source")) { + event->delayed = TRUE; + } else if (event->type < 0) { + event->type = TRACKER_NOTIFIER_EVENT_UPDATE; + } + } +} + +static void +handle_updates (TrackerNotifier *notifier, + TrackerNotifierEventCache *cache, + GVariantIter *iter) +{ + gint graph, subject, predicate, object; + TrackerNotifierPrivate *priv; + + priv = tracker_notifier_get_instance_private (notifier); + + while (g_variant_iter_loop (iter, "(iiii)", + &graph, &subject, &predicate, &object)) { + TrackerNotifierEvent *event; + + event = tracker_notifier_event_cache_get_event (cache, subject); + + if (tracker_notifier_id_matches (notifier, predicate, "rdf:type")) { + event->type = TRACKER_NOTIFIER_EVENT_CREATE; + + if ((priv->flags & TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED) == 0) + event->delayed = TRUE; + } else if (tracker_notifier_id_matches (notifier, predicate, "nie:dataSource") && + tracker_notifier_id_matches (notifier, object, "tracker:extractor-data-source")) { + if (event->type < 0) + event->type = TRACKER_NOTIFIER_EVENT_UPDATE; + event->delayed = FALSE; + } else if (event->type < 0) { + event->type = TRACKER_NOTIFIER_EVENT_UPDATE; + } + } +} + +static GPtrArray * +tracker_notifier_event_cache_flush_events (TrackerNotifierEventCache *cache) +{ + TrackerNotifierEvent *event; + GSequenceIter *iter, *next; + GPtrArray *events; + + events = g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_notifier_event_unref); + + iter = g_sequence_get_begin_iter (cache->sequence); + + while (!g_sequence_iter_is_end (iter)) { + next = g_sequence_iter_next (iter); + event = g_sequence_get (iter); + + if (event->type == -1) { + /* This event turned out a NO-OP, just remove it */ + g_sequence_remove (iter); + } else if (!event->delayed) { + g_ptr_array_add (events, tracker_notifier_event_ref (event)); + g_sequence_remove (iter); + } + + iter = next; + } + + if (events->len == 0) { + g_ptr_array_unref (events); + return NULL; + } + + return events; +} + +static TrackerNotifierEvent * +find_event_in_array (GPtrArray *events, + gint64 id, + gint *idx) +{ + TrackerNotifierEvent *event; + + while (*idx < events->len) { + event = g_ptr_array_index (events, *idx); + (*idx)++; + if (event->id == id) + return event; + } + + return NULL; +} + +static gchar * +create_extra_info_query (TrackerNotifier *notifier, + GPtrArray *events) +{ + TrackerNotifierPrivate *priv; + GString *sparql, *filter; + gboolean has_elements = FALSE; + gint idx; + + priv = tracker_notifier_get_instance_private (notifier); + filter = g_string_new (NULL); + + for (idx = 0; idx < events->len; idx++) { + TrackerNotifierEvent *event; + + event = g_ptr_array_index (events, idx); + + /* Skip delete events, we can't get anything from those here */ + if (event->type == TRACKER_NOTIFIER_EVENT_DELETE) + continue; + if (has_elements) + g_string_append_c (filter, ','); + + g_string_append_printf (filter, "%ld", event->id); + has_elements = TRUE; + } + + if (!has_elements) { + g_string_free (filter, TRUE); + return NULL; + } + + sparql = g_string_new ("SELECT tracker:id(?u) "); + + if (priv->flags & TRACKER_NOTIFIER_FLAG_QUERY_URN) + g_string_append (sparql, "?u "); + if (priv->flags & TRACKER_NOTIFIER_FLAG_QUERY_LOCATION) + g_string_append (sparql, "nie:url(nie:isStoredAs(?u)) "); + + g_string_append_printf (sparql, + "{ ?u a rdfs:Resource . " + " FILTER (tracker:id(?u) IN (%s)) } " + "ORDER BY tracker:id(?u)", filter->str); + g_string_free (filter, TRUE); + + return g_string_free (sparql, FALSE); +} + +static void +tracker_notifier_query_extra_info (TrackerNotifier *notifier, + GPtrArray *events) +{ + TrackerNotifierPrivate *priv; + TrackerSparqlCursor *cursor; + TrackerNotifierEvent *event; + gchar *sparql; + gint idx = 0, col; + gint64 id; + + sparql = create_extra_info_query (notifier, events); + if (!sparql) + return; + + priv = tracker_notifier_get_instance_private (notifier); + cursor = tracker_sparql_connection_query (priv->connection, sparql, + NULL, NULL); + g_free (sparql); + + /* We rely here in both the GPtrArray and the query items being + * sorted by tracker:id, the former will be so because the way it's + * extracted from the GSequence, the latter because of the ORDER BY + * clause. + */ + while (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + col = 0; + id = tracker_sparql_cursor_get_integer (cursor, col++); + event = find_event_in_array (events, id, &idx); + + if (!event) { + g_critical ("Queried for id %ld but it is not found, " + "bailing out", id); + break; + } + + if (priv->flags & TRACKER_NOTIFIER_FLAG_QUERY_URN) + event->urn = g_strdup (tracker_sparql_cursor_get_string (cursor, col++, NULL)); + if (priv->flags & TRACKER_NOTIFIER_FLAG_QUERY_LOCATION) + event->location = g_strdup (tracker_sparql_cursor_get_string (cursor, col++, NULL)); + } + + g_object_unref (cursor); +} + +static gchar * +create_extra_deleted_info_query (TrackerNotifier *notifier, + GPtrArray *events) +{ + gboolean has_elements = FALSE; + GString *sparql; + gint idx; + + sparql = g_string_new ("SELECT "); + + for (idx = 0; idx < events->len; idx++) { + TrackerNotifierEvent *event; + + event = g_ptr_array_index (events, idx); + + /* This is for delete events, skip all others */ + if (event->type != TRACKER_NOTIFIER_EVENT_DELETE) + continue; + + g_string_append_printf (sparql, "%ld tracker:uri(%ld) ", + event->id, event->id); + has_elements = TRUE; + } + + if (!has_elements) { + g_string_free (sparql, TRUE); + return NULL; + } + + g_string_append (sparql, "{}"); + + return g_string_free (sparql, FALSE); +} + +static void +tracker_notifier_query_extra_deleted_info (TrackerNotifier *notifier, + GPtrArray *events) +{ + TrackerNotifierPrivate *priv; + TrackerSparqlCursor *cursor; + TrackerNotifierEvent *event; + const gchar *urn; + gchar *sparql; + gint idx = 0, col = 0; + gint64 id; + + sparql = create_extra_deleted_info_query (notifier, events); + if (!sparql) + return; + + priv = tracker_notifier_get_instance_private (notifier); + cursor = tracker_sparql_connection_query (priv->connection, sparql, + NULL, NULL); + g_free (sparql); + + if (!cursor || !tracker_sparql_cursor_next (cursor, NULL, NULL)) { + g_clear_object (&cursor); + return; + } + + /* We rely here in both the GPtrArray and the query columns having + * the same sort criteria. + */ + while (col < tracker_sparql_cursor_get_n_columns (cursor)) { + id = tracker_sparql_cursor_get_integer (cursor, col++); + urn = tracker_sparql_cursor_get_string (cursor, col++, NULL); + event = find_event_in_array (events, id, &idx); + + if (!event) { + g_critical ("Queried for id %ld in column %d but it " + "is not found, bailing out", id, col); + break; + } + + if (priv->flags & TRACKER_NOTIFIER_FLAG_QUERY_URN) + event->urn = g_strdup (urn); + } + + g_object_unref (cursor); +} + +static void +graph_updated_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + TrackerNotifier *notifier = user_data; + TrackerNotifierPrivate *priv; + TrackerNotifierEventCache *cache; + GVariantIter *deletes, *updates; + GPtrArray *events; + const gchar *class; + + priv = tracker_notifier_get_instance_private (notifier); + g_variant_get (parameters, "(&sa(iiii)a(iiii))", &class, &deletes, &updates); + + if (!priv->has_arg0_filter && priv->expanded_classes && + !g_strv_contains ((const gchar * const *) priv->expanded_classes, class)) { + /* This class is not listened for */ + g_variant_iter_free (deletes); + g_variant_iter_free (updates); + return; + } + + cache = tracker_notifier_get_event_cache (notifier, class); + handle_deletes (notifier, cache, deletes); + handle_updates (notifier, cache, updates); + + g_variant_iter_free (deletes); + g_variant_iter_free (updates); + + events = tracker_notifier_event_cache_flush_events (cache); + if (events) { + if (priv->flags & + (TRACKER_NOTIFIER_FLAG_QUERY_URN | + TRACKER_NOTIFIER_FLAG_QUERY_LOCATION)) + tracker_notifier_query_extra_info (notifier, events); + + if (priv->flags & TRACKER_NOTIFIER_FLAG_QUERY_URN) + tracker_notifier_query_extra_deleted_info (notifier, events); + + g_signal_emit (notifier, signals[EVENTS], 0, events); + g_ptr_array_unref (events); + } +} + +static gboolean +expand_class_iris (TrackerNotifier *notifier, + GCancellable *cancellable, + GError **error) +{ + TrackerNotifierPrivate *priv; + TrackerSparqlCursor *cursor; + GArray *expanded; + GString *sparql; + gint i, n_classes; + + priv = tracker_notifier_get_instance_private (notifier); + + if (!priv->classes) { + priv->expanded_classes = NULL; + return TRUE; + } + + n_classes = g_strv_length (priv->classes); + + sparql = g_string_new ("SELECT "); + for (i = 0; i < n_classes; i++) + g_string_append_printf (sparql, "%s ", priv->classes[i]); + g_string_append_printf (sparql, "{}"); + + cursor = tracker_sparql_connection_query (priv->connection, sparql->str, + cancellable, error); + if (!cursor) + return FALSE; + if (!tracker_sparql_cursor_next (cursor, cancellable, error)) + return FALSE; + + expanded = g_array_new (TRUE, TRUE, sizeof (gchar*)); + + for (i = 0; i < tracker_sparql_cursor_get_n_columns (cursor); i++) { + gchar *str = g_strdup (tracker_sparql_cursor_get_string (cursor, i, NULL)); + g_array_append_val (expanded, str); + } + + priv->expanded_classes = (gchar **) g_array_free (expanded, FALSE); + g_object_unref (cursor); + + return TRUE; +} + +static gboolean +tracker_notifier_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + TrackerNotifier *notifier = TRACKER_NOTIFIER (initable); + TrackerNotifierPrivate *priv; + + priv = tracker_notifier_get_instance_private (notifier); + priv->connection = tracker_sparql_connection_get (cancellable, error); + if (!priv->connection) + return FALSE; + + if (!expand_class_iris (notifier, cancellable, error)) + return FALSE; + + tracker_notifier_cache_id (notifier, "rdf:type"); + tracker_notifier_cache_id (notifier, "nie:dataSource"); + tracker_notifier_cache_id (notifier, "tracker:extractor-data-source"); + + priv->dbus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, error); + if (!priv->dbus_connection) + return FALSE; + + priv->has_arg0_filter = + priv->expanded_classes && g_strv_length (priv->expanded_classes) == 1; + priv->graph_updated_signal_id = + g_dbus_connection_signal_subscribe (priv->dbus_connection, + TRACKER_DBUS_SERVICE, + TRACKER_DBUS_INTERFACE_RESOURCES, + "GraphUpdated", + TRACKER_DBUS_OBJECT_RESOURCES, + priv->has_arg0_filter ? priv->expanded_classes[0] : NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + graph_updated_cb, + initable, NULL); + + return TRUE; +} + +static void +tracker_notifier_initable_iface_init (GInitableIface *iface) +{ + iface->init = tracker_notifier_initable_init; +} + +static void +tracker_notifier_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TrackerNotifier *notifier = TRACKER_NOTIFIER (object); + TrackerNotifierPrivate *priv = tracker_notifier_get_instance_private (notifier); + + switch (prop_id) { + case PROP_CLASSES: + priv->classes = g_value_dup_boxed (value); + break; + case PROP_FLAGS: + priv->flags = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tracker_notifier_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TrackerNotifier *notifier = TRACKER_NOTIFIER (object); + TrackerNotifierPrivate *priv = tracker_notifier_get_instance_private (notifier); + + switch (prop_id) { + case PROP_CLASSES: + g_value_set_boxed (value, priv->classes); + break; + case PROP_FLAGS: + g_value_set_flags (value, priv->flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tracker_notifier_finalize (GObject *object) +{ + TrackerNotifierPrivate *priv; + + priv = tracker_notifier_get_instance_private (TRACKER_NOTIFIER (object)); + + g_dbus_connection_signal_unsubscribe (priv->dbus_connection, + priv->graph_updated_signal_id); + + g_object_unref (priv->dbus_connection); + g_object_unref (priv->connection); + g_hash_table_unref (priv->cached_ids); + g_hash_table_unref (priv->cached_events); + g_strfreev (priv->expanded_classes); + g_strfreev (priv->classes); + + G_OBJECT_CLASS (tracker_notifier_parent_class)->finalize (object); +} + +static void +tracker_notifier_class_init (TrackerNotifierClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *pspecs[N_PROPS] = { 0 }; + + object_class->set_property = tracker_notifier_set_property; + object_class->get_property = tracker_notifier_get_property; + object_class->finalize = tracker_notifier_finalize; + + /** + * TrackerNotifier::events: + * @self: The #TrackerNotifier + * @events: (element-type TrackerNotifierEvent): A #GPtrArray of #TrackerNotifierEvent + * + * Notifies of changes in the Tracker database. + */ + signals[EVENTS] = + g_signal_new ("events", + TRACKER_TYPE_NOTIFIER, 0, + G_STRUCT_OFFSET (TrackerNotifierClass, events), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + G_TYPE_PTR_ARRAY | G_SIGNAL_TYPE_STATIC_SCOPE); + + /** + * TrackerNotifier:classes: + * + * RDF classes to listen notifications about. + */ + pspecs[PROP_CLASSES] = + g_param_spec_boxed ("classes", + "Classes", + "Classes", + G_TYPE_STRV, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * TrackerNotifier:flags: + * + * Flags affecting #TrackerNotifier behavior. + */ + pspecs[PROP_FLAGS] = + g_param_spec_flags ("flags", + "Flags", + "Flags", + TRACKER_TYPE_NOTIFIER_FLAGS, + TRACKER_NOTIFIER_FLAG_NONE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, pspecs); +} + +static void +tracker_notifier_init (TrackerNotifier *notifier) +{ + TrackerNotifierPrivate *priv; + + priv = tracker_notifier_get_instance_private (notifier); + priv->cached_events = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) tracker_notifier_event_cache_free); + priv->cached_ids = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, g_free); +} + +/** + * tracker_notifier_new: + * @classes: Array of RDF classes to receive notifications from, or %NULL for all. + * @flags: flags affecting the notifier behavior + * @cancellable: Cancellable for the operation + * @error: location for the possible resulting error. + * + * Creates a new notifier, events can be listened through the + * TrackerNotifier::events signal. + * + * Returns: (nullable): a newly created #TrackerNotifier, %NULL on error. + * + * Since: 1.12 + **/ +TrackerNotifier* +tracker_notifier_new (const gchar * const *classes, + TrackerNotifierFlags flags, + GCancellable *cancellable, + GError **error) +{ + return g_initable_new (TRACKER_TYPE_NOTIFIER, + cancellable, error, + "classes", classes, + "flags", flags, + NULL); +} + +/** + * tracker_notifier_event_get_event_type: + * @event: A #TrackerNotifierEvent + * + * Returns the event type. + * + * Returns: The event type + * + * Since: 1.12 + **/ +TrackerNotifierEventType +tracker_notifier_event_get_event_type (TrackerNotifierEvent *event) +{ + g_return_val_if_fail (event != NULL, -1); + return event->type; +} + +/** + * tracker_notifier_event_get_id: + * @event: A #TrackerNotifierEvent + * + * Returns the tracker:id of the element being notified upon. + * + * Returns: the resource ID + * + * Since: 1.12 + **/ +gint64 +tracker_notifier_event_get_id (TrackerNotifierEvent *event) +{ + g_return_val_if_fail (event != NULL, 0); + return event->id; +} + +/** + * tracker_notifier_event_get_type: + * @event: A #TrackerNotifierEvent + * + * Returns the RDF type that this notification event relates to, in their + * expanded forms (for example, + * http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio). + * + * A resource may have multiple RDF types. In the case of changes to a + * resource with multiple types, one event will be notified for each + * RDF type the notifier is subscribed to. + * + * For performance reasons, Tracker only sends notifications for events that + * are explicitly marked with the tracker:notify property in their ontology. + * + * Returns: the RDF type of the element + * + * Since: 1.12 + **/ +const gchar * +tracker_notifier_event_get_type (TrackerNotifierEvent *event) +{ + g_return_val_if_fail (event != NULL, NULL); + return event->rdf_type; +} + +/** + * tracker_notifier_event_get_urn: + * @event: A #TrackerNotifierEvent + * + * Returns the Uniform Resource Name of the element if the + * notifier has the flag %TRACKER_NOTIFIER_FLAG_QUERY_URN enabled. + * + * This URN is an unique string identifier for the resource being + * notified upon, typically of the form "urn:uuid:...". + * + * Returns: The element URN + * + * Since: 1.12 + **/ +const gchar * +tracker_notifier_event_get_urn (TrackerNotifierEvent *event) +{ + g_return_val_if_fail (event != NULL, NULL); + return event->urn; +} + +/** + * tracker_notifier_event_get_location: + * @event: A #TrackerNotifierEvent + * + * Returns the location (e.g. an URI) of the element if the notifier + * has the flag %TRACKER_NOTIFIER_FLAG_QUERY_LOCATION, and it can + * be obtained at the time of emission. + * + * Returns: (nullable): The element location, or %NULL + * + * Since: 1.12 + **/ +const gchar * +tracker_notifier_event_get_location (TrackerNotifierEvent *event) +{ + g_return_val_if_fail (event != NULL, NULL); + return event->location; +} diff --git a/src/libtracker-sparql/tracker-notifier.h b/src/libtracker-sparql/tracker-notifier.h new file mode 100644 index 000000000..f9519f369 --- /dev/null +++ b/src/libtracker-sparql/tracker-notifier.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 Red Hat Inc. + * + * 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. + */ + +#ifndef __TRACKER_NOTIFIER_H__ +#define __TRACKER_NOTIFIER_H__ + +#if !defined (__LIBTRACKER_SPARQL_INSIDE__) && !defined (TRACKER_COMPILATION) +#error "only <libtracker-sparql/tracker-sparql.h> must be included directly." +#endif + +#include <gio/gio.h> + +#define TRACKER_TYPE_NOTIFIER (tracker_notifier_get_type ()) + +G_DECLARE_DERIVABLE_TYPE (TrackerNotifier, tracker_notifier, TRACKER, NOTIFIER, GObject) + +typedef struct _TrackerNotifierEvent TrackerNotifierEvent; + +struct _TrackerNotifierClass { + GObjectClass parent_class; + + void (* events) (TrackerNotifier *notifier, + const GPtrArray *events); + + /* <Private> */ + gpointer padding[20]; +}; + +/** + * TrackerNotifierFlags: + * @TRACKER_NOTIFIER_FLAG_NONE: No flags + * @TRACKER_NOTIFIER_FLAG_QUERY_URN: Query URN of notified elements + * @TRACKER_NOTIFIER_FLAG_QUERY_LOCATION: Query location of notified elements + * @TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED: Added/updated Elements are + * notified in 2 steps (a CREATE/UPDATE event after the file is first + * known, and an UPDATE event after metadata is extracted). The default + * #TrackerNotifier behavior coalesces those events in one. + * + * Flags affecting #TrackerNotifier behavior. + */ +typedef enum { + TRACKER_NOTIFIER_FLAG_NONE = 0, + TRACKER_NOTIFIER_FLAG_QUERY_URN = 1 << 1, + TRACKER_NOTIFIER_FLAG_QUERY_LOCATION = 1 << 2, + TRACKER_NOTIFIER_FLAG_NOTIFY_UNEXTRACTED = 1 << 3, +} TrackerNotifierFlags; + +/** + * TrackerNotifierEventType: + * @TRACKER_NOTIFIER_EVENT_CREATE: An element was created. + * @TRACKER_NOTIFIER_EVENT_DELETE: An element was deleted. + * @TRACKER_NOTIFIER_EVENT_UPDATE: An element was updated. + * + * Notifier event types. + */ +typedef enum { + TRACKER_NOTIFIER_EVENT_CREATE, + TRACKER_NOTIFIER_EVENT_DELETE, + TRACKER_NOTIFIER_EVENT_UPDATE +} TrackerNotifierEventType; + +TrackerNotifier * tracker_notifier_new (const gchar * const *classes, + TrackerNotifierFlags flags, + GCancellable *cancellable, + GError **error); + +TrackerNotifierEventType + tracker_notifier_event_get_event_type (TrackerNotifierEvent *event); +gint64 tracker_notifier_event_get_id (TrackerNotifierEvent *event); +const gchar * tracker_notifier_event_get_type (TrackerNotifierEvent *event); +const gchar * tracker_notifier_event_get_urn (TrackerNotifierEvent *event); +const gchar * tracker_notifier_event_get_location (TrackerNotifierEvent *event); + +#endif /* __TRACKER_NOTIFIER_H__ */ diff --git a/src/libtracker-sparql/tracker-sparql-enum-types.c.template b/src/libtracker-sparql/tracker-sparql-enum-types.c.template new file mode 100644 index 000000000..11fc8c06d --- /dev/null +++ b/src/libtracker-sparql/tracker-sparql-enum-types.c.template @@ -0,0 +1,44 @@ +/*** BEGIN file-header ***/ +#include "config.h" + +#include "tracker-sparql-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +#include "@filename@" +/*** END file-production ***/ + + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + +/*** END file-tail ***/ diff --git a/src/libtracker-sparql/tracker-sparql-enum-types.h.template b/src/libtracker-sparql/tracker-sparql-enum-types.h.template new file mode 100644 index 000000000..6628c94db --- /dev/null +++ b/src/libtracker-sparql/tracker-sparql-enum-types.h.template @@ -0,0 +1,26 @@ +/*** BEGIN file-header ***/ + +#ifndef __TRACKER_NOTIFY_ENUM_TYPES_H__ +#define __TRACKER_NOTIFY_ENUM_TYPES_H__ + +#include <glib-object.h> +#include "tracker-sparql.h" + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define TRACKER_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __TRACKER_ENUMS_TYPES_H__ */ +/*** END file-tail ***/ diff --git a/src/libtracker-sparql/tracker-sparql.h b/src/libtracker-sparql/tracker-sparql.h index 8b4d9aede..693895011 100644 --- a/src/libtracker-sparql/tracker-sparql.h +++ b/src/libtracker-sparql/tracker-sparql.h @@ -25,6 +25,7 @@ #include <libtracker-sparql/tracker-version.h> #include <libtracker-sparql/tracker-ontologies.h> #include <libtracker-sparql/tracker-resource.h> +#include <libtracker-sparql/tracker-notifier.h> #include <libtracker-sparql/tracker-generated.h> #undef __LIBTRACKER_SPARQL_INSIDE__ |