[grilo-plugins/wip/carlosg/tracker3: 2/40] plugins: Add tracker3 plugin



commit 04a5c352b85562066eff0b97e2fce1bb4448921e
Author: Carlos Garnacho <carlosg gnome org>
Date:   Tue May 5 19:00:30 2020 +0200

    plugins: Add tracker3 plugin
    
    This is a separate plugin as it requires a version bump, different
    queries, and can do some niceties supported in the new version.

 meson.build                              |    2 +
 meson_options.txt                        |    1 +
 src/tracker3/grl-tracker-request-queue.c |  227 +++++
 src/tracker3/grl-tracker-request-queue.h |   84 ++
 src/tracker3/grl-tracker-source-api.c    | 1468 ++++++++++++++++++++++++++++++
 src/tracker3/grl-tracker-source-api.h    |   77 ++
 src/tracker3/grl-tracker-source-cache.c  |  190 ++++
 src/tracker3/grl-tracker-source-cache.h  |   47 +
 src/tracker3/grl-tracker-source-notif.c  |  234 +++++
 src/tracker3/grl-tracker-source-notif.h  |   55 ++
 src/tracker3/grl-tracker-source-priv.h   |   87 ++
 src/tracker3/grl-tracker-source.c        |  316 +++++++
 src/tracker3/grl-tracker-source.h        |   97 ++
 src/tracker3/grl-tracker-utils.c         |  879 ++++++++++++++++++
 src/tracker3/grl-tracker-utils.h         |   95 ++
 src/tracker3/grl-tracker.c               |  316 +++++++
 src/tracker3/grl-tracker.h               |   37 +
 src/tracker3/meson.build                 |   37 +
 18 files changed, 4249 insertions(+)
---
diff --git a/meson.build b/meson.build
index 8a590b93..e0a77fc8 100644
--- a/meson.build
+++ b/meson.build
@@ -77,6 +77,7 @@ sqlite3_dep = dependency('sqlite3', required: false)
 totem_plparser_dep = dependency('totem-plparser', version: '>= 3.4.1', required: false)
 totem_plparser_mini_dep = dependency('totem-plparser-mini', version: '>= 3.4.1', required: false)
 tracker_sparql_dep = dependency('tracker-sparql-2.0', version: '>= 2.3.0', required: false)
+tracker3_dep = dependency('tracker-sparql-3.0', required: false)
 
 lua_dep = dependency('lua', version: '>= 5.3.0', required: false)
 if not lua_dep.found()
@@ -128,6 +129,7 @@ plugins = [ # NAME, REQ_DEPS, OPT_DEPS
     ['thetvdb', [grilo_net_dep, libxml_dep, libarchive_dep, gom_dep], []],
     ['tmdb', [json_glib_dep, libsoup_dep, grilo_net_dep], []],
     ['tracker', [tracker_sparql_dep], []],
+    ['tracker3', [tracker3_dep], []],
     ['vimeo', [grilo_net_dep, libxml_dep, totem_plparser_dep], []],
     ['youtube', [grilo_net_dep, libxml_dep, libgdata_dep, totem_plparser_dep], []],
 ]
