1 /*
2  * Copyright (C) 2016-2018 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  */
19 
20 /**
21  * SECTION: tracker-notifier
22  * @short_description: Listen to changes in the Tracker database
23  * @include: libtracker-sparql/tracker-sparql.h
24  *
25  * #TrackerNotifier is an object that receives notifications about
26  * changes to the Tracker database. A #TrackerNotifier is created
27  * through tracker_sparql_connection_create_notifier(), after the notifier
28  * is created, events can be listened for by connecting to the
29  * #TrackerNotifier::events signal. This object was added in Tracker 1.12.
30  *
31  * # Known caveats # {#trackernotifier-caveats}
32  *
33  * * The %TRACKER_NOTIFIER_EVENT_DELETE events will be received after the
34  *   resource has been deleted. At that time queries on those elements will
35  *   not bring any metadata. Only the ID/URN obtained through the event
36  *   remain meaningful.
37  * * Notifications of files being moved across indexed folders will
38  *   appear as %TRACKER_NOTIFIER_EVENT_UPDATE events, containing
39  *   the new location (if requested). The older location is no longer
40  *   known to Tracker, this may make tracking of elements in specific
41  *   folders hard using solely the #TrackerNotifier/Tracker data
42  *   available at event notification time.
43  */
44 
45 #include "config.h"
46 
47 #include "tracker-connection.h"
48 #include "tracker-notifier.h"
49 #include "tracker-notifier-private.h"
50 #include "tracker-private.h"
51 #include "tracker-sparql-enum-types.h"
52 #include <libtracker-common/tracker-common.h>
53 #include <direct/tracker-direct.h>
54 
55 typedef struct _TrackerNotifierPrivate TrackerNotifierPrivate;
56 typedef struct _TrackerNotifierSubscription TrackerNotifierSubscription;
57 typedef struct _TrackerNotifierEventCache TrackerNotifierEventCache;
58 
59 struct _TrackerNotifierSubscription {
60 	GDBusConnection *connection;
61 	TrackerNotifier *notifier;
62 	TrackerSparqlStatement *statement;
63 	gint n_statement_slots;
64 	gchar *service;
65 	gchar *object_path;
66 	guint handler_id;
67 };
68 
69 struct _TrackerNotifierPrivate {
70 	TrackerSparqlConnection *connection;
71 	GHashTable *subscriptions; /* guint -> TrackerNotifierSubscription */
72 	GCancellable *cancellable;
73 	TrackerSparqlStatement *local_statement;
74 	GAsyncQueue *queue;
75 	gint n_local_statement_slots;
76 	gboolean querying;
77 	GMutex mutex;
78 };
79 
80 struct _TrackerNotifierEventCache {
81 	TrackerNotifierSubscription *subscription;
82 	gchar *graph;
83 	TrackerNotifier *notifier;
84 	GSequence *sequence;
85 	GSequenceIter *first;
86 };
87 
88 struct _TrackerNotifierEvent {
89 	gint8 type;
90 	gint64 id;
91 	gchar *urn;
92 	guint ref_count;
93 };
94 
95 enum {
96 	PROP_0,
97 	PROP_CONNECTION,
98 	N_PROPS
99 };
100 
101 enum {
102 	EVENTS,
103 	N_SIGNALS
104 };
105 
106 static guint signals[N_SIGNALS] = { 0 };
107 
108 #define N_SLOTS 50 /* In sync with tracker-vtab-service.c parameters */
109 
110 #define DEFAULT_OBJECT_PATH "/org/freedesktop/Tracker3/Endpoint"
111 
112 G_DEFINE_TYPE_WITH_CODE (TrackerNotifier, tracker_notifier, G_TYPE_OBJECT,
113                          G_ADD_PRIVATE (TrackerNotifier))
114 
115 static void tracker_notifier_query_extra_info (TrackerNotifier           *notifier,
116                                                TrackerNotifierEventCache *cache);
117 
118 static TrackerNotifierSubscription *
tracker_notifier_subscription_new(TrackerNotifier * notifier,GDBusConnection * connection,const gchar * service,const gchar * object_path)119 tracker_notifier_subscription_new (TrackerNotifier *notifier,
120                                    GDBusConnection *connection,
121                                    const gchar     *service,
122                                    const gchar     *object_path)
123 {
124 	TrackerNotifierSubscription *subscription;
125 
126 	subscription = g_new0 (TrackerNotifierSubscription, 1);
127 	subscription->connection = g_object_ref (connection);
128 	subscription->notifier = notifier;
129 	subscription->service = g_strdup (service);
130 	subscription->object_path = g_strdup (object_path);
131 
132 	return subscription;
133 }
134 
135 static void
tracker_notifier_subscription_free(TrackerNotifierSubscription * subscription)136 tracker_notifier_subscription_free (TrackerNotifierSubscription *subscription)
137 {
138 	g_dbus_connection_signal_unsubscribe (subscription->connection,
139 	                                      subscription->handler_id);
140 	g_object_unref (subscription->connection);
141 	g_clear_object (&subscription->statement);
142 	g_free (subscription->service);
143 	g_free (subscription->object_path);
144 	g_free (subscription);
145 }
146 
147 static TrackerNotifierEvent *
tracker_notifier_event_new(gint64 id)148 tracker_notifier_event_new (gint64 id)
149 {
150 	TrackerNotifierEvent *event;
151 
152 	event = g_new0 (TrackerNotifierEvent, 1);
153 	event->type = -1;
154 	event->id = id;
155 	event->ref_count = 1;
156 	return event;
157 }
158 
159 static TrackerNotifierEvent *
tracker_notifier_event_ref(TrackerNotifierEvent * event)160 tracker_notifier_event_ref (TrackerNotifierEvent *event)
161 {
162 	g_atomic_int_inc (&event->ref_count);
163 	return event;
164 }
165 
166 static void
tracker_notifier_event_unref(TrackerNotifierEvent * event)167 tracker_notifier_event_unref (TrackerNotifierEvent *event)
168 {
169 	if (g_atomic_int_dec_and_test (&event->ref_count)) {
170 		g_free (event->urn);
171 		g_free (event);
172 	}
173 }
174 
G_DEFINE_BOXED_TYPE(TrackerNotifierEvent,tracker_notifier_event,tracker_notifier_event_ref,tracker_notifier_event_unref)175 G_DEFINE_BOXED_TYPE (TrackerNotifierEvent,
176                      tracker_notifier_event,
177                      tracker_notifier_event_ref,
178                      tracker_notifier_event_unref)
179 
180 static gint
181 compare_event_cb (gconstpointer a,
182                   gconstpointer b,
183                   gpointer      user_data)
184 {
185 	const TrackerNotifierEvent *event1 = a, *event2 = b;
186 	return event1->id - event2->id;
187 }
188 
189 static TrackerNotifierEventCache *
_tracker_notifier_event_cache_new_full(TrackerNotifier * notifier,TrackerNotifierSubscription * subscription,const gchar * graph)190 _tracker_notifier_event_cache_new_full (TrackerNotifier             *notifier,
191                                         TrackerNotifierSubscription *subscription,
192                                         const gchar                 *graph)
193 {
194 	TrackerNotifierEventCache *event_cache;
195 
196 	event_cache = g_new0 (TrackerNotifierEventCache, 1);
197 	event_cache->notifier = g_object_ref (notifier);
198 	event_cache->subscription = subscription;
199 	event_cache->graph = g_strdup (graph);
200 	event_cache->sequence = g_sequence_new ((GDestroyNotify) tracker_notifier_event_unref);
201 
202 	return event_cache;
203 }
204 
205 TrackerNotifierEventCache *
_tracker_notifier_event_cache_new(TrackerNotifier * notifier,const gchar * graph)206 _tracker_notifier_event_cache_new (TrackerNotifier *notifier,
207                                    const gchar     *graph)
208 {
209 	return _tracker_notifier_event_cache_new_full (notifier, NULL, graph);
210 }
211 
212 void
_tracker_notifier_event_cache_free(TrackerNotifierEventCache * event_cache)213 _tracker_notifier_event_cache_free (TrackerNotifierEventCache *event_cache)
214 {
215 	g_sequence_free (event_cache->sequence);
216 	g_object_unref (event_cache->notifier);
217 	g_free (event_cache->graph);
218 	g_free (event_cache);
219 }
220 
221 /* This is always meant to return a pointer */
222 static TrackerNotifierEvent *
tracker_notifier_event_cache_get_event(TrackerNotifierEventCache * cache,gint64 id)223 tracker_notifier_event_cache_get_event (TrackerNotifierEventCache *cache,
224                                         gint64                     id)
225 {
226 	TrackerNotifierEvent *event = NULL, search;
227 	GSequenceIter *iter, *prev;
228 
229 	search.id = id;
230 	iter = g_sequence_search (cache->sequence, &search,
231 	                          compare_event_cb, NULL);
232 
233 	if (!g_sequence_iter_is_begin (iter)) {
234 		prev = g_sequence_iter_prev (iter);
235 		event = g_sequence_get (prev);
236 		if (event->id == id)
237 			return event;
238 	} else if (!g_sequence_iter_is_end (iter)) {
239 		event = g_sequence_get (iter);
240 		if (event->id == id)
241 			return event;
242 	}
243 
244 	event = tracker_notifier_event_new (id);
245 	g_sequence_insert_before (iter, event);
246 
247 	return event;
248 }
249 
250 void
_tracker_notifier_event_cache_push_event(TrackerNotifierEventCache * cache,gint64 id,TrackerNotifierEventType event_type)251 _tracker_notifier_event_cache_push_event (TrackerNotifierEventCache *cache,
252                                           gint64                     id,
253                                           TrackerNotifierEventType   event_type)
254 {
255 	TrackerNotifierEvent *event;
256 
257 	event = tracker_notifier_event_cache_get_event (cache, id);
258 
259 	if (event->type < 0 || event_type != TRACKER_NOTIFIER_EVENT_UPDATE)
260 		event->type = event_type;
261 }
262 
263 static void
handle_events(TrackerNotifier * notifier,TrackerNotifierEventCache * cache,GVariantIter * iter)264 handle_events (TrackerNotifier           *notifier,
265                TrackerNotifierEventCache *cache,
266                GVariantIter              *iter)
267 {
268 	gint32 type, resource;
269 
270 	while (g_variant_iter_loop (iter, "{ii}", &type, &resource))
271 		_tracker_notifier_event_cache_push_event (cache, resource, type);
272 }
273 
274 static GPtrArray *
tracker_notifier_event_cache_take_events(TrackerNotifierEventCache * cache)275 tracker_notifier_event_cache_take_events (TrackerNotifierEventCache *cache)
276 {
277 	TrackerNotifierEvent *event;
278 	GSequenceIter *iter, *next;
279 	GPtrArray *events;
280 
281 	events = g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_notifier_event_unref);
282 
283 	iter = g_sequence_get_begin_iter (cache->sequence);
284 
285 	while (!g_sequence_iter_is_end (iter)) {
286 		next = g_sequence_iter_next (iter);
287 		event = g_sequence_get (iter);
288 
289 		g_ptr_array_add (events, tracker_notifier_event_ref (event));
290 		g_sequence_remove (iter);
291 		iter = next;
292 	}
293 
294 	if (events->len == 0) {
295 		g_ptr_array_unref (events);
296 		return NULL;
297 	}
298 
299 	return events;
300 }
301 
302 static gchar *
compose_uri(const gchar * service,const gchar * object_path)303 compose_uri (const gchar *service,
304              const gchar *object_path)
305 {
306 	if (object_path && g_strcmp0 (object_path, DEFAULT_OBJECT_PATH) != 0)
307 		return g_strdup_printf ("dbus:%s:%s", service, object_path);
308 	else
309 		return g_strdup_printf ("dbus:%s", service);
310 }
311 
312 static gchar *
get_service_name(TrackerNotifier * notifier,TrackerNotifierEventCache * cache)313 get_service_name (TrackerNotifier           *notifier,
314                   TrackerNotifierEventCache *cache)
315 {
316 	TrackerNotifierSubscription *subscription;
317 	TrackerNotifierPrivate *priv;
318 
319 	priv = tracker_notifier_get_instance_private (notifier);
320 	subscription = cache->subscription;
321 
322 	if (!subscription)
323 		return NULL;
324 
325 	/* This is a hackish way to find out we are dealing with DBus connections,
326 	 * without pulling its header.
327 	 */
328 	if (g_object_class_find_property (G_OBJECT_GET_CLASS (priv->connection), "bus-name")) {
329 		gchar *bus_name, *bus_object_path;
330 		gboolean is_self;
331 
332 		g_object_get (priv->connection,
333 		              "bus-name", &bus_name,
334 		              "bus-object-path", &bus_object_path,
335 		              NULL);
336 
337 		is_self = (g_strcmp0 (bus_name, subscription->service) == 0 &&
338 		           g_strcmp0 (bus_object_path, subscription->object_path) == 0);
339 		g_free (bus_name);
340 		g_free (bus_object_path);
341 
342 		if (is_self)
343 			return NULL;
344 	}
345 
346 	return compose_uri (subscription->service, subscription->object_path);
347 }
348 
349 static gboolean
tracker_notifier_emit_events(TrackerNotifierEventCache * cache)350 tracker_notifier_emit_events (TrackerNotifierEventCache *cache)
351 {
352 	GPtrArray *events;
353 	gchar *service;
354 
355 	events = tracker_notifier_event_cache_take_events (cache);
356 
357 	if (events) {
358 		service = get_service_name (cache->notifier, cache);
359 		g_signal_emit (cache->notifier, signals[EVENTS], 0, service, cache->graph, events);
360 		g_ptr_array_unref (events);
361 		g_free (service);
362 	}
363 
364 	return G_SOURCE_REMOVE;
365 }
366 
367 static void
tracker_notifier_emit_events_in_idle(TrackerNotifierEventCache * cache)368 tracker_notifier_emit_events_in_idle (TrackerNotifierEventCache *cache)
369 {
370 	g_idle_add_full (G_PRIORITY_DEFAULT,
371 	                 (GSourceFunc) tracker_notifier_emit_events,
372 	                 cache,
373 	                 (GDestroyNotify) _tracker_notifier_event_cache_free);
374 }
375 
376 static gchar *
create_extra_info_query(TrackerNotifier * notifier,TrackerNotifierEventCache * cache)377 create_extra_info_query (TrackerNotifier           *notifier,
378                          TrackerNotifierEventCache *cache)
379 {
380 	GString *sparql;
381 	gchar *service;
382 	gint i;
383 
384 	sparql = g_string_new ("SELECT ?id ?uri ");
385 
386 	service = get_service_name (notifier, cache);
387 
388 	if (service) {
389 		g_string_append_printf (sparql,
390 		                        "{ SERVICE <%s> ",
391 		                        service);
392 	}
393 
394 	g_string_append (sparql, "{ VALUES ?id { ");
395 
396 	for (i = 0; i < N_SLOTS; i++) {
397 		g_string_append_printf (sparql, "~arg%d ", i + 1);
398 	}
399 
400 	g_string_append (sparql,
401 	                 "  } ."
402 	                 "  BIND (tracker:uri(xsd:integer(?id)) AS ?uri)"
403 	                 "} ");
404 
405 	if (service)
406 		g_string_append (sparql, "} ");
407 
408 	g_string_append (sparql, "ORDER BY ?id");
409 
410 	g_free (service);
411 
412 	return g_string_free (sparql, FALSE);
413 }
414 
415 static TrackerSparqlStatement *
ensure_extra_info_statement(TrackerNotifier * notifier,TrackerNotifierEventCache * cache)416 ensure_extra_info_statement (TrackerNotifier           *notifier,
417                              TrackerNotifierEventCache *cache)
418 {
419 	TrackerSparqlStatement **ptr;
420 	TrackerNotifierPrivate *priv;
421 	gchar *sparql;
422 	GError *error = NULL;
423 
424 	priv = tracker_notifier_get_instance_private (notifier);
425 
426 	if (cache->subscription) {
427 		ptr = &cache->subscription->statement;
428 	} else {
429 		ptr = &priv->local_statement;
430 	}
431 
432 	if (*ptr) {
433 		return *ptr;
434 	}
435 
436 	sparql = create_extra_info_query (notifier, cache);
437 	*ptr = tracker_sparql_connection_query_statement (priv->connection,
438 	                                                  sparql,
439 	                                                  priv->cancellable,
440 	                                                  &error);
441 	g_free (sparql);
442 
443 	if (error) {
444 		g_warning ("Error querying notifier info: %s\n", error->message);
445 		g_error_free (error);
446 		return NULL;
447 	}
448 
449 	return *ptr;
450 }
451 
452 static void
handle_cursor(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)453 handle_cursor (GTask        *task,
454 	       gpointer      source_object,
455 	       gpointer      task_data,
456 	       GCancellable *cancellable)
457 {
458 	TrackerNotifierEventCache *cache = task_data;
459 	TrackerSparqlCursor *cursor = source_object;
460 	TrackerNotifier *notifier = cache->notifier;
461 	TrackerNotifierPrivate *priv = tracker_notifier_get_instance_private (notifier);
462 	TrackerNotifierEvent *event;
463 	GSequenceIter *iter;
464 	gint64 id;
465 
466 	iter = cache->first;
467 
468 	/* We rely here in both the GPtrArray and the query items being
469 	 * sorted by tracker:id, the former will be so because the way it's
470 	 * extracted from the GSequence, the latter because of the ORDER BY
471 	 * clause.
472 	 */
473 	while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
474 		id = tracker_sparql_cursor_get_integer (cursor, 0);
475 		if (id == 0)
476 			continue;
477 
478 		event = g_sequence_get (iter);
479 		iter = g_sequence_iter_next (iter);
480 
481 		if (!event || event->id != id) {
482 			g_critical ("Queried for id %" G_GINT64_FORMAT " but it is not "
483 			            "found, bailing out", id);
484 			break;
485 		}
486 
487 		event->urn = g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL));
488 	}
489 
490 	tracker_sparql_cursor_close (cursor);
491 	cache->first = iter;
492 
493 	if (g_sequence_iter_is_end (cache->first)) {
494 		TrackerNotifierEventCache *next;
495 
496 		tracker_notifier_emit_events_in_idle (cache);
497 
498 		g_async_queue_lock (priv->queue);
499 		next = g_async_queue_try_pop_unlocked (priv->queue);
500 		if (next)
501 			tracker_notifier_query_extra_info (notifier, next);
502 		else
503 			priv->querying = FALSE;
504 		g_async_queue_unlock (priv->queue);
505 	} else {
506 		tracker_notifier_query_extra_info (notifier, cache);
507 	}
508 
509 	g_task_return_boolean (task, TRUE);
510 }
511 
512 static void
finish_query(GObject * source_object,GAsyncResult * res,gpointer user_data)513 finish_query (GObject      *source_object,
514               GAsyncResult *res,
515               gpointer      user_data)
516 {
517 	TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR (source_object);
518 	GError *error = NULL;
519 
520 	if (!g_task_propagate_boolean (G_TASK (res), &error)) {
521 		if (!g_error_matches (error,
522 				      G_IO_ERROR,
523 				      G_IO_ERROR_CANCELLED)) {
524 			g_critical ("Error querying notified data: %s\n", error->message);
525 		}
526 	}
527 
528 	g_object_unref (cursor);
529 	g_clear_error (&error);
530 }
531 
532 static void
query_extra_info_cb(GObject * object,GAsyncResult * res,gpointer user_data)533 query_extra_info_cb (GObject      *object,
534                      GAsyncResult *res,
535                      gpointer      user_data)
536 {
537 	TrackerNotifierEventCache *cache = user_data;
538 	TrackerSparqlStatement *statement;
539 	TrackerNotifierPrivate *priv;
540 	TrackerSparqlCursor *cursor;
541 	GError *error = NULL;
542 	GTask *task;
543 
544 	statement = TRACKER_SPARQL_STATEMENT (object);
545 	cursor = tracker_sparql_statement_execute_finish (statement, res, &error);
546 	priv = tracker_notifier_get_instance_private (cache->notifier);
547 
548 	if (!cursor) {
549 		if (!g_error_matches (error,
550 				      G_IO_ERROR,
551 				      G_IO_ERROR_CANCELLED)) {
552 			g_critical ("Could not get cursor: %s\n", error->message);
553 		}
554 
555 		_tracker_notifier_event_cache_free (cache);
556 		g_clear_error (&error);
557 		return;
558 	}
559 
560 	task = g_task_new (cursor, priv->cancellable, finish_query, NULL);
561 	g_task_set_task_data (task, cache, NULL);
562 	g_task_run_in_thread (task, handle_cursor);
563 	g_object_unref (task);
564 }
565 
566 static void
bind_arguments(TrackerSparqlStatement * statement,TrackerNotifierEventCache * cache)567 bind_arguments (TrackerSparqlStatement    *statement,
568                 TrackerNotifierEventCache *cache)
569 {
570 	GSequenceIter *iter;
571 	gchar *arg_name;
572 	gint i = 0;
573 
574 	tracker_sparql_statement_clear_bindings (statement);
575 
576 	for (iter = cache->first;
577 	     !g_sequence_iter_is_end (iter) && i < N_SLOTS;
578 	     iter = g_sequence_iter_next (iter)) {
579 		TrackerNotifierEvent *event;
580 
581 		event = g_sequence_get (iter);
582 
583 		arg_name = g_strdup_printf ("arg%d", i + 1);
584 		tracker_sparql_statement_bind_int (statement, arg_name, event->id);
585 		g_free (arg_name);
586 		i++;
587 	}
588 
589 	/* Fill in missing slots with 0's */
590 	while (i < N_SLOTS) {
591 		arg_name = g_strdup_printf ("arg%d", i + 1);
592 		tracker_sparql_statement_bind_int (statement, arg_name, 0);
593 		g_free (arg_name);
594 		i++;
595 	}
596 }
597 
598 static void
tracker_notifier_query_extra_info(TrackerNotifier * notifier,TrackerNotifierEventCache * cache)599 tracker_notifier_query_extra_info (TrackerNotifier           *notifier,
600                                    TrackerNotifierEventCache *cache)
601 {
602 	TrackerNotifierPrivate *priv;
603 	TrackerSparqlStatement *statement;
604 
605 	priv = tracker_notifier_get_instance_private (notifier);
606 
607 	g_mutex_lock (&priv->mutex);
608 
609 	statement = ensure_extra_info_statement (notifier, cache);
610 	if (!statement)
611 		goto out;
612 
613 	bind_arguments (statement, cache);
614 	tracker_sparql_statement_execute_async (statement,
615 	                                        priv->cancellable,
616 	                                        query_extra_info_cb,
617 	                                        cache);
618 
619 out:
620 	g_mutex_unlock (&priv->mutex);
621 }
622 
623 void
_tracker_notifier_event_cache_flush_events(TrackerNotifierEventCache * cache)624 _tracker_notifier_event_cache_flush_events (TrackerNotifierEventCache *cache)
625 {
626 	TrackerNotifier *notifier = cache->notifier;
627 	TrackerNotifierPrivate *priv = tracker_notifier_get_instance_private (notifier);
628 
629 	if (g_sequence_is_empty (cache->sequence)) {
630 		_tracker_notifier_event_cache_free (cache);
631 		return;
632 	}
633 
634 	cache->first = g_sequence_get_begin_iter (cache->sequence);
635 
636 	g_async_queue_lock (priv->queue);
637 	if (priv->querying) {
638 		g_async_queue_push_unlocked (priv->queue, cache);
639 	} else {
640 		priv->querying = TRUE;
641 		tracker_notifier_query_extra_info (notifier, cache);
642 	}
643 	g_async_queue_unlock (priv->queue);
644 }
645 
646 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)647 graph_updated_cb (GDBusConnection *connection,
648                   const gchar     *sender_name,
649                   const gchar     *object_path,
650                   const gchar     *interface_name,
651                   const gchar     *signal_name,
652                   GVariant        *parameters,
653                   gpointer         user_data)
654 {
655 	TrackerNotifierSubscription *subscription = user_data;
656 	TrackerNotifier *notifier = subscription->notifier;
657 	TrackerNotifierEventCache *cache;
658 	GVariantIter *events;
659 	const gchar *graph;
660 
661 	g_variant_get (parameters, "(sa{ii})", &graph, &events);
662 
663 	cache = _tracker_notifier_event_cache_new_full (notifier, subscription, graph);
664 	handle_events (notifier, cache, events);
665 	g_variant_iter_free (events);
666 
667 	_tracker_notifier_event_cache_flush_events (cache);
668 }
669 
670 static void
tracker_notifier_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)671 tracker_notifier_set_property (GObject      *object,
672                                guint         prop_id,
673                                const GValue *value,
674                                GParamSpec   *pspec)
675 {
676 	TrackerNotifier *notifier = TRACKER_NOTIFIER (object);
677 	TrackerNotifierPrivate *priv = tracker_notifier_get_instance_private (notifier);
678 
679 	switch (prop_id) {
680 	case PROP_CONNECTION:
681 		priv->connection = g_value_dup_object (value);
682 		break;
683 	default:
684 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
685 		break;
686 	}
687 }
688 
689 static void
tracker_notifier_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)690 tracker_notifier_get_property (GObject    *object,
691                                guint       prop_id,
692                                GValue     *value,
693                                GParamSpec *pspec)
694 {
695 	TrackerNotifier *notifier = TRACKER_NOTIFIER (object);
696 	TrackerNotifierPrivate *priv = tracker_notifier_get_instance_private (notifier);
697 
698 	switch (prop_id) {
699 	case PROP_CONNECTION:
700 		g_value_set_object (value, priv->connection);
701 		break;
702 	default:
703 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
704 		break;
705 	}
706 }
707 
708 static void
tracker_notifier_finalize(GObject * object)709 tracker_notifier_finalize (GObject *object)
710 {
711 	TrackerNotifierPrivate *priv;
712 
713 	priv = tracker_notifier_get_instance_private (TRACKER_NOTIFIER (object));
714 
715 	g_cancellable_cancel (priv->cancellable);
716 	g_clear_object (&priv->cancellable);
717 	g_clear_object (&priv->local_statement);
718 	g_async_queue_unref (priv->queue);
719 
720 	if (priv->connection)
721 		g_object_unref (priv->connection);
722 
723 	g_hash_table_unref (priv->subscriptions);
724 
725 	G_OBJECT_CLASS (tracker_notifier_parent_class)->finalize (object);
726 }
727 
728 static void
tracker_notifier_class_init(TrackerNotifierClass * klass)729 tracker_notifier_class_init (TrackerNotifierClass *klass)
730 {
731 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
732 	GParamSpec *pspecs[N_PROPS] = { 0 };
733 
734 	object_class->set_property = tracker_notifier_set_property;
735 	object_class->get_property = tracker_notifier_get_property;
736 	object_class->finalize = tracker_notifier_finalize;
737 
738 	/**
739 	 * TrackerNotifier::events:
740 	 * @self: The #TrackerNotifier
741 	 * @service: The SPARQL service that originated the events, %NULL for the local store
742 	 * @graph: The graph where the events happened on, %NULL for the default anonymous graph
743 	 * @events: (element-type TrackerNotifierEvent): A #GPtrArray of #TrackerNotifierEvent
744 	 *
745 	 * Notifies of changes in the Tracker database.
746 	 */
747 	signals[EVENTS] =
748 		g_signal_new ("events",
749 		              TRACKER_TYPE_NOTIFIER, 0,
750 		              G_STRUCT_OFFSET (TrackerNotifierClass, events),
751 		              NULL, NULL, NULL,
752 		              G_TYPE_NONE, 3,
753 		              G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
754 		              G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
755 		              G_TYPE_PTR_ARRAY | G_SIGNAL_TYPE_STATIC_SCOPE);
756 
757 	/**
758 	 * TrackerNotifier:connection:
759 	 *
760 	 * SPARQL connection to listen to.
761 	 */
762 	pspecs[PROP_CONNECTION] =
763 		g_param_spec_object ("connection",
764 		                     "SPARQL connection",
765 		                     "SPARQL connection",
766 		                     TRACKER_SPARQL_TYPE_CONNECTION,
767 		                     G_PARAM_READWRITE |
768 		                     G_PARAM_STATIC_STRINGS |
769 		                     G_PARAM_CONSTRUCT_ONLY);
770 
771 	g_object_class_install_properties (object_class, N_PROPS, pspecs);
772 }
773 
774 static void
tracker_notifier_init(TrackerNotifier * notifier)775 tracker_notifier_init (TrackerNotifier *notifier)
776 {
777 	TrackerNotifierPrivate *priv;
778 
779 	priv = tracker_notifier_get_instance_private (notifier);
780 	priv->subscriptions = g_hash_table_new_full (NULL, NULL, NULL,
781 	                                             (GDestroyNotify) tracker_notifier_subscription_free);
782 	priv->cancellable = g_cancellable_new ();
783 	priv->queue = g_async_queue_new ();
784 }
785 
786 /**
787  * tracker_notifier_signal_subscribe:
788  * @notifier: a #TrackerNotifier
789  * @connection: a #GDBusConnection
790  * @service: DBus service name to subscribe to events for
791  * @object_path: (nullable): DBus object path to subscribe to events for, or %NULL
792  * @graph: (nullable): graph to listen events for, or %NULL
793  *
794  * Listens to notification events from a remote SPARQL endpoint as a DBus
795  * service (see #TrackerEndpointDBus). If the @object_path argument is
796  * %NULL, the default "/org/freedesktop/Tracker3/Endpoint" path will be
797  * used. If @graph is %NULL, all graphs will be listened for.
798  *
799  * The signal subscription can be removed with
800  * tracker_notifier_signal_unsubscribe().
801  *
802  * Returns: An ID for this subscription
803  *
804  * Since: 3.0
805  **/
806 guint
tracker_notifier_signal_subscribe(TrackerNotifier * notifier,GDBusConnection * connection,const gchar * service,const gchar * object_path,const gchar * graph)807 tracker_notifier_signal_subscribe (TrackerNotifier *notifier,
808                                    GDBusConnection *connection,
809                                    const gchar     *service,
810                                    const gchar     *object_path,
811                                    const gchar     *graph)
812 {
813 	TrackerNotifierSubscription *subscription;
814 	TrackerNotifierPrivate *priv;
815 	gchar *dbus_name = NULL, *dbus_path = NULL, *full_graph = NULL;
816 
817 	g_return_val_if_fail (TRACKER_IS_NOTIFIER (notifier), 0);
818 	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
819 	g_return_val_if_fail (service != NULL, 0);
820 
821 	priv = tracker_notifier_get_instance_private (notifier);
822 
823 	if (!object_path)
824 		object_path = DEFAULT_OBJECT_PATH;
825 
826 	if (graph) {
827 		TrackerNamespaceManager *namespaces;
828 
829 		namespaces = tracker_sparql_connection_get_namespace_manager (priv->connection);
830 		if (namespaces) {
831 			full_graph = tracker_namespace_manager_expand_uri (namespaces,
832 			                                                   graph);
833 		}
834 	}
835 
836 	tracker_sparql_connection_lookup_dbus_service (priv->connection,
837 	                                               service,
838 	                                               object_path,
839 	                                               &dbus_name,
840 	                                               &dbus_path);
841 
842 	subscription = tracker_notifier_subscription_new (notifier, connection,
843 	                                                  service, object_path);
844 
845 	subscription->handler_id =
846 		g_dbus_connection_signal_subscribe (connection,
847 		                                    dbus_name ? dbus_name : service,
848 		                                    "org.freedesktop.Tracker3.Endpoint",
849 		                                    "GraphUpdated",
850 		                                    dbus_path ? dbus_path : object_path,
851 		                                    full_graph ? full_graph : graph,
852 		                                    G_DBUS_SIGNAL_FLAGS_NONE,
853 		                                    graph_updated_cb,
854 		                                    subscription, NULL);
855 
856 	g_hash_table_insert (priv->subscriptions,
857 	                     GUINT_TO_POINTER (subscription->handler_id),
858 	                     subscription);
859 
860 	g_free (dbus_name);
861 	g_free (dbus_path);
862 	g_free (full_graph);
863 
864 	return subscription->handler_id;
865 }
866 
867 /**
868  * tracker_notifier_signal_unsubscribe:
869  * @notifier: a #TrackerNotifier
870  * @handler_id: a handler ID obtained with tracker_notifier_signal_subscribe()
871  *
872  * Undoes a DBus signal subscription, the @handler_id argument was previously
873  * obtained with a tracker_notifier_signal_subscribe() call.
874  *
875  * Since: 3.0
876  **/
877 void
tracker_notifier_signal_unsubscribe(TrackerNotifier * notifier,guint handler_id)878 tracker_notifier_signal_unsubscribe (TrackerNotifier *notifier,
879                                      guint            handler_id)
880 {
881 	TrackerNotifierPrivate *priv;
882 
883 	g_return_if_fail (TRACKER_IS_NOTIFIER (notifier));
884 	g_return_if_fail (handler_id != 0);
885 
886 	priv = tracker_notifier_get_instance_private (notifier);
887 
888 	g_hash_table_remove (priv->subscriptions, GUINT_TO_POINTER (handler_id));
889 }
890 
891 gpointer
_tracker_notifier_get_connection(TrackerNotifier * notifier)892 _tracker_notifier_get_connection (TrackerNotifier *notifier)
893 {
894 	TrackerNotifierPrivate *priv;
895 
896 	priv = tracker_notifier_get_instance_private (notifier);
897 
898 	return priv->connection;
899 }
900 
901 /**
902  * tracker_notifier_event_get_event_type:
903  * @event: A #TrackerNotifierEvent
904  *
905  * Returns the event type.
906  *
907  * Returns: The event type
908  **/
909 TrackerNotifierEventType
tracker_notifier_event_get_event_type(TrackerNotifierEvent * event)910 tracker_notifier_event_get_event_type (TrackerNotifierEvent *event)
911 {
912 	g_return_val_if_fail (event != NULL, -1);
913 	return event->type;
914 }
915 
916 /**
917  * tracker_notifier_event_get_id:
918  * @event: A #TrackerNotifierEvent
919  *
920  * Returns the tracker:id of the element being notified upon. This is a #gint64
921  * which is used as efficient internal identifier for the resource.
922  *
923  * Returns: the resource ID
924  **/
925 gint64
tracker_notifier_event_get_id(TrackerNotifierEvent * event)926 tracker_notifier_event_get_id (TrackerNotifierEvent *event)
927 {
928 	g_return_val_if_fail (event != NULL, 0);
929 	return event->id;
930 }
931 
932 /**
933  * tracker_notifier_event_get_urn:
934  * @event: A #TrackerNotifierEvent
935  *
936  * Returns the Uniform Resource Name of the element. This is Tracker's
937  * public identifier for the resource.
938  *
939  * This URN is an unique string identifier for the resource being
940  * notified upon, typically of the form "urn:uuid:...".
941  *
942  * Returns: The element URN
943  **/
944 const gchar *
tracker_notifier_event_get_urn(TrackerNotifierEvent * event)945 tracker_notifier_event_get_urn (TrackerNotifierEvent *event)
946 {
947 	g_return_val_if_fail (event != NULL, NULL);
948 	return event->urn;
949 }
950