/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 | } |