Following Data Updates

Tracker 0.9.x and 0.10.x

Introduction

If your application is interested in changes in Tracker's RDF store you need to subscribe to the appropriate D-Bus signal from the Tracker service.

The daemon tracker-store is responsible for publishing the D-Bus interface:

  • D-Bus service : org.freedesktop.Tracker1
  • D-Bus interface : org.freedesktop.Tracker1.Resources
  • Signal name : GraphUpdated

org.freedesktop.Tracker1.Resources

<node name="/org/freedesktop/Tracker1/Resources">
  <interface name="org.freedesktop.Tracker1.Resources">
   <signal name="GraphUpdated">
      <arg type="s" name ="class_name" />
      <arg type="a(iiii)" name="deletes" />
      <arg type="a(iiii)" name="inserts" />
   </signal>
  </interface>
</node>

Detail of the arrays:

  • The first i is the graph-ID
  • The second i is the subject-ID
  • The third i is the predicate-ID
  • The forth i is the object-ID if the object isn't a literal, else its value is 0

The arrays are sorted on subject-ID.

Which classes

The signaling system isn't enabled for all classes. Only the classes that have tracker:notify set to true have support. This is defined in the ontology. If it is not, the class will not be signalled when data changes for that class.

You can see which classes qualify for notifications using the following SPARQL command:

$ tracker-sparql -q "select ?class where { ?class tracker:notify true }"
Result: 19
  http://www.semanticdesktop.org/ontologies/2007/08/15/nao#Tag
  http://www.semanticdesktop.org/ontologies/2007/03/22/nco#Contact
  http://www.semanticdesktop.org/ontologies/2007/03/22/nco#IMAccount
  http://www.semanticdesktop.org/ontologies/2007/03/22/nco#PersonContact
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Image
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#BookmarkFolder
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Bookmark
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#WebHistory
  http://www.semanticdesktop.org/ontologies/2007/03/22/nmo#Message
  http://www.semanticdesktop.org/ontologies/2007/04/02/ncal#Event
  http://www.semanticdesktop.org/ontologies/2007/04/02/ncal#Todo
  http://www.semanticdesktop.org/ontologies/2007/04/02/ncal#Journal
  http://www.tracker-project.org/temp/nmm#MusicPiece
  http://www.tracker-project.org/temp/nmm#Video
  http://www.tracker-project.org/temp/mto#TransferElement
  http://www.tracker-project.org/temp/mto#Transfer
  http://www.tracker-project.org/temp/mfo#FeedChannel
  http://www.tracker-project.org/temp/mfo#FeedMessage

Interesting predicates

A interesting predicate to consider is tracker:available. This single value boolean property will be set on resources that are available to true and will either be unset or will be set to false for resources that aren't available. For example with the removable volume support you can detect this way which resources disappear as a result of a removable volume being unmounted.

SPARQL features that will help you

tracker:id() combined with IN

SELECT ?t { ?r nie:title ?t .
            FILTER (tracker:id(?r) IN (800, 801, 802, 807)) }

Where 800, 801, 802 and 807 will be the IDs that you receive in the class signal. And with tracker:uri(int id) it goes like:

tracker:subject()

SELECT tracker:uri (800) tracker:uri (801)
       tracker:uri (802) tracker:uri (807) { }

Deletes and creates

Note that if you want to track deletes of resources, that you'll need a local table with ID -> for example URI. As you can't query on the ID anymore by the time the resource is deleted. Resource deletes and creates have the predicate ID for rdf:type in the array.

More info on SPARQL features like IN

Examples

GDBus

#include <gio/gio.h>
#include <libtracker-sparql/tracker-sparql.h>
static TrackerSparqlConnection *con;

static void
handle_statement (gint subject, gint predicate)
{
        gchar *query, *pred;
        TrackerSparqlCursor *cursor;

        query = g_strdup_printf ("SELECT tracker:uri (%d) tracker:uri(%d) {}",
                                 subject, predicate);
        cursor = tracker_sparql_connection_query (con, query, NULL, NULL);
        g_free (query);
        tracker_sparql_cursor_next (cursor, NULL, NULL);
        pred = g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL));
        query = g_strdup_printf ("SELECT ?t { <%s> <%s> ?t }",
                                 tracker_sparql_cursor_get_string (cursor, 0, NULL),
                                 pred);
        g_object_unref (cursor);
        cursor = tracker_sparql_connection_query (con, query, NULL, NULL);
        g_free (query);
        while (tracker_sparql_cursor_next (cursor, NULL, NULL))
                g_print ("\t%s = %s\n", pred, tracker_sparql_cursor_get_string (cursor, 0, NULL));
        g_print ("\n");
        g_free (pred);
        g_object_unref (cursor);
}

static void
class_signal_cb (GDBusConnection *connection,
                 const gchar     *sender_name,
                 const gchar     *object_path,
                 const gchar     *interface_name,
                 const gchar     *signal_name,
                 GVariant        *parameters,
                 gpointer         user_data)

