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