diff --git a/meson_options.txt b/meson_options.txt
index ec324f63..77558aed 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -20,5 +20,6 @@ option('enable-shoutcast', type: 'combo', choices: [ 'auto', 'yes', 'no' ], valu
 option('enable-thetvdb', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 
'Enable Thetvdb plugin')
 option('enable-tmdb', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable 
TMDb plugin')
 option('enable-tracker', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 
'Enable Tracker plugin')
+option('enable-tracker3', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 
'Enable Tracker3 plugin')
 option('enable-vimeo', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable 
Vimeo plugin')
 option('enable-youtube', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 
'Enable YouTube plugin')
diff --git a/src/tracker3/grl-tracker-request-queue.c b/src/tracker3/grl-tracker-request-queue.c
new file mode 100644
index 00000000..fb7d59ea
--- /dev/null
+++ b/src/tracker3/grl-tracker-request-queue.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include "grl-tracker.h"
+#include "grl-tracker-request-queue.h"
+
+/**/
+
+struct _GrlTrackerQueue {
+  GList      *head;
+  GList      *tail;
+  GHashTable *operations;
+  GHashTable *operations_ids;
+};
+
+/**/
+
+static void
+grl_tracker_op_terminate (GrlTrackerOp *os)
+{
+  if (os == NULL)
+    return;
+
+  g_clear_object (&os->cursor);
+  g_object_unref (os->cancel);
+  g_free (os->request);
+
+  g_slice_free (GrlTrackerOp, os);
+}
+
+static GrlTrackerOp *
+grl_tracker_op_initiate (gchar               *request,
+                         GAsyncReadyCallback  callback,
+                         gpointer             data)
+{
+  GrlTrackerOp *os = g_slice_new0 (GrlTrackerOp);
+
+  os->request  = request;
+  os->callback = callback;
+  os->data     = data;
+  os->cancel   = g_cancellable_new ();
+
+  return os;
+}
+
+GrlTrackerOp *
+grl_tracker_op_initiate_query (guint                operation_id,
+                               gchar               *request,
+                               GAsyncReadyCallback  callback,
+                               gpointer             data)
+{
+  GrlTrackerOp *os = grl_tracker_op_initiate (request,
+                                              callback,
+                                              data);
+
+  os->type         = GRL_TRACKER_OP_TYPE_QUERY;
+  os->operation_id = operation_id;
+
+  /* g_hash_table_insert (grl_tracker_operations, */
+  /*                      GSIZE_TO_POINTER (operation_id), os); */
+
+  return os;
+}
+
+GrlTrackerOp *
+grl_tracker_op_initiate_metadata (gchar               *request,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             data)
+{
+  GrlTrackerOp *os = grl_tracker_op_initiate (request,
+                                              callback,
+                                              data);
+
+  os->type = GRL_TRACKER_OP_TYPE_QUERY;
+
+  return os;
+}
+
+GrlTrackerOp *
+grl_tracker_op_initiate_set_metadata (gchar               *request,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             data)
+{
+  GrlTrackerOp *os = grl_tracker_op_initiate (request,
+                                              callback,
+                                              data);
+
+  os->type = GRL_TRACKER_OP_TYPE_UPDATE;
+
+  return os;
+}
+
+static void
+grl_tracker_op_start (GrlTrackerOp *os)
+{
+  switch (os->type) {
+  case GRL_TRACKER_OP_TYPE_QUERY:
+    tracker_sparql_connection_query_async (grl_tracker_connection,
+                                           os->request,
+                                           NULL,
+                                           os->callback,
+                                           os);
+    break;
+
+  case GRL_TRACKER_OP_TYPE_UPDATE:
+    tracker_sparql_connection_update_async (grl_tracker_connection,
+                                            os->request,
+                                            G_PRIORITY_DEFAULT,
+                                            NULL,
+                                            os->callback,
+                                            os);
+    break;
+
+  default:
+    g_assert_not_reached();
+    break;
+  }
+}
+
+/**/
+
+GrlTrackerQueue *
+grl_tracker_queue_new (void)
+{
+  GrlTrackerQueue *queue = g_new0 (GrlTrackerQueue, 1);
+
+  queue->operations     = g_hash_table_new (g_direct_hash, g_direct_equal);
+  queue->operations_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  return queue;
+}
+
+void
+grl_tracker_queue_push (GrlTrackerQueue *queue,
+                        GrlTrackerOp    *os)
+{
+  gboolean first = FALSE;
+
+  queue->tail = g_list_append (queue->tail, os);
+  if (queue->tail->next)
+    queue->tail = queue->tail->next;
+  else {
+    queue->head = queue->tail;
+    first = TRUE;
+  }
+
+  g_assert (queue->tail->next == NULL);
+
+  g_hash_table_insert (queue->operations, os, queue->tail);
+  if (os->operation_id != 0)
+    g_hash_table_insert (queue->operations_ids,
+                         GSIZE_TO_POINTER (os->operation_id), os);
+
+  if (first)
+    grl_tracker_op_start (os);
+}
+
+void
+grl_tracker_queue_cancel (GrlTrackerQueue *queue,
+                          GrlTrackerOp    *os)
+{
+  GList *item = g_hash_table_lookup (queue->operations, os);
+
+  if (!item)
+    return;
+
+  g_cancellable_cancel (os->cancel);
+
+  g_hash_table_remove (queue->operations, os);
+  if (os->operation_id != 0)
+    g_hash_table_remove (queue->operations_ids,
+                         GSIZE_TO_POINTER (os->operation_id));
+
+  if (item == queue->head) {
+    queue->head = queue->head->next;
+  }
+  if (item == queue->tail) {
+    queue->tail = queue->tail->prev;
+  }
+
+  if (item->prev)
+    item->prev->next = item->next;
+  if (item->next)
+    item->next->prev = item->prev;
+
+  item->next = NULL;
+  item->prev = NULL;
+  g_list_free (item);
+}
+
+void
+grl_tracker_queue_done (GrlTrackerQueue *queue,
+                        GrlTrackerOp    *os)
+{
+  GrlTrackerOp *next_os;
+
+  grl_tracker_queue_cancel (queue, os);
+  grl_tracker_op_terminate (os);
+
+  if (!queue->head)
+    return;
+
+  next_os = queue->head->data;
+
+  grl_tracker_op_start (next_os);
+}
diff --git a/src/tracker3/grl-tracker-request-queue.h b/src/tracker3/grl-tracker-request-queue.h
new file mode 100644
index 00000000..8805b9cf
--- /dev/null
+++ b/src/tracker3/grl-tracker-request-queue.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_REQUEST_QUEUE_H_
+#define _GRL_TRACKER_REQUEST_QUEUE_H_
+
+#include <grilo.h>
+#include <tracker-sparql.h>
+
+/**/
+
+typedef enum {
+  GRL_TRACKER_OP_TYPE_QUERY,
+  GRL_TRACKER_OP_TYPE_UPDATE,
+} GrlTrackerOpType;
+
+typedef struct {
+  GrlTrackerOpType         type;
+  GAsyncReadyCallback      callback;
+  GCancellable            *cancel;
+  TrackerSparqlConnection *connection;
+  gchar                   *request;
+  const GList             *keys;
+  gpointer                 data;
+
+  TrackerSparqlCursor *cursor;
+
+  guint operation_id;
+
+  guint skip;
+  guint count;
+  guint current;
+  GrlTypeFilter type_filter;
+} GrlTrackerOp;
+
+typedef struct _GrlTrackerQueue GrlTrackerQueue;
+
+/**/
+
+GrlTrackerOp *grl_tracker_op_initiate_query (guint                operation_id,
+                                             gchar               *request,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             data);
+
+GrlTrackerOp *grl_tracker_op_initiate_metadata (gchar               *request,
+                                                GAsyncReadyCallback  callback,
+                                                gpointer             data);
+
+GrlTrackerOp *grl_tracker_op_initiate_set_metadata (gchar               *request,
+                                                    GAsyncReadyCallback  callback,
+                                                    gpointer             data);
+
+/**/
+
+GrlTrackerQueue *grl_tracker_queue_new (void);
+
+void grl_tracker_queue_push (GrlTrackerQueue *queue, GrlTrackerOp *os);
+
+void grl_tracker_queue_cancel (GrlTrackerQueue *queue, GrlTrackerOp *os);
+
+void grl_tracker_queue_done (GrlTrackerQueue *queue, GrlTrackerOp *os);
+
+#endif /* _GRL_TRACKER_REQUEST_QUEUE_H_ */
diff --git a/src/tracker3/grl-tracker-source-api.c b/src/tracker3/grl-tracker-source-api.c
new file mode 100644
index 00000000..5abf3a05
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-api.c
@@ -0,0 +1,1468 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gio/gio.h>
+#include <tracker-sparql.h>
+
+#include "grl-tracker.h"
+#include "grl-tracker-source-api.h"
+#include "grl-tracker-source-cache.h"
+#include "grl-tracker-source-priv.h"
+#include "grl-tracker-request-queue.h"
+#include "grl-tracker-utils.h"
+
+/* --------- Logging  -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT tracker_source_request_log_domain
+
+GRL_LOG_DOMAIN_STATIC(tracker_source_request_log_domain);
+GRL_LOG_DOMAIN_STATIC(tracker_source_result_log_domain);
+
+/* Inputs/requests */
+#define GRL_IDEBUG(args...)                     \
+  GRL_LOG (tracker_source_request_log_domain,   \
+           GRL_LOG_LEVEL_DEBUG, args)
+
+/* Outputs/results */
+#define GRL_ODEBUG(args...)                     \
+  GRL_LOG (tracker_source_result_log_domain,    \
+           GRL_LOG_LEVEL_DEBUG, args)
+
+/* ------- Definitions ------- */
+
+#define TRACKER_QUERY_LIMIT                     \
+  "OFFSET %u "                                  \
+  "LIMIT %u"
+
+#define TRACKER_QUERY_PARTIAL_REQUEST           \
+  "SELECT rdf:type(?urn) %s "                   \
+  "WHERE { %s . %s } "                          \
+  "ORDER BY DESC(nfo:fileLastModified(?urn)) "  \
+  TRACKER_QUERY_LIMIT
+
+#define TRACKER_QUERY_FULL_REQUEST              \
+  "%s "                                         \
+  TRACKER_QUERY_LIMIT
+
+#define TRACKER_SEARCH_REQUEST                  \
+  "SELECT DISTINCT rdf:type(?urn) %s "          \
+  "WHERE "                                      \
+  "{ "                                          \
+  "%s "                                         \
+  "?urn tracker:available ?tr . "               \
+  "?urn fts:match \"%s\" . "                    \
+  "%s %s "                                      \
+  "} "                                          \
+  "ORDER BY DESC(nfo:fileLastModified(?urn)) "  \
+  "OFFSET %u "                                  \
+  "LIMIT %u"
+
+#define TRACKER_SEARCH_ALL_REQUEST              \
+  "SELECT DISTINCT rdf:type(?urn) %s "          \
+  "WHERE "                                      \
+  "{ "                                          \
+  "%s "                                         \
+  "?urn tracker:available ?tr . "               \
+  "%s %s "                                      \
+  "} "                                          \
+  "ORDER BY DESC(nfo:fileLastModified(?urn)) "  \
+  "OFFSET %u "                                  \
+  "LIMIT %u"
+
+#define TRACKER_BROWSE_SHOW_DOCUMENTS           \
+  "{ ?urn a nfo:Document } UNION"
+
+#define TRACKER_BROWSE_CATEGORY_REQUEST         \
+  "SELECT rdf:type(?urn) %s "                   \
+  "WHERE "                                      \
+  "{ "                                          \
+  "?urn a %s . "                                \
+  "?urn nie:isStoredAs ?file . "                \
+  "?file tracker:available ?tr . "              \
+  "%s "                                         \
+  "%s "                                         \
+  "} "                                          \
+  "ORDER BY DESC(nfo:fileLastModified(?urn)) "  \
+  "OFFSET %u "                                  \
+  "LIMIT %u"
+
+#define TRACKER_BROWSE_FILESYSTEM_ROOT_REQUEST          \
+  "SELECT DISTINCT rdf:type(?urn) %s "                  \
+  "WHERE "                                              \
+  "{ "                                                  \
+  "%s "                                                 \
+  "{ ?urn a nfo:Folder } %s "                           \
+  "%s "                                                 \
+  "FILTER (!bound(nfo:belongsToContainer(?urn))) "      \
+  "} "                                                  \
+  "ORDER BY DESC(nfo:fileLastModified(?urn)) "          \
+  "OFFSET %u "                                          \
+  "LIMIT %u"
+
+#define TRACKER_BROWSE_FILESYSTEM_REQUEST                       \
+  "SELECT DISTINCT rdf:type(?urn) %s "                          \
+  "WHERE "                                                      \
+  "{ "                                                          \
+    "%s "                                                       \
+  "{ ?urn a nfo:Folder } %s "                                   \
+  "%s "                                                         \
+  "FILTER(tracker:id(nfo:belongsToContainer(?urn)) = %s) "      \
+  "} "                                                          \
+  "ORDER BY DESC(nfo:fileLastModified(?urn)) "                  \
+  "OFFSET %u "                                                  \
+  "LIMIT %u"
+
+#define TRACKER_RESOLVE_REQUEST                 \
+  "SELECT %s "                                  \
+  "WHERE "                                      \
+  "{ "                                          \
+  "?urn a nie:InformationElement ; "            \
+  "  nie:isStoredAs ?file . "                   \
+  "FILTER (tracker:id(?urn) = %s) "             \
+  "}"
+
+#define TRACKER_RESOLVE_URL_REQUEST             \
+  "SELECT %s "                                  \
+  "WHERE "                                      \
+  "{ "                                          \
+  "?urn a nie:DataObject . "                    \
+  "?urn nie:url \"%s\" "                        \
+  "}"
+
+#define TRACKER_DELETE_REQUEST                          \
+  "DELETE { <%s> %s } WHERE { <%s> a nfo:Media . %s }"
+
+#define TRACKER_SAVE_REQUEST                            \
+  "DELETE { <%s> %s } WHERE { <%s> a nfo:Media . %s } " \
+  "INSERT { <%s> a nfo:Media ; %s . }"
+
+#define TRACKER_TEST_MEDIA_FROM_URI_REQUEST             \
+  "SELECT ?urn "                                        \
+  "WHERE "                                              \
+  "{ "                                                  \
+  "?urn nie:url \"%s\" ; "                              \
+  "tracker:available true ; "                           \
+  "a nfo:Media . "                                      \
+  "%s "                                                 \
+  "}"
+
+#define TRACKER_TEST_MEDIA_FROM_URI_REQUEST_WITH_DOCUMENTS  \
+  "SELECT ?urn "                                            \
+  "WHERE "                                                  \
+  "{ "                                                      \
+  "?urn nie:url \"%s\" ; "                                  \
+  "tracker:available true . "                               \
+  "%s "                                                     \
+  "FILTER (?type IN ( nfo:Media, nfo:Document ))"           \
+  "}"
+
+#define TRACKER_MEDIA_FROM_URI_REQUEST          \
+  "SELECT rdf:type(?urn) %s "                   \
+  "WHERE "                                      \
+  "{ "                                          \
+  "?urn nie:url \"%s\" ; "                      \
+  "tracker:available ?tr . "                    \
+  "%s "                                         \
+  "} "                                          \
+
+/**/
+
+/**/
+
+static GrlKeyID    grl_metadata_key_tracker_category;
+static GHashTable *grl_tracker_operations;
+
+/**/
+
+
+/**/
+
+/**/
+
+static void
+set_title_from_filename (GrlMedia *media)
+{
+  const gchar *url;
+  gchar *path, *display_name, *ext, *title;
+  guint suffix_len;
+
+  url = grl_media_get_url (media);
+  if (url == NULL)
+    return;
+
+  path = g_filename_from_uri (url, NULL, NULL);
+  if (!path)
+    return;
+  display_name = g_filename_display_basename (path);
+  g_free (path);
+  ext = strrchr (display_name, '.');
+  if (!ext)
+    goto out;
+
+  suffix_len = strlen (ext);
+  if (suffix_len != 4 && suffix_len != 5)
+    goto out;
+
+  title = g_strndup (display_name, ext - display_name);
+  if (g_strcmp0 (grl_media_get_title (media), title) == 0)
+    grl_data_set_boolean (GRL_DATA (media), GRL_METADATA_KEY_TITLE_FROM_FILENAME, TRUE);
+  g_free (title);
+
+out:
+  g_free (display_name);
+}
+
+static void
+fill_grilo_media_from_sparql (GrlTrackerSource    *source,
+                              GrlMedia            *media,
+                              TrackerSparqlCursor *cursor,
+                              gint                 column)
+{
+  const gchar *sparql_key = tracker_sparql_cursor_get_variable_name (cursor,
+                                                                     column);
+  tracker_grl_sparql_t *assoc =
+    grl_tracker_get_mapping_from_sparql (sparql_key);
+  union {
+    gint64 int_val;
+    gdouble double_val;
+    const gchar *str_val;
+  } val;
+
+  GrlKeyID grl_key;
+
+  if (assoc == NULL) {
+    /* Maybe the user is setting the key */
+    GrlRegistry *registry = grl_registry_get_default ();
+    grl_key = grl_registry_lookup_metadata_key (registry, sparql_key);
+    if (grl_key == GRL_METADATA_KEY_INVALID) {
+      return;
+    }
+  } else {
+    grl_key = assoc->grl_key;
+  }
+
+  GRL_ODEBUG ("\tSetting media prop (col=%i/var=%s/prop=%s) %s",
+              column,
+              sparql_key,
+              GRL_METADATA_KEY_GET_NAME (grl_key),
+              tracker_sparql_cursor_get_string (cursor, column, NULL));
+
+  if (tracker_sparql_cursor_is_bound (cursor, column) == FALSE) {
+    GRL_ODEBUG ("\t\tDropping, no data");
+    return;
+  }
+
+  if (grl_data_has_key (GRL_DATA (media), grl_key)) {
+    GRL_ODEBUG ("\t\tDropping, already here");
+    return;
+  }
+
+  if (assoc && assoc->set_value) {
+    assoc->set_value (cursor, column, media, assoc->grl_key);
+  } else {
+    GType grl_type = GRL_METADATA_KEY_GET_TYPE (grl_key);
+    if (grl_type == G_TYPE_STRING) {
+      /* Cache the source associated to this result. */
+      if (grl_key == GRL_METADATA_KEY_ID) {
+        grl_tracker_source_cache_add_item (grl_tracker_item_cache,
+                                           tracker_sparql_cursor_get_integer (cursor,
+                                                                              column),
+                                           source);
+      }
+      val.str_val = tracker_sparql_cursor_get_string (cursor, column, NULL);
+      if (val.str_val != NULL)
+        grl_data_set_string (GRL_DATA (media), grl_key, val.str_val);
+    } else if (grl_type == G_TYPE_INT) {
+      val.int_val = tracker_sparql_cursor_get_integer (cursor, column);
+      grl_data_set_int (GRL_DATA (media), grl_key, val.int_val);
+    } else if (grl_type == G_TYPE_INT64) {
+      val.int_val = tracker_sparql_cursor_get_integer (cursor, column);
+      grl_data_set_int64 (GRL_DATA (media), grl_key, val.int_val);
+    } else if (grl_type == G_TYPE_FLOAT) {
+      val.double_val = tracker_sparql_cursor_get_double (cursor, column);
+      grl_data_set_float (GRL_DATA (media), grl_key, (gfloat) val.double_val);
+    } else if (grl_type == G_TYPE_DATE_TIME) {
+      val.str_val = tracker_sparql_cursor_get_string (cursor, column, NULL);
+      GDateTime *date_time = grl_date_time_from_iso8601 (val.str_val);
+      grl_data_set_boxed (GRL_DATA (media), grl_key, date_time);
+      g_date_time_unref (date_time);
+    } else {
+      GRL_ODEBUG ("\t\tUnexpected data type");
+    }
+  }
+}
+
+static gchar *
+get_sparql_type_filter (GrlOperationOptions *options,
+                        gboolean prepend_union)
+{
+  GrlTypeFilter filter = grl_operation_options_get_type_filter (options);
+  GString *sparql_filter = g_string_new ("");
+
+  if (filter & GRL_TYPE_FILTER_AUDIO) {
+    if (prepend_union) {
+      sparql_filter = g_string_append (sparql_filter,
+                                       "UNION { ?urn a nfo:Audio } ");
+    } else {
+      sparql_filter = g_string_append (sparql_filter,
+                                       "{ ?urn a nfo:Audio } ");
+      prepend_union = TRUE;
+    }
+  }
+  if (filter & GRL_TYPE_FILTER_VIDEO) {
+    if (prepend_union) {
+      sparql_filter = g_string_append (sparql_filter,
+                                       "UNION { ?urn a nmm:Video } ");
+    } else {
+      sparql_filter = g_string_append (sparql_filter,
+                                       "{ ?urn a nmm:Video } ");
+      prepend_union = TRUE;
+    }
+  }
+  if (filter & GRL_TYPE_FILTER_IMAGE) {
+    if (prepend_union) {
+      sparql_filter = g_string_append (sparql_filter,
+                                       "UNION { ?urn a nmm:Photo } ");
+    } else {
+      sparql_filter = g_string_append (sparql_filter,
+                                       "{ ?urn a nmm:Photo } ");
+    }
+  }
+
+  sparql_filter = g_string_append_c (sparql_filter, '.');
+
+  return g_string_free (sparql_filter, FALSE);
+}
+
+/* I can haz templatze ?? */
+#define TRACKER_QUERY_CB(spec_type,name,error)                          \
+                                                                        \
+  static void                                                           \
+  tracker_##name##_result_cb (GObject      *source_object,              \
+                              GAsyncResult *result,                     \
+                              GrlTrackerOp *os)                         \
+  {                                                                     \
+    gint         col;                                                   \
+    const gchar *sparql_type;                                           \
+    GError      *tracker_error = NULL, *error = NULL;                   \
+    GrlMedia    *media;                                                 \
+    spec_type   *spec =                                                 \
+      (spec_type *) os->data;                                           \
+                                                                        \
+    GRL_ODEBUG ("%s", __FUNCTION__);                                    \
+                                                                        \
+    if (g_cancellable_is_cancelled (os->cancel)) {                      \
+      GRL_ODEBUG ("\tOperation %u cancelled", spec->operation_id);      \
+      spec->callback (spec->source,                        \
+                      spec->operation_id,                               \
+                      NULL, 0,                                          \
+                      spec->user_data, NULL);                           \
+      grl_tracker_queue_done (grl_tracker_queue, os);                   \
+                                                                        \
+      return;                                                           \
+    }                                                                   \
+                                                                        \
+    if (!tracker_sparql_cursor_next_finish (os->cursor,                 \
+                                            result,                     \
+                                            &tracker_error)) {          \
+      if (tracker_error != NULL) {                                      \
+        GRL_WARNING ("\terror in parsing query id=%u : %s",             \
+                     spec->operation_id, tracker_error->message);       \
+                                                                        \
+        error = g_error_new (GRL_CORE_ERROR,                            \
+                             GRL_CORE_ERROR_##error##_FAILED,           \
+                             _("Failed to query: %s"),                  \
+                             tracker_error->message);                   \
+                                                                        \
+        spec->callback (spec->source,                      \
+                        spec->operation_id,                             \
+                        NULL, 0,                                        \
+                        spec->user_data, error);                        \
+                                                                        \
+        g_error_free (error);                                           \
+        g_error_free (tracker_error);                                   \
+      } else {                                                          \
+        GRL_ODEBUG ("\tend of parsing id=%u :)", spec->operation_id);   \
+                                                                        \
+        /* Only emit this last one if more result than expected */      \
+        if (os->count > 1)                                              \
+          spec->callback (spec->source,                    \
+                          spec->operation_id,                           \
+                          NULL, 0,                                      \
+                          spec->user_data, NULL);                       \
+      }                                                                 \
+                                                                        \
+      grl_tracker_queue_done (grl_tracker_queue, os);                   \
+      return;                                                           \
+    }                                                                   \
+                                                                        \
+    sparql_type = tracker_sparql_cursor_get_string (os->cursor,         \
+                                                    0,                  \
+                                                    NULL);              \
+                                                                        \
+    GRL_ODEBUG ("\tParsing line %i of type %s",                         \
+                os->current, sparql_type);                              \
+                                                                        \
+    media = grl_tracker_build_grilo_media (sparql_type, os->type_filter);\
+                                                                        \
+    if (media != NULL) {                                                \
+      for (col = 1 ;                                                    \
+           col < tracker_sparql_cursor_get_n_columns (os->cursor) ;     \
+           col++) {                                                     \
+        fill_grilo_media_from_sparql (GRL_TRACKER_SOURCE (spec->source), \
+                                      media, os->cursor, col);          \
+      }                                                                 \
+      set_title_from_filename (media);                                  \
+                                                                        \
+      spec->callback (spec->source,                                     \
+                      spec->operation_id,                               \
+                      media,                                            \
+                      --os->count,                                      \
+                      spec->user_data,                                  \
+                      NULL);                                            \
+    }                                                                   \
+                                                                        \
+    /* Schedule the next line to parse */                               \
+    os->current++;                                                      \
+    if (os->count < 1)                                                  \
+      grl_tracker_queue_done (grl_tracker_queue, os);                   \
+    else                                                                \
+      tracker_sparql_cursor_next_async (os->cursor, os->cancel,         \
+                                        (GAsyncReadyCallback) tracker_##name##_result_cb, \
+                                        (gpointer) os);                 \
+  }                                                                     \
+                                                                        \
+  static void                                                           \
+  tracker_##name##_cb (GObject      *source_object,                     \
+                       GAsyncResult *result,                            \
+                       GrlTrackerOp *os)                                \
+  {                                                                     \
+    GError *tracker_error = NULL, *error = NULL;                        \
+    spec_type *spec = (spec_type *) os->data;                           \
+    TrackerSparqlConnection *connection =                               \
+      grl_tracker_source_get_tracker_connection (GRL_TRACKER_SOURCE (spec->source)); \
+                                                                        \
+    GRL_ODEBUG ("%s", __FUNCTION__);                                    \
+                                                                        \
+    os->cursor =                                                        \
+      tracker_sparql_connection_query_finish (connection,               \
+                                              result, &tracker_error);  \
+                                                                        \
+    if (tracker_error) {                                                \
+      GRL_WARNING ("Could not execute sparql query id=%u: %s",          \
+                   spec->operation_id, tracker_error->message);         \
+                                                                        \
+      error = g_error_new (GRL_CORE_ERROR,                              \
+                           GRL_CORE_ERROR_##error##_FAILED,             \
+                           _("Failed to query: %s"),                    \
+                           tracker_error->message);                     \
+                                                                        \
+      spec->callback (spec->source, spec->operation_id, NULL, 0,           \
+                      spec->user_data, error);                          \
+                                                                        \
+      g_error_free (tracker_error);                                     \
+      g_error_free (error);                                             \
+      grl_tracker_queue_done (grl_tracker_queue, os);                   \
+                                                                        \
+      return;                                                           \
+    }                                                                   \
+                                                                        \
+    /* Start parsing results */                                         \
+    os->current = 0;                                                    \
+    tracker_sparql_cursor_next_async (os->cursor, NULL,                 \
+                                      (GAsyncReadyCallback) tracker_##name##_result_cb, \
+                                      (gpointer) os);                   \
+  }
+
+TRACKER_QUERY_CB(GrlSourceQuerySpec, query, QUERY)
+TRACKER_QUERY_CB(GrlSourceBrowseSpec, browse, BROWSE)
+TRACKER_QUERY_CB(GrlSourceSearchSpec, search, SEARCH)
+
+static void
+tracker_resolve_cb (GObject      *source_object,
+                    GAsyncResult *result,
+                    GrlTrackerOp *os)
+{
+  GrlSourceResolveSpec *rs = (GrlSourceResolveSpec *) os->data;
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (rs->source);
+  gint                  col;
+  GError               *tracker_error = NULL, *error = NULL;
+  TrackerSparqlCursor  *cursor;
+
+  GRL_ODEBUG ("%s", __FUNCTION__);
+
+  cursor = tracker_sparql_connection_query_finish (priv->tracker_connection,
+                                                   result, &tracker_error);
+
+  if (tracker_error) {
+    GRL_WARNING ("Could not execute sparql resolve query : %s",
+                 tracker_error->message);
+
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_RESOLVE_FAILED,
+                         _("Failed to resolve: %s"),
+                         tracker_error->message);
+
+    rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, error);
+
+    g_error_free (tracker_error);
+    g_error_free (error);
+
+    goto end_operation;
+  }
+
+
+  if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+    /* Translate Sparql result into Grilo result */
+    for (col = 0 ; col < tracker_sparql_cursor_get_n_columns (cursor) ; col++) {
+      fill_grilo_media_from_sparql (GRL_TRACKER_SOURCE (rs->source),
+                                    rs->media, cursor, col);
+    }
+    set_title_from_filename (rs->media);
+
+    rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL);
+  } else {
+    rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL);
+  }
+
+ end_operation:
+  g_clear_object (&cursor);
+
+  grl_tracker_queue_done (grl_tracker_queue, os);
+}
+
+static void
+tracker_media_from_uri_cb (GObject      *source_object,
+                           GAsyncResult *result,
+                           GrlTrackerOp *os)
+{
+  GrlSourceMediaFromUriSpec *mfus = (GrlSourceMediaFromUriSpec *) os->data;
+  GrlTrackerSourcePriv      *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (mfus->source);
+  GError                    *tracker_error = NULL, *error = NULL;
+  GrlMedia                  *media;
+  TrackerSparqlCursor       *cursor;
+  const gchar               *sparql_type;
+  gint                       col;
+
+  GRL_ODEBUG ("%s", __FUNCTION__);
+
+  cursor = tracker_sparql_connection_query_finish (priv->tracker_connection,
+                                                   result, &tracker_error);
+
+  if (tracker_error) {
+    GRL_WARNING ("Could not execute sparql media from uri query : %s",
+                 tracker_error->message);
+
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
+                         _("Failed to get media from uri: %s"),
+                         tracker_error->message);
+
+    mfus->callback (mfus->source, mfus->operation_id, NULL, mfus->user_data, error);
+
+    g_error_free (tracker_error);
+    g_error_free (error);
+
+    goto end_operation;
+  }
+
+
+  if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+    /* Build grilo media */
+    sparql_type = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+    media = grl_tracker_build_grilo_media (sparql_type, GRL_TYPE_FILTER_NONE);
+
+    /* Translate Sparql result into Grilo result */
+    for (col = 0 ; col < tracker_sparql_cursor_get_n_columns (cursor) ; col++) {
+      fill_grilo_media_from_sparql (GRL_TRACKER_SOURCE (mfus->source),
+                                    media, cursor, col);
+    }
+    set_title_from_filename (media);
+
+    mfus->callback (mfus->source, mfus->operation_id, media, mfus->user_data, NULL);
+  } else {
+    mfus->callback (mfus->source, mfus->operation_id, NULL, mfus->user_data, NULL);
+  }
+
+ end_operation:
+  g_clear_object (&cursor);
+
+  grl_tracker_queue_done (grl_tracker_queue, os);
+}
+
+static void
+tracker_store_metadata_cb (GObject      *source_object,
+                           GAsyncResult *result,
+                           GrlTrackerOp *os)
+{
+  GrlSourceStoreMetadataSpec *sms =
+    (GrlSourceStoreMetadataSpec *) os->data;
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (sms->source);
+  GError *tracker_error = NULL, *error = NULL;
+
+  tracker_sparql_connection_update_finish (priv->tracker_connection,
+                                           result,
+                                           &tracker_error);
+
+  if (tracker_error) {
+    GRL_WARNING ("Could not execute sparql update : %s",
+                 tracker_error->message);
+
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_STORE_METADATA_FAILED,
+                         _("Failed to update metadata: %s"),
+                         tracker_error->message);
+
+    sms->callback (sms->source, sms->media, NULL, sms->user_data, error);
+
+    g_error_free (tracker_error);
+    g_error_free (error);
+  } else {
+    sms->callback (sms->source, sms->media, NULL, sms->user_data, error);
+  }
+
+  grl_tracker_queue_done (grl_tracker_queue, os);
+}
+
+/**/
+
+const GList *
+grl_tracker_source_writable_keys (GrlSource *source)
+{
+  static GList *keys = NULL;
+  GrlRegistry *registry;
+  GrlKeyID grl_metadata_key_chromaprint;
+
+  if (!keys) {
+    registry = grl_registry_get_default ();
+    grl_metadata_key_chromaprint = grl_registry_lookup_metadata_key (registry, "chromaprint");
+
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_PLAY_COUNT,
+                                      GRL_METADATA_KEY_LAST_PLAYED,
+                                      GRL_METADATA_KEY_LAST_POSITION,
+                                      GRL_METADATA_KEY_FAVOURITE,
+                                      GRL_METADATA_KEY_TITLE,
+                                      GRL_METADATA_KEY_TRACK_NUMBER,
+                                      GRL_METADATA_KEY_CREATION_DATE,
+                                      grl_metadata_key_chromaprint,
+                                      NULL);
+  }
+  return keys;
+}
+
+static void
+grl_tracker_source_get_duration_min_max (GrlOperationOptions *options,
+                                         int                 *min,
+                                         int                 *max)
+{
+  GValue *min_val, *max_val;
+
+  grl_operation_options_get_key_range_filter (options, GRL_METADATA_KEY_DURATION,
+                                              &min_val, &max_val);
+  if (min_val)
+    *min = g_value_get_int (min_val);
+  else
+    *min = -1;
+  if (max_val)
+    *max = g_value_get_int (max_val);
+  else
+    *max = -1;
+}
+
+static char *
+grl_tracker_source_create_constraint (int min, int max)
+{
+  if (min <= 0 && max <= 0)
+    return g_strdup ("");
+  if (max <= 0) {
+    return g_strdup_printf ("?urn a nfo:FileDataObject . "
+                            "OPTIONAL {"
+                            "  ?urn nfo:duration ?duration "
+                            "} . "
+                            "FILTER(?duration > %d || !BOUND(?duration))",
+                             min);
+  }
+  if (min <= 0) {
+    return g_strdup_printf ("?urn a nfo:FileDataObject . "
+                            "OPTIONAL {"
+                            "  ?urn nfo:duration ?duration "
+                            "} . "
+                            "FILTER(?duration < %d || !BOUND(?duration))",
+                             max);
+  }
+  return g_strdup_printf ("?urn a nfo:FileDataObject . "
+                          "OPTIONAL {"
+                          "  ?urn nfo:duration ?duration "
+                          "} . "
+                          "FILTER(?duration < %d || ?duration > %d || !BOUND(?duration))",
+                           max, min);
+}
+
+/**
+ * Query is a SPARQL query.
+ *
+ * Columns must be named with the Grilo key name that the column
+ * represent. Unnamed or unknown columns will be ignored.
+ *
+ * First column must be the media type, and it does not need to be named.  It
+ * must match with any value supported in rdf:type() property, or
+ * grilo#Container. Types understood are:
+ *
+ * <itemizedlist>
+ *   <listitem>
+ *     <para>
+ *       <literal>nmm#MusicPiece</literal>
+ *     </para>
+ *   </listitem>
+ *   <listitem>
+ *     <para>
+ *       <literal>nmm#Video</literal>
+ *     </para>
+ *   </listitem>
+ *   <listitem>
+ *     <para>
+ *       <literal>nmm#Photo</literal>
+ *     </para>
+ *   </listitem>
+ *   <listitem>
+ *     <para>
+ *       <literal>nmm#Artist</literal>
+ *     </para>
+ *   </listitem>
+ *   <listitem>
+ *     <para>
+ *       <literal>nmm#MusicAlbum</literal>
+ *     </para>
+ *   </listitem>
+ *   <listitem>
+ *     <para>
+ *       <literal>grilo#Container</literal>
+ *     </para>
+ *   </listitem>
+ * </itemizedlist>
+ *
+ * An example for searching all songs:
+ *
+ * <informalexample>
+ *   <programlisting>
+ *     SELECT rdf:type(?song)
+ *            ?song            AS ?id
+ *            nie:title(?song) AS ?title
+ *            nie:url(?song)   AS ?url
+ *     WHERE { ?song a nmm:MusicPiece }
+ *   </programlisting>
+ * </informalexample>
+ *
+ * Alternatively, we can use a partial SPARQL query: just specify the sentence
+ * in the WHERE part. In this case, "?urn" is the ontology concept to be used in
+ * the clause.
+ *
+ * An example of such partial query:
+ *
+ * <informalexample>
+ *   <programlisting>
+ *     ?urn a nfo:Media
+ *   </programlisting>
+ * </informalexample>
+ *
+ * In this case, all data required to build a full SPARQL query will be get from
+ * the query spec.
+ */
+void
+grl_tracker_source_query (GrlSource *source,
+                          GrlSourceQuerySpec *qs)
+{
+  GError               *error = NULL;
+  GrlTrackerSourcePriv *priv  = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  gchar                *constraint;
+  gchar                *sparql_final;
+  gchar                *sparql_select;
+  GrlTrackerOp         *os;
+  gint count = grl_operation_options_get_count (qs->options);
+  guint skip = grl_operation_options_get_skip (qs->options);
+
+  GRL_IDEBUG ("%s: id=%u", __FUNCTION__, qs->operation_id);
+
+  if (!qs->query || qs->query[0] == '\0') {
+    error = g_error_new_literal (GRL_CORE_ERROR,
+                                 GRL_CORE_ERROR_QUERY_FAILED,
+                                 _("Empty query"));
+    goto send_error;
+  }
+
+  /* Check if it is a full sparql query */
+  if (g_ascii_strncasecmp (qs->query, "select ", 7) != 0) {
+    constraint = grl_tracker_source_get_device_constraint (priv);
+    sparql_select = grl_tracker_source_get_select_string (qs->keys);
+    sparql_final = g_strdup_printf (TRACKER_QUERY_PARTIAL_REQUEST,
+                                    sparql_select,
+                                    qs->query,
+                                    constraint,
+                                    skip,
+                                    count);
+    g_free (constraint);
+    g_free (qs->query);
+    g_free (sparql_select);
+    qs->query = sparql_final;
+  } else {
+    /* Append offset and limit */
+    sparql_final = g_strdup_printf (TRACKER_QUERY_FULL_REQUEST,
+                                    qs->query,
+                                    skip,
+                                    count);
+    g_free (qs->query);
+    qs->query = sparql_final;
+  }
+
+  os = grl_tracker_op_initiate_query (qs->operation_id,
+                                      g_strdup (qs->query),
+                                      (GAsyncReadyCallback) tracker_query_cb,
+                                      qs);
+
+  os->keys  = qs->keys;
+  os->skip  = skip;
+  os->count = count;
+  os->type_filter = grl_operation_options_get_type_filter (qs->options);
+  os->data  = qs;
+  /* os->cb.sr     = qs->callback; */
+  /* os->user_data = qs->user_data; */
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  return;
+
+ send_error:
+  qs->callback (qs->source, qs->operation_id, NULL, 0, qs->user_data, error);
+  g_error_free (error);
+}
+
+void
+grl_tracker_source_resolve (GrlSource *source,
+                            GrlSourceResolveSpec *rs)
+{
+  GrlTrackerSourcePriv *priv               = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  gchar                *constraint         = NULL, *sparql_select, *sparql_final;
+  gchar                *sparql_type_filter = NULL;
+  const gchar          *url                = grl_media_get_url (rs->media);
+  GrlTrackerOp         *os;
+
+  GRL_IDEBUG ("%s: id=%i", __FUNCTION__, rs->operation_id);
+
+  /* Check if the media comes from this source or another */
+  if (g_strcmp0 (priv->tracker_datasource, grl_source_get_id (rs->source)) == 0) {
+    if (grl_media_get_id (rs->media) == NULL) {
+      rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL);
+      return;
+    } else {
+      sparql_select = grl_tracker_source_get_select_string (rs->keys);
+      sparql_final = g_strdup_printf (TRACKER_RESOLVE_REQUEST, sparql_select,
+                                      grl_media_get_id (rs->media));
+    }
+  } else {
+    if (url) {
+      sparql_select = grl_tracker_source_get_select_string (rs->keys);
+      sparql_final = g_strdup_printf (TRACKER_RESOLVE_URL_REQUEST,
+                                      sparql_select, url);
+    } else {
+      rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL);
+      return;
+    }
+  }
+
+  GRL_IDEBUG ("\request: '%s'", sparql_final);
+
+  os = grl_tracker_op_initiate_metadata (sparql_final,
+                                         (GAsyncReadyCallback) tracker_resolve_cb,
+                                         rs);
+  os->keys = rs->keys;
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  g_clear_pointer (&sparql_type_filter, g_free);
+  g_clear_pointer (&constraint, g_free);
+  g_clear_pointer (&sparql_select, g_free);
+}
+
+gboolean
+grl_tracker_source_may_resolve (GrlSource *source,
+                                GrlMedia  *media,
+                                GrlKeyID   key_id,
+                                GList    **missing_keys)
+{
+  GRL_IDEBUG ("%s: key=%s", __FUNCTION__, GRL_METADATA_KEY_GET_NAME (key_id));
+
+  if (media && grl_tracker_source_find_source (grl_media_get_source (media))) {
+    return TRUE;
+  }
+
+  if (!grl_tracker_key_is_supported (key_id)) {
+    return FALSE;
+  }
+
+  if (media) {
+    if (grl_media_get_url (media)) {
+      return TRUE;
+    } else {
+      if (missing_keys) {
+        *missing_keys = g_list_append (*missing_keys,
+                                       GRLKEYID_TO_POINTER (GRL_METADATA_KEY_URL));
+      }
+    }
+  }
+
+  return FALSE;
+}
+
+void
+grl_tracker_source_store_metadata (GrlSource *source,
+                                   GrlSourceStoreMetadataSpec *sms)
+{
+  gchar *sparql_delete, *sparql_cdelete, *sparql_insert, *sparql_final;
+  const gchar *urn = grl_data_get_string (GRL_DATA (sms->media),
+                                          grl_metadata_key_tracker_urn);
+  GrlTrackerOp *os;
+
+  GRL_IDEBUG ("%s: urn=%s", G_STRFUNC, urn);
+
+  sparql_delete = grl_tracker_get_delete_string (sms->keys);
+  sparql_cdelete = grl_tracker_get_delete_conditional_string (urn, sms->keys);
+  sparql_insert = grl_tracker_tracker_get_insert_string (sms->media, sms->keys);
+
+  if (g_strcmp0 (sparql_insert, "") == 0) {
+    sparql_final = g_strdup_printf (TRACKER_DELETE_REQUEST,
+                                    urn, sparql_delete,
+                                    urn, sparql_cdelete);
+  } else {
+    sparql_final = g_strdup_printf (TRACKER_SAVE_REQUEST,
+                                    urn, sparql_delete,
+                                    urn, sparql_cdelete,
+                                    urn, sparql_insert);
+  }
+
+  os = grl_tracker_op_initiate_set_metadata (sparql_final,
+                                             (GAsyncReadyCallback) tracker_store_metadata_cb,
+                                             sms);
+  os->keys = sms->keys;
+
+  GRL_IDEBUG ("\trequest: '%s'", sparql_final);
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  g_free (sparql_delete);
+  g_free (sparql_cdelete);
+  g_free (sparql_insert);
+}
+
+void
+grl_tracker_source_search (GrlSource *source, GrlSourceSearchSpec *ss)
+{
+  GrlTrackerSourcePriv *priv  = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  gchar                *constraint;
+  gchar                *sparql_select;
+  gchar                *sparql_final;
+  gchar                *sparql_type_filter;
+  gchar                *escaped_text;
+  GrlTrackerOp         *os;
+  gint count = grl_operation_options_get_count (ss->options);
+  guint skip = grl_operation_options_get_skip (ss->options);
+  int min_dur, max_dur;
+  char *duration_constraint;
+  GRL_IDEBUG ("%s: id=%u", __FUNCTION__, ss->operation_id);
+
+  constraint = grl_tracker_source_get_device_constraint (priv);
+  sparql_select = grl_tracker_source_get_select_string (ss->keys);
+  sparql_type_filter = get_sparql_type_filter (ss->options, FALSE);
+  grl_tracker_source_get_duration_min_max (ss->options, &min_dur, &max_dur);
+  duration_constraint = grl_tracker_source_create_constraint (min_dur, max_dur);
+  if (!ss->text || ss->text[0] == '\0') {
+    /* Search all */
+    sparql_final = g_strdup_printf (TRACKER_SEARCH_ALL_REQUEST, sparql_select,
+                                    constraint, duration_constraint, sparql_type_filter,
+                                    skip, count);
+  } else {
+    escaped_text = tracker_sparql_escape_string (ss->text);
+    sparql_final = g_strdup_printf (TRACKER_SEARCH_REQUEST, sparql_select,
+                                    sparql_type_filter, escaped_text,
+                                    constraint, duration_constraint, skip, count);
+    g_free (escaped_text);
+  }
+
+  GRL_IDEBUG ("\tselect: '%s'", sparql_final);
+
+  os = grl_tracker_op_initiate_query (ss->operation_id,
+                                      sparql_final,
+                                      (GAsyncReadyCallback) tracker_search_cb,
+                                      ss);
+  os->keys  = ss->keys;
+  os->skip  = skip;
+  os->count = count;
+  os->type_filter = grl_operation_options_get_type_filter (ss->options);
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  g_free (constraint);
+  g_free (sparql_select);
+  g_free (sparql_type_filter);
+  g_free (duration_constraint);
+}
+
+static gboolean
+is_root_box (GrlMedia *container)
+{
+  if (container == NULL)
+    return TRUE;
+  if (!grl_media_get_id (container))
+    return TRUE;
+  return FALSE;
+}
+
+static void
+grl_tracker_source_browse_category (GrlSource *source,
+                                    GrlSourceBrowseSpec *bs)
+{
+  GrlTrackerSourcePriv *priv  = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  gchar                *constraint;
+  gchar                *sparql_select;
+  gchar                *sparql_final;
+  GrlTrackerOp         *os;
+  GrlMedia             *media;
+  const gchar          *category;
+  gint remaining;
+  gint count = grl_operation_options_get_count (bs->options);
+  guint skip = grl_operation_options_get_skip (bs->options);
+  GrlTypeFilter filter = grl_operation_options_get_type_filter (bs->options);
+  int min_dur, max_dur;
+  char *duration_constraint;
+
+  GRL_IDEBUG ("%s: id=%u", __FUNCTION__, bs->operation_id);
+
+  /* If the category is missing, try to get it from the
+   * container's ID */
+  if (!is_root_box (bs->container) &&
+      !grl_data_has_key (GRL_DATA (bs->container),
+                         grl_metadata_key_tracker_category)) {
+    const char *id;
+
+    id = grl_media_get_id (bs->container);
+    if (g_strcmp0 (id, "documents") == 0)
+      category = "nfo:Document";
+    else if (g_strcmp0 (id, "music") == 0)
+      category = "nmm:MusicPiece";
+    else if (g_strcmp0 (id, "photos") == 0)
+      category = "nmm:Photo";
+    else if (g_strcmp0 (id, "videos") == 0)
+      category = "nmm:Video";
+    else {
+      GError *error;
+
+      error = g_error_new (GRL_CORE_ERROR,
+                           GRL_CORE_ERROR_BROWSE_FAILED,
+                           _("ID ā€œ%sā€ is not known in this source"),
+                           id);
+
+      bs->callback (bs->source, bs->operation_id, NULL, 0,
+                    bs->user_data, error);
+
+      g_error_free (error);
+      return;
+    }
+
+    grl_data_set_string (GRL_DATA (bs->container),
+                         grl_metadata_key_tracker_category,
+                         category);
+  }
+
+  if (is_root_box (bs->container) ||
+      !grl_data_has_key (GRL_DATA (bs->container),
+                         grl_metadata_key_tracker_category)) {
+    /* Hardcoded categories */
+    if (filter == GRL_TYPE_FILTER_ALL) {
+      remaining = 3;
+      if (grl_tracker_show_documents)  {
+        remaining++;
+      }
+    } else {
+      remaining = 0;
+      if (filter & GRL_TYPE_FILTER_AUDIO) {
+        remaining++;
+      }
+      if (filter & GRL_TYPE_FILTER_VIDEO) {
+        remaining++;
+      }
+      if (filter & GRL_TYPE_FILTER_IMAGE) {
+        remaining++;
+      }
+    }
+
+    if (remaining == 0) {
+      bs->callback (bs->source, bs->operation_id, NULL, 0,
+                    bs->user_data, NULL);
+      return;
+    }
+
+    /* Special case: if everthing is filtered except one category, then skip the
+       intermediate level and go straightly to the elements */
+    if (remaining == 1) {
+      if (filter & GRL_TYPE_FILTER_AUDIO) {
+        category = "nmm:MusicPiece";
+      } else if (filter & GRL_TYPE_FILTER_IMAGE) {
+        category = "nmm:Photo";
+      } else {
+        category = "nmm:Video";
+      }
+    } else {
+      if (remaining == 4) {
+        media = grl_media_container_new ();
+        grl_media_set_title (media, "Documents");
+        grl_media_set_id (media, "documents");
+        grl_data_set_string (GRL_DATA (media),
+                             grl_metadata_key_tracker_category,
+                             "nfo:Document");
+        bs->callback (bs->source, bs->operation_id, media, --remaining,
+                      bs->user_data, NULL);
+      }
+
+      if (filter & GRL_TYPE_FILTER_AUDIO) {
+        media = grl_media_container_new ();
+        grl_media_set_title (media, "Music");
+        grl_media_set_id (media, "music");
+        grl_data_set_string (GRL_DATA (media),
+                             grl_metadata_key_tracker_category,
+                             "nmm:MusicPiece");
+        bs->callback (bs->source, bs->operation_id, media, --remaining,
+                      bs->user_data, NULL);
+      }
+
+      if (filter & GRL_TYPE_FILTER_IMAGE) {
+        media = grl_media_container_new ();
+        grl_media_set_title (media, "Photos");
+        grl_media_set_id (media, "photos");
+        grl_data_set_string (GRL_DATA (media),
+                             grl_metadata_key_tracker_category,
+                             "nmm:Photo");
+        bs->callback (bs->source, bs->operation_id, media, --remaining,
+                      bs->user_data, NULL);
+      }
+
+      if (filter & GRL_TYPE_FILTER_VIDEO) {
+        media = grl_media_container_new ();
+        grl_media_set_title (media, "Videos");
+        grl_media_set_id (media, "videos");
+        grl_data_set_string (GRL_DATA (media),
+                             grl_metadata_key_tracker_category,
+                             "nmm:Video");
+        bs->callback (bs->source, bs->operation_id, media, --remaining,
+                      bs->user_data, NULL);
+      }
+      return;
+    }
+  } else {
+    category = grl_data_get_string (GRL_DATA (bs->container),
+                                    grl_metadata_key_tracker_category);
+  }
+
+  grl_tracker_source_get_duration_min_max (bs->options, &min_dur, &max_dur);
+  duration_constraint = grl_tracker_source_create_constraint (min_dur, max_dur);
+  constraint = grl_tracker_source_get_device_constraint (priv);
+  sparql_select = grl_tracker_source_get_select_string (bs->keys);
+  sparql_final = g_strdup_printf (TRACKER_BROWSE_CATEGORY_REQUEST,
+                                  sparql_select,
+                                  category,
+                                  constraint,
+                                  duration_constraint,
+                                  skip, count);
+
+  GRL_IDEBUG ("\tselect: '%s'", sparql_final);
+
+  os = grl_tracker_op_initiate_query (bs->operation_id,
+                                      sparql_final,
+                                      (GAsyncReadyCallback) tracker_browse_cb,
+                                      bs);
+  os->keys  = bs->keys;
+  os->skip  = skip;
+  os->count = count;
+  os->type_filter = grl_operation_options_get_type_filter (bs->options);
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  g_free (constraint);
+  g_free (sparql_select);
+  g_free (duration_constraint);
+}
+
+static void
+grl_tracker_source_browse_filesystem (GrlSource *source,
+                                      GrlSourceBrowseSpec *bs)
+{
+  GrlTrackerSourcePriv *priv  = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  gchar                *constraint;
+  gchar                *sparql_select;
+  gchar                *sparql_final;
+  gchar                *sparql_type_filter;
+  GrlTrackerOp         *os;
+  gint count = grl_operation_options_get_count (bs->options);
+  guint skip = grl_operation_options_get_skip (bs->options);
+  int min_dur, max_dur;
+  char *duration_constraint;
+
+  GRL_IDEBUG ("%s: id=%u", __FUNCTION__, bs->operation_id);
+
+  sparql_select = grl_tracker_source_get_select_string (bs->keys);
+  constraint = grl_tracker_source_get_device_constraint (priv);
+  sparql_type_filter = get_sparql_type_filter (bs->options, TRUE);
+  grl_tracker_source_get_duration_min_max (bs->options, &min_dur, &max_dur);
+  duration_constraint = grl_tracker_source_create_constraint (min_dur, max_dur);
+
+  if (bs->container == NULL ||
+      !grl_media_get_id (bs->container)) {
+    sparql_final = g_strdup_printf (TRACKER_BROWSE_FILESYSTEM_ROOT_REQUEST,
+                                    sparql_select,
+                                    grl_tracker_show_documents? TRACKER_BROWSE_SHOW_DOCUMENTS: "",
+                                    sparql_type_filter,
+                                    constraint,
+                                    skip, count);
+
+  } else {
+    sparql_final = g_strdup_printf (TRACKER_BROWSE_FILESYSTEM_REQUEST,
+                                    sparql_select,
+                                    grl_tracker_show_documents? TRACKER_BROWSE_SHOW_DOCUMENTS: "",
+                                    sparql_type_filter,
+                                    constraint,
+                                    grl_media_get_id (bs->container),
+                                    skip, count);
+  }
+
+  GRL_IDEBUG ("\tselect: '%s'", sparql_final);
+
+  os = grl_tracker_op_initiate_query (bs->operation_id,
+                                      sparql_final,
+                                      (GAsyncReadyCallback) tracker_browse_cb,
+                                      bs);
+  os->keys  = bs->keys;
+  os->skip  = skip;
+  os->count = count;
+  os->type_filter = grl_operation_options_get_type_filter (bs->options);
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  g_free (sparql_type_filter);
+  g_free (constraint);
+  g_free (sparql_select);
+  g_free (duration_constraint);
+}
+
+void
+grl_tracker_source_browse (GrlSource *source,
+                           GrlSourceBrowseSpec *bs)
+{
+  /* Ensure GRL_METADATA_KEY_ID is always requested */
+  if (!g_list_find (bs->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID)))
+    bs->keys = g_list_prepend (bs->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID));
+
+  if (grl_tracker_browse_filesystem)
+    grl_tracker_source_browse_filesystem (source, bs);
+  else
+    grl_tracker_source_browse_category (source, bs);
+}
+
+void
+grl_tracker_source_cancel (GrlSource *source, guint operation_id)
+{
+  GrlTrackerOp *os;
+
+  GRL_IDEBUG ("%s: id=%u", __FUNCTION__, operation_id);
+
+  os = g_hash_table_lookup (grl_tracker_operations,
+                            GSIZE_TO_POINTER (operation_id));
+
+  if (os != NULL)
+    grl_tracker_queue_cancel (grl_tracker_queue, os);
+}
+
+gboolean
+grl_tracker_source_change_start (GrlSource *source, GError **error)
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+  priv->notify_changes = TRUE;
+
+  return TRUE;
+}
+
+gboolean
+grl_tracker_source_change_stop (GrlSource *source, GError **error)
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+  priv->notify_changes = FALSE;
+
+  return TRUE;
+}
+
+void
+grl_tracker_source_init_requests (void)
+{
+  GrlRegistry *registry = grl_registry_get_default ();
+
+  grl_metadata_key_tracker_category =
+    grl_registry_lookup_metadata_key (registry, "tracker-category");
+
+  grl_tracker_operations = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  GRL_LOG_DOMAIN_INIT (tracker_source_request_log_domain,
+                       "tracker-source-request");
+  GRL_LOG_DOMAIN_INIT (tracker_source_result_log_domain,
+                       "tracker-source-result");
+}
+
+GrlCaps *
+grl_tracker_source_get_caps (GrlSource *source,
+                             GrlSupportedOps operation)
+{
+  static GrlCaps *caps;
+
+  if (!caps) {
+    GList *range_list;
+    caps = grl_caps_new ();
+    grl_caps_set_type_filter (caps, GRL_TYPE_FILTER_ALL);
+    range_list = grl_metadata_key_list_new (GRL_METADATA_KEY_DURATION,
+                                            GRL_METADATA_KEY_INVALID);
+    grl_caps_set_key_range_filter (caps, range_list);
+    g_list_free (range_list);
+  }
+
+  return caps;
+}
+
+GrlSupportedOps
+grl_tracker_source_supported_operations (GrlSource *source)
+{
+  gboolean is_extractor;
+  GrlSupportedOps ops;
+
+  /* Always supported operations. */
+  ops = GRL_OP_RESOLVE | GRL_OP_MEDIA_FROM_URI | GRL_OP_SEARCH | GRL_OP_QUERY |
+        GRL_OP_STORE_METADATA | GRL_OP_NOTIFY_CHANGE;
+
+  /* The extractor doesnā€™t support browsing; only resolving. */
+  is_extractor = g_str_has_prefix (grl_source_get_id (source),
+                                   "http://www.tracker-project.org";
+                                   "/ontologies/tracker"
+                                   "#extractor-data-source,");
+  if (!is_extractor) {
+    ops |= GRL_OP_BROWSE;
+  }
+
+  return ops;
+}
+
+gboolean
+grl_tracker_source_test_media_from_uri (GrlSource *source,
+                                        const gchar *uri)
+{
+  GrlTrackerSourcePriv *priv  = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  GError               *error = NULL;
+  TrackerSparqlCursor  *cursor;
+  gboolean              empty;
+  gchar                *constraint;
+  gchar                *sparql_final;
+
+  constraint = grl_tracker_source_get_device_constraint (priv);
+  if (grl_tracker_show_documents) {
+    sparql_final = g_strdup_printf (TRACKER_TEST_MEDIA_FROM_URI_REQUEST_WITH_DOCUMENTS,
+                                    uri,
+                                    constraint);
+  } else {
+    sparql_final = g_strdup_printf (TRACKER_TEST_MEDIA_FROM_URI_REQUEST,
+                                    uri,
+                                    constraint);
+  }
+
+  cursor = tracker_sparql_connection_query (grl_tracker_connection,
+                                            sparql_final,
+                                            NULL,
+                                            &error);
+  g_free (constraint);
+  g_free (sparql_final);
+
+  if (error) {
+    GRL_WARNING ("Error when executig sparql query: %s",
+                 error->message);
+    g_error_free (error);
+    return FALSE;
+  }
+
+  /* Check if there are results */
+  if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+    empty = FALSE;
+  } else {
+    empty = TRUE;
+  }
+
+  g_object_unref (cursor);
+
+  return !empty;
+}
+
+void
+grl_tracker_source_get_media_from_uri (GrlSource *source,
+                                       GrlSourceMediaFromUriSpec *mfus)
+{
+  GrlTrackerSourcePriv *priv  = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+  gchar                *constraint;
+  gchar                *sparql_select;
+  gchar                *sparql_final;
+  GrlTrackerOp         *os;
+
+  GRL_IDEBUG ("%s: id=%u", __FUNCTION__, mfus->operation_id);
+
+  /* Ensure GRL_METADATA_KEY_ID is always requested */
+  if (!g_list_find (mfus->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID)))
+    mfus->keys = g_list_prepend (mfus->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID));
+
+  constraint = grl_tracker_source_get_device_constraint (priv);
+  sparql_select = grl_tracker_source_get_select_string (mfus->keys);
+  sparql_final = g_strdup_printf (TRACKER_MEDIA_FROM_URI_REQUEST,
+                                  sparql_select,
+                                  mfus->uri,
+                                  constraint);
+
+  GRL_IDEBUG ("\tselect: '%s'", sparql_final);
+
+  os = grl_tracker_op_initiate_metadata (sparql_final,
+                                         (GAsyncReadyCallback) tracker_media_from_uri_cb,
+                                         mfus);
+  os->keys  = mfus->keys;
+
+  grl_tracker_queue_push (grl_tracker_queue, os);
+
+  g_free (constraint);
+  g_free (sparql_select);
+}
diff --git a/src/tracker3/grl-tracker-source-api.h b/src/tracker3/grl-tracker-source-api.h
new file mode 100644
index 00000000..f452e750
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-api.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_SOURCE_API_H_
+#define _GRL_TRACKER_SOURCE_API_H_
+
+#include "grl-tracker-source.h"
+
+/**/
+
+void grl_tracker_source_init_requests (void);
+
+const GList *grl_tracker_source_writable_keys (GrlSource *source);
+
+void grl_tracker_source_query (GrlSource *source,
+                               GrlSourceQuerySpec *qs);
+
+void grl_tracker_source_resolve (GrlSource *source,
+                                 GrlSourceResolveSpec *rs);
+
+gboolean grl_tracker_source_may_resolve (GrlSource *source,
+                                         GrlMedia *media,
+                                         GrlKeyID key_id,
+                                         GList **missing_keys);
+
+void grl_tracker_source_store_metadata (GrlSource *source,
+                                        GrlSourceStoreMetadataSpec *sms);
+
+void grl_tracker_source_cancel (GrlSource *source, guint operation_id);
+
+void grl_tracker_source_search (GrlSource *source,
+                                GrlSourceSearchSpec *ss);
+
+void grl_tracker_source_browse (GrlSource *source,
+                                GrlSourceBrowseSpec *bs);
+
+gboolean grl_tracker_source_change_start (GrlSource *source,
+                                          GError **error);
+
+gboolean grl_tracker_source_change_stop (GrlSource *source,
+                                         GError **error);
+
+GrlCaps *grl_tracker_source_get_caps (GrlSource *source,
+                                      GrlSupportedOps operation);
+
+GrlSupportedOps grl_tracker_source_supported_operations (GrlSource *source);
+
+gboolean grl_tracker_source_test_media_from_uri (GrlSource *source,
+                                                 const gchar *uri);
+
+void grl_tracker_source_get_media_from_uri (GrlSource *source,
+                                            GrlSourceMediaFromUriSpec *mfus);
+
+#endif /* _GRL_TRACKER_SOURCE_API_H_ */
diff --git a/src/tracker3/grl-tracker-source-cache.c b/src/tracker3/grl-tracker-source-cache.c
new file mode 100644
index 00000000..0ad04b2d
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-cache.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2011 Intel Corporation.
+ * Copyright (C) 2011 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <glib.h>
+
+#include "grl-tracker-source-cache.h"
+
+typedef struct {
+  GrlTrackerSource *source;
+
+  GHashTable *id_table;
+} GrlTrackerCacheSource;
+
+struct _GrlTrackerCache {
+  gsize size_limit;
+  gsize size_current;
+
+  GHashTable *id_table;
+  GHashTable *source_table;
+  GList      *id_list;
+};
+
+static GrlTrackerCacheSource *
+grl_tracker_cache_source_new (GrlTrackerSource *source)
+{
+  GrlTrackerCacheSource *csource = g_slice_new0 (GrlTrackerCacheSource);
+
+  csource->source = source;
+  csource->id_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  return csource;
+}
+
+static void
+grl_tracker_cache_source_free (GrlTrackerCacheSource *csource)
+{
+  g_hash_table_destroy (csource->id_table);
+
+  g_slice_free (GrlTrackerCacheSource, csource);
+}
+
+/**/
+
+GrlTrackerCache *
+grl_tracker_source_cache_new (gsize size)
+{
+  GrlTrackerCache *cache;
+
+  g_return_val_if_fail (size > 0, NULL);
+
+  cache = g_slice_new0 (GrlTrackerCache);
+
+  if (!cache)
+    return NULL;
+
+  cache->size_limit   = size;
+  cache->id_table     = g_hash_table_new (g_direct_hash, g_direct_equal);
+  cache->source_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  return cache;
+}
+
+void
+grl_tracker_source_cache_free (GrlTrackerCache *cache)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_return_if_fail (cache != NULL);
+
+  g_hash_table_iter_init (&iter, cache->source_table);
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    grl_tracker_source_cache_del_source (cache, key);
+  }
+
+  if (cache->id_list) {
+    g_warning ("Memleak detected");
+    g_list_free (cache->id_list);
+  }
+  g_hash_table_destroy (cache->id_table);
+  g_hash_table_destroy (cache->source_table);
+
+  g_slice_free (GrlTrackerCache, cache);
+}
+
+void
+grl_tracker_source_cache_add_item (GrlTrackerCache *cache,
+                                   guint id,
+                                   GrlTrackerSource *source)
+{
+  GList *lid;
+  GrlTrackerCacheSource *csource;
+
+  g_return_if_fail (cache != NULL);
+
+  if (g_hash_table_lookup (cache->id_table, GSIZE_TO_POINTER (id)) != NULL)
+    return; /* TODO: is it worth to have an LRU ? */
+
+  csource = g_hash_table_lookup (cache->source_table, source);
+
+  if (!csource) {
+    csource = grl_tracker_cache_source_new (source);
+    g_hash_table_insert (cache->source_table, source, csource);
+  }
+
+  if (cache->size_current >= cache->size_limit) {
+    lid = g_list_last (cache->id_list); /* TODO: optimize that ! */
+    g_hash_table_remove (cache->id_table, lid->data);
+    cache->id_list = g_list_remove_link (cache->id_list, lid);
+
+    lid->data = GSIZE_TO_POINTER (id);
+    lid->next = cache->id_list;
+    cache->id_list->prev = lid;
+    cache->id_list = lid;
+  } else {
+    cache->id_list = g_list_prepend (cache->id_list, GSIZE_TO_POINTER (id));
+    cache->size_current++;
+  }
+
+  g_hash_table_insert (cache->id_table, GSIZE_TO_POINTER (id), csource);
+  g_hash_table_insert (csource->id_table, GSIZE_TO_POINTER (id),
+                       cache->id_list);
+}
+
+void
+grl_tracker_source_cache_del_source (GrlTrackerCache *cache,
+                                     GrlTrackerSource *source)
+{
+  GrlTrackerCacheSource *csource;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_return_if_fail (cache != NULL);
+  g_return_if_fail (source != NULL);
+
+  csource = g_hash_table_lookup (cache->source_table, source);
+
+  if (!csource)
+    return;
+
+  g_hash_table_iter_init (&iter, csource->id_table);
+
+  while (g_hash_table_iter_next (&iter, &key, &value)) {
+    g_hash_table_remove (cache->id_table, key);
+    cache->id_list = g_list_delete_link (cache->id_list, value);
+  }
+
+  g_hash_table_remove (cache->source_table, source);
+  grl_tracker_cache_source_free (csource);
+}
+
+GrlTrackerSource *
+grl_tracker_source_cache_get_source (GrlTrackerCache *cache, guint id)
+{
+  GrlTrackerCacheSource *csource;
+
+  g_return_val_if_fail (cache != NULL, NULL);
+
+  csource = (GrlTrackerCacheSource *) g_hash_table_lookup (cache->id_table,
+                                                           GSIZE_TO_POINTER (id));
+
+  if (csource) {
+    return csource->source;
+  }
+
+  return NULL;
+}
diff --git a/src/tracker3/grl-tracker-source-cache.h b/src/tracker3/grl-tracker-source-cache.h
new file mode 100644
index 00000000..a84930b0
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-cache.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 Intel Corporation.
+ * Copyright (C) 2011-2012 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_SOURCE_CACHE_H_
+#define _GRL_TRACKER_SOURCE_CACHE_H_
+
+#include "grl-tracker-source.h"
+
+typedef struct _GrlTrackerCache GrlTrackerCache;
+
+GrlTrackerCache *grl_tracker_source_cache_new (gsize size);
+
+void grl_tracker_source_cache_free (GrlTrackerCache *cache);
+
+void grl_tracker_source_cache_add_item (GrlTrackerCache *cache,
+                                        guint id,
+                                        GrlTrackerSource *source);
+void grl_tracker_source_cache_del_source (GrlTrackerCache *cache,
+                                          GrlTrackerSource *source);
+
+GrlTrackerSource *grl_tracker_source_cache_get_source (GrlTrackerCache *cache,
+                                                       guint id);
+
+#endif /* _GRL_TRACKER_SOURCE_CACHE_H_ */
diff --git a/src/tracker3/grl-tracker-source-notif.c b/src/tracker3/grl-tracker-source-notif.c
new file mode 100644
index 00000000..fa415bec
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-notif.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ * Copyright (C) 2015 Collabora Ltd.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *          Xavier Claessens <xavier claessens collabora com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "grl-tracker.h"
+#include "grl-tracker-source-notif.h"
+#include "grl-tracker-source-priv.h"
+#include "grl-tracker-utils.h"
+
+#define GRL_LOG_DOMAIN_DEFAULT tracker_notif_log_domain
+GRL_LOG_DOMAIN_STATIC(tracker_notif_log_domain);
+
+#define GRL_TRACKER_TYPE_SOURCE_NOTIFY grl_tracker_source_notify_get_type ()
+G_DECLARE_FINAL_TYPE (GrlTrackerSourceNotify, grl_tracker_source_notify, GRL_TRACKER, SOURCE_NOTIFY, GObject)
+
+struct _GrlTrackerSourceNotify {
+  GObject parent;
+  TrackerSparqlConnection *connection;
+  TrackerNotifier *notifier;
+  guint events_signal_id;
+};
+
+enum {
+  PROP_0,
+  PROP_CONNECTION,
+  N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { 0, };
+
+G_DEFINE_TYPE (GrlTrackerSourceNotify, grl_tracker_source_notify, G_TYPE_OBJECT)
+
+static GrlTrackerSourceNotify *singleton = NULL;
+
+static GrlMedia *
+media_for_event (GrlTrackerSourceNotify *self,
+                 TrackerNotifierEvent   *event)
+{
+  gchar *id_str;
+  GrlMedia *media;
+
+  id_str = g_strdup_printf ("%" G_GINT64_FORMAT, tracker_notifier_event_get_id (event));
+  // FIXME
+  media = grl_tracker_build_grilo_media (NULL,//tracker_notifier_event_get_type (event),
+                                         GRL_TYPE_FILTER_NONE);
+  grl_media_set_id (media, id_str);
+  grl_media_set_url (media, tracker_notifier_event_get_urn (event));
+
+  g_free (id_str);
+
+  return media;
+}
+
+static void
+handle_changes (GrlTrackerSourceNotify   *self,
+                GPtrArray                *events,
+                TrackerNotifierEventType  tracker_type,
+                GrlSourceChangeType       change_type)
+{
+  GrlTrackerSource *source = NULL;
+  TrackerNotifierEvent *event;
+  GPtrArray *change_list;
+  GrlMedia *media;
+  gint i;
+
+  source = grl_tracker_source_find ("");
+
+  if (!source || !grl_tracker_source_can_notify (source))
+    return;
+
+  change_list = g_ptr_array_new ();
+
+  for (i = 0; i < events->len; i++) {
+    event = g_ptr_array_index (events, i);
+    if (tracker_notifier_event_get_event_type (event) != tracker_type)
+      continue;
+
+    media = media_for_event (self, event);
+    g_ptr_array_add (change_list, media);
+  }
+
+  grl_source_notify_change_list (GRL_SOURCE (source), change_list,
+                                 change_type, FALSE);
+}
+
+static void
+notifier_event_cb (GrlTrackerSourceNotify *self,
+                   const gchar            *service,
+                   const gchar            *graph,
+                   GPtrArray              *events,
+                   gpointer                user_data)
+{
+  handle_changes (self, events,
+                  TRACKER_NOTIFIER_EVENT_CREATE,
+                  GRL_CONTENT_ADDED);
+  handle_changes (self, events,
+                  TRACKER_NOTIFIER_EVENT_UPDATE,
+                  GRL_CONTENT_CHANGED);
+  handle_changes (self, events,
+                  TRACKER_NOTIFIER_EVENT_DELETE,
+                  GRL_CONTENT_REMOVED);
+}
+
+static void
+grl_tracker_source_notify_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object);
+
+  switch (prop_id) {
+  case PROP_CONNECTION:
+    g_value_set_object (value, self->connection);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+grl_tracker_source_notify_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object);
+
+  switch (prop_id) {
+  case PROP_CONNECTION:
+    self->connection = g_value_get_object (value);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+grl_tracker_source_notify_constructed (GObject *object)
+{
+  GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object);
+
+  self->notifier =
+    tracker_sparql_connection_create_notifier (self->connection);
+  self->events_signal_id =
+    g_signal_connect_swapped (self->notifier, "events",
+                              G_CALLBACK (notifier_event_cb), object);
+
+  G_OBJECT_CLASS (grl_tracker_source_notify_parent_class)->constructed (object);
+}
+
+static void
+grl_tracker_source_notify_finalize (GObject *object)
+{
+  GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object);
+
+  if (self->events_signal_id)
+    g_signal_handler_disconnect (self->notifier, self->events_signal_id);
+  g_clear_object (&self->notifier);
+  G_OBJECT_CLASS (grl_tracker_source_notify_parent_class)->finalize (object);
+}
+
+static void
+grl_tracker_source_notify_class_init (GrlTrackerSourceNotifyClass *klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+
+  GRL_LOG_DOMAIN_INIT (tracker_notif_log_domain, "tracker-notif");
+  object_class->set_property = grl_tracker_source_notify_set_property;
+  object_class->get_property = grl_tracker_source_notify_get_property;
+  object_class->finalize = grl_tracker_source_notify_finalize;
+  object_class->constructed = grl_tracker_source_notify_constructed;
+
+  props[PROP_CONNECTION] =
+    g_param_spec_object ("connection",
+                         "SPARQL Connection",
+                         "SPARQL Connection",
+                         TRACKER_TYPE_SPARQL_CONNECTION,
+                         G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+grl_tracker_source_notify_init (GrlTrackerSourceNotify *self)
+{
+}
+
+void
+grl_tracker_notify_init (TrackerSparqlConnection *sparql_conn)
+{
+  if (singleton != NULL)
+    return;
+
+  singleton = g_object_new (GRL_TRACKER_TYPE_SOURCE_NOTIFY,
+                            "connection", sparql_conn,
+                            NULL);
+}
+
+void
+grl_tracker_notify_shutdown (void)
+{
+  g_clear_object (&singleton);
+}
diff --git a/src/tracker3/grl-tracker-source-notif.h b/src/tracker3/grl-tracker-source-notif.h
new file mode 100644
index 00000000..dd787221
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-notif.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 Intel Corporation.
+ * Copyright (C) 2011-2012 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_SOURCE_NOTIF_H_
+#define _GRL_TRACKER_SOURCE_NOTIF_H_
+
+#include "grl-tracker-source.h"
+
+/* ------- Definitions ------- */
+
+#define TRACKER_DATASOURCES_REQUEST                                     \
+  "SELECT "                                                             \
+  "(SELECT GROUP_CONCAT(rdf:type(?source), \":\") "                     \
+  " WHERE { ?urn nie:dataSource ?source }) "                            \
+  "nie:dataSource(?urn) "                                               \
+  "(SELECT GROUP_CONCAT(nie:title(?source), \":\") "                    \
+  " WHERE { ?urn nie:dataSource ?source }) "                            \
+  "(SELECT GROUP_CONCAT(nie:url(tracker:mountPoint(?source)), \":\") "  \
+  " WHERE { ?urn nie:dataSource ?source }) "                            \
+  "tracker:available(?urn) "                                            \
+  "WHERE "                                                              \
+  "{ "                                                                  \
+  "?urn a nfo:FileDataObject . FILTER (bound(nie:dataSource(?urn)))"    \
+  "} "                                                                  \
+  "GROUP BY (nie:dataSource(?urn))"
+
+/**/
+
+void grl_tracker_notify_init     (TrackerSparqlConnection *sparql_conn);
+void grl_tracker_notify_shutdown (void);
+
+#endif /* _GRL_TRACKER_SOURCE_NOTIF_H_ */
diff --git a/src/tracker3/grl-tracker-source-priv.h b/src/tracker3/grl-tracker-source-priv.h
new file mode 100644
index 00000000..95dc5d7f
--- /dev/null
+++ b/src/tracker3/grl-tracker-source-priv.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *          Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_SOURCE_PRIV_H_
+#define _GRL_TRACKER_SOURCE_PRIV_H_
+
+#include "grl-tracker-source.h"
+#include "grl-tracker-source-cache.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <tracker-sparql.h>
+
+/* ---- Source information ---- */
+
+#define GRL_TRACKER_SOURCE_ID   "grl-tracker3-source"
+#define GRL_TRACKER_SOURCE_NAME "Tracker3"
+#define GRL_TRACKER_SOURCE_DESC _("A plugin for searching multimedia content using Tracker3")
+
+#define GRL_TRACKER_AUTHOR  "Igalia S.L."
+#define GRL_TRACKER_LICENSE "LGPL"
+#define GRL_TRACKER_SITE    "http://www.igalia.com";
+
+/**/
+
+#define GRL_TRACKER_SOURCE_GET_PRIVATE(object)           \
+  (G_TYPE_INSTANCE_GET_PRIVATE((object),                 \
+                               GRL_TRACKER_SOURCE_TYPE,        \
+                               GrlTrackerSourcePriv))
+
+typedef enum {
+  GRL_TRACKER_SOURCE_STATE_INSERTING,
+  GRL_TRACKER_SOURCE_STATE_RUNNING,
+  GRL_TRACKER_SOURCE_STATE_DELETING,
+  GRL_TRACKER_SOURCE_STATE_DELETED,
+} GrlTrackerSourceState;
+
+struct _GrlTrackerSourcePriv {
+  TrackerSparqlConnection *tracker_connection;
+
+  GHashTable *operations;
+
+  gchar *tracker_datasource;
+  gboolean notify_changes;
+
+  GrlTrackerSourceState state;
+};
+
+/**/
+
+extern GrlPlugin *grl_tracker_plugin;
+
+/* shared data across  */
+extern GrlTrackerCache *grl_tracker_item_cache;
+extern gboolean grl_tracker_upnp_present;
+
+/* tracker plugin config */
+extern gboolean grl_tracker_browse_filesystem;
+extern gboolean grl_tracker_show_documents;
+
+#endif /* _GRL_TRACKER_SOURCE_PRIV_H_ */
diff --git a/src/tracker3/grl-tracker-source.c b/src/tracker3/grl-tracker-source.c
new file mode 100644
index 00000000..78c39980
--- /dev/null
+++ b/src/tracker3/grl-tracker-source.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <grilo.h>
+#include <string.h>
+#include <tracker-sparql.h>
+
+#include "grl-tracker.h"
+#include "grl-tracker-source.h"
+#include "grl-tracker-source-priv.h"
+#include "grl-tracker-source-api.h"
+#include "grl-tracker-source-cache.h"
+#include "grl-tracker-source-notif.h"
+#include "grl-tracker-utils.h"
+
+/* --------- Logging  -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT tracker_source_log_domain
+GRL_LOG_DOMAIN_STATIC(tracker_source_log_domain);
+
+/* ------- Definitions ------- */
+
+#define MEDIA_TYPE "grilo-media-type"
+
+#define TRACKER_ITEM_CACHE_SIZE (10000)
+
+/* --- Other --- */
+
+enum {
+  PROP_0,
+  PROP_TRACKER_CONNECTION,
+  PROP_TRACKER_DATASOURCE,
+};
+
+static void grl_tracker_source_set_property (GObject      *object,
+                                             guint         propid,
+                                             const GValue *value,
+                                             GParamSpec   *pspec);
+
+static void grl_tracker_source_finalize (GObject *object);
+
+/* ===================== Globals  ================= */
+
+/* shared data across  */
+GrlTrackerCache *grl_tracker_item_cache;
+GHashTable *grl_tracker_source_sources;
+
+/* ================== TrackerSource GObject ================ */
+
+G_DEFINE_TYPE (GrlTrackerSource, grl_tracker_source, GRL_TYPE_SOURCE);
+
+static GrlTrackerSource *
+grl_tracker_source_new (TrackerSparqlConnection *connection)
+{
+  GRL_DEBUG ("%s", __FUNCTION__);
+
+  return g_object_new (GRL_TRACKER_SOURCE_TYPE,
+                       "source-id", GRL_TRACKER_SOURCE_ID,
+                       "source-name", GRL_TRACKER_SOURCE_NAME,
+                       "source-desc", GRL_TRACKER_SOURCE_DESC,
+                       "tracker-connection", connection,
+                       "tracker-datasource", "",
+                       NULL);
+}
+
+static void
+grl_tracker_source_class_init (GrlTrackerSourceClass * klass)
+{
+  GObjectClass        *g_class      = G_OBJECT_CLASS (klass);
+  GrlSourceClass      *source_class = GRL_SOURCE_CLASS (klass);
+
+  g_class->finalize     = grl_tracker_source_finalize;
+  g_class->set_property = grl_tracker_source_set_property;
+
+  source_class->cancel              = grl_tracker_source_cancel;
+  source_class->supported_keys      = grl_tracker_supported_keys;
+  source_class->writable_keys       = grl_tracker_source_writable_keys;
+  source_class->store_metadata      = grl_tracker_source_store_metadata;
+  source_class->query               = grl_tracker_source_query;
+  source_class->resolve             = grl_tracker_source_resolve;
+  source_class->may_resolve         = grl_tracker_source_may_resolve;
+  source_class->search              = grl_tracker_source_search;
+  source_class->browse              = grl_tracker_source_browse;
+  source_class->notify_change_start = grl_tracker_source_change_start;
+  source_class->notify_change_stop  = grl_tracker_source_change_stop;
+  source_class->supported_operations = grl_tracker_source_supported_operations;
+  source_class->get_caps            = grl_tracker_source_get_caps;
+  source_class->test_media_from_uri = grl_tracker_source_test_media_from_uri;
+  source_class->media_from_uri      = grl_tracker_source_get_media_from_uri;
+
+  g_object_class_install_property (g_class,
+                                   PROP_TRACKER_CONNECTION,
+                                   g_param_spec_object ("tracker-connection",
+                                                        "tracker connection",
+                                                        "A Tracker connection",
+                                                        TRACKER_SPARQL_TYPE_CONNECTION,
+                                                        G_PARAM_WRITABLE
+                                                        | G_PARAM_CONSTRUCT_ONLY
+                                                        | G_PARAM_STATIC_NAME));
+
+ g_object_class_install_property (g_class,
+                                  PROP_TRACKER_DATASOURCE,
+                                  g_param_spec_string ("tracker-datasource",
+                                                       "tracker datasource",
+                                                       "A Tracker nie:DataSource URN",
+                                                       NULL,
+                                                       G_PARAM_WRITABLE
+                                                       | G_PARAM_CONSTRUCT_ONLY
+                                                       | G_PARAM_STATIC_NAME));
+
+  g_type_class_add_private (klass, sizeof (GrlTrackerSourcePriv));
+}
+
+static void
+grl_tracker_source_init (GrlTrackerSource *source)
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+  source->priv = priv;
+
+  priv->operations = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+grl_tracker_source_finalize (GObject *object)
+{
+  GrlTrackerSource *self;
+
+  self = GRL_TRACKER_SOURCE (object);
+
+  g_clear_object (&self->priv->tracker_connection);
+
+  G_OBJECT_CLASS (grl_tracker_source_parent_class)->finalize (object);
+}
+
+static void
+grl_tracker_source_set_property (GObject      *object,
+                                 guint         propid,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (object);
+
+  switch (propid) {
+    case PROP_TRACKER_CONNECTION:
+      g_clear_object (&priv->tracker_connection);
+      priv->tracker_connection = g_object_ref (g_value_get_object (value));
+      break;
+
+    case PROP_TRACKER_DATASOURCE:
+      g_clear_pointer (&priv->tracker_datasource, g_free);
+      priv->tracker_datasource = g_strdup (g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+  }
+}
+
+const gchar *
+grl_tracker_source_get_tracker_source (GrlTrackerSource *source)
+{
+  GrlTrackerSourcePriv *priv;
+
+  g_return_val_if_fail (GRL_IS_TRACKER_SOURCE (source), NULL);
+
+  priv = source->priv;
+
+  return priv->tracker_datasource;
+}
+
+TrackerSparqlConnection *
+grl_tracker_source_get_tracker_connection (GrlTrackerSource *source)
+{
+  GrlTrackerSourcePriv *priv;
+
+  g_return_val_if_fail (GRL_IS_TRACKER_SOURCE (source), NULL);
+
+  priv = source->priv;
+
+  return priv->tracker_connection;
+}
+
+/* =================== TrackerSource Plugin  =============== */
+
+void
+grl_tracker_add_source (GrlTrackerSource *source)
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+  GRL_DEBUG ("====================>add source '%s'",
+             grl_source_get_name (GRL_SOURCE (source)));
+
+  g_hash_table_insert (grl_tracker_source_sources,
+                       (gpointer) grl_tracker_source_get_tracker_source (source),
+                       g_object_ref (source));
+  priv->state = GRL_TRACKER_SOURCE_STATE_RUNNING;
+  grl_registry_register_source (grl_registry_get_default (),
+                                grl_tracker_plugin,
+                                GRL_SOURCE (g_object_ref (source)),
+                                NULL);
+}
+
+void
+grl_tracker_del_source (GrlTrackerSource *source)
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+  GRL_DEBUG ("==================>del source '%s'",
+             grl_source_get_name (GRL_SOURCE (source)));
+
+  g_hash_table_remove (grl_tracker_source_sources,
+                       grl_tracker_source_get_tracker_source (source));
+  grl_tracker_source_cache_del_source (grl_tracker_item_cache, source);
+  priv->state = GRL_TRACKER_SOURCE_STATE_DELETED;
+  grl_registry_unregister_source (grl_registry_get_default (),
+                                  GRL_SOURCE (source),
+                                  NULL);
+}
+
+gboolean
+grl_tracker_source_can_notify (GrlTrackerSource *source)
+{
+  GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+  if (priv->state == GRL_TRACKER_SOURCE_STATE_RUNNING)
+    return priv->notify_changes;
+
+  return FALSE;
+}
+
+GrlTrackerSource *
+grl_tracker_source_find (const gchar *id)
+{
+  GrlTrackerSource *source;
+
+  source = g_hash_table_lookup (grl_tracker_source_sources, id);
+  return source;
+}
+
+static gboolean
+match_plugin_id (gpointer key,
+                 gpointer value,
+                 gpointer user_data)
+{
+  if (g_strcmp0 (grl_source_get_id (GRL_SOURCE (value)),
+                 (gchar *) user_data) == 0) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/* Search for registered plugin with @id */
+GrlTrackerSource *
+grl_tracker_source_find_source (const gchar *id)
+{
+  GrlTrackerSource *source;
+
+  source = g_hash_table_find (grl_tracker_source_sources,
+                              match_plugin_id,
+                              (gpointer) id);
+  return source;
+}
+
+void
+grl_tracker_source_sources_init (void)
+{
+
+  GRL_LOG_DOMAIN_INIT (tracker_source_log_domain, "tracker-source");
+
+  GRL_DEBUG ("%s", __FUNCTION__);
+
+  grl_tracker_item_cache =
+    grl_tracker_source_cache_new (TRACKER_ITEM_CACHE_SIZE);
+  grl_tracker_source_sources = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                      NULL, g_object_unref);
+
+  if (grl_tracker_connection != NULL) {
+    GrlTrackerSource *source;
+
+    grl_tracker_notify_init (grl_tracker_connection);
+
+    /* One source to rule them all. */
+    source = grl_tracker_source_new (grl_tracker_connection);
+    grl_tracker_add_source (source);
+    g_object_unref (source);
+  }
+}
diff --git a/src/tracker3/grl-tracker-source.h b/src/tracker3/grl-tracker-source.h
new file mode 100644
index 00000000..a6157263
--- /dev/null
+++ b/src/tracker3/grl-tracker-source.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_SOURCE_H_
+#define _GRL_TRACKER_SOURCE_H_
+
+#include <grilo.h>
+#include <tracker-sparql.h>
+
+#define GRL_TRACKER_SOURCE_TYPE                 \
+  (grl_tracker_source_get_type ())
+
+#define GRL_TRACKER_SOURCE(obj)                         \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj),                   \
+                               GRL_TRACKER_SOURCE_TYPE, \
+                               GrlTrackerSource))
+
+#define GRL_IS_TRACKER_SOURCE(obj)                      \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj),                   \
+                               GRL_TRACKER_SOURCE_TYPE))
+
+#define GRL_TRACKER_SOURCE_CLASS(klass)                 \
+  (G_TYPE_CHECK_CLASS_CAST((klass),                     \
+                           GRL_TRACKER_SOURCE_TYPE,     \
+                           GrlTrackerSourceClass))
+
+#define GRL_IS_TRACKER_SOURCE_CLASS(klass)              \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),                     \
+                           GRL_TRACKER_SOURCE_TYPE))
+
+#define GRL_TRACKER_SOURCE_GET_CLASS(obj)               \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj),                    \
+                              GRL_TRACKER_SOURCE_TYPE,  \
+                              GrlTrackerSourceClass))
+
+typedef struct _GrlTrackerSource GrlTrackerSource;
+typedef struct _GrlTrackerSourcePriv GrlTrackerSourcePriv;
+
+struct _GrlTrackerSource {
+
+  GrlSource parent;
+
+  /*< private >*/
+  GrlTrackerSourcePriv *priv;
+
+};
+
+typedef struct _GrlTrackerSourceClass GrlTrackerSourceClass;
+
+struct _GrlTrackerSourceClass {
+
+  GrlSourceClass parent_class;
+
+};
+
+GType grl_tracker_source_get_type (void);
+
+gboolean grl_tracker_source_can_notify (GrlTrackerSource *source);
+
+const gchar *grl_tracker_source_get_tracker_source (GrlTrackerSource *source);
+
+TrackerSparqlConnection *grl_tracker_source_get_tracker_connection (GrlTrackerSource *source);
+
+/**/
+
+void grl_tracker_source_sources_init (void);
+
+void grl_tracker_add_source (GrlTrackerSource *source);
+
+void grl_tracker_del_source (GrlTrackerSource *source);
+
+GrlTrackerSource *grl_tracker_source_find (const gchar *id);
+
+GrlTrackerSource *grl_tracker_source_find_source (const gchar *id);
+
+#endif /* _GRL_TRACKER_SOURCE_H_ */
diff --git a/src/tracker3/grl-tracker-utils.c b/src/tracker3/grl-tracker-utils.c
new file mode 100644
index 00000000..e83ec5e1
--- /dev/null
+++ b/src/tracker3/grl-tracker-utils.c
@@ -0,0 +1,879 @@
+/*
+ * Copyright (C) 2011 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "grl-tracker-utils.h"
+#include <glib/gi18n-lib.h>
+
+/**/
+
+static GHashTable *grl_to_sparql_mapping = NULL;
+static GHashTable *sparql_to_grl_mapping = NULL;
+
+GrlKeyID grl_metadata_key_tracker_urn;
+GrlKeyID grl_metadata_key_gibest_hash;
+
+
+/**/
+
+static gchar *
+build_flavored_key (gchar *key, const gchar *flavor)
+{
+  gint i = 0;
+
+  while (key[i] != '\0') {
+    if (!g_ascii_isalnum (key[i])) {
+      key[i] = '_';
+     }
+    i++;
+  }
+
+  return g_strdup_printf ("%s_%s", key, flavor);
+}
+
+static void
+set_orientation (TrackerSparqlCursor *cursor,
+                 gint                 column,
+                 GrlMedia            *media,
+                 GrlKeyID             key)
+{
+  const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL);
+
+  if (g_str_has_suffix (str, "nfo#orientation-top"))
+    grl_data_set_int (GRL_DATA (media), key, 0);
+  else if (g_str_has_suffix (str, "nfo#orientation-right"))
+    grl_data_set_int (GRL_DATA (media), key, 90);
+  else if (g_str_has_suffix (str, "nfo#orientation-bottom"))
+    grl_data_set_int (GRL_DATA (media), key, 180);
+  else if (g_str_has_suffix (str, "nfo#orientation-left"))
+    grl_data_set_int (GRL_DATA (media), key, 270);
+}
+
+static void
+set_date (TrackerSparqlCursor *cursor,
+          gint                 column,
+          GrlMedia            *media,
+          GrlKeyID             key)
+{
+  const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL);
+  if (key == GRL_METADATA_KEY_CREATION_DATE
+      || key == GRL_METADATA_KEY_LAST_PLAYED
+      || key == GRL_METADATA_KEY_MODIFICATION_DATE) {
+    GDateTime *date = grl_date_time_from_iso8601 (str);
+    if (date) {
+      grl_data_set_boxed (GRL_DATA (media), key, date);
+      g_date_time_unref (date);
+    }
+  }
+}
+
+static void
+set_favourite (TrackerSparqlCursor *cursor,
+               gint                 column,
+               GrlMedia            *media,
+               GrlKeyID             key)
+{
+  const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL);
+  gboolean is_favourite = FALSE;
+
+  if (str != NULL && g_str_has_suffix (str, "predefined-tag-favorite"))
+    is_favourite = TRUE;
+
+  grl_data_set_boolean (GRL_DATA (media), key, is_favourite);
+}
+
+static void
+set_title_from_filename (TrackerSparqlCursor *cursor,
+                         gint                 column,
+                         GrlMedia            *media,
+                         GrlKeyID             key)
+{
+  const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL);
+  if (key == GRL_METADATA_KEY_TITLE) {
+    grl_data_set_boolean (GRL_DATA (media), GRL_METADATA_KEY_TITLE_FROM_FILENAME, TRUE);
+    grl_media_set_title (media, str);
+  }
+}
+
+static void
+set_title (TrackerSparqlCursor *cursor,
+           gint                 column,
+           GrlMedia            *media,
+           GrlKeyID             key)
+{
+  const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL);
+  grl_data_set_boolean (GRL_DATA (media), GRL_METADATA_KEY_TITLE_FROM_FILENAME, FALSE);
+  grl_media_set_title (media, str);
+}
+
+static void
+set_string_metadata_keys (TrackerSparqlCursor *cursor,
+                          gint                 column,
+                          GrlMedia            *media,
+                          GrlKeyID             key)
+{
+  const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL);
+  grl_data_set_string (GRL_DATA (media), key, str);
+}
+
+static void
+set_int_metadata_keys (TrackerSparqlCursor *cursor,
+                       gint                 column,
+                       GrlMedia            *media,
+                       GrlKeyID             key)
+{
+  const gint64 value = tracker_sparql_cursor_get_integer (cursor, column);
+  grl_data_set_int (GRL_DATA (media), key, value);
+}
+
+static tracker_grl_sparql_t *
+insert_key_mapping (GrlKeyID     grl_key,
+                    const gchar *sparql_key_attr,
+                    const gchar *sparql_key_attr_call,
+                    const gchar *sparql_key_flavor)
+{
+  tracker_grl_sparql_t *assoc;
+  GList *assoc_list;
+  gchar *canon_name;
+
+  g_return_val_if_fail (grl_key != GRL_METADATA_KEY_INVALID, NULL);
+
+  assoc = g_new0 (tracker_grl_sparql_t, 1);
+  assoc_list = g_hash_table_lookup (grl_to_sparql_mapping,
+                                    GRLKEYID_TO_POINTER (grl_key));
+  canon_name = g_strdup (GRL_METADATA_KEY_GET_NAME (grl_key));
+
+  assoc->grl_key               = grl_key;
+  assoc->sparql_key_name       = build_flavored_key (canon_name,
+                                                     sparql_key_flavor);
+  assoc->sparql_key_name_canon = g_strdup (canon_name);
+  assoc->sparql_key_attr       = sparql_key_attr;
+  assoc->sparql_key_attr_call  = sparql_key_attr_call;
+  assoc->sparql_key_flavor     = sparql_key_flavor;
+
+  assoc_list = g_list_append (assoc_list, assoc);
+
+  g_hash_table_insert (grl_to_sparql_mapping,
+                       GRLKEYID_TO_POINTER (grl_key),
+                       assoc_list);
+  g_hash_table_insert (sparql_to_grl_mapping,
+                       (gpointer) assoc->sparql_key_name,
+                       assoc);
+  g_hash_table_insert (sparql_to_grl_mapping,
+                       (gpointer) GRL_METADATA_KEY_GET_NAME (grl_key),
+                       assoc);
+
+  /* Grilo maps key names to SPARQL variables. Key names can contain dashes,
+   * however SPARQL does not allow dashes in variable names. So use the to
+   * underscores converted canon_name as additional mapping.
+   */
+  if (g_strrstr (assoc->sparql_key_name_canon, "_")) {
+    g_hash_table_insert (sparql_to_grl_mapping,
+                         (gpointer) assoc->sparql_key_name_canon,
+                         assoc);
+  }
+
+  g_free (canon_name);
+
+  return assoc;
+}
+
+static tracker_grl_sparql_t *
+insert_key_mapping_with_setter (GrlKeyID                       grl_key,
+                                const gchar                   *sparql_key_attr,
+                                const gchar                   *sparql_key_attr_call,
+                                const gchar                   *sparql_key_flavor,
+                                tracker_grl_sparql_setter_cb_t setter)
+{
+  tracker_grl_sparql_t *assoc;
+
+  assoc = insert_key_mapping (grl_key,
+                              sparql_key_attr,
+                              sparql_key_attr_call,
+                              sparql_key_flavor);
+
+  assoc->set_value = setter;
+
+  return assoc;
+}
+
+void
+grl_tracker_setup_key_mappings (void)
+{
+  GrlRegistry *registry = grl_registry_get_default ();
+  GrlKeyID grl_metadata_key_chromaprint;
+
+  grl_metadata_key_tracker_urn =
+    grl_registry_lookup_metadata_key (registry, "tracker-urn");
+
+  grl_metadata_key_gibest_hash =
+    grl_registry_lookup_metadata_key (registry, "gibest-hash");
+
+  grl_metadata_key_chromaprint =
+    grl_registry_lookup_metadata_key (registry, "chromaprint");
+
+  grl_to_sparql_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
+  sparql_to_grl_mapping = g_hash_table_new (g_str_hash, g_str_equal);
+
+  insert_key_mapping (grl_metadata_key_tracker_urn,
+                      NULL,
+                      "?urn",
+                      "file");
+
+  insert_key_mapping (GRL_METADATA_KEY_ALBUM,
+                      NULL,
+                      "nie:title(nmm:musicAlbum(?urn))",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_ALBUM_DISC_NUMBER,
+                      NULL,
+                      "nmm:setNumber(nmm:musicAlbumDisc(?urn))",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_ARTIST,
+                      NULL,
+                      "nmm:artistName(nmm:performer(?urn))",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_ALBUM_ARTIST,
+                      NULL,
+                      "nmm:artistName(nmm:albumArtist(nmm:musicAlbum(?urn)))",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_AUTHOR,
+                      NULL,
+                      "nmm:artistName(nmm:performer(?urn))",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_BITRATE,
+                      "nfo:averageBitrate",
+                      "nfo:averageBitrate(?urn)",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_CHILDCOUNT,
+                      "nfo:entryCounter",
+                      "nfo:entryCounter(?urn)",
+                      "directory");
+
+  insert_key_mapping (GRL_METADATA_KEY_COMPOSER,
+                      NULL,
+                      "nmm:artistName(nmm:composer(?urn))",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_SIZE,
+                      NULL,
+                      "nfo:fileSize(?urn)",
+                      "file");
+
+  insert_key_mapping (grl_metadata_key_gibest_hash,
+                      NULL,
+                      "(select nfo:hashValue(?h) { ?urn nfo:hasHash ?h . ?h nfo:hashAlgorithm \"gibest\" })",
+                      "video");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_MODIFICATION_DATE,
+                                  "nfo:fileLastModified",
+                                  "nfo:fileLastModified(?urn)",
+                                  "file",
+                                  set_date);
+
+  insert_key_mapping (GRL_METADATA_KEY_DURATION,
+                      "nfo:duration",
+                      "nfo:duration(?urn)",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_MB_TRACK_ID,
+                      NULL,
+                      "(SELECT tracker:referenceIdentifier(?t) AS ?t_id { ?urn tracker:hasExternalReference 
?t . ?t tracker:referenceSource \"https://musicbrainz.org/doc/Track\"; })",
+                     "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_MB_ARTIST_ID,
+                      NULL,
+                     "(SELECT tracker:referenceIdentifier(?a) AS ?a_id { ?urn nmm:performer ?artist . 
?artist tracker:hasExternalReference ?a . ?a tracker:referenceSource \"https://musicbrainz.org/doc/Artist\"; 
})",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_MB_RECORDING_ID,
+                      NULL,
+                     "(SELECT tracker:referenceIdentifier(?r) AS ?r_id { ?urn tracker:hasExternalReference 
?r . ?r tracker:referenceSource \"https://musicbrainz.org/doc/Recording\"; })",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_MB_RELEASE_ID,
+                      NULL,
+                     "(SELECT tracker:referenceIdentifier(?re) AS ?re_id { ?urn nmm:musicAlbum ?album . 
?album tracker:hasExternalReference ?re . ?re tracker:referenceSource \"https://musicbrainz.org/doc/Release\"; 
})",
+                      "audio");
+
+  insert_key_mapping (GRL_METADATA_KEY_MB_RELEASE_GROUP_ID,
+                      NULL,
+                     "(SELECT tracker:referenceIdentifier(?rg) AS ?rg_id { ?urn nmm:musicAlbum ?album . 
?album tracker:hasExternalReference ?rg . ?rg tracker:referenceSource 
\"https://musicbrainz.org/doc/Release_Group\"; })",
+                     "audio");
+
+  if (grl_metadata_key_chromaprint != 0) {
+    insert_key_mapping_with_setter (grl_metadata_key_chromaprint,
+                                    NULL,
+                                    "(select nfo:hashValue(?h) { ?urn nfo:hasHash ?h . ?h nfo:hashAlgorithm 
\"chromaprint\" })",
+                                    "audio",
+                                    set_string_metadata_keys);
+  };
+
+  insert_key_mapping (GRL_METADATA_KEY_FRAMERATE,
+                      "nfo:frameRate",
+                      "nfo:frameRate(?urn)",
+                      "video");
+
+  insert_key_mapping (GRL_METADATA_KEY_HEIGHT,
+                      "nfo:height",
+                      "nfo:height(?urn)",
+                      "video");
+
+  insert_key_mapping (GRL_METADATA_KEY_ID,
+                      "tracker:id",
+                      "tracker:id(?urn)",
+                      "file");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_LAST_PLAYED,
+                                  "nfo:fileLastAccessed",
+                                  "nfo:fileLastAccessed(?urn)",
+                                  "file",
+                                  set_date);
+
+  insert_key_mapping (GRL_METADATA_KEY_MIME,
+                      "nie:mimeType",
+                      "nie:mimeType(?urn)",
+                      "file");
+
+  insert_key_mapping (GRL_METADATA_KEY_SITE,
+                      "nie:url",
+                      "nie:url(?urn)",
+                      "file");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_TITLE,
+                                  "nie:title",
+                                  "nie:title(?urn)",
+                                  "audio",
+                                  set_title);
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_TITLE,
+                                  "nfo:fileName",
+                                  "nfo:fileName(?urn)",
+                                  "file",
+                                  set_title_from_filename);
+
+  insert_key_mapping (GRL_METADATA_KEY_URL,
+                      "nie:url",
+                      "nie:url(?urn)",
+                      "file");
+
+  insert_key_mapping (GRL_METADATA_KEY_WIDTH,
+                      "nfo:width",
+                      "nfo:width(?urn)",
+                      "video");
+
+  insert_key_mapping (GRL_METADATA_KEY_SEASON,
+                      "nmm:season",
+                      "nmm:season(?urn)",
+                      "video");
+
+  insert_key_mapping (GRL_METADATA_KEY_EPISODE,
+                      "nmm:episodeNumber",
+                      "nmm:episodeNumber(?urn)",
+                      "video");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_CREATION_DATE,
+                                  "nie:contentCreated",
+                                  "nie:contentCreated(?urn)",
+                                  "image",
+                                  set_date);
+
+  insert_key_mapping (GRL_METADATA_KEY_CAMERA_MODEL,
+                      NULL,
+                      "nfo:model(nfo:equipment(?urn))",
+                      "image");
+
+  insert_key_mapping (GRL_METADATA_KEY_FLASH_USED,
+                      "nmm:flash",
+                      "nmm:flash(?urn)",
+                      "image");
+
+  insert_key_mapping (GRL_METADATA_KEY_EXPOSURE_TIME,
+                      "nmm:exposureTime",
+                      "nmm:exposureTime(?urn)",
+                      "image");
+
+  insert_key_mapping (GRL_METADATA_KEY_ISO_SPEED,
+                      "nmm:isoSpeed",
+                      "nmm:isoSpeed(?urn)",
+                      "image");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_ORIENTATION,
+                                  "nfo:orientation",
+                                  "nfo:orientation(?urn)",
+                                  "image",
+                                  set_orientation);
+
+  insert_key_mapping (GRL_METADATA_KEY_PLAY_COUNT,
+                      "nie:usageCounter",
+                      "nie:usageCounter(?urn)",
+                      "media");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_LAST_PLAYED,
+                                  "nie:contentAccessed",
+                                  "nie:contentAccessed(?urn)",
+                                  "media",
+                                  set_date);
+
+  insert_key_mapping (GRL_METADATA_KEY_LAST_POSITION,
+                      "nfo:lastPlayedPosition",
+                      "nfo:lastPlayedPosition(?urn)",
+                      "media");
+
+  insert_key_mapping (GRL_METADATA_KEY_START_TIME,
+                      "nfo:audioOffset",
+                      "nfo:audioOffset(?urn)",
+                      "media");
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_TRACK_NUMBER,
+                                  "nmm:trackNumber",
+                                  "nmm:trackNumber(?urn)",
+                                  "audio",
+                                  set_int_metadata_keys);
+
+  insert_key_mapping_with_setter (GRL_METADATA_KEY_FAVOURITE,
+                                  "nao:hasTag",
+                                  "nao:hasTag(?urn)",
+                                  "audio",
+                                  set_favourite);
+}
+
+tracker_grl_sparql_t *
+grl_tracker_get_mapping_from_sparql (const gchar *key)
+{
+  return (tracker_grl_sparql_t *) g_hash_table_lookup (sparql_to_grl_mapping,
+                                                       key);
+}
+
+static GList *
+get_mapping_from_grl (const GrlKeyID key)
+{
+  return (GList *) g_hash_table_lookup (grl_to_sparql_mapping,
+                                        GRLKEYID_TO_POINTER (key));
+}
+
+gboolean
+grl_tracker_key_is_supported (const GrlKeyID key)
+{
+  return g_hash_table_lookup (grl_to_sparql_mapping,
+                              GRLKEYID_TO_POINTER (key)) != NULL;
+}
+
+/**/
+
+gchar *
+grl_tracker_source_get_device_constraint (GrlTrackerSourcePriv *priv)
+{
+  if (priv->tracker_datasource == NULL ||
+      priv->tracker_datasource[0] == '\0')
+    return g_strdup ("");
+
+  return g_strdup_printf ("?urn nie:dataSource <%s> .",
+                          priv->tracker_datasource);
+}
+
+gchar *
+grl_tracker_source_get_select_string (const GList *keys)
+{
+  const GList *key = keys;
+  GString *gstr = g_string_new ("");
+  GList *assoc_list;
+  tracker_grl_sparql_t *assoc;
+
+  assoc_list = get_mapping_from_grl (grl_metadata_key_tracker_urn);
+  assoc = (tracker_grl_sparql_t *) assoc_list->data;
+  g_string_append_printf (gstr, "%s AS ?%s ",
+                          assoc->sparql_key_attr_call,
+                          assoc->sparql_key_name);
+
+  while (key != NULL) {
+    assoc_list = get_mapping_from_grl (GRLPOINTER_TO_KEYID (key->data));
+    while (assoc_list != NULL) {
+      assoc = (tracker_grl_sparql_t *) assoc_list->data;
+      if (assoc != NULL) {
+        g_string_append_printf (gstr, "%s AS ?%s ",
+                                assoc->sparql_key_attr_call,
+                                assoc->sparql_key_name);
+      }
+      assoc_list = assoc_list->next;
+    }
+    key = key->next;
+  }
+
+  return g_string_free (gstr, FALSE);
+}
+
+static void
+gen_prop_insert_string (GString *gstr,
+                        tracker_grl_sparql_t *assoc,
+                        GrlData *data)
+{
+  gchar *tmp;
+  GType type = GRL_METADATA_KEY_GET_TYPE (assoc->grl_key);
+
+  switch (type) {
+  case G_TYPE_STRING:
+    tmp = g_strescape (grl_data_get_string (data, assoc->grl_key), NULL);
+    g_string_append_printf (gstr, "%s \"%s\"",
+                            assoc->sparql_key_attr, tmp);
+    g_free (tmp);
+    break;
+
+  case G_TYPE_INT:
+    g_string_append_printf (gstr, "%s %i",
+                            assoc->sparql_key_attr,
+                            grl_data_get_int (data, assoc->grl_key));
+    break;
+
+  case G_TYPE_FLOAT:
+    g_string_append_printf (gstr, "%s %f",
+                            assoc->sparql_key_attr,
+                            grl_data_get_float (data, assoc->grl_key));
+    break;
+
+  case G_TYPE_BOOLEAN:
+    /* Special case for favourite tag, see comment in
+     * grl_tracker_tracker_get_insert_string for more details.
+     */
+    if (assoc->grl_key == GRL_METADATA_KEY_FAVOURITE) {
+      g_string_append_printf (gstr, "%s nao:predefined-tag-favorite",
+                              assoc->sparql_key_attr);
+    }
+    break;
+
+  default:
+    if (type == G_TYPE_DATE_TIME) {
+      tmp = g_date_time_format (grl_data_get_boxed (data, assoc->grl_key),
+                                "%FT%T%:z");
+      g_string_append_printf (gstr, "%s '%s'",
+                              assoc->sparql_key_attr,
+                              tmp);
+
+      g_free (tmp);
+    }
+    break;
+  }
+}
+
+gchar *
+grl_tracker_tracker_get_insert_string (GrlMedia *media, const GList *keys)
+{
+  gboolean first = TRUE;
+  const GList *key;
+  GString *gstr = g_string_new ("");
+
+  for (key = keys; key != NULL; key = key->next) {
+    const GList *assoc_list;
+    GrlKeyID key_id = GRLPOINTER_TO_KEYID (key->data);
+
+    for (assoc_list = get_mapping_from_grl (key_id);
+         assoc_list != NULL;
+         assoc_list = assoc_list->next) {
+      tracker_grl_sparql_t *assoc = assoc_list->data;
+
+      if (assoc == NULL)
+        continue;
+
+      /* The favourite key is really setting or deleting a tag
+       * in tracker, so in the case of setting it to false skip
+       * the insert string creation step for this key completely.
+       */
+      if (assoc->grl_key == GRL_METADATA_KEY_FAVOURITE &&
+          !grl_media_get_favourite (media))
+        continue;
+
+      if (!grl_data_has_key (GRL_DATA (media), key_id))
+        continue;
+
+      /* Special case for key title, nfo:fileName is read-only.
+       * It cannot be modified.
+       */
+      if (assoc->grl_key == GRL_METADATA_KEY_TITLE &&
+          g_strcmp0 (assoc->sparql_key_attr, "nfo:fileName") == 0) {
+        continue;
+      }
+
+      if (!first)
+        g_string_append (gstr, " ; ");
+
+      gen_prop_insert_string (gstr, assoc, GRL_DATA (media));
+      first = FALSE;
+    }
+  }
+
+  return g_string_free (gstr, FALSE);
+}
+
+gchar *
+grl_tracker_get_delete_string (const GList *keys)
+{
+  gboolean first = TRUE;
+  const GList *key = keys, *assoc_list;
+  tracker_grl_sparql_t *assoc;
+  GString *gstr = g_string_new ("");
+  gchar *ret;
+  gint var_n = 0;
+
+  while (key != NULL) {
+    assoc_list = get_mapping_from_grl (GRLPOINTER_TO_KEYID (key->data));
+    while (assoc_list != NULL) {
+      assoc = (tracker_grl_sparql_t *) assoc_list->data;
+      if (assoc != NULL) {
+        /* Special case for key title, nfo:fileName is read-only.
+         * It cannot be modified.
+         */
+        if (assoc->grl_key == GRL_METADATA_KEY_TITLE &&
+            g_strcmp0 (assoc->sparql_key_attr, "nfo:fileName") == 0) {
+          assoc_list = assoc_list->next;
+          continue;
+        }
+
+        if (first) {
+          g_string_append_printf (gstr, "%s ?v%i",
+                                  assoc->sparql_key_attr, var_n);
+          first = FALSE;
+        } else {
+          g_string_append_printf (gstr, " ; %s ?v%i",
+                                  assoc->sparql_key_attr, var_n);
+        }
+        var_n++;
+      }
+      assoc_list = assoc_list->next;
+    }
+    key = key->next;
+  }
+
+  ret = gstr->str;
+  g_string_free (gstr, FALSE);
+
+  return ret;
+}
+
+gchar *
+grl_tracker_get_delete_conditional_string (const gchar *urn,
+                                           const GList *keys)
+{
+  gboolean first = TRUE;
+  const GList *key = keys, *assoc_list;
+  tracker_grl_sparql_t *assoc;
+  GString *gstr = g_string_new ("");
+  gchar *ret;
+  gint var_n = 0;
+
+  while (key != NULL) {
+    assoc_list = get_mapping_from_grl (GRLPOINTER_TO_KEYID (key->data));
+    while (assoc_list != NULL) {
+      assoc = (tracker_grl_sparql_t *) assoc_list->data;
+      if (assoc != NULL) {
+        /* Special case for key title, nfo:fileName is read-only.
+         * It cannot be modified.
+         */
+        if (assoc->grl_key == GRL_METADATA_KEY_TITLE &&
+            g_strcmp0 (assoc->sparql_key_attr, "nfo:fileName") == 0) {
+          assoc_list = assoc_list->next;
+          continue;
+        }
+
+        if (first) {
+          g_string_append_printf (gstr, "OPTIONAL { <%s>  %s ?v%i }",
+                                  urn,  assoc->sparql_key_attr, var_n);
+          first = FALSE;
+        } else {
+          g_string_append_printf (gstr, " . OPTIONAL { <%s> %s ?v%i }",
+                                  urn, assoc->sparql_key_attr, var_n);
+        }
+        var_n++;
+      }
+      assoc_list = assoc_list->next;
+    }
+    key = key->next;
+  }
+
+  ret = gstr->str;
+  g_string_free (gstr, FALSE);
+
+  return ret;
+}
+
+static GrlMedia *
+grl_tracker_build_grilo_media_default (GHashTable *ht)
+{
+  if (g_hash_table_lookup (ht, RDF_TYPE_MUSIC)) {
+    return grl_media_audio_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_VIDEO)) {
+    return grl_media_video_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_IMAGE)) {
+    return grl_media_image_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_ARTIST)) {
+    return grl_media_container_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_ALBUM)) {
+    return grl_media_container_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_CONTAINER)) {
+    return grl_media_container_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_FOLDER)) {
+    return grl_media_container_new ();
+  } else if (g_hash_table_lookup (ht, RDF_TYPE_PLAYLIST)) {
+    return grl_media_container_new ();
+  }
+
+  return NULL;
+}
+
+/**/
+
+/* Builds an appropriate GrlMedia based on ontology type returned by
+   tracker, or NULL if unknown */
+GrlMedia *
+grl_tracker_build_grilo_media (const gchar   *rdf_type,
+                               GrlTypeFilter  type_filter)
+{
+  GrlMedia *media = NULL;
+  gchar **rdf_single_type;
+  int i;
+  GHashTable *ht;
+
+  if (!rdf_type) {
+    return NULL;
+  }
+
+  /* As rdf_type can be formed by several types, split them */
+  rdf_single_type = g_strsplit (rdf_type, ",", -1);
+  i = g_strv_length (rdf_single_type) - 1;
+  ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  for (; i>= 0; i--)
+    g_hash_table_insert (ht, g_path_get_basename (rdf_single_type[i]), GINT_TO_POINTER(TRUE));
+
+  if (type_filter == GRL_TYPE_FILTER_NONE ||
+      type_filter == GRL_TYPE_FILTER_ALL) {
+    media = grl_tracker_build_grilo_media_default (ht);
+  } else if ((type_filter & GRL_TYPE_FILTER_AUDIO) &&
+             g_hash_table_lookup (ht, RDF_TYPE_MUSIC)) {
+    media = grl_media_audio_new ();
+  } else if ((type_filter & GRL_TYPE_FILTER_VIDEO) &&
+             g_hash_table_lookup (ht, RDF_TYPE_VIDEO)) {
+    media = grl_media_video_new ();
+  } else if ((type_filter & GRL_TYPE_FILTER_IMAGE) &&
+             g_hash_table_lookup (ht, RDF_TYPE_IMAGE)) {
+    media = grl_media_image_new ();
+  } else {
+    media = grl_tracker_build_grilo_media_default (ht);
+  }
+
+  g_hash_table_destroy (ht);
+  g_strfreev (rdf_single_type);
+
+  if (!media)
+    media = grl_media_new ();
+
+  return media;
+}
+
+/**/
+
+static gchar *
+get_tracker_volume_name (const gchar *uri,
+                        const gchar *datasource)
+{
+  gchar *source_name = NULL;
+  GVolumeMonitor *volume_monitor;
+  GList *mounts, *mount;
+  GFile *file;
+
+  if (uri != NULL && *uri != '\0') {
+    volume_monitor = g_volume_monitor_get ();
+    mounts = g_volume_monitor_get_mounts (volume_monitor);
+    file = g_file_new_for_uri (uri);
+
+    mount = mounts;
+    while (mount != NULL) {
+      GFile *m_file = g_mount_get_root (G_MOUNT (mount->data));
+
+      if (g_file_equal (m_file, file)) {
+        gchar *m_name = g_mount_get_name (G_MOUNT (mount->data));
+        g_object_unref (G_OBJECT (m_file));
+        source_name = g_strdup_printf (_("Removable ā€” %s"), m_name);
+        g_free (m_name);
+        break;
+      }
+      g_object_unref (G_OBJECT (m_file));
+
+      mount = mount->next;
+    }
+    g_list_free_full (mounts, g_object_unref);
+    g_object_unref (G_OBJECT (file));
+    g_object_unref (G_OBJECT (volume_monitor));
+  } else {
+    source_name = g_strdup (_("Local files"));
+  }
+
+  return source_name;
+}
+
+gchar *
+grl_tracker_get_source_name (const gchar *rdf_type,
+                             const gchar *uri,
+                             const gchar *datasource,
+                             const gchar *datasource_name)
+{
+  gchar *source_name = NULL;
+  gchar **rdf_single_type;
+  gint i;
+
+  /* As rdf_type can be formed by several types, split them */
+  rdf_single_type = g_strsplit (rdf_type, ",", -1);
+  i = g_strv_length (rdf_single_type) - 1;
+
+  while (i >= 0) {
+    if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_VOLUME)) {
+      source_name = get_tracker_volume_name (uri, datasource);
+      break;
+    }
+    i--;
+  }
+
+  g_strfreev (rdf_single_type);
+
+  return source_name;
+}
+
+const GList *
+grl_tracker_supported_keys (GrlSource *source)
+{
+  static GList *supported_keys = NULL;
+
+  if (!supported_keys) {
+    supported_keys =  g_hash_table_get_keys (grl_to_sparql_mapping);
+  }
+
+  return supported_keys;
+}
diff --git a/src/tracker3/grl-tracker-utils.h b/src/tracker3/grl-tracker-utils.h
new file mode 100644
index 00000000..bf6c0ef5
--- /dev/null
+++ b/src/tracker3/grl-tracker-utils.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_UTILS_H_
+#define _GRL_TRACKER_UTILS_H_
+
+#include "grl-tracker-source-priv.h"
+
+/* ------- Definitions ------- */
+
+#define RDF_TYPE_ALBUM     "nmm#MusicAlbum"
+#define RDF_TYPE_ARTIST    "nmm#Artist"
+#define RDF_TYPE_AUDIO     "nfo#Audio"
+#define RDF_TYPE_MUSIC     "nmm#MusicPiece"
+#define RDF_TYPE_IMAGE     "nmm#Photo"
+#define RDF_TYPE_VIDEO     "nmm#Video"
+#define RDF_TYPE_FOLDER    "nfo#Folder"
+#define RDF_TYPE_DOCUMENT  "nfo#Document"
+#define RDF_TYPE_CONTAINER "grilo#Container"
+#define RDF_TYPE_PLAYLIST  "nmm#Playlist"
+
+#define RDF_TYPE_VOLUME "tracker#Volume"
+#define RDF_TYPE_UPNP   "upnp#ContentDirectory"
+
+/**/
+
+typedef void (*tracker_grl_sparql_setter_cb_t) (TrackerSparqlCursor *cursor,
+                                                gint                 column,
+                                                GrlMedia            *media,
+                                                GrlKeyID             key);
+
+typedef struct {
+  GrlKeyID     grl_key;
+  const gchar *sparql_key_name;
+  const gchar *sparql_key_name_canon;
+  const gchar *sparql_key_attr;
+  const gchar *sparql_key_attr_call;
+  const gchar *sparql_key_flavor;
+
+  tracker_grl_sparql_setter_cb_t set_value;
+} tracker_grl_sparql_t;
+
+extern GrlKeyID grl_metadata_key_tracker_urn;
+
+const GList *grl_tracker_supported_keys (GrlSource *source);
+
+gboolean grl_tracker_key_is_supported (const GrlKeyID key);
+
+void grl_tracker_setup_key_mappings (void);
+
+tracker_grl_sparql_t *grl_tracker_get_mapping_from_sparql (const gchar *key);
+
+GrlMedia *grl_tracker_build_grilo_media (const gchar   *rdf_type,
+                                         GrlTypeFilter  type_filter);
+
+gchar *grl_tracker_source_get_device_constraint (GrlTrackerSourcePriv *priv);
+
+gchar *grl_tracker_source_get_select_string (const GList *keys);
+
+gchar *grl_tracker_tracker_get_insert_string (GrlMedia *media,
+                                              const GList *keys);
+
+gchar *grl_tracker_get_delete_string (const GList *keys);
+
+gchar *grl_tracker_get_delete_conditional_string (const gchar *urn,
+                                                  const GList *keys);
+
+gchar *grl_tracker_get_source_name (const gchar *rdf_type,
+                                    const gchar *uri,
+                                    const gchar *datasource,
+                                    const gchar *datasource_name);
+
+#endif /* _GRL_TRACKER_UTILS_H_ */
diff --git a/src/tracker3/grl-tracker.c b/src/tracker3/grl-tracker.c
new file mode 100644
index 00000000..652e77bd
--- /dev/null
+++ b/src/tracker3/grl-tracker.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2011-2012 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <grilo.h>
+#include <string.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "grl-tracker.h"
+#include "grl-tracker-source.h"
+#include "grl-tracker-source-api.h"
+#include "grl-tracker-source-notif.h"
+#include "grl-tracker-request-queue.h"
+#include "grl-tracker-utils.h"
+
+/* --------- Logging  -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT tracker_general_log_domain
+GRL_LOG_DOMAIN_STATIC(tracker_general_log_domain);
+
+/* ------- Definitions ------- */
+
+#define TRACKER_FOLDER_CLASS_REQUEST           \
+  "SELECT ?urn WHERE "                         \
+  "{ "                                         \
+  "?urn a rdfs:Class . "                       \
+  "FILTER(fn:ends-with(?urn,\"nfo#Folder\")) " \
+  "}"
+
+#define TRACKER_UPNP_CLASS_REQUEST                      \
+  "SELECT ?urn WHERE "                                  \
+  "{ "                                                  \
+  "?urn a rdfs:Class . "                                \
+  "FILTER(fn:ends-with(?urn,\"upnp#UPnPDataObject\")) " \
+  "}"
+
+#define TRACKER_NOTIFY_FOLDER_UPDATE            \
+  "INSERT "                                     \
+  "{ "                                          \
+  "<%s> tracker:notify true "                   \
+  "}"
+
+/* --- Other --- */
+
+gboolean grl_tracker3_plugin_init (GrlRegistry *registry,
+                                   GrlPlugin *plugin,
+                                   GList *configs);
+
+/* ===================== Globals  ================= */
+
+TrackerSparqlConnection *grl_tracker_connection = NULL;
+GrlPlugin *grl_tracker_plugin;
+GCancellable *grl_tracker_plugin_init_cancel = NULL;
+gboolean grl_tracker_upnp_present = FALSE;
+GrlTrackerQueue *grl_tracker_queue = NULL;
+
+/* tracker plugin config */
+gboolean grl_tracker_browse_filesystem = FALSE;
+gboolean grl_tracker_show_documents    = FALSE;
+
+/* =================== Tracker Plugin  =============== */
+
+static void
+init_sources (void)
+{
+  grl_tracker_setup_key_mappings ();
+
+  grl_tracker_queue = grl_tracker_queue_new ();
+
+  if (grl_tracker_connection != NULL) {
+    grl_tracker_notify_init (grl_tracker_connection);
+
+    grl_tracker_source_sources_init ();
+  }
+}
+
+static void
+tracker_update_folder_class_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      data)
+{
+  init_sources ();
+}
+
+static void
+tracker_get_folder_class_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer       data)
+{
+  GError *error = NULL;
+  TrackerSparqlCursor  *cursor;
+
+  GRL_DEBUG ("%s", __FUNCTION__);
+
+  cursor = tracker_sparql_connection_query_finish (grl_tracker_connection,
+                                                   result, &error);
+
+  if (error) {
+    GRL_INFO ("Could not execute sparql query for folder class: %s",
+              error->message);
+    g_error_free (error);
+  }
+
+  if (!cursor) {
+    init_sources ();
+    return;
+  }
+
+  if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+    gchar *update = g_strdup_printf (TRACKER_NOTIFY_FOLDER_UPDATE,
+                                     tracker_sparql_cursor_get_string (cursor,
+                                                                       0,
+                                                                       NULL));
+
+    GRL_DEBUG ("\tupdate query: '%s'", update);
+
+    tracker_sparql_connection_update_async (grl_tracker_connection,
+                                            update,
+                                            G_PRIORITY_DEFAULT,
+                                            NULL,
+                                            tracker_update_folder_class_cb,
+                                            NULL);
+
+    g_free (update);
+  }
+
+  g_object_unref (cursor);
+}
+
+static void
+tracker_get_upnp_class_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      data)
+{
+  GError *error = NULL;
+  TrackerSparqlCursor *cursor;
+
+  GRL_DEBUG ("%s", G_STRFUNC);
+
+  cursor = tracker_sparql_connection_query_finish (grl_tracker_connection,
+                                                   result, &error);
+  if (error) {
+    GRL_INFO ("Could not execute sparql query for upnp class: %s",
+              error->message);
+    g_error_free (error);
+  } else {
+    if (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+      GRL_DEBUG ("\tUPnP ontology present");
+      grl_tracker_upnp_present = TRUE;
+    }
+  }
+
+  g_clear_object (&cursor);
+
+  if (grl_tracker_browse_filesystem)
+    tracker_sparql_connection_query_async (grl_tracker_connection,
+                                           TRACKER_FOLDER_CLASS_REQUEST,
+                                           grl_tracker_plugin_init_cancel,
+                                           tracker_get_folder_class_cb,
+                                           NULL);
+  else
+    init_sources ();
+}
+
+static void
+tracker_new_connection_cb (GObject      *object,
+                           GAsyncResult *res,
+                           GrlPlugin    *plugin)
+{
+  GError *error = NULL;
+
+  GRL_DEBUG ("%s", __FUNCTION__);
+
+  grl_tracker_connection = tracker_sparql_connection_new_finish (res, &error);
+
+  if (error) {
+    GRL_INFO ("Could not get connection to Tracker: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  GRL_DEBUG ("\trequest : '%s'", TRACKER_UPNP_CLASS_REQUEST);
+
+  tracker_sparql_connection_query_async (grl_tracker_connection,
+                                         TRACKER_UPNP_CLASS_REQUEST,
+                                         grl_tracker_plugin_init_cancel,
+                                         tracker_get_upnp_class_cb,
+                                         NULL);
+}
+
+gboolean
+grl_tracker3_plugin_init (GrlRegistry *registry,
+                          GrlPlugin *plugin,
+                          GList *configs)
+{
+  GrlConfig *config;
+  gint config_count;
+  GFile *ontology;
+
+  GRL_LOG_DOMAIN_INIT (tracker_general_log_domain, "tracker3-general");
+
+  /* Initialize i18n */
+  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+  grl_tracker_source_init_requests ();
+
+  grl_tracker_plugin = plugin;
+
+  if (!configs) {
+    GRL_INFO ("\tConfiguration not provided! Using default configuration.");
+  } else {
+    config_count = g_list_length (configs);
+    if (config_count > 1) {
+      GRL_INFO ("\tProvided %i configs, but will only use one", config_count);
+    }
+
+    config = GRL_CONFIG (configs->data);
+
+    grl_tracker_browse_filesystem =
+      grl_config_get_boolean (config, "browse-filesystem");
+    grl_tracker_show_documents =
+      grl_config_get_boolean (config, "show-documents");
+  }
+
+  grl_tracker_plugin_init_cancel = g_cancellable_new ();
+  ontology = tracker_sparql_get_ontology_nepomuk ();
+  tracker_sparql_connection_new_async (TRACKER_SPARQL_CONNECTION_FLAGS_NONE,
+                                       NULL,
+                                       ontology,
+                                       grl_tracker_plugin_init_cancel,
+                                       (GAsyncReadyCallback) tracker_new_connection_cb,
+                                       plugin);
+  g_object_unref (ontology);
+
+  return TRUE;
+}
+
+static void
+grl_tracker3_plugin_deinit (GrlPlugin *plugin)
+{
+  g_cancellable_cancel (grl_tracker_plugin_init_cancel);
+  g_clear_object (&grl_tracker_plugin_init_cancel);
+  g_clear_object (&grl_tracker_connection);
+}
+
+static void
+grl_tracker3_plugin_register_keys (GrlRegistry *registry,
+                                   GrlPlugin   *plugin)
+{
+  grl_registry_register_metadata_key (grl_registry_get_default (),
+                                      g_param_spec_string ("tracker-category",
+                                                           "Tracker category",
+                                                           "Category a media belongs to",
+                                                           NULL,
+                                                           G_PARAM_STATIC_STRINGS |
+                                                           G_PARAM_READWRITE),
+                                      GRL_METADATA_KEY_INVALID,
+                                      NULL);
+  grl_registry_register_metadata_key (grl_registry_get_default (),
+                                      g_param_spec_string ("gibest-hash",
+                                                           "Gibest hash",
+                                                           "Gibest hash of the video file",
+                                                           NULL,
+                                                           G_PARAM_STATIC_STRINGS |
+                                                           G_PARAM_READWRITE),
+                                      GRL_METADATA_KEY_INVALID,
+                                      NULL);
+  grl_registry_register_metadata_key (grl_registry_get_default (),
+                                      g_param_spec_string ("tracker-urn",
+                                                           "Tracker URN",
+                                                           "Universal resource number in Tracker's store",
+                                                           NULL,
+                                                           G_PARAM_STATIC_STRINGS |
+                                                           G_PARAM_READWRITE),
+                                      GRL_METADATA_KEY_INVALID,
+                                      NULL);
+}
+
+GRL_PLUGIN_DEFINE (GRL_MAJOR,
+                   GRL_MINOR,
+                   GRL_TRACKER_PLUGIN_ID,
+                   "Tracker3",
+                   "A plugin for searching multimedia content using Tracker Miners 3.x",
+                   "Igalia S.L.",
+                   VERSION,
+                   "LGPL",
+                   "http://www.igalia.com";,
+                   grl_tracker3_plugin_init,
+                   grl_tracker3_plugin_deinit,
+                   grl_tracker3_plugin_register_keys);
diff --git a/src/tracker3/grl-tracker.h b/src/tracker3/grl-tracker.h
new file mode 100644
index 00000000..6aacf099
--- /dev/null
+++ b/src/tracker3/grl-tracker.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Lionel Landwerlin <lionel g landwerlin linux intel com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_TRACKER_H_
+#define _GRL_TRACKER_H_
+
+#include "grl-tracker-request-queue.h"
+
+/* ---- Plugin information --- */
+
+#define GRL_TRACKER_PLUGIN_ID   TRACKER_PLUGIN_ID
+
+extern GrlTrackerQueue *grl_tracker_queue;
+extern TrackerSparqlConnection *grl_tracker_connection;
+
+#endif /* _GRL_TRACKER_H_ */
diff --git a/src/tracker3/meson.build b/src/tracker3/meson.build
new file mode 100644
index 00000000..b86297f6
--- /dev/null
+++ b/src/tracker3/meson.build
@@ -0,0 +1,37 @@
+#
+# meson.build
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2016 Igalia S.L. All rights reserved.
+
+tracker_sources = [
+    'grl-tracker-request-queue.c',
+    'grl-tracker-request-queue.h',
+    'grl-tracker-source-api.c',
+    'grl-tracker-source-api.h',
+    'grl-tracker-source-cache.c',
+    'grl-tracker-source-cache.h',
+    'grl-tracker-source-notif.c',
+    'grl-tracker-source-notif.h',
+    'grl-tracker-source-priv.h',
+    'grl-tracker-source.c',
+    'grl-tracker-source.h',
+    'grl-tracker-utils.c',
+    'grl-tracker-utils.h',
+    'grl-tracker.c',
+    'grl-tracker.h',
+]
+
+configure_file(output: 'config.h',
+    configuration: cdata)
+
+shared_library('grltracker',
+    sources: tracker_sources,
+    install: true,
+    install_dir: pluginsdir,
+    dependencies: must_deps + plugins[tracker3_idx][REQ_DEPS] + plugins[tracker3_idx][OPT_DEPS],
+    c_args: [
+        '-DG_LOG_DOMAIN="GrlTracker3"',
+        '-DHAVE_CONFIG_H',
+    ])


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]