{
        GVariantIter *iter1, *iter2;
        gchar *class_name;
        gint graph = 0, subject = 0, predicate = 0, object = 0;
        g_variant_get (parameters, "(&sa(iiii)a(iiii))", &class_name, &iter1, &iter2);
        g_print ("%s:\n", class_name);
        while (g_variant_iter_loop (iter1, "(iiii)", &graph, &subject, &predicate, &object))
                handle_statement (subject, predicate);
        while (g_variant_iter_loop (iter2, "(iiii)", &graph, &subject, &predicate, &object))
                handle_statement (subject, predicate);
        g_variant_iter_free (iter1);
        g_variant_iter_free (iter2);
}

gint
main (gint argc, gchar *argv[])
{
        GMainLoop *loop;
        GError *error = NULL;
        GDBusConnection *connection;
        guint signal_id;
        g_type_init ();
        loop = g_main_loop_new (NULL, FALSE);
        con = tracker_sparql_connection_get (NULL, &error);
        connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
        signal_id = g_dbus_connection_signal_subscribe (connection,
                                                        TRACKER_DBUS_SERVICE,
                                                        TRACKER_DBUS_INTERFACE_RESOURCES,
                                                        "GraphUpdated",
                                                        TRACKER_DBUS_OBJECT_RESOURCES,
                                                        NULL, /* Use class-name here */
                                                        G_DBUS_SIGNAL_FLAGS_NONE,
                                                        class_signal_cb,
                                                        NULL,
                                                        NULL);

        g_main_loop_run (loop);
        g_dbus_connection_signal_unsubscribe (connection, signal_id);
        g_main_loop_unref (loop);
        g_object_unref (con);
        g_object_unref (connection);
        return 0;
}

dbus-1

#include <dbus/dbus-glib-bindings.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <libtracker-sparql/tracker-sparql.h>

#define TRACKER_SERVICE                 "org.freedesktop.Tracker1"
#define TRACKER_RESOURCES_OBJECT        "/org/freedesktop/Tracker1/Resources"
#define TRACKER_INTERFACE_RESOURCES     "org.freedesktop.Tracker1.Resources"
#define DBUS_MATCH_STR  "type='signal', " \
        "sender='" TRACKER_SERVICE "', " \
        "path='" TRACKER_RESOURCES_OBJECT "', " \
        "interface='" TRACKER_INTERFACE_RESOURCES "'"

static TrackerSparqlConnection *con;

static void
handle_statement (gint subject, gint predicate)
{
        gchar *query, *pred;
        TrackerSparqlCursor *cursor;

        query = g_strdup_printf ("SELECT tracker:uri (%d) tracker:uri (%d) {}",
                                 subject, predicate);
        cursor = tracker_sparql_connection_query (con, query, NULL, NULL);
        g_free (query);
        tracker_sparql_cursor_next (cursor, NULL, NULL);
        pred = g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL));
        query = g_strdup_printf ("SELECT ?t { <%s> <%s> ?t }",
                                 tracker_sparql_cursor_get_string (cursor, 0, NULL),
                                 pred);
        g_object_unref (cursor);
        cursor = tracker_sparql_connection_query (con, query, NULL, NULL);
        g_free (query);
        while (tracker_sparql_cursor_next (cursor, NULL, NULL))
                g_print ("\t%s = %s\n", pred, tracker_sparql_cursor_get_string (cursor, 0, NULL));
        g_print ("\n");
        g_free (pred);
        g_object_unref (cursor);
}

static void
class_signal_cb (DBusMessage *message)
{
        DBusMessageIter iter, arr;
        gchar *class_name;
        gint arg_type, i;

        dbus_message_iter_init (message, &iter);
        dbus_message_iter_get_basic (&iter, &class_name);
        g_print ("%s:\n", class_name);

        for (i = 0; i < 2; i++) {
                dbus_message_iter_next (&iter);
                dbus_message_iter_recurse (&iter, &arr);

                while ((arg_type = dbus_message_iter_get_arg_type (&arr)) != DBUS_TYPE_INVALID) {
                        DBusMessageIter strct;
                        gint graph = 0, subject = 0, predicate = 0, object = 0;

                        dbus_message_iter_recurse (&arr, &strct);
                        dbus_message_iter_get_basic (&strct, &graph);
                        dbus_message_iter_next (&strct);
                        dbus_message_iter_get_basic (&strct, &subject);
                        dbus_message_iter_next (&strct);
                        dbus_message_iter_get_basic (&strct, &predicate);
                        dbus_message_iter_next (&strct);
                        dbus_message_iter_get_basic (&strct, &object);
                        handle_statement (subject, predicate);
                        dbus_message_iter_next (&arr);
                }
        }
}

