Coverage Report

Created: 2025-12-23 06:29

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