static DBusHandlerResult
message_filter (DBusConnection *connection, DBusMessage *message, gpointer ud)
{
        if (dbus_message_is_signal (message, TRACKER_INTERFACE_RESOURCES, "GraphUpdated")) {
                class_signal_cb (message);
                return DBUS_HANDLER_RESULT_HANDLED;
        }
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

gint
main (gint argc, gchar *argv[])
{
        GMainLoop *loop;
        GError *error = NULL;
        DBusConnection *connection;

        g_type_init ();
        loop = g_main_loop_new (NULL, FALSE);
        con = tracker_sparql_connection_get (&error);
        connection = dbus_bus_get_private (DBUS_BUS_SESSION, NULL);
        dbus_bus_request_name (connection, TRACKER_SERVICE, 0, NULL);
        dbus_connection_add_filter (connection, message_filter, NULL, NULL);
        dbus_bus_add_match (connection, DBUS_MATCH_STR, NULL);
        dbus_connection_setup_with_g_main (connection, NULL);
        g_main_loop_run (loop);
        g_main_loop_unref (loop);
        g_object_unref (con);
        dbus_connection_unref (connection);

        return 0;
}

Tracker 0.8

Introduction

If your application is interested in changes in Tracker's RDF store you need to subscribe to the appropriate D-Bus signals from the Tracker service.

The daemon tracker-store is responsible for publishing these D-Bus interfaces:

  • D-Bus service : org.freedesktop.Tracker1
  • D-Bus interface : org.freedesktop.Tracker1.Resources.Class

org.freedesktop.Tracker1.Resources.Class

<node name="/org/freedesktop/Tracker1/Resources/Classes/$pfx/$class_name">
  <interface name="org.freedesktop.Tracker1.Resources.Class">
    <signal name="SubjectsChanged">
      <arg type="as" name="subject" />
      <arg type="as" name="predicate" />
    </signal>
    <signal name="SubjectsAdded">
      <arg type="as" name="subject" />
    </signal>
    <signal name="SubjectsRemoved">
      <arg type="as" name="subject" />
    </signal>
  </interface>
</node>

D-Bus Objects

The allowance filter is a query that matches every class that has tracker:notify set to true. This is defined in the ontology. If it is not, the class will not be signalled when data changes for that class.

allowance_filter = SELECT ?class WHERE { ?class tracker:notify true }

foreach $pfx_colon_class_name in $allowance_filter.exec ():
    /org/freedesktop/Tracker1/Resources/Classes/$pfx_colon_class_name
        (with : replaced by / in $pfx_colon_class_name)

You can see which classes qualify for notifications using the following SPARQL command:

$ tracker-sparql -q "select ?class where { ?class tracker:notify true }"
Result: 19
  http://www.semanticdesktop.org/ontologies/2007/08/15/nao#Tag
  http://www.semanticdesktop.org/ontologies/2007/03/22/nco#Contact
  http://www.semanticdesktop.org/ontologies/2007/03/22/nco#IMAccount
  http://www.semanticdesktop.org/ontologies/2007/03/22/nco#PersonContact
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Image
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#BookmarkFolder
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Bookmark
  http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#WebHistory
  http://www.semanticdesktop.org/ontologies/2007/03/22/nmo#Message
  http://www.semanticdesktop.org/ontologies/2007/04/02/ncal#Event
  http://www.semanticdesktop.org/ontologies/2007/04/02/ncal#Todo
  http://www.semanticdesktop.org/ontologies/2007/04/02/ncal#Journal
  http://www.tracker-project.org/temp/nmm#MusicPiece
  http://www.tracker-project.org/temp/nmm#Video
  http://www.tracker-project.org/temp/mto#TransferElement
  http://www.tracker-project.org/temp/mto#Transfer
  http://www.tracker-project.org/temp/mfo#FeedChannel
  http://www.tracker-project.org/temp/mfo#FeedMessage

These classes are used in the following form:

/org/freedesktop/Tracker1/Resources/Classes/nfo/Document
/org/freedesktop/Tracker1/Resources/Classes/nco/Contact

Application developers can use the SPARQL query, or the DBus method ListNames to get all classes that can be monitored.

Example

#include <dbus/dbus-glib-bindings.h>
#include <dbus/dbus-glib-lowlevel.h>

static void
subjects_changed_cb (DBusGProxy *proxy, const gchar **subjects, const gchar **predicates)
{
  int i;

  for (i = 0; subjects[i] != NULL && predicates[i] != NULL; i++) {
    g_print ("<%s>'s <%s> changed", subjects[i], predicates[i]);
  }
}

int main (int **argc, char **argv)
{
  GMainLoop *loop;
  GError *error = NULL;
  DBusGConnection *connection;
  DBusGProxy *dbus_proxy;

  loop = g_main_loop_new (NULL, FALSE);

  connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
  dbus_proxy = dbus_g_proxy_new_for_name (connection,
                                          "org.freedesktop.Tracker1",
                                          "/org/freedesktop/Tracker1/Resources/Classes/nco/Contact",
                                          "org.freedesktop.Tracker1.Resources.Class");

  dbus_g_proxy_add_signal (dbus_proxy, "SubjectsChanged",
                           G_TYPE_STRV, G_TYPE_STRV
                           G_TYPE_INVALID);

  dbus_g_proxy_connect_signal (dbus_proxy, "SubjectsChanged",
                               G_CALLBACK (subjects_changed_cb),
                               NULL, NULL);


  g_main_loop_run (loop);

  g_object_unref (dbus_proxy);
  g_main_loop_unref (loop);

  return 0;
}

Attic/Tracker/Documentation/SignalsOnChanges (last edited 2023-08-14 12:50:17 by CarlosGarnacho)