[tracker-miners/wip/carlosg/cli-split: 2/23] tracker: Restore CLI subcommands relevant to tracker-miners



commit 8fff2a59c727b050ef114ce69116b3e2d68bb83b
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Dec 19 16:06:14 2019 +0100

    tracker: Restore CLI subcommands relevant to tracker-miners
    
    We are going for a command split, where the tracker CLI tool will
    look up for subcommands at $libexecdir/tracker/. Move back to this
    repo all commands that deal with miner data/instances, tracker-extract
    and whatnot.

 src/tracker/meson.build       |   38 +
 src/tracker/tracker-color.h   |   35 +
 src/tracker/tracker-config.c  |  212 +++++
 src/tracker/tracker-config.h  |   39 +
 src/tracker/tracker-daemon.c  | 1754 ++++++++++++++++++++++++++++++++++++++++
 src/tracker/tracker-dbus.c    |   70 ++
 src/tracker/tracker-dbus.h    |   32 +
 src/tracker/tracker-extract.c |  160 ++++
 src/tracker/tracker-index.c   |  458 +++++++++++
 src/tracker/tracker-process.c |  370 +++++++++
 src/tracker/tracker-process.h |   49 ++
 src/tracker/tracker-reset.c   |  462 +++++++++++
 src/tracker/tracker-search.c  | 1791 +++++++++++++++++++++++++++++++++++++++++
 src/tracker/tracker-status.c  |  719 +++++++++++++++++
 src/tracker/tracker-tag.c     | 1109 +++++++++++++++++++++++++
 15 files changed, 7298 insertions(+)
---
diff --git a/src/tracker/meson.build b/src/tracker/meson.build
new file mode 100644
index 000000000..f773d339b
--- /dev/null
+++ b/src/tracker/meson.build
@@ -0,0 +1,38 @@
+sources = [
+    'tracker-main.c',
+    'tracker-config.c',
+    'tracker-daemon.c',
+    'tracker-dbus.c',
+    'tracker-extract.c',
+    'tracker-help.c',
+    'tracker-index.c',
+    'tracker-info.c',
+    'tracker-process.c',
+    'tracker-reset.c',
+    'tracker-search.c',
+    'tracker-sparql.c',
+    'tracker-sql.c',
+    'tracker-status.c',
+    'tracker-tag.c',
+]
+
+executable('tracker', sources,
+    c_args: tracker_c_args + [
+        '-DLIBEXECDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('libexecdir'))),
+        '-DMANDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('datadir'), 'man')),
+        '-DTRACKER_EXTRACTOR_RULES_DIR="@0@"'.format(tracker_extract_rules_dir),
+    ],
+    install: true,
+    install_rpath: tracker_internal_libs_dir,
+    # This doesn't depend on tracker_common_dep because of
+    # https://github.com/mesonbuild/meson/issues/671
+    dependencies: [tracker_control_dep, tracker_sparql_dep, tracker_data_dep],
+    include_directories: [commoninc, configinc, srcinc],
+)
+
+
+if install_bash_completion
+    install_data(
+        sources: 'bash-completion/tracker',
+        install_dir: bash_completion_dir)
+endif
diff --git a/src/tracker/tracker-color.h b/src/tracker/tracker-color.h
new file mode 100644
index 000000000..53d730376
--- /dev/null
+++ b/src/tracker/tracker-color.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __TRACKER_COLOR_H__
+#define __TRACKER_COLOR_H__
+
+#define TITLE_BEGIN   "\033[32m"   /* Green */
+#define TITLE_END     "\033[0m"
+
+#define SNIPPET_BEGIN "\033[1;31m" /* Red */
+#define SNIPPET_END   "\033[0m"
+
+#define WARN_BEGIN    "\033[33m"   /* Yellow */
+#define WARN_END      "\033[0m"
+
+#define CRIT_BEGIN "\033[1;31m" /* Red */
+#define CRIT_END   "\033[0m"
+
+#endif /* __TRACKER_COLOR_H__ */
diff --git a/src/tracker/tracker-config.c b/src/tracker/tracker-config.c
new file mode 100644
index 000000000..9dfc143db
--- /dev/null
+++ b/src/tracker/tracker-config.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-control/tracker-control.h>
+
+#include "tracker-config.h"
+
+GSList *
+tracker_gsettings_get_all (gint *longest_name_length)
+{
+       typedef struct {
+               const gchar *schema;
+               const gchar *path;
+       } SchemaWithPath;
+
+       TrackerMinerManager *manager;
+       GSettingsSchemaSource *source;
+       GError *error = NULL;
+       GSettings *settings;
+       GSList *all = NULL;
+       GSList *l;
+       GSList *miners_available;
+       GSList *valid_schemas = NULL;
+       gchar **schemas;
+       gint i, len = 0;
+       SchemaWithPath components[] = {
+               { "Store", "store" },
+               { "Extract", "extract" },
+               { "Writeback", "writeback" },
+               { 0 }
+       };
+       SchemaWithPath *swp;
+
+       /* Don't auto-start the miners here */
+       manager = tracker_miner_manager_new_full (FALSE, &error);
+       if (!manager) {
+               g_printerr (_("Could not get GSettings for miners, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return NULL;
+       }
+
+       miners_available = tracker_miner_manager_get_available (manager);
+
+       source = g_settings_schema_source_get_default ();
+       g_settings_schema_source_list_schemas (source, TRUE, &schemas, NULL);
+
+       for (i = 0; schemas[i]; i++) {
+               if (!g_str_has_prefix (schemas[i], "org.freedesktop.Tracker.")) {
+                       continue;
+               }
+
+               valid_schemas = g_slist_prepend (valid_schemas, g_strdup (schemas[i]));
+       }
+
+       /* Store / General */
+       for (swp = components; swp && swp->schema; swp++) {
+               GSettingsSchema *settings_schema;
+               gchar *schema;
+               gchar *path;
+
+               schema = g_strdup_printf ("org.freedesktop.Tracker.%s", swp->schema);
+               path = g_strdup_printf ("/org/freedesktop/tracker/%s/", swp->path);
+
+               settings_schema = g_settings_schema_source_lookup (source, schema, FALSE);
+
+               /* If miner doesn't have a schema, no point in getting config */
+               if (!tracker_string_in_gslist (schema, valid_schemas)) {
+                       g_free (path);
+                       g_free (schema);
+                       continue;
+               }
+
+               len = MAX (len, strlen (swp->schema));
+
+               settings = g_settings_new_with_path (schema, path);
+               if (settings) {
+                       ComponentGSettings *c = g_slice_new (ComponentGSettings);
+
+                       c->name = g_strdup (swp->schema);
+                       c->settings = settings;
+                       c->schema = settings_schema;
+                       c->is_miner = FALSE;
+
+                       all = g_slist_prepend (all, c);
+               }
+       }
+
+       /* Miners */
+       for (l = miners_available; l; l = l->next) {
+               const gchar *name;
+               gchar *schema;
+               gchar *name_lowercase;
+               gchar *path;
+               gchar *miner;
+
+               miner = l->data;
+               if (!miner) {
+                       continue;
+               }
+
+               name = g_utf8_strrchr (miner, -1, '.');
+               if (!name) {
+                       continue;
+               }
+
+               name++;
+               name_lowercase = g_utf8_strdown (name, -1);
+
+               schema = g_strdup_printf ("org.freedesktop.Tracker.Miner.%s", name);
+               path = g_strdup_printf ("/org/freedesktop/tracker/miner/%s/", name_lowercase);
+               g_free (name_lowercase);
+
+               /* If miner doesn't have a schema, no point in getting config */
+               if (!tracker_string_in_gslist (schema, valid_schemas)) {
+                       g_free (path);
+                       g_free (schema);
+                       continue;
+               }
+
+               settings = g_settings_new_with_path (schema, path);
+               g_free (path);
+               g_free (schema);
+
+               if (settings) {
+                       ComponentGSettings *c = g_slice_new (ComponentGSettings);
+
+                       c->name = g_strdup (name);
+                       c->settings = settings;
+                       c->is_miner = TRUE;
+
+                       all = g_slist_prepend (all, c);
+                       len = MAX (len, strlen (name));
+               }
+       }
+
+       g_slist_foreach (valid_schemas, (GFunc) g_free, NULL);
+       g_slist_free (valid_schemas);
+       g_slist_foreach (miners_available, (GFunc) g_free, NULL);
+       g_slist_free (miners_available);
+       g_object_unref (manager);
+       g_strfreev (schemas);
+
+       if (longest_name_length) {
+               *longest_name_length = len;
+       }
+
+       return g_slist_reverse (all);
+}
+
+gboolean
+tracker_gsettings_set_all (GSList           *all,
+                           TrackerVerbosity  verbosity)
+{
+       GSList *l;
+       gboolean success = TRUE;
+
+       for (l = all; l && success; l = l->next) {
+               ComponentGSettings *c = l->data;
+
+               if (!c) {
+                       continue;
+               }
+
+               success &= g_settings_set_enum (c->settings, "verbosity", verbosity);
+               g_settings_apply (c->settings);
+       }
+
+       g_settings_sync ();
+
+       return success;
+}
+
+void
+tracker_gsettings_free (GSList *all)
+{
+       GSList *l;
+
+       /* Clean up */
+       for (l = all; l; l = l->next) {
+               ComponentGSettings *c = l->data;
+
+               g_free (c->name);
+               g_object_unref (c->settings);
+               g_object_unref (c->schema);
+               g_slice_free (ComponentGSettings, c);
+       }
+}
diff --git a/src/tracker/tracker-config.h b/src/tracker/tracker-config.h
new file mode 100644
index 000000000..66cf4b890
--- /dev/null
+++ b/src/tracker/tracker-config.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#ifndef __TRACKER_CONFIG_H__
+#define __TRACKER_CONFIG_H__
+
+typedef struct {
+       gchar *name;
+       GSettingsSchema *schema;
+       GSettings *settings;
+       gboolean is_miner;
+} ComponentGSettings;
+
+GSList   *tracker_gsettings_get_all (gint *longest_name_length);
+gboolean  tracker_gsettings_set_all (GSList           *all,
+                                     TrackerVerbosity  verbosity);
+void      tracker_gsettings_free    (GSList *all);
+
+#endif /* __TRACKER_CONFIG_H__ */
diff --git a/src/tracker/tracker-daemon.c b/src/tracker/tracker-daemon.c
new file mode 100644
index 000000000..290b3c0da
--- /dev/null
+++ b/src/tracker/tracker-daemon.c
@@ -0,0 +1,1754 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+
+#ifdef __sun
+#include <procfs.h>
+#endif
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-miner/tracker-miner.h>
+#include <libtracker-control/tracker-control.h>
+
+#include "tracker-daemon.h"
+#include "tracker-config.h"
+#include "tracker-process.h"
+#include "tracker-dbus.h"
+
+typedef struct {
+       TrackerSparqlConnection *connection;
+       GHashTable *prefixes;
+       GStrv filter;
+} WatchData;
+
+static gboolean parse_watch (const gchar  *option_name,
+                             const gchar  *value,
+                             gpointer      data,
+                             GError      **error);
+
+static GDBusConnection *connection = NULL;
+static GDBusProxy *proxy = NULL;
+static GMainLoop *main_loop;
+static GHashTable *miners_progress;
+static GHashTable *miners_status;
+static gint longest_miner_name_length = 0;
+static gint paused_length = 0;
+
+static gboolean full_namespaces = FALSE; /* Can be turned on if needed, or made cmd line option */
+
+#define OPTION_TERM_ALL "all"
+#define OPTION_TERM_STORE "store"
+#define OPTION_TERM_MINERS "miners"
+
+
+/* Note:
+ * Every time a new option is added, make sure it is considered in the
+ * 'STATUS_OPTIONS_ENABLED' macro below
+ */
+static gboolean status;
+static gboolean follow;
+static gchar *watch = NULL;
+static gboolean list_common_statuses;
+
+static gchar *miner_name;
+static gchar *pause_reason;
+static gchar *pause_for_process_reason;
+static gint resume_cookie = -1;
+static gboolean list_miners_running;
+static gboolean list_miners_available;
+static gboolean pause_details;
+
+static gboolean list_processes;
+static TrackerProcessTypes daemons_to_kill = TRACKER_PROCESS_TYPE_NONE;
+static TrackerProcessTypes daemons_to_term = TRACKER_PROCESS_TYPE_NONE;
+static gchar *set_log_verbosity;
+static gboolean get_log_verbosity;
+static gboolean start;
+static gchar *backup;
+static gchar *restore;
+
+#define DAEMON_OPTIONS_ENABLED() \
+       ((status || follow || watch || list_common_statuses) || \
+        (miner_name || \
+         pause_reason || \
+         pause_for_process_reason || \
+         resume_cookie != -1 || \
+         list_miners_running || \
+         list_miners_available || \
+         pause_details) || \
+        (list_processes || \
+         daemons_to_kill != TRACKER_PROCESS_TYPE_NONE || \
+         daemons_to_term != TRACKER_PROCESS_TYPE_NONE || \
+         get_log_verbosity || \
+         set_log_verbosity || \
+         start || \
+         backup || \
+         restore));
+
+static gboolean term_option_arg_func (const gchar  *option_value,
+                                      const gchar  *value,
+                                      gpointer      data,
+                                      GError      **error);
+
+
+/* Make sure our statuses are translated (most from libtracker-miner) */
+static const gchar *statuses[8] = {
+       N_("Unavailable"), /* generic */
+       N_("Initializing"),
+       N_("Processing…"),
+       N_("Fetching…"), /* miner/rss */
+       N_("Crawling single directory “%s”"),
+       N_("Crawling recursively directory “%s”"),
+       N_("Paused"),
+       N_("Idle")
+};
+
+static GOptionEntry entries[] = {
+       /* Status */
+       { "follow", 'f', 0, G_OPTION_ARG_NONE, &follow,
+         N_("Follow status changes as they happen"),
+         NULL
+       },
+       { "watch", 'w', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_watch,
+         N_("Watch changes to the database in real time (e.g. resources or files being added)"),
+         N_("ONTOLOGY")
+       },
+       { "list-common-statuses", 0, 0, G_OPTION_ARG_NONE, &list_common_statuses,
+         N_("List common statuses for miners and the store"),
+         NULL
+       },
+       /* Miners */
+       { "pause", 0 , 0, G_OPTION_ARG_STRING, &pause_reason,
+         N_("Pause a miner (you must use this with --miner)"),
+         N_("REASON")
+       },
+       { "pause-for-process", 0 , 0, G_OPTION_ARG_STRING, &pause_for_process_reason,
+         N_("Pause a miner while the calling process is alive or until resumed (you must use this with 
--miner)"),
+         N_("REASON")
+       },
+       { "resume", 0 , 0, G_OPTION_ARG_INT, &resume_cookie,
+         N_("Resume a miner (you must use this with --miner)"),
+         N_("COOKIE")
+       },
+       { "miner", 0 , 0, G_OPTION_ARG_STRING, &miner_name,
+         N_("Miner to use with --resume or --pause (you can use suffixes, e.g. Files or Applications)"),
+         N_("MINER")
+       },
+       { "list-miners-running", 0, 0, G_OPTION_ARG_NONE, &list_miners_running,
+         N_("List all miners currently running"),
+         NULL
+       },
+       { "list-miners-available", 0, 0, G_OPTION_ARG_NONE, &list_miners_available,
+         N_("List all miners installed"),
+         NULL
+       },
+       { "pause-details", 0, 0, G_OPTION_ARG_NONE, &pause_details,
+         N_("List pause reasons"),
+         NULL
+       },
+       /* Processes */
+       { "list-processes", 'p', 0, G_OPTION_ARG_NONE, &list_processes,
+         N_("List all Tracker processes") },
+       { "kill", 'k', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, term_option_arg_func,
+         N_("Use SIGKILL to stop all matching processes, either “store”, “miners” or “all” may be used, no 
parameter equals “all”"),
+         N_("APPS") },
+       { "terminate", 't', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, term_option_arg_func,
+         N_("Use SIGTERM to stop all matching processes, either “store”, “miners” or “all” may be used, no 
parameter equals “all”"),
+         N_("APPS") },
+       { "start", 's', 0, G_OPTION_ARG_NONE, &start,
+         N_("Starts miners (which indirectly starts tracker-store too)"),
+         NULL },
+       { "set-log-verbosity", 0, 0, G_OPTION_ARG_STRING, &set_log_verbosity,
+         N_("Sets the logging verbosity to LEVEL (“debug”, “detailed”, “minimal”, “errors”) for all 
processes"),
+         N_("LEVEL") },
+       { "get-log-verbosity", 0, 0, G_OPTION_ARG_NONE, &get_log_verbosity,
+         N_("Show logging values in terms of log verbosity for each process"),
+         NULL },
+       { NULL }
+};
+
+static gboolean
+parse_watch (const gchar  *option_name,
+             const gchar  *value,
+             gpointer      data,
+             GError      **error)
+{
+       if (!value) {
+               watch = g_strdup ("");
+       } else {
+               watch = g_strdup (value);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+signal_handler (gpointer user_data)
+{
+       int signo = GPOINTER_TO_INT (user_data);
+
+       static gboolean in_loop = FALSE;
+
+       /* Die if we get re-entrant signals handler calls */
+       if (in_loop) {
+               exit (EXIT_FAILURE);
+       }
+
+       switch (signo) {
+       case SIGTERM:
+       case SIGINT:
+               in_loop = TRUE;
+               g_main_loop_quit (main_loop);
+
+               /* Fall through */
+       default:
+               if (g_strsignal (signo)) {
+                       g_print ("\n");
+                       g_print ("Received signal:%d->'%s'\n",
+                                signo,
+                                g_strsignal (signo));
+               }
+               break;
+       }
+
+       return G_SOURCE_CONTINUE;
+}
+
+static void
+initialize_signal_handler (void)
+{
+       g_unix_signal_add (SIGTERM, signal_handler, GINT_TO_POINTER (SIGTERM));
+       g_unix_signal_add (SIGINT, signal_handler, GINT_TO_POINTER (SIGINT));
+}
+
+static gboolean
+miner_get_details (TrackerMinerManager  *manager,
+                   const gchar          *miner,
+                   gchar               **status,
+                   gdouble              *progress,
+                   gint                 *remaining_time,
+                   GStrv                *pause_applications,
+                   GStrv                *pause_reasons)
+{
+       if ((status || progress || remaining_time) &&
+           !tracker_miner_manager_get_status (manager,
+                                              miner,
+                                              status,
+                                              progress,
+                                              remaining_time)) {
+               g_printerr (_("Could not get status from miner: %s"), miner);
+               return FALSE;
+       }
+
+       tracker_miner_manager_is_paused (manager, miner,
+                                        pause_applications,
+                                        pause_reasons);
+
+       if (!(*pause_applications) || !(*pause_reasons)) {
+               /* unable to get pause details,
+                  already logged by tracker_miner_manager_is_paused */
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+miner_print_state (TrackerMinerManager *manager,
+                   const gchar         *miner_name,
+                   const gchar         *status,
+                   gdouble              progress,
+                   gint                 remaining_time,
+                   gboolean             is_running,
+                   gboolean             is_paused)
+{
+       const gchar *name;
+       time_t now;
+       gchar time_str[64];
+       size_t len;
+       struct tm *local_time;
+
+       now = time ((time_t *) NULL);
+       local_time = localtime (&now);
+       len = strftime (time_str,
+                       sizeof (time_str) - 1,
+                       "%d %b %Y, %H:%M:%S:",
+                       local_time);
+       time_str[len] = '\0';
+
+       name = tracker_miner_manager_get_display_name (manager, miner_name);
+
+       if (is_running) {
+               gchar *progress_str = NULL;
+               gchar *remaining_time_str = NULL;
+
+               if (progress >= 0.0 && progress < 1.0) {
+                       progress_str = g_strdup_printf ("%3u%%", (guint)(progress * 100));
+               }
+
+               /* Progress > 0.01 here because we want to avoid any message
+                * during crawling, as we don't have the remaining time in that
+                * case and it would just print "unknown time left" */
+               if (progress > 0.01 &&
+                   progress < 1.0 &&
+                   remaining_time >= 0) {
+                       /* 0 means that we couldn't properly compute the remaining
+                        * time. */
+                       if (remaining_time > 0) {
+                               gchar *seconds_str = tracker_seconds_to_string (remaining_time, TRUE);
+
+                               /* Translators: %s is a time string */
+                               remaining_time_str = g_strdup_printf (_("%s remaining"), seconds_str);
+                               g_free (seconds_str);
+                       } else {
+                               remaining_time_str = g_strdup (_("unknown time left"));
+                       }
+               }
+
+               g_print ("%s  %s  %-*.*s %s%-*.*s%s %s %s %s\n",
+                        time_str,
+                        progress_str ? progress_str : "✓   ",
+                        longest_miner_name_length,
+                        longest_miner_name_length,
+                        name,
+                        is_paused ? "(" : " ",
+                        paused_length,
+                        paused_length,
+                        is_paused ? _("PAUSED") : " ",
+                        is_paused ? ")" : " ",
+                        status ? "-" : "",
+                        status ? _(status) : "",
+                        remaining_time_str ? remaining_time_str : "");
+
+               g_free (progress_str);
+               g_free (remaining_time_str);
+       } else {
+               g_print ("%s  ✗     %-*.*s  %-*.*s  - %s\n",
+                        time_str,
+                        longest_miner_name_length,
+                        longest_miner_name_length,
+                        name,
+                        paused_length,
+                        paused_length,
+                        " ",
+                        _("Not running or is a disabled plugin"));
+       }
+}
+
+static void
+store_print_state (const gchar *status,
+                   gdouble      progress)
+{
+       gchar time_str[64];
+       struct tm *local_time;
+       time_t now;
+       size_t len;
+
+       now = time ((time_t *) NULL);
+       local_time = localtime (&now);
+       len = strftime (time_str,
+                       sizeof (time_str) - 1,
+                       "%d %b %Y, %H:%M:%S:",
+                       local_time);
+       time_str[len] = '\0';
+
+       if (status) {
+               gchar *operation = NULL;
+               gchar *operation_status = NULL;
+               gchar *progress_str;
+
+               if (strstr (status, "-")) {
+                       gchar **status_split;
+
+                       status_split = g_strsplit (status, "-", 2);
+                       if (status_split[0] && status_split[1]) {
+                               operation = g_strstrip (status_split[0]);
+                               operation_status = g_strstrip (status_split[1]);
+                               /* Free the array, not the contents */
+                               g_free (status_split);
+                       } else {
+                               /* Free everything */
+                               g_strfreev (status_split);
+                       }
+               }
+
+               if (progress >= 0.0 && progress < 1.0) {
+                       progress_str = g_strdup_printf ("%3u%%", (guint)(progress * 100));
+               } else {
+                       progress_str = g_strdup_printf ("✓   ");
+               }
+
+               g_print ("%s  %s  %-*.*s    - %s %s%s%s\n",
+                        time_str,
+                        progress_str ? progress_str : "    ",
+                        longest_miner_name_length + paused_length,
+                        longest_miner_name_length + paused_length,
+                        "Store",
+                        /*(operation ? _(operation) : _(status)),*/
+                        /*operation ? "-" : "",*/
+                        operation ? _(operation) : _(status),
+                        operation_status ? "(" : "",
+                        operation_status ? operation_status : "",
+                        operation_status ? ")" : "");
+
+               g_free (progress_str);
+               g_free (operation);
+               g_free (operation_status);
+       } else {
+               g_print ("%s  %s %-*.*s    - %s\n",
+                        time_str,
+                        "✗    ", /* Progress */
+                        longest_miner_name_length + paused_length,
+                        longest_miner_name_length + paused_length,
+                        "Store",
+                        _("Unavailable"));
+       }
+}
+
+static void
+store_get_and_print_state (void)
+{
+       GVariant *v_status, *v_progress;
+       const gchar *status = NULL;
+       gdouble progress = -1.0;
+       GError *error = NULL;
+       gchar *owner;
+
+       owner = g_dbus_proxy_get_name_owner (proxy);
+       if (!owner) {
+               /* Name is not owned yet, store is not running */
+               store_print_state (NULL, -1);
+               return;
+       }
+       g_free (owner);
+
+       /* Status */
+       v_status = g_dbus_proxy_call_sync (proxy,
+                                          "GetStatus",
+                                          NULL,
+                                          G_DBUS_CALL_FLAGS_NONE,
+                                          -1,
+                                          NULL,
+                                          &error);
+
+       if (!v_status || error) {
+               g_critical ("%s, %s",
+                           _("Could not retrieve tracker-store status"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return;
+       }
+
+       g_variant_get (v_status, "(&s)", &status);
+
+       /* Progress */
+       v_progress = g_dbus_proxy_call_sync (proxy,
+                                            "GetProgress",
+                                            NULL,
+                                            G_DBUS_CALL_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+
+       g_variant_get (v_progress, "(d)", &progress);
+
+       if (progress < 0.0 || error) {
+               g_critical ("%s, %s",
+                           _("Could not retrieve tracker-store progress"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return;
+       }
+
+       /* Print */
+       store_print_state (status, progress);
+
+       g_variant_unref (v_progress);
+       g_variant_unref (v_status);
+}
+
+static void
+manager_miner_progress_cb (TrackerMinerManager *manager,
+                           const gchar         *miner_name,
+                           const gchar         *status,
+                           gdouble              progress,
+                           gint                 remaining_time)
+{
+       GValue *gvalue;
+
+       gvalue = g_slice_new0 (GValue);
+
+       g_value_init (gvalue, G_TYPE_DOUBLE);
+       g_value_set_double (gvalue, progress);
+
+       miner_print_state (manager, miner_name, status, progress, remaining_time, TRUE, FALSE);
+
+       g_hash_table_replace (miners_status,
+                             g_strdup (miner_name),
+                             g_strdup (status));
+       g_hash_table_replace (miners_progress,
+                             g_strdup (miner_name),
+                             gvalue);
+}
+
+static void
+manager_miner_paused_cb (TrackerMinerManager *manager,
+                         const gchar         *miner_name)
+{
+       GValue *gvalue;
+
+       gvalue = g_hash_table_lookup (miners_progress, miner_name);
+
+       miner_print_state (manager, miner_name,
+                          g_hash_table_lookup (miners_status, miner_name),
+                          gvalue ? g_value_get_double (gvalue) : 0.0,
+                          -1,
+                          TRUE,
+                          TRUE);
+}
+
+static void
+manager_miner_resumed_cb (TrackerMinerManager *manager,
+                          const gchar         *miner_name)
+{
+       GValue *gvalue;
+
+       gvalue = g_hash_table_lookup (miners_progress, miner_name);
+
+       miner_print_state (manager, miner_name,
+                          g_hash_table_lookup (miners_status, miner_name),
+                          gvalue ? g_value_get_double (gvalue) : 0.0,
+                          0,
+                          TRUE,
+                          FALSE);
+}
+
+static void
+miners_progress_destroy_notify (gpointer data)
+{
+       GValue *value;
+
+       value = data;
+       g_value_unset (value);
+       g_slice_free (GValue, value);
+}
+
+static gchar *
+get_shorthand (GHashTable  *prefixes,
+               const gchar *namespace)
+{
+       gchar *hash;
+
+       hash = strrchr (namespace, '#');
+
+       if (hash) {
+               gchar *property;
+               const gchar *prefix;
+
+               property = hash + 1;
+               *hash = '\0';
+
+               prefix = g_hash_table_lookup (prefixes, namespace);
+
+               return g_strdup_printf ("%s:%s", prefix, property);
+       }
+
+       return g_strdup (namespace);
+}
+
+static GHashTable *
+get_prefixes (TrackerSparqlConnection *connection)
+{
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       GHashTable *retval;
+       const gchar *query;
+
+       retval = g_hash_table_new_full (g_str_hash,
+                                       g_str_equal,
+                                       g_free,
+                                       g_free);
+
+       /* FIXME: Would like to get this in the same SPARQL that we
+        * use to get the info, but doesn't seem possible at the
+        * moment with the limited string manipulation features we
+        * support in SPARQL.
+        */
+       query = "SELECT ?ns ?prefix "
+               "WHERE {"
+               "  ?ns a tracker:Namespace ;"
+               "  tracker:prefix ?prefix "
+               "}";
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Unable to retrieve namespace prefixes"),
+                           error->message);
+
+               g_error_free (error);
+               return retval;
+       }
+
+       if (!cursor) {
+               g_printerr ("%s\n", _("No namespace prefixes were returned"));
+               return retval;
+       }
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               const gchar *key, *value;
+
+               key = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+               value = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+
+               if (!key || !value) {
+                       continue;
+               }
+
+               g_hash_table_insert (retval,
+                                    g_strndup (key, strlen (key) - 1),
+                                    g_strdup (value));
+       }
+
+       if (cursor) {
+               g_object_unref (cursor);
+       }
+
+       return retval;
+}
+
+static inline void
+print_key (GHashTable  *prefixes,
+           const gchar *key)
+{
+       if (G_UNLIKELY (full_namespaces)) {
+               g_print ("'%s'\n", key);
+       } else {
+               gchar *shorthand;
+
+               shorthand = get_shorthand (prefixes, key);
+               g_print ("'%s'\n", shorthand);
+               g_free (shorthand);
+       }
+}
+
+static void
+store_progress (GDBusConnection *connection,
+                const gchar     *sender_name,
+                const gchar     *object_path,
+                const gchar     *interface_name,
+                const gchar     *signal_name,
+                GVariant        *parameters,
+                gpointer         user_data)
+{
+       const gchar *status = NULL;
+       gdouble progress = 0.0;
+
+       g_variant_get (parameters, "(sd)", &status, &progress);
+       store_print_state (status, progress);
+}
+
+static void
+store_graph_update_interpret (WatchData  *wd,
+                              GHashTable *updates,
+                              gint        subject,
+                              gint        predicate)
+{
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       gchar *query, *key;
+       gboolean ok = TRUE;
+
+       query = g_strdup_printf ("SELECT tracker:uri (%d) tracker:uri(%d) {}",
+                                subject,
+                                predicate);
+       cursor = tracker_sparql_connection_query (wd->connection,
+                                                 query,
+                                                 NULL,
+                                                 &error);
+       g_free (query);
+
+       if (error) {
+               g_critical ("%s, %s",
+                           _("Could not run SPARQL query"),
+                           error->message);
+               g_clear_error (&error);
+               return;
+       }
+
+       if (!tracker_sparql_cursor_next (cursor, NULL, &error) || error) {
+               g_critical ("%s, %s",
+                           _("Could not call tracker_sparql_cursor_next() on SPARQL query"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return;
+       }
+
+       /* Key = predicate */
+       key = g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL));
+       query = g_strdup_printf ("SELECT ?t { <%s> <%s> ?t } ORDER BY DESC(<%s>)",
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                key,
+                                key);
+       g_object_unref (cursor);
+
+       cursor = tracker_sparql_connection_query (wd->connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_critical ("%s, %s",
+                           _("Could not run SPARQL query"),
+                           error->message);
+               g_clear_error (&error);
+               return;
+       }
+
+       while (ok) {
+               const gchar *value;
+
+               ok = tracker_sparql_cursor_next (cursor, NULL, &error);
+
+               if (error) {
+                       g_critical ("%s, %s",
+                                   _("Could not call tracker_sparql_cursor_next() on SPARQL query"),
+                                   error ? error->message : _("No error given"));
+                       g_clear_error (&error);
+                       break;
+               }
+
+               value = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+
+               if (!key || !value) {
+                       continue;
+               }
+
+               /* Don't display nie:plainTextContent */
+               if (strcmp (key, "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#plainTextContent";) 
== 0) {
+                       continue;
+               }
+
+               if (G_UNLIKELY (full_namespaces)) {
+                       if (wd->filter == NULL ||
+                           tracker_string_in_string_list (key, wd->filter) != -1) {
+                               g_hash_table_replace (updates, g_strdup (key), g_strdup (value));
+                       }
+               } else {
+                       gchar *shorthand;
+
+                       shorthand = get_shorthand (wd->prefixes, key);
+
+                       if (wd->filter == NULL ||
+                           tracker_string_in_string_list (shorthand, wd->filter) != -1) {
+                               g_hash_table_replace (updates, shorthand, g_strdup (value));
+                       } else {
+                               g_free (shorthand);
+                       }
+               }
+       }
+
+       g_free (key);
+       g_object_unref (cursor);
+}
+
+static void
+store_graph_update_cb (GDBusConnection *connection,
+                       const gchar     *sender_name,
+                       const gchar     *object_path,
+                       const gchar     *interface_name,
+                       const gchar     *signal_name,
+                       GVariant        *parameters,
+                       gpointer         user_data)
+
+{
+       WatchData *wd;
+       GHashTable *updates;
+       GVariantIter *iter1, *iter2;
+       gchar *class_name;
+       gint graph = 0, subject = 0, predicate = 0, object = 0;
+
+       wd = user_data;
+
+       updates = g_hash_table_new_full (g_str_hash,
+                                        g_str_equal,
+                                        (GDestroyNotify) g_free,
+                                        (GDestroyNotify) g_free);
+
+       g_variant_get (parameters, "(&sa(iiii)a(iiii))", &class_name, &iter1, &iter2);
+
+       while (g_variant_iter_loop (iter1, "(iiii)", &graph, &subject, &predicate, &object)) {
+               store_graph_update_interpret (wd, updates, subject, predicate);
+       }
+
+       while (g_variant_iter_loop (iter2, "(iiii)", &graph, &subject, &predicate, &object)) {
+               store_graph_update_interpret (wd, updates, subject, predicate);
+       }
+
+       /* Print updates sorted and filtered */
+       GList *keys, *l;
+
+       keys = g_hash_table_get_keys (updates);
+       keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
+
+       if (g_hash_table_size (updates) > 0) {
+               print_key (wd->prefixes, class_name);
+       }
+
+       for (l = keys; l; l = l->next) {
+               gchar *key = l->data;
+               gchar *value = g_hash_table_lookup (updates, l->data);
+
+               g_print ("  '%s' = '%s'\n", key, value);
+       }
+
+       g_list_free (keys);
+       g_hash_table_unref (updates);
+       g_variant_iter_free (iter1);
+       g_variant_iter_free (iter2);
+}
+
+static WatchData *
+watch_data_new (TrackerSparqlConnection *sparql_connection,
+                GHashTable              *sparql_prefixes,
+                const gchar             *watch_filter)
+{
+       WatchData *data;
+
+       data = g_new0 (WatchData, 1);
+       data->connection = g_object_ref (sparql_connection);
+       data->prefixes = g_hash_table_ref (sparql_prefixes);
+
+       if (watch_filter && strlen (watch_filter) > 0) {
+               data->filter = g_strsplit (watch_filter, ",", -1);
+       }
+
+       return data;
+}
+
+static void
+watch_data_free (WatchData *data)
+{
+       if (!data) {
+               return;
+       }
+
+       if (data->filter) {
+               g_strfreev (data->filter);
+       }
+
+       if (data->prefixes) {
+               g_hash_table_unref (data->prefixes);
+       }
+
+       if (data->connection) {
+               g_object_unref (data->connection);
+       }
+
+       g_free (data);
+}
+
+
+static gint
+miner_pause (const gchar *miner,
+             const gchar *reason,
+             gboolean     for_process)
+{
+       TrackerMinerManager *manager;
+       GError *error = NULL;
+       gchar *str;
+       guint32 cookie;
+
+       /* Don't auto-start the miners here */
+       manager = tracker_miner_manager_new_full (FALSE, &error);
+       if (!manager) {
+               g_printerr (_("Could not pause miner, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       str = g_strdup_printf (_("Attempting to pause miner “%s” with reason “%s”"),
+                              miner,
+                              reason);
+       g_print ("%s\n", str);
+       g_free (str);
+
+       if (for_process) {
+               if (!tracker_miner_manager_pause_for_process (manager, miner, reason, &cookie)) {
+                       g_printerr (_("Could not pause miner: %s"), miner);
+                       g_printerr ("\n");
+                       return EXIT_FAILURE;
+               }
+       } else {
+               if (!tracker_miner_manager_pause (manager, miner, reason, &cookie)) {
+                       g_printerr (_("Could not pause miner: %s"), miner);
+                       g_printerr ("\n");
+                       return EXIT_FAILURE;
+               }
+       }
+
+       str = g_strdup_printf (_("Cookie is %d"), cookie);
+       g_print ("  %s\n", str);
+       g_free (str);
+
+       if (for_process) {
+               GMainLoop *main_loop;
+
+               g_print ("%s\n", _("Press Ctrl+C to stop"));
+
+               main_loop = g_main_loop_new (NULL, FALSE);
+               /* Block until Ctrl+C */
+               g_main_loop_run (main_loop);
+               g_object_unref (main_loop);
+       }
+
+       g_object_unref (manager);
+
+       return EXIT_SUCCESS;
+}
+
+static gint
+miner_resume (const gchar *miner,
+              gint         cookie)
+{
+       TrackerMinerManager *manager;
+       GError *error = NULL;
+       gchar *str;
+
+       /* Don't auto-start the miners here */
+       manager = tracker_miner_manager_new_full (FALSE, &error);
+       if (!manager) {
+               g_printerr (_("Could not resume miner, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       str = g_strdup_printf (_("Attempting to resume miner %s with cookie %d"),
+                              miner,
+                              cookie);
+       g_print ("%s\n", str);
+       g_free (str);
+
+       if (!tracker_miner_manager_resume (manager, miner, cookie)) {
+               g_printerr (_("Could not resume miner: %s"), miner);
+               return EXIT_FAILURE;
+       }
+
+       g_print ("  %s\n", _("Done"));
+
+       g_object_unref (manager);
+
+       return EXIT_SUCCESS;
+}
+
+static gint
+miner_list (gboolean available,
+            gboolean running)
+{
+       TrackerMinerManager *manager;
+       GError *error = NULL;
+
+       /* Don't auto-start the miners here */
+       manager = tracker_miner_manager_new_full (FALSE, &error);
+       if (!manager) {
+               g_printerr (_("Could not list miners, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       if (available) {
+               GSList *miners_available;
+               gchar *str;
+               GSList *l;
+
+               miners_available = tracker_miner_manager_get_available (manager);
+
+               str = g_strdup_printf (ngettext ("Found %d miner installed",
+                                                "Found %d miners installed",
+                                                g_slist_length (miners_available)),
+                                      g_slist_length (miners_available));
+
+               g_print ("%s%s\n", str, g_slist_length (miners_available) > 0 ? ":" : "");
+               g_free (str);
+
+               for (l = miners_available; l; l = l->next) {
+                       g_print ("  %s\n", (gchar*) l->data);
+               }
+
+               g_slist_foreach (miners_available, (GFunc) g_free, NULL);
+               g_slist_free (miners_available);
+       }
+
+       if (running) {
+               GSList *miners_running;
+               gchar *str;
+               GSList *l;
+
+               miners_running = tracker_miner_manager_get_running (manager);
+
+               str = g_strdup_printf (ngettext ("Found %d miner running",
+                                                "Found %d miners running",
+                                                g_slist_length (miners_running)),
+                                      g_slist_length (miners_running));
+
+               g_print ("%s%s\n", str, g_slist_length (miners_running) > 0 ? ":" : "");
+               g_free (str);
+
+               for (l = miners_running; l; l = l->next) {
+                       g_print ("  %s\n", (gchar*) l->data);
+               }
+
+               g_slist_foreach (miners_running, (GFunc) g_free, NULL);
+               g_slist_free (miners_running);
+       }
+
+       g_object_unref (manager);
+
+       return EXIT_SUCCESS;
+}
+
+static gint
+miner_pause_details (void)
+{
+       TrackerMinerManager *manager;
+       GError *error = NULL;
+       GSList *miners_running, *l;
+       gint paused_miners = 0;
+
+       /* Don't auto-start the miners here */
+       manager = tracker_miner_manager_new_full (FALSE, &error);
+       if (!manager) {
+               g_printerr (_("Could not get pause details, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       miners_running = tracker_miner_manager_get_running (manager);
+
+       if (!miners_running) {
+               g_print ("%s\n", _("No miners are running"));
+
+               g_slist_foreach (miners_running, (GFunc) g_free, NULL);
+               g_slist_free (miners_running);
+               g_object_unref (manager);
+
+               return EXIT_SUCCESS;
+       }
+
+       for (l = miners_running; l; l = l->next) {
+               const gchar *name;
+               GStrv pause_applications, pause_reasons;
+               gint i;
+
+               name = tracker_miner_manager_get_display_name (manager, l->data);
+
+               if (!name) {
+                       g_critical ("Could not get name for '%s'", (gchar *) l->data);
+                       continue;
+               }
+
+               tracker_miner_manager_is_paused (manager,
+                                                l->data,
+                                                &pause_applications,
+                                                &pause_reasons);
+
+               if (!pause_applications || !pause_reasons) {
+                       /* unable to get pause details,
+                          already logged by tracker_miner_manager_is_paused */
+                       continue;
+               }
+
+               if (!(*pause_applications) || !(*pause_reasons)) {
+                       g_strfreev (pause_applications);
+                       g_strfreev (pause_reasons);
+                       continue;
+               }
+
+               paused_miners++;
+               if (paused_miners == 1) {
+                       g_print ("%s:\n", _("Miners"));
+               }
+
+               g_print ("  %s:\n", name);
+
+               for (i = 0; pause_applications[i] != NULL; i++) {
+                       g_print ("    %s: '%s', %s: '%s'\n",
+                                _("Application"),
+                                pause_applications[i],
+                                _("Reason"),
+                                pause_reasons[i]);
+               }
+
+               g_strfreev (pause_applications);
+               g_strfreev (pause_reasons);
+       }
+
+       if (paused_miners < 1) {
+               g_print ("%s\n", _("No miners are paused"));
+       }
+
+       g_slist_foreach (miners_running, (GFunc) g_free, NULL);
+       g_slist_free (miners_running);
+
+       g_object_unref (manager);
+
+       return EXIT_SUCCESS;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+inline static const gchar *
+verbosity_to_string (TrackerVerbosity verbosity)
+{
+        GType type;
+        GEnumClass *enum_class;
+        GEnumValue *enum_value;
+
+        type = tracker_verbosity_get_type ();
+        enum_class = G_ENUM_CLASS (g_type_class_peek (type));
+        enum_value = g_enum_get_value (enum_class, verbosity);
+
+        if (!enum_value) {
+                return "unknown";
+        }
+
+        return enum_value->value_nick;
+}
+
+inline static void
+tracker_gsettings_print_verbosity (GSList   *all,
+                                   gint      longest,
+                                   gboolean  miners)
+{
+       GSList *l;
+
+       for (l = all; l; l = l->next) {
+               ComponentGSettings *c;
+               TrackerVerbosity v;
+
+               c = l->data;
+
+               if (c->is_miner == miners) {
+                       continue;
+               }
+
+               v = g_settings_get_enum (c->settings, "verbosity");
+
+               g_print ("  %-*.*s: %s\n",
+                        longest,
+                        longest,
+                        c->name,
+                        verbosity_to_string (v));
+       }
+}
+
+static gboolean
+term_option_arg_func (const gchar  *option_value,
+                      const gchar  *value,
+                      gpointer      data,
+                      GError      **error)
+{
+       TrackerProcessTypes option;
+
+       if (!value) {
+               value = OPTION_TERM_ALL;
+       }
+
+       if (strcmp (value, OPTION_TERM_ALL) == 0) {
+               option = TRACKER_PROCESS_TYPE_ALL;
+       } else if (strcmp (value, OPTION_TERM_STORE) == 0) {
+               option = TRACKER_PROCESS_TYPE_STORE;
+       } else if (strcmp (value, OPTION_TERM_MINERS) == 0) {
+               option = TRACKER_PROCESS_TYPE_MINERS;
+       } else {
+               g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                                    _("Only one of “all”, “store” and “miners” options are allowed"));
+               return FALSE;
+       }
+
+       if (strcmp (option_value, "-k") == 0 ||
+           strcmp (option_value, "--kill") == 0) {
+               daemons_to_kill = option;
+       } else if (strcmp (option_value, "-t") == 0 ||
+                  strcmp (option_value, "--terminate") == 0) {
+               daemons_to_term = option;
+       }
+
+       return TRUE;
+}
+
+static gint
+daemon_run (void)
+{
+       TrackerMinerManager *manager;
+
+       /* --follow implies --status */
+       if (follow) {
+               status = TRUE;
+       }
+
+       if (watch != NULL) {
+               TrackerSparqlConnection *sparql_connection;
+               GHashTable *sparql_prefixes;
+               GError *error = NULL;
+               guint signal_id;
+
+               sparql_connection = tracker_sparql_connection_get (NULL, &error);
+
+               if (!sparql_connection) {
+                       g_critical ("%s, %s",
+                                   _("Could not get SPARQL connection"),
+                                   error ? error->message : _("No error given"));
+                       g_clear_error (&error);
+                       return EXIT_FAILURE;
+               }
+
+               if (!tracker_dbus_get_connection ("org.freedesktop.Tracker1",
+                                                 "/org/freedesktop/Tracker1/Resources",
+                                                 "org.freedesktop.Tracker1.Resources",
+                                                 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+                                                 &connection,
+                                                 &proxy)) {
+                       g_object_unref (sparql_connection);
+                       return EXIT_FAILURE;
+               }
+
+               sparql_prefixes = get_prefixes (sparql_connection);
+
+               signal_id = g_dbus_connection_signal_subscribe (connection,
+                                                               TRACKER_DBUS_SERVICE,
+                                                               TRACKER_DBUS_INTERFACE_RESOURCES,
+                                                               "GraphUpdated",
+                                                               TRACKER_DBUS_OBJECT_RESOURCES,
+                                                               NULL, /* TODO: Use class-name here */
+                                                               G_DBUS_SIGNAL_FLAGS_NONE,
+                                                               store_graph_update_cb,
+                                                               watch_data_new (sparql_connection, 
sparql_prefixes, watch),
+                                                               (GDestroyNotify) watch_data_free);
+
+               g_hash_table_unref (sparql_prefixes);
+               g_object_unref (sparql_connection);
+
+               g_print ("%s\n", _("Now listening for resource updates to the database"));
+               g_print ("%s\n\n", _("All nie:plainTextContent properties are omitted"));
+               g_print ("%s\n", _("Press Ctrl+C to stop"));
+
+               main_loop = g_main_loop_new (NULL, FALSE);
+               g_main_loop_run (main_loop);
+               g_main_loop_unref (main_loop);
+
+               g_dbus_connection_signal_unsubscribe (connection, signal_id);
+
+               return EXIT_SUCCESS;
+       }
+
+       if (list_common_statuses) {
+               gint i;
+
+               g_print ("%s:\n", _("Common statuses include"));
+
+               for (i = 0; i < G_N_ELEMENTS (statuses); i++) {
+                       g_print ("  %s\n", _(statuses[i]));
+               }
+
+               return EXIT_SUCCESS;
+       }
+
+       if (status) {
+               GError *error = NULL;
+               GSList *miners_available;
+               GSList *miners_running;
+               GSList *l;
+
+               /* Don't auto-start the miners here */
+               manager = tracker_miner_manager_new_full (FALSE, &error);
+               if (!manager) {
+                       g_printerr (_("Could not get status, manager could not be created, %s"),
+                                   error ? error->message : _("No error given"));
+                       g_printerr ("\n");
+                       g_clear_error (&error);
+                       return EXIT_FAILURE;
+               }
+
+               miners_available = tracker_miner_manager_get_available (manager);
+               miners_running = tracker_miner_manager_get_running (manager);
+
+               /* Work out lengths for output spacing */
+               paused_length = strlen (_("PAUSED"));
+
+               for (l = miners_available; l; l = l->next) {
+                       const gchar *name;
+
+                       name = tracker_miner_manager_get_display_name (manager, l->data);
+                       longest_miner_name_length = MAX (longest_miner_name_length, strlen (name));
+               }
+
+               /* Display states */
+               g_print ("%s:\n", _("Store"));
+
+               if (!tracker_dbus_get_connection ("org.freedesktop.Tracker1",
+                                                 "/org/freedesktop/Tracker1/Status",
+                                                 "org.freedesktop.Tracker1.Status",
+                                                 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+                                                 &connection,
+                                                 &proxy)) {
+                       return EXIT_FAILURE;
+               }
+
+               g_dbus_connection_signal_subscribe (connection,
+                                                   "org.freedesktop.Tracker1",
+                                                   "org.freedesktop.Tracker1.Status",
+                                                   "Progress",
+                                                   "/org/freedesktop/Tracker1/Status",
+                                                   NULL,
+                                                   G_DBUS_SIGNAL_FLAGS_NONE,
+                                                   store_progress,
+                                                   NULL,
+                                                   NULL);
+
+               store_get_and_print_state ();
+
+               g_print ("\n");
+
+               g_print ("%s:\n", _("Miners"));
+
+               for (l = miners_available; l; l = l->next) {
+                       const gchar *name;
+                       gboolean is_running;
+
+                       name = tracker_miner_manager_get_display_name (manager, l->data);
+                       if (!name) {
+                               g_critical (_("Could not get display name for miner “%s”"),
+                                           (const gchar*) l->data);
+                               continue;
+                       }
+
+                       is_running = tracker_string_in_gslist (l->data, miners_running);
+
+                       if (is_running) {
+                               GStrv pause_applications, pause_reasons;
+                               gchar *status = NULL;
+                               gdouble progress;
+                               gint remaining_time;
+                               gboolean is_paused;
+
+                               if (!miner_get_details (manager,
+                                                       l->data,
+                                                       &status,
+                                                       &progress,
+                                                       &remaining_time,
+                                                       &pause_applications,
+                                                       &pause_reasons)) {
+                                       continue;
+                               }
+
+                               is_paused = *pause_applications || *pause_reasons;
+
+                               miner_print_state (manager,
+                                                  l->data,
+                                                  status,
+                                                  progress,
+                                                  remaining_time,
+                                                  TRUE,
+                                                  is_paused);
+
+                               g_strfreev (pause_applications);
+                               g_strfreev (pause_reasons);
+                               g_free (status);
+                       } else {
+                               miner_print_state (manager, l->data, NULL, 0.0, -1, FALSE, FALSE);
+                       }
+               }
+
+               g_slist_foreach (miners_available, (GFunc) g_free, NULL);
+               g_slist_free (miners_available);
+
+               g_slist_foreach (miners_running, (GFunc) g_free, NULL);
+               g_slist_free (miners_running);
+
+               if (!follow) {
+                       /* Do nothing further */
+                       if (proxy) {
+                               g_object_unref (proxy);
+                       }
+                       g_print ("\n");
+                       return EXIT_SUCCESS;
+               }
+
+               g_print ("%s\n", _("Press Ctrl+C to stop"));
+
+               g_signal_connect (manager, "miner-progress",
+                                 G_CALLBACK (manager_miner_progress_cb), NULL);
+               g_signal_connect (manager, "miner-paused",
+                                 G_CALLBACK (manager_miner_paused_cb), NULL);
+               g_signal_connect (manager, "miner-resumed",
+                                 G_CALLBACK (manager_miner_resumed_cb), NULL);
+
+               initialize_signal_handler ();
+
+               miners_progress = g_hash_table_new_full (g_str_hash,
+                                                        g_str_equal,
+                                                        (GDestroyNotify) g_free,
+                                                        (GDestroyNotify) miners_progress_destroy_notify);
+               miners_status = g_hash_table_new_full (g_str_hash,
+                                                      g_str_equal,
+                                                      (GDestroyNotify) g_free,
+                                                      (GDestroyNotify) g_free);
+
+               main_loop = g_main_loop_new (NULL, FALSE);
+               g_main_loop_run (main_loop);
+               g_main_loop_unref (main_loop);
+
+               g_hash_table_unref (miners_progress);
+               g_hash_table_unref (miners_status);
+
+               if (proxy) {
+                       g_object_unref (proxy);
+               }
+
+               if (manager) {
+                       g_object_unref (manager);
+               }
+
+               return EXIT_SUCCESS;
+       }
+
+       /* Miners */
+       if (pause_reason && resume_cookie != -1) {
+               g_printerr ("%s\n",
+                           _("You can not use miner pause and resume switches together"));
+               return EXIT_FAILURE;
+       }
+
+       if ((pause_reason || pause_for_process_reason  || resume_cookie != -1) && !miner_name) {
+               g_printerr ("%s\n",
+                           _("You must provide the miner for pause or resume commands"));
+               return EXIT_FAILURE;
+       }
+
+       if ((!pause_reason && !pause_for_process_reason && resume_cookie == -1) && miner_name) {
+               g_printerr ("%s\n",
+                           _("You must provide a pause or resume command for the miner"));
+               return EXIT_FAILURE;
+       }
+
+       /* Known actions */
+
+       if (list_miners_running || list_miners_available) {
+               return miner_list (list_miners_available,
+                                  list_miners_running);
+       }
+
+       if (pause_reason) {
+               return miner_pause (miner_name, pause_reason, FALSE);
+       }
+
+       if (pause_for_process_reason) {
+               return miner_pause (miner_name, pause_for_process_reason, TRUE);
+       }
+
+       if (resume_cookie != -1) {
+               return miner_resume (miner_name, resume_cookie);
+       }
+
+       if (pause_details) {
+               return miner_pause_details ();
+       }
+
+       /* Processes */
+       GError *error = NULL;
+       gpointer verbosity_type_enum_class_pointer = NULL;
+       TrackerVerbosity set_log_verbosity_value = TRACKER_VERBOSITY_ERRORS;
+
+       /* Constraints */
+
+       if (daemons_to_kill != TRACKER_PROCESS_TYPE_NONE && daemons_to_term != TRACKER_PROCESS_TYPE_NONE) {
+               g_printerr ("%s\n",
+                           _("You can not use the --kill and --terminate arguments together"));
+               return EXIT_FAILURE;
+       }
+
+       if (get_log_verbosity && set_log_verbosity) {
+               g_printerr ("%s\n",
+                           _("You can not use the --get-logging and --set-logging arguments together"));
+               return EXIT_FAILURE;
+       }
+
+       if (set_log_verbosity) {
+               if (g_ascii_strcasecmp (set_log_verbosity, "debug") == 0) {
+                       set_log_verbosity_value = TRACKER_VERBOSITY_DEBUG;
+               } else if (g_ascii_strcasecmp (set_log_verbosity, "detailed") == 0) {
+                       set_log_verbosity_value = TRACKER_VERBOSITY_DETAILED;
+               } else if (g_ascii_strcasecmp (set_log_verbosity, "minimal") == 0) {
+                       set_log_verbosity_value = TRACKER_VERBOSITY_MINIMAL;
+               } else if (g_ascii_strcasecmp (set_log_verbosity, "errors") == 0) {
+                       set_log_verbosity_value = TRACKER_VERBOSITY_ERRORS;
+               } else {
+                       g_printerr ("%s\n",
+                                   _("Invalid log verbosity, try “debug”, “detailed”, “minimal” or 
“errors”"));
+                       return EXIT_FAILURE;
+               }
+       }
+
+       if (get_log_verbosity || set_log_verbosity) {
+               GType etype;
+
+               /* Since we don't reference this enum anywhere, we do
+                * it here to make sure it exists when we call
+                * g_type_class_peek(). This wouldn't be necessary if
+                * it was a param in a GObject for example.
+                *
+                * This does mean that we are leaking by 1 reference
+                * here and should clean it up, but it doesn't grow so
+                * this is acceptable.
+                */
+               etype = tracker_verbosity_get_type ();
+               verbosity_type_enum_class_pointer = g_type_class_ref (etype);
+       }
+
+       if (list_processes) {
+               GSList *pids, *l;
+               gchar *str;
+
+               pids = tracker_process_find_all ();
+
+               str = g_strdup_printf (g_dngettext (NULL,
+                                                   "Found %d PID…",
+                                                   "Found %d PIDs…",
+                                                   g_slist_length (pids)),
+                                      g_slist_length (pids));
+               g_print ("%s\n", str);
+               g_free (str);
+
+               for (l = pids; l; l = l->next) {
+                       TrackerProcessData *pd = l->data;
+
+                       str = g_strdup_printf (_("Found process ID %d for “%s”"), pd->pid, pd->cmd);
+                       g_print ("%s\n", str);
+                       g_free (str);
+               }
+
+               g_slist_foreach (pids, (GFunc) tracker_process_data_free, NULL);
+               g_slist_free (pids);
+
+               return EXIT_SUCCESS;
+       }
+
+       if (daemons_to_kill != TRACKER_PROCESS_TYPE_NONE ||
+           daemons_to_term != TRACKER_PROCESS_TYPE_NONE) {
+               exit (tracker_process_stop (daemons_to_term, daemons_to_kill));
+       }
+
+       /* Deal with logging changes AFTER the config may have been
+        * reset, this way users can actually use --remove-config with
+        * the --set-logging switch.
+        */
+       if (get_log_verbosity) {
+               GSList *all;
+               gint longest = 0;
+
+               all = tracker_gsettings_get_all (&longest);
+
+               if (!all) {
+                       return EXIT_FAILURE;
+               }
+
+               g_print ("%s:\n", _("Components"));
+               tracker_gsettings_print_verbosity (all, longest, TRUE);
+               g_print ("\n");
+
+               /* Miners */
+               g_print ("%s (%s):\n",
+                        _("Miners"),
+                        _("Only those with config listed"));
+               tracker_gsettings_print_verbosity (all, longest, FALSE);
+               g_print ("\n");
+
+               tracker_gsettings_free (all);
+       }
+
+       if (set_log_verbosity) {
+               GSList *all;
+               gchar *str;
+               gint longest = 0;
+
+               all = tracker_gsettings_get_all (&longest);
+
+               if (!all) {
+                       return EXIT_FAILURE;
+               }
+
+               str = g_strdup_printf (_("Setting log verbosity for all components to “%s”…"), 
set_log_verbosity);
+               g_print ("%s\n", str);
+               g_print ("\n");
+               g_free (str);
+
+               tracker_gsettings_set_all (all, set_log_verbosity_value);
+               tracker_gsettings_free (all);
+
+               /* We free to make sure we get new settings and that
+                * they're saved properly.
+                */
+               all = tracker_gsettings_get_all (&longest);
+
+               if (!all) {
+                       return EXIT_FAILURE;
+               }
+
+               g_print ("%s:\n", _("Components"));
+               tracker_gsettings_print_verbosity (all, longest, TRUE);
+               g_print ("\n");
+
+               /* Miners */
+               g_print ("%s (%s):\n",
+                        _("Miners"),
+                        _("Only those with config listed"));
+               tracker_gsettings_print_verbosity (all, longest, FALSE);
+               g_print ("\n");
+
+               tracker_gsettings_free (all);
+       }
+
+       if (verbosity_type_enum_class_pointer) {
+               g_type_class_unref (verbosity_type_enum_class_pointer);
+       }
+
+       if (start) {
+               TrackerMinerManager *manager;
+               GSList *miners, *l;
+
+               g_print ("%s\n", _("Starting miners…"));
+
+               /* Auto-start the miners here */
+               manager = tracker_miner_manager_new_full (TRUE, &error);
+               if (!manager) {
+                       g_printerr (_("Could not start miners, manager could not be created, %s"),
+                                   error ? error->message : _("No error given"));
+                       g_printerr ("\n");
+                       g_clear_error (&error);
+                       return EXIT_FAILURE;
+               }
+
+               miners = tracker_miner_manager_get_available (manager);
+
+               /* Get the status of all miners, this will start all
+                * miners not already running.
+                */
+               for (l = miners; l; l = l->next) {
+                       const gchar *display_name;
+                       gdouble progress = 0.0;
+
+                       display_name = tracker_miner_manager_get_display_name (manager, l->data);
+
+                       if (!tracker_miner_manager_get_status (manager,
+                                                              l->data,
+                                                              NULL,
+                                                              &progress,
+                                                              NULL)) {
+                               g_printerr ("  ✗ %s (%s)\n",
+                                           display_name,
+                                           _("perhaps a disabled plugin?"));
+                       } else {
+                               g_print ("  ✓ %s\n",
+                                        display_name);
+                       }
+
+                       g_free (l->data);
+               }
+
+               g_slist_free (miners);
+               g_object_unref (manager);
+
+               return EXIT_SUCCESS;
+       }
+
+       /* All known options have their own exit points */
+       g_warn_if_reached ();
+
+       return EXIT_FAILURE;
+}
+
+static int
+daemon_run_default (void)
+{
+       /* Enable status output in the default run */
+       status = TRUE;
+
+       return daemon_run ();
+}
+
+static gboolean
+daemon_options_enabled (void)
+{
+       return DAEMON_OPTIONS_ENABLED ();
+}
+
+int
+tracker_daemon (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+       g_option_context_set_summary (context, _("If no arguments are given, the status of the store and data 
miners is shown"));
+
+       argv[0] = "tracker daemon";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (daemon_options_enabled ()) {
+               return daemon_run ();
+       }
+
+       return daemon_run_default ();
+}
diff --git a/src/tracker/tracker-dbus.c b/src/tracker/tracker-dbus.c
new file mode 100644
index 000000000..4644d33cd
--- /dev/null
+++ b/src/tracker/tracker-dbus.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2008, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include <libtracker-common/tracker-common.h>
+
+#include "tracker-dbus.h"
+
+gboolean
+tracker_dbus_get_connection (const gchar      *name,
+                             const gchar      *object_path,
+                             const gchar      *interface_name,
+                             GDBusProxyFlags   flags,
+                             GDBusConnection **connection,
+                             GDBusProxy      **proxy)
+{
+       GError *error = NULL;
+
+       *connection = g_bus_get_sync (TRACKER_IPC_BUS, NULL, &error);
+
+       if (!*connection) {
+               g_critical ("%s, %s",
+                           _("Could not get D-Bus connection"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+
+               return FALSE;
+       }
+
+       *proxy = g_dbus_proxy_new_sync (*connection,
+                                       flags,
+                                       NULL,
+                                       name,
+                                       object_path,
+                                       interface_name,
+                                       NULL,
+                                       &error);
+
+       if (error) {
+               g_critical ("%s, %s",
+                           _("Could not create D-Bus proxy to tracker-store"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
diff --git a/src/tracker/tracker-dbus.h b/src/tracker/tracker-dbus.h
new file mode 100644
index 000000000..13d30c047
--- /dev/null
+++ b/src/tracker/tracker-dbus.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <glib.h>
+
+#ifndef __TRACKER_DBUS_H__
+#define __TRACKER_DBUS_H__
+
+gboolean tracker_dbus_get_connection (const gchar      *name,
+                                      const gchar      *object_path,
+                                      const gchar      *interface_name,
+                                      GDBusProxyFlags   flags,
+                                      GDBusConnection **connection,
+                                      GDBusProxy      **proxy);
+
+#endif /* __TRACKER_DBUS_H__ */
diff --git a/src/tracker/tracker-extract.c b/src/tracker/tracker-extract.c
new file mode 100644
index 000000000..9b5e737d8
--- /dev/null
+++ b/src/tracker/tracker-extract.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015-2016, Sam Thursfield <sam afuera me uk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <libtracker-common/tracker-common.h>
+
+#include "tracker-config.h"
+#include "tracker-extract.h"
+
+static gchar *verbosity;
+static gchar *output_format = "turtle";
+static gchar **filenames;
+
+#define EXTRACT_OPTIONS_ENABLED()        \
+       ((filenames && g_strv_length (filenames) > 0))
+
+static GOptionEntry entries[] = {
+       { "verbosity", 'v', 0, G_OPTION_ARG_STRING, &verbosity,
+         N_("Sets the logging verbosity to LEVEL (“debug”, “detailed”, “minimal”, “errors”) for all 
processes"),
+         N_("LEVEL") },
+       { "output-format", 'o', 0, G_OPTION_ARG_STRING, &output_format,
+         N_("Output results format: “sparql”, or “turtle”"),
+         N_("FORMAT") },
+       { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
+         N_("FILE"),
+         N_("FILE") },
+       { NULL }
+};
+
+
+static gint
+extract_files (TrackerVerbosity  verbosity,
+               char             *output_format)
+{
+       char **p;
+       char *tracker_extract_path;
+       char verbosity_str[2];
+       GError *error = NULL;
+
+       snprintf (verbosity_str, 2, "%i", verbosity);
+
+       tracker_extract_path = g_build_filename(LIBEXECDIR, "tracker-extract", NULL);
+
+       for (p = filenames; *p; p++) {
+               char *argv[] = {tracker_extract_path,
+                               "--output-format", output_format,
+                               "--verbosity", verbosity_str,
+                               "--file", *p, NULL };
+
+               g_spawn_sync(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, NULL, &error);
+
+               if (error) {
+                       g_printerr ("%s: %s\n",
+                                   _("Could not run tracker-extract: "),
+                                   error->message);
+                       g_error_free (error);
+                       g_free (tracker_extract_path);
+                       return EXIT_FAILURE;
+               }
+       }
+
+       g_free (tracker_extract_path);
+       return EXIT_SUCCESS;
+}
+
+static int
+extract_run (void)
+{
+       TrackerVerbosity verbosity_level = TRACKER_VERBOSITY_ERRORS;
+
+       if (verbosity) {
+               if (g_ascii_strcasecmp (verbosity, "debug") == 0) {
+                       verbosity_level = TRACKER_VERBOSITY_DEBUG;
+               } else if (g_ascii_strcasecmp (verbosity, "detailed") == 0) {
+                       verbosity_level = TRACKER_VERBOSITY_DETAILED;
+               } else if (g_ascii_strcasecmp (verbosity, "minimal") == 0) {
+                       verbosity_level = TRACKER_VERBOSITY_MINIMAL;
+               } else if (g_ascii_strcasecmp (verbosity, "errors") == 0) {
+                       verbosity_level = TRACKER_VERBOSITY_ERRORS;
+               } else {
+                       g_printerr ("%s\n",
+                                   _("Invalid log verbosity, try “debug”, “detailed”, “minimal” or 
“errors”"));
+                       return EXIT_FAILURE;
+               }
+       }
+
+       return extract_files (verbosity_level, output_format);
+}
+
+static int
+extract_run_default (void)
+{
+       GOptionContext *context;
+       gchar *help;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+       help = g_option_context_get_help (context, TRUE, NULL);
+       g_option_context_free (context);
+       g_printerr ("%s\n", help);
+       g_free (help);
+
+       return EXIT_FAILURE;
+}
+
+static gboolean
+extract_options_enabled (void)
+{
+       return EXTRACT_OPTIONS_ENABLED ();
+}
+
+int
+tracker_extract (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       argv[0] = "tracker extract";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (extract_options_enabled ()) {
+               return extract_run ();
+       }
+
+       return extract_run_default ();
+}
diff --git a/src/tracker/tracker-index.c b/src/tracker/tracker-index.c
new file mode 100644
index 000000000..9658652b0
--- /dev/null
+++ b/src/tracker/tracker-index.c
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <errno.h>
+
+#ifdef __sun
+#include <procfs.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <libtracker-control/tracker-control.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-index.h"
+#include "tracker-dbus.h"
+
+static gchar **reindex_mime_types;
+static gboolean index_file;
+static gboolean backup;
+static gboolean restore;
+static gboolean import;
+static gchar **filenames;
+
+#define INDEX_OPTIONS_ENABLED()          \
+       ((filenames && g_strv_length (filenames) > 0) || \
+        (index_file || \
+         backup || \
+         restore || \
+         import) || \
+        reindex_mime_types)
+
+static GOptionEntry entries[] = {
+       { "reindex-mime-type", 'm', 0, G_OPTION_ARG_STRING_ARRAY, &reindex_mime_types,
+         N_("Tell miners to reindex files which match the mime type supplied (for new extractors), use -m 
MIME1 -m MIME2"),
+         N_("MIME") },
+       { "file", 'f', 0, G_OPTION_ARG_NONE, &index_file,
+         N_("Tell miners to (re)index a given file"),
+         N_("FILE") },
+       { "backup", 'b', 0, G_OPTION_ARG_NONE, &backup,
+         N_("Backup current index / database to the file provided"),
+         NULL },
+       { "restore", 'o', 0, G_OPTION_ARG_NONE, &restore,
+         N_("Restore a database from a previous backup (see --backup)"),
+         NULL },
+       { "import", 'i', 0, G_OPTION_ARG_NONE, &import,
+         N_("Import a dataset from the provided file (in Turtle format)"),
+         NULL },
+       { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
+         N_("FILE"),
+         N_("FILE") },
+       { NULL }
+};
+
+static gboolean
+has_valid_uri_scheme (const gchar *uri)
+{
+       const gchar *s;
+
+       s = uri;
+
+       if (!g_ascii_isalpha (*s)) {
+               return FALSE;
+       }
+
+       do {
+               s++;
+       } while (g_ascii_isalnum (*s) || *s == '+' || *s == '.' || *s == '-');
+
+       return (*s == ':');
+}
+
+static gchar *
+get_uri_from_arg (const gchar *arg)
+{
+       gchar *uri;
+
+       /* support both, URIs and local file paths */
+       if (has_valid_uri_scheme (arg)) {
+               uri = g_strdup (arg);
+       } else {
+               GFile *file;
+
+               file = g_file_new_for_commandline_arg (arg);
+               uri = g_file_get_uri (file);
+               g_object_unref (file);
+       }
+
+       return uri;
+}
+
+static int
+reindex_mimes (void)
+{
+       GError *error = NULL;
+       TrackerMinerManager *manager;
+
+       /* Auto-start the miners here if we need to */
+       manager = tracker_miner_manager_new_full (TRUE, &error);
+       if (!manager) {
+               g_printerr (_("Could not reindex mimetypes, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       tracker_miner_manager_reindex_by_mimetype (manager, (GStrv) reindex_mime_types, &error);
+       if (error) {
+               g_printerr ("%s: %s\n",
+                           _("Could not reindex mimetypes"),
+                           error->message);
+               g_error_free (error);
+               return EXIT_FAILURE;
+       }
+
+       g_print ("%s\n", _("Reindexing mime types was successful"));
+       g_object_unref (manager);
+
+       return EXIT_SUCCESS;
+}
+
+static gint
+index_or_reindex_file (void)
+{
+       TrackerMinerManager *manager;
+       GError *error = NULL;
+       gchar **p;
+
+       /* Auto-start the miners here if we need to */
+       manager = tracker_miner_manager_new_full (TRUE, &error);
+       if (!manager) {
+               g_printerr (_("Could not (re)index file, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       for (p = filenames; *p; p++) {
+               GFile *file;
+
+               file = g_file_new_for_commandline_arg (*p);
+               tracker_miner_manager_index_file (manager, file, &error);
+
+               if (error) {
+                       g_printerr ("%s: %s\n",
+                                   _("Could not (re)index file"),
+                                   error->message);
+                       g_error_free (error);
+                       return EXIT_FAILURE;
+               }
+
+               g_print ("%s\n", _("(Re)indexing file was successful"));
+               g_object_unref (file);
+       }
+
+       g_object_unref (manager);
+
+       return EXIT_SUCCESS;
+}
+
+static int
+import_turtle_files (void)
+{
+       TrackerSparqlConnection *connection;
+       GError *error = NULL;
+       gchar **p;
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (!connection) {
+               g_printerr ("%s: %s\n",
+                           _("Could not establish a connection to Tracker"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       for (p = filenames; *p; p++) {
+               GError *error = NULL;
+               GFile *file;
+
+               g_print ("%s:'%s'\n",
+                        _("Importing Turtle file"),
+                        *p);
+
+               file = g_file_new_for_commandline_arg (*p);
+               tracker_sparql_connection_load (connection, file, NULL, &error);
+               g_object_unref (file);
+
+               if (error) {
+                       g_printerr ("  %s, %s\n",
+                                   _("Unable to import Turtle file"),
+                                   error->message);
+
+                       g_error_free (error);
+                       continue;
+               }
+
+               g_print ("  %s\n", _("Done"));
+               g_print ("\n");
+       }
+
+       g_object_unref (connection);
+
+       return EXIT_SUCCESS;
+}
+
+static int
+backup_index (void)
+{
+       GDBusConnection *connection;
+       GDBusProxy *proxy;
+       GError *error = NULL;
+       GVariant *v;
+       gchar *uri;
+
+       if (!tracker_dbus_get_connection ("org.freedesktop.Tracker1",
+                                         "/org/freedesktop/Tracker1/Backup",
+                                         "org.freedesktop.Tracker1.Backup",
+                                         G_DBUS_PROXY_FLAGS_NONE,
+                                         &connection,
+                                         &proxy)) {
+               return EXIT_FAILURE;
+       }
+
+       uri = get_uri_from_arg (filenames[0]);
+
+       g_print ("%s\n", _("Backing up database"));
+       g_print ("  %s\n", uri);
+
+       /* Backup/Restore can take some time */
+       g_dbus_proxy_set_default_timeout (proxy, G_MAXINT);
+
+       v = g_dbus_proxy_call_sync (proxy,
+                                   "Save",
+                                   g_variant_new ("(s)", uri),
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   &error);
+
+       if (proxy) {
+               g_object_unref (proxy);
+       }
+
+       if (error) {
+               g_critical ("%s, %s",
+                           _("Could not backup database"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               g_free (uri);
+
+               return EXIT_FAILURE;
+       }
+
+       if (v) {
+               g_variant_unref (v);
+       }
+
+       g_free (uri);
+
+       return EXIT_SUCCESS;
+}
+
+static int
+restore_index (void)
+{
+       GDBusConnection *connection;
+       GDBusProxy *proxy;
+       GError *error = NULL;
+       GVariant *v;
+       gchar *uri;
+
+       if (!tracker_dbus_get_connection ("org.freedesktop.Tracker1",
+                                         "/org/freedesktop/Tracker1/Backup",
+                                         "org.freedesktop.Tracker1.Backup",
+                                         G_DBUS_PROXY_FLAGS_NONE,
+                                         &connection,
+                                         &proxy)) {
+               return EXIT_FAILURE;
+       }
+
+       uri = get_uri_from_arg (filenames[0]);
+
+       g_print ("%s\n", _("Restoring database from backup"));
+       g_print ("  %s\n", uri);
+
+       /* Backup/Restore can take some time */
+       g_dbus_proxy_set_default_timeout (proxy, G_MAXINT);
+
+       v = g_dbus_proxy_call_sync (proxy,
+                                   "Restore",
+                                   g_variant_new ("(s)", uri),
+                                   G_DBUS_CALL_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   &error);
+
+       if (proxy) {
+               g_object_unref (proxy);
+       }
+
+       if (error) {
+               g_critical ("%s, %s",
+                           _("Could not backup database"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               g_free (uri);
+
+               return EXIT_FAILURE;
+       }
+
+       if (v) {
+               g_variant_unref (v);
+       }
+
+       g_free (uri);
+
+       return EXIT_SUCCESS;
+}
+
+static int
+index_run (void)
+{
+       if (reindex_mime_types) {
+               return reindex_mimes ();
+       }
+
+       if (index_file) {
+               return index_or_reindex_file ();
+       }
+
+       if (import) {
+               return import_turtle_files ();
+       }
+
+       if (backup) {
+               return backup_index ();
+       }
+
+       if (restore) {
+               return restore_index ();
+       }
+
+       /* All known options have their own exit points */
+       g_printerr("Use `tracker index --file` when giving a specific file or "
+                  "directory to index. See `tracker help index` for more "
+                  "information.\n");
+
+       return EXIT_FAILURE;
+}
+
+static int
+index_run_default (void)
+{
+       GOptionContext *context;
+       gchar *help;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+       help = g_option_context_get_help (context, TRUE, NULL);
+       g_option_context_free (context);
+       g_printerr ("%s\n", help);
+       g_free (help);
+
+       return EXIT_FAILURE;
+}
+
+static gboolean
+index_options_enabled (void)
+{
+       return INDEX_OPTIONS_ENABLED ();
+}
+
+int
+tracker_index (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+       const gchar *failed;
+       gint actions = 0;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       argv[0] = "tracker index";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (backup) {
+               actions++;
+       }
+
+       if (restore) {
+               actions++;
+       }
+
+       if (index_file) {
+               actions++;
+       }
+
+       if (import) {
+               actions++;
+       }
+
+       if (actions > 1) {
+               failed = _("Only one action (--backup, --restore, --index-file or --import) can be used at a 
time");
+       } else if (actions > 0 && (!filenames || g_strv_length (filenames) < 1)) {
+               failed = _("Missing one or more files which are required");
+       } else if ((backup || restore) && (filenames && g_strv_length (filenames) > 1)) {
+               failed = _("Only one file can be used with --backup and --restore");
+       } else if (actions > 0 && (reindex_mime_types && g_strv_length (reindex_mime_types) > 0)) {
+               failed = _("Actions (--backup, --restore, --index-file and --import) can not be used with 
--reindex-mime-type");
+       } else {
+               failed = NULL;
+       }
+
+       if (failed) {
+               g_printerr ("%s\n\n", failed);
+               return EXIT_FAILURE;
+       }
+
+       if (index_options_enabled ()) {
+               return index_run ();
+       }
+
+       return index_run_default ();
+}
diff --git a/src/tracker/tracker-process.c b/src/tracker/tracker-process.c
new file mode 100644
index 000000000..ee02b02d1
--- /dev/null
+++ b/src/tracker/tracker-process.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2010, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#ifdef __OpenBSD__
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sysctl.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <unistd.h>
+#endif
+
+#ifdef __sun
+#define _STRUCTURED_PROC 1
+#include <sys/procfs.h>
+#endif
+
+#include "tracker-process.h"
+
+static TrackerProcessData *
+process_data_new (gchar *cmd, pid_t pid)
+{
+       TrackerProcessData *pd;
+
+       pd = g_slice_new0 (TrackerProcessData);
+       pd->cmd = cmd;
+       pd->pid = pid;
+
+       return pd;
+}
+
+void
+tracker_process_data_free (TrackerProcessData *pd)
+{
+       if (!pd) {
+               return;
+       }
+
+       g_free (pd->cmd);
+       g_slice_free (TrackerProcessData, pd);
+}
+
+GSList *
+tracker_process_get_pids (void)
+{
+       GError *error = NULL;
+       GDir *dir;
+       GSList *pids = NULL;
+       const gchar *name;
+
+       dir = g_dir_open ("/proc", 0, &error);
+       if (error) {
+               g_printerr ("%s: %s\n",
+                           _("Could not open /proc"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return NULL;
+       }
+
+       while ((name = g_dir_read_name (dir)) != NULL) {
+               gchar c;
+               gboolean is_pid = TRUE;
+
+               for (c = *name; c && c != ':' && is_pid; c++) {
+                       is_pid &= g_ascii_isdigit (c);
+               }
+
+               if (!is_pid) {
+                       continue;
+               }
+
+               pids = g_slist_prepend (pids, g_strdup (name));
+       }
+
+       g_dir_close (dir);
+
+       return g_slist_reverse (pids);
+}
+
+guint32
+tracker_process_get_uid_for_pid (const gchar  *pid_as_string,
+                                 gchar       **filename)
+{
+       GFile *f;
+       GFileInfo *info;
+       GError *error = NULL;
+       gchar *fn;
+       gchar *proc_dir_name;
+       guint uid;
+
+       proc_dir_name = g_build_filename ("/proc", pid_as_string, NULL);
+
+#ifdef __sun /* Solaris */
+       fn = g_build_filename (proc_dir_name, "psinfo", NULL);
+#else
+       fn = g_build_filename (proc_dir_name, "cmdline", NULL);
+#endif
+
+       f = g_file_new_for_path (proc_dir_name);
+       info = g_file_query_info (f,
+                                 G_FILE_ATTRIBUTE_UNIX_UID,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 NULL,
+                                 &error);
+
+       if (error) {
+               g_printerr ("%s '%s', %s", _("Could not stat() file"), proc_dir_name, error->message);
+               g_error_free (error);
+               uid = 0;
+       } else {
+               uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
+               g_object_unref (info);
+       }
+
+       g_free (proc_dir_name);
+
+       if (filename) {
+               *filename = fn;
+       } else {
+               g_free (fn);
+       }
+
+       g_object_unref (f);
+
+       return uid;
+}
+
+GSList *
+tracker_process_find_all (void)
+{
+#ifndef __OpenBSD__
+       GSList *pids, *l;
+       GSList *found_pids = NULL;
+       guint32 own_pid;
+       guint32 own_uid;
+       gchar *own_pid_str;
+
+       /* Unless we are stopping processes or listing processes,
+        * don't iterate them.
+        */
+       pids = tracker_process_get_pids ();
+
+       /* Establish own uid/pid */
+       own_pid = (guint32) getpid ();
+       own_pid_str = g_strdup_printf ("%d", own_pid);
+       own_uid = tracker_process_get_uid_for_pid (own_pid_str, NULL);
+       g_free (own_pid_str);
+
+       for (l = pids; l; l = l->next) {
+               GError *error = NULL;
+               gchar *filename;
+#ifdef __sun /* Solaris */
+               psinfo_t psinfo = { 0 };
+#endif
+               gchar *contents = NULL;
+               gchar **strv;
+               guint uid;
+               pid_t pid;
+
+               uid = tracker_process_get_uid_for_pid (l->data, &filename);
+
+               /* Stat the file and make sure current user == file owner */
+               if (uid != own_uid) {
+                       continue;
+               }
+
+               pid = atoi (l->data);
+
+               /* Don't return our own PID */
+               if (pid == own_pid) {
+                       continue;
+               }
+
+               /* Get contents to determine basename */
+               if (!g_file_get_contents (filename, &contents, NULL, &error)) {
+                       gchar *str;
+
+                       str = g_strdup_printf (_("Could not open “%s”"), filename);
+                       g_printerr ("%s: %s\n",
+                                   str,
+                                   error ? error->message : _("No error given"));
+                       g_free (str);
+                       g_clear_error (&error);
+                       g_free (contents);
+                       g_free (filename);
+
+                       continue;
+               }
+#ifdef __sun /* Solaris */
+               memcpy (&psinfo, contents, sizeof (psinfo));
+
+               /* won't work with paths containing spaces :( */
+               strv = g_strsplit (psinfo.pr_psargs, " ", 2);
+#else
+               strv = g_strsplit (contents, "^@", 2);
+#endif
+               if (strv && strv[0]) {
+                       gchar *basename;
+
+                       basename = g_path_get_basename (strv[0]);
+
+                       if ((g_str_has_prefix (basename, "tracker") ||
+                            g_str_has_prefix (basename, "lt-tracker"))) {
+                               found_pids = g_slist_prepend (found_pids, process_data_new (basename, pid));
+                       } else {
+                               g_free (basename);
+                       }
+               }
+
+               g_strfreev (strv);
+               g_free (contents);
+               g_free (filename);
+       }
+
+       g_slist_foreach (pids, (GFunc) g_free, NULL);
+       g_slist_free (pids);
+
+       return g_slist_reverse (found_pids);
+#else /* ! __OpenBSD__ */
+       GSList *found_pids = NULL;
+       gchar **strv;
+       gchar *basename;
+       pid_t pid;
+       gint i, nproc;
+       gchar buf[_POSIX2_LINE_MAX];
+       struct kinfo_proc *plist, *kp;
+       kvm_t *kd;
+
+       if ((kd = kvm_openfiles (NULL, NULL, NULL, KVM_NO_FILES, buf)) == NULL)
+               return NULL;
+
+       if ((plist = kvm_getprocs (kd, KERN_PROC_ALL, 0, sizeof (*plist), &nproc)) == NULL)
+               return NULL;
+
+       for (i = 0, kp = plist; i < nproc; i++, kp++) {
+               if ((kp->p_flag & P_SYSTEM) != 0)
+                       continue;
+               if ((strv = kvm_getargv (kd, kp, 0)) == NULL)
+                       continue;
+
+               pid = kp->p_pid;
+
+               /* Don't return our own PID */
+               if (pid == getpid ())
+                       continue;
+
+               /* Don't return PID we don't own */
+               if (kp->p_uid != getuid ())
+                       continue;
+
+               basename = g_path_get_basename (strv[0]);
+
+               if ((g_str_has_prefix (basename, "tracker") ||
+                    g_str_has_prefix (basename, "lt-tracker"))) {
+                       found_pids = g_slist_prepend (found_pids, process_data_new (basename, pid));
+               } else {
+                       g_free (basename);
+               }
+       }
+
+       return g_slist_reverse (found_pids);
+#endif
+}
+
+gint
+tracker_process_stop (TrackerProcessTypes daemons_to_term,
+                      TrackerProcessTypes daemons_to_kill)
+{
+       GSList *pids, *l;
+       gchar *str;
+
+       if (daemons_to_kill == TRACKER_PROCESS_TYPE_NONE &&
+           daemons_to_term == TRACKER_PROCESS_TYPE_NONE) {
+               return 0;
+       }
+
+       pids = tracker_process_find_all ();
+
+       str = g_strdup_printf (g_dngettext (NULL,
+                                           "Found %d PID…",
+                                           "Found %d PIDs…",
+                                           g_slist_length (pids)),
+                              g_slist_length (pids));
+       g_print ("%s\n", str);
+       g_free (str);
+
+       for (l = pids; l; l = l->next) {
+               TrackerProcessData *pd;
+               const gchar *basename;
+               pid_t pid;
+
+               pd = l->data;
+               basename = pd->cmd;
+               pid = pd->pid;
+               
+               if (daemons_to_term != TRACKER_PROCESS_TYPE_NONE) {
+                       if ((daemons_to_term == TRACKER_PROCESS_TYPE_STORE &&
+                            !g_str_has_suffix (basename, "tracker-store")) ||
+                           (daemons_to_term == TRACKER_PROCESS_TYPE_MINERS &&
+                            !strstr (basename, "tracker-miner"))) {
+                               continue;
+                       }
+
+                       if (kill (pid, SIGTERM) == -1) {
+                               const gchar *errstr = g_strerror (errno);
+                                               
+                               str = g_strdup_printf (_("Could not terminate process %d — “%s”"), pid, 
basename);
+                               g_printerr ("  %s: %s\n",
+                                           str,
+                                           errstr ? errstr : _("No error given"));
+                               g_free (str);
+                       } else {
+                               str = g_strdup_printf (_("Terminated process %d — “%s”"), pid, basename);
+                               g_print ("  %s\n", str);
+                               g_free (str);
+                       }
+               } else if (daemons_to_kill != TRACKER_PROCESS_TYPE_NONE) {
+                       if ((daemons_to_kill == TRACKER_PROCESS_TYPE_STORE &&
+                            !g_str_has_suffix (basename, "tracker-store")) ||
+                           (daemons_to_kill == TRACKER_PROCESS_TYPE_MINERS &&
+                            !strstr (basename, "tracker-miner"))) {
+                               continue;
+                       }
+
+                       if (kill (pid, SIGKILL) == -1) {
+                               const gchar *errstr = g_strerror (errno);
+
+                               str = g_strdup_printf (_("Could not kill process %d — “%s”"), pid, basename);
+                               g_printerr ("  %s: %s\n",
+                                           str,
+                                           errstr ? errstr : _("No error given"));
+                               g_free (str);
+                       } else {
+                               str = g_strdup_printf (_("Killed process %d — “%s”"), pid, basename);
+                               g_print ("  %s\n", str);
+                               g_free (str);
+                       }
+               }
+       }
+
+       g_slist_foreach (pids, (GFunc) tracker_process_data_free, NULL);
+       g_slist_free (pids);
+
+       return 0;
+}
diff --git a/src/tracker/tracker-process.h b/src/tracker/tracker-process.h
new file mode 100644
index 000000000..1432da3b8
--- /dev/null
+++ b/src/tracker/tracker-process.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#ifndef __TRACKER_PROCESS_H__
+#define __TRACKER_PROCESS_H__
+
+typedef struct {
+       char *cmd;
+       pid_t pid;
+} TrackerProcessData;
+
+typedef enum {
+       TRACKER_PROCESS_TYPE_NONE,
+       TRACKER_PROCESS_TYPE_ALL,
+       TRACKER_PROCESS_TYPE_STORE,
+       TRACKER_PROCESS_TYPE_MINERS
+} TrackerProcessTypes;
+
+GSList * tracker_process_get_pids        (void);
+guint32  tracker_process_get_uid_for_pid (const gchar  *pid_as_string,
+                                          gchar       **filename);
+
+void     tracker_process_data_free       (TrackerProcessData *pd);
+
+GSList * tracker_process_find_all        (void);
+gint     tracker_process_stop            (TrackerProcessTypes    daemons_to_term,
+                                          TrackerProcessTypes    daemons_to_kill);
+
+#endif /* __TRACKER_PROCESS_H__ */
diff --git a/src/tracker/tracker-reset.c b/src/tracker/tracker-reset.c
new file mode 100644
index 000000000..fa7a2eeec
--- /dev/null
+++ b/src/tracker/tracker-reset.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <gio/gio.h>
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-data/tracker-data.h>
+#include <libtracker-control/tracker-control.h>
+
+#include "tracker-reset.h"
+#include "tracker-daemon.h"
+#include "tracker-process.h"
+#include "tracker-config.h"
+#include "tracker-color.h"
+
+static gboolean hard_reset;
+static gboolean soft_reset;
+static gboolean remove_config;
+static gchar *filename = NULL;
+
+#define RESET_OPTIONS_ENABLED() \
+       (hard_reset || \
+        soft_reset || \
+        remove_config || \
+        filename)
+
+static GOptionEntry entries[] = {
+       { "hard", 'r', 0, G_OPTION_ARG_NONE, &hard_reset,
+         N_("Kill all Tracker processes and remove all databases"),
+         NULL },
+       { "soft", 'e', 0, G_OPTION_ARG_NONE, &soft_reset,
+         N_("Same as --hard but the backup & journal are restored after restart"),
+         NULL },
+       { "config", 'c', 0, G_OPTION_ARG_NONE, &remove_config,
+         N_("Remove all configuration files so they are re-generated on next start"),
+         NULL },
+       { "file", 'f', 0, G_OPTION_ARG_FILENAME, &filename,
+         N_("Erase indexed information about a file, works recursively for directories"),
+         N_("FILE") },
+       { NULL }
+};
+
+static void
+log_handler (const gchar    *domain,
+             GLogLevelFlags  log_level,
+             const gchar    *message,
+             gpointer        user_data)
+{
+       switch (log_level) {
+       case G_LOG_LEVEL_WARNING:
+       case G_LOG_LEVEL_CRITICAL:
+       case G_LOG_LEVEL_ERROR:
+       case G_LOG_FLAG_RECURSION:
+       case G_LOG_FLAG_FATAL:
+               g_fprintf (stderr, "%s\n", message);
+               fflush (stderr);
+               break;
+       case G_LOG_LEVEL_MESSAGE:
+       case G_LOG_LEVEL_INFO:
+       case G_LOG_LEVEL_DEBUG:
+       case G_LOG_LEVEL_MASK:
+       default:
+               g_fprintf (stdout, "%s\n", message);
+               fflush (stdout);
+               break;
+       }
+}
+
+static void
+delete_file (GFile    *file,
+             gpointer  user_data)
+{
+       if (g_file_delete (file, NULL, NULL)) {
+               gchar *path;
+
+               path = g_file_get_path (file);
+               g_print ("  %s\n", path);
+               g_free (path);
+       }
+}
+
+static void
+directory_foreach (GFile    *file,
+                   gchar    *suffix,
+                   GFunc     func,
+                   gpointer  user_data)
+{
+       GFileEnumerator *enumerator;
+       GFileInfo *info;
+       GFile *child;
+
+       enumerator = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                               G_FILE_QUERY_INFO_NONE, NULL, NULL);
+
+       while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
+
+               if (!suffix || g_str_has_suffix (g_file_info_get_name (info), suffix)) {
+                       child = g_file_enumerator_get_child (enumerator, info);
+                       (func) (child, user_data);
+                       g_object_unref (child);
+               }
+
+               g_object_unref (info);
+       }
+
+       g_object_unref (enumerator);
+}
+
+static int
+delete_info_recursively (GFile *file)
+{
+       TrackerSparqlConnection *connection;
+       TrackerMinerManager *miner_manager;
+       TrackerSparqlCursor *cursor;
+       gchar *query, *uri;
+       GError *error = NULL;
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (error)
+               goto error;
+
+       uri = g_file_get_uri (file);
+
+       /* First, query whether the item exists */
+       query = g_strdup_printf ("SELECT ?u { ?u nie:url '%s' }", uri);
+       cursor = tracker_sparql_connection_query (connection, query,
+                                                 NULL, &error);
+
+       /* If the item doesn't exist, bail out. */
+       if (!cursor || !tracker_sparql_cursor_next (cursor, NULL, &error)) {
+               g_clear_object (&cursor);
+
+               if (error)
+                       goto error;
+
+               return EXIT_SUCCESS;
+       }
+
+       g_object_unref (cursor);
+
+       /* Now, delete the element recursively */
+       g_print ("%s\n", _("Deleting…"));
+       query = g_strdup_printf ("DELETE { "
+                                "  ?f a rdfs:Resource . "
+                                "  ?ie a rdfs:Resource "
+                                "} WHERE {"
+                                "  ?f nie:url ?url . "
+                                "  ?ie nie:isStoredAs ?f . "
+                                "  FILTER (?url = '%s' ||"
+                                "          STRSTARTS (?url, '%s/'))"
+                                "}", uri, uri);
+       g_free (uri);
+
+       tracker_sparql_connection_update (connection, query,
+                                         G_PRIORITY_DEFAULT, NULL, &error);
+       g_free (query);
+
+       if (error)
+               goto error;
+
+       g_object_unref (connection);
+
+       g_print ("%s\n", _("The indexed data for this file has been deleted "
+                          "and will be reindexed again."));
+
+       /* Request reindexing of this data, it was previously in the store. */
+       miner_manager = tracker_miner_manager_new_full (FALSE, NULL);
+       tracker_miner_manager_index_file (miner_manager, file, &error);
+       g_object_unref (miner_manager);
+
+       if (error)
+               goto error;
+
+       return EXIT_SUCCESS;
+
+error:
+       g_warning ("%s", error->message);
+       g_error_free (error);
+       return EXIT_FAILURE;
+}
+
+static gint
+reset_run (void)
+{
+       GError *error = NULL;
+
+       if (hard_reset && soft_reset) {
+               g_printerr ("%s\n",
+                           /* TRANSLATORS: --hard and --soft are commandline arguments */
+                           _("You can not use the --hard and --soft arguments together"));
+               return EXIT_FAILURE;
+       }
+
+       if (hard_reset || soft_reset) {
+               gchar response[100] = { 0 };
+
+               g_print (CRIT_BEGIN "%s" CRIT_END "\n%s\n\n%s %s: ",
+                        _("CAUTION: This process may irreversibly delete data."),
+                        _("Although most content indexed by Tracker can "
+                          "be safely reindexed, it can’t be assured that "
+                          "this is the case for all data. Be aware that "
+                          "you may be incurring in a data loss situation, "
+                          "proceed at your own risk."),
+                        _("Are you sure you want to proceed?"),
+                        /* TRANSLATORS: This is to be displayed on command line output */
+                        _("[y|N]"));
+
+               fgets (response, 100, stdin);
+               response[strlen (response) - 1] = '\0';
+
+               /* TRANSLATORS: this is our test for a [y|N] question in the command line.
+                * A partial or full match will be considered an affirmative answer,
+                * it is intentionally lowercase, so please keep it like this.
+                */
+               if (!response[0] || !g_str_has_prefix (_("yes"), response)) {
+                       return EXIT_FAILURE;
+               }
+       }
+
+       if (filename) {
+               GFile *file;
+               gint retval;
+
+               file = g_file_new_for_commandline_arg (filename);
+               retval = delete_info_recursively (file);
+               g_object_unref (file);
+               return retval;
+       }
+
+       /* KILL processes first... */
+       if (hard_reset || soft_reset) {
+               tracker_process_stop (TRACKER_PROCESS_TYPE_NONE, TRACKER_PROCESS_TYPE_ALL);
+       }
+
+       if (hard_reset || soft_reset) {
+               guint log_handler_id;
+               GFile *cache_location, *data_location;
+               gchar *dir;
+               TrackerDBManager *db_manager;
+#ifndef DISABLE_JOURNAL
+               gchar *rotate_to;
+               TrackerDBConfig *db_config;
+               gsize chunk_size;
+               gint chunk_size_mb;
+               TrackerDBJournal *journal_writer;
+#endif /* DISABLE_JOURNAL */
+
+               dir = g_build_filename (g_get_user_cache_dir (), "tracker", NULL);
+               cache_location = g_file_new_for_path (dir);
+               g_free (dir);
+
+               dir = g_build_filename (g_get_user_data_dir (), "tracker", "data", NULL);
+               data_location = g_file_new_for_path (dir);
+               g_free (dir);
+
+               /* Set log handler for library messages */
+               log_handler_id = g_log_set_handler (NULL,
+                                                   G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL,
+                                                   log_handler,
+                                                   NULL);
+
+               g_log_set_default_handler (log_handler, NULL);
+
+#ifndef DISABLE_JOURNAL
+               db_config = tracker_db_config_new ();
+
+               chunk_size_mb = tracker_db_config_get_journal_chunk_size (db_config);
+               chunk_size = (gsize) ((gsize) chunk_size_mb * (gsize) 1024 * (gsize) 1024);
+               rotate_to = tracker_db_config_get_journal_rotate_destination (db_config);
+
+               /* This call is needed to set the journal's filename */
+               tracker_db_journal_set_rotating ((chunk_size_mb != -1),
+                                                chunk_size, rotate_to);
+
+               g_free (rotate_to);
+               g_object_unref (db_config);
+
+#endif /* DISABLE_JOURNAL */
+
+               /* Clean up (select_cache_size and update_cache_size don't matter here) */
+               db_manager = tracker_db_manager_new (TRACKER_DB_MANAGER_REMOVE_ALL,
+                                                    cache_location, data_location,
+                                                    NULL,
+                                                    FALSE,
+                                                    FALSE,
+                                                    100,
+                                                    100,
+                                                    NULL,
+                                                    NULL,
+                                                    NULL,
+                                                    NULL,
+                                                    &error);
+
+               if (!db_manager) {
+                       g_message ("Error initializing database: %s", error->message);
+                       g_free (error);
+
+                       return EXIT_FAILURE;
+               }
+
+               tracker_db_manager_remove_all (db_manager);
+#ifndef DISABLE_JOURNAL
+               journal_writer = tracker_db_journal_new (data_location, FALSE, NULL);
+               tracker_db_journal_remove (journal_writer);
+#endif /* DISABLE_JOURNAL */
+
+               tracker_db_manager_remove_version_file (db_manager);
+               tracker_db_manager_free (db_manager);
+
+               /* Unset log handler */
+               g_log_remove_handler (NULL, log_handler_id);
+
+               if (!remove_config) {
+                       return EXIT_SUCCESS;
+               }
+       }
+
+       if (remove_config) {
+               GFile *file;
+               const gchar *home_conf_dir;
+               gchar *path;
+               GSList *all, *l;
+
+               /* Check the default XDG_DATA_HOME location */
+               home_conf_dir = g_getenv ("XDG_CONFIG_HOME");
+
+               if (home_conf_dir && tracker_path_has_write_access_or_was_created (home_conf_dir)) {
+                       path = g_build_path (G_DIR_SEPARATOR_S, home_conf_dir, "tracker", NULL);
+               } else {
+                       home_conf_dir = g_getenv ("HOME");
+
+                       if (!home_conf_dir || !tracker_path_has_write_access_or_was_created (home_conf_dir)) {
+                               home_conf_dir = g_get_home_dir ();
+                       }
+                       path = g_build_path (G_DIR_SEPARATOR_S, home_conf_dir, ".config", "tracker", NULL);
+               }
+
+               file = g_file_new_for_path (path);
+               g_free (path);
+
+               g_print ("%s\n", _("Removing configuration files…"));
+
+               directory_foreach (file, ".cfg", (GFunc) delete_file, NULL);
+               g_object_unref (file);
+
+               g_print ("%s\n", _("Resetting existing configuration…"));
+
+               all = tracker_gsettings_get_all (NULL);
+
+               if (!all) {
+                       return EXIT_FAILURE;
+               }
+
+               for (l = all; l; l = l->next) {
+                       ComponentGSettings *c = l->data;
+                       gchar **keys, **p;
+
+                       if (!c) {
+                               continue;
+                       }
+
+                       g_print ("  %s\n", c->name);
+
+                       keys = g_settings_schema_list_keys (c->schema);
+                       for (p = keys; p && *p; p++) {
+                               g_print ("    %s\n", *p);
+                               g_settings_reset (c->settings, *p);
+                       }
+
+                       if (keys) {
+                               g_strfreev (keys);
+                       }
+
+                       g_settings_apply (c->settings);
+               }
+
+               g_settings_sync ();
+
+               tracker_gsettings_free (all);
+
+               return EXIT_SUCCESS;
+       }
+
+       /* All known options have their own exit points */
+       g_warn_if_reached ();
+
+       return EXIT_FAILURE;
+}
+
+static int
+reset_run_default (void)
+{
+       GOptionContext *context;
+       gchar *help;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+       help = g_option_context_get_help (context, FALSE, NULL);
+       g_option_context_free (context);
+       g_printerr ("%s\n", help);
+       g_free (help);
+
+       return EXIT_FAILURE;
+}
+
+static gboolean
+reset_options_enabled (void)
+{
+       return RESET_OPTIONS_ENABLED ();
+}
+
+int
+tracker_reset (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       argv[0] = "tracker reset";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (reset_options_enabled ()) {
+               return reset_run ();
+       }
+
+       return reset_run_default ();
+}
diff --git a/src/tracker/tracker-search.c b/src/tracker/tracker-search.c
new file mode 100644
index 000000000..782d1bb50
--- /dev/null
+++ b/src/tracker/tracker-search.c
@@ -0,0 +1,1791 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, SoftAtHome <contact softathome com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-color.h"
+#include "tracker-search.h"
+
+static gint limit = -1;
+static gint offset;
+static gchar **terms;
+static gboolean or_operator;
+static gboolean detailed;
+static gboolean all;
+static gboolean disable_snippets;
+static gboolean disable_fts;
+static gboolean disable_color;
+static gboolean files;
+static gboolean folders;
+static gboolean music_albums;
+static gboolean music_artists;
+static gboolean music_files;
+static gboolean image_files;
+static gboolean video_files;
+static gboolean document_files;
+static gboolean emails;
+static gboolean contacts;
+static gboolean feeds;
+static gboolean software;
+static gboolean software_categories;
+static gboolean bookmarks;
+
+#define SEARCH_OPTIONS_ENABLED() \
+       (music_albums || music_artists || music_files || \
+        bookmarks || \
+        feeds || \
+        software || \
+        software_categories || \
+        image_files || \
+        video_files || \
+        document_files || \
+        emails || \
+        contacts || \
+        files || \
+        folders || \
+        (terms && g_strv_length (terms) > 0))
+
+static GOptionEntry entries[] = {
+       /* Search types */
+       { "files", 'f', 0, G_OPTION_ARG_NONE, &files,
+         N_("Search for files"),
+         NULL
+       },
+       { "folders", 's', 0, G_OPTION_ARG_NONE, &folders,
+         N_("Search for folders"),
+         NULL
+       },
+       { "music", 'm', 0, G_OPTION_ARG_NONE, &music_files,
+         N_("Search for music files"),
+         NULL
+       },
+       { "music-albums", 0, 0, G_OPTION_ARG_NONE, &music_albums,
+         N_("Search for music albums (--all has no effect on this)"),
+         NULL
+       },
+       { "music-artists", 0, 0, G_OPTION_ARG_NONE, &music_artists,
+         N_("Search for music artists (--all has no effect on this)"),
+         NULL
+       },
+       { "images", 'i', 0, G_OPTION_ARG_NONE, &image_files,
+         N_("Search for image files"),
+         NULL
+       },
+       { "videos", 'v', 0, G_OPTION_ARG_NONE, &video_files,
+         N_("Search for video files"),
+         NULL
+       },
+       { "documents", 't', 0, G_OPTION_ARG_NONE, &document_files,
+         N_("Search for document files"),
+         NULL
+       },
+       { "emails", 'e', 0, G_OPTION_ARG_NONE, &emails,
+         N_("Search for emails"),
+         NULL
+       },
+       { "contacts", 'c', 0, G_OPTION_ARG_NONE, &contacts,
+         N_("Search for contacts"),
+         NULL
+       },
+       { "software", 0, 0, G_OPTION_ARG_NONE, &software,
+         N_("Search for software (--all has no effect on this)"),
+         NULL
+       },
+       { "software-categories", 0, 0, G_OPTION_ARG_NONE, &software_categories,
+         N_("Search for software categories (--all has no effect on this)"),
+         NULL
+       },
+       { "feeds", 0, 0, G_OPTION_ARG_NONE, &feeds,
+         N_("Search for feeds (--all has no effect on this)"),
+         NULL
+       },
+       { "bookmarks", 'b', 0, G_OPTION_ARG_NONE, &bookmarks,
+         N_("Search for bookmarks (--all has no effect on this)"),
+         NULL
+       },
+
+       /* Semantic options */
+       { "limit", 'l', 0, G_OPTION_ARG_INT, &limit,
+         N_("Limit the number of results shown"),
+         "512"
+       },
+       { "offset", 'o', 0, G_OPTION_ARG_INT, &offset,
+         N_("Offset the results"),
+         "0"
+       },
+       { "or-operator", 'r', 0, G_OPTION_ARG_NONE, &or_operator,
+         N_("Use OR for search terms instead of AND (the default)"),
+         NULL
+       },
+       { "detailed", 'd', 0, G_OPTION_ARG_NONE, &detailed,
+         N_("Show URNs for results (doesn’t apply to --music-albums, --music-artists, --feeds, --software, 
--software-categories)"),
+         NULL
+       },
+       { "all", 'a', 0, G_OPTION_ARG_NONE, &all,
+         N_("Return all non-existing matches too (i.e. include unmounted volumes)"),
+         NULL
+       },
+       { "disable-snippets", 0, 0, G_OPTION_ARG_NONE, &disable_snippets,
+         N_("Disable showing snippets with results. This is only shown for some categories, e.g. Documents, 
Music…"),
+         NULL,
+       },
+       { "disable-fts", 0, 0, G_OPTION_ARG_NONE, &disable_fts,
+         N_("Disable Full Text Search (FTS). Implies --disable-snippets"),
+         NULL,
+       },
+       { "disable-color", 0, 0, G_OPTION_ARG_NONE, &disable_color,
+         N_("Disable color when printing snippets and results"),
+         NULL,
+       },
+
+       /* Main arguments, the search terms */
+       { G_OPTION_REMAINING, 0, 0,
+         G_OPTION_ARG_STRING_ARRAY, &terms,
+         N_("search terms"),
+         N_("EXPRESSION")
+       },
+       { NULL }
+};
+
+static void
+show_limit_warning (void)
+{
+       /* Display '...' so the user thinks there is
+        * more items.
+        */
+       g_print ("  ...\n");
+
+       /* Display warning so the user knows this is
+        * not the WHOLE data set.
+        */
+       g_printerr ("\n%s%s%s\n",
+                   disable_color ? "" : WARN_BEGIN,
+                   _("NOTE: Limit was reached, there are more items in the database not listed here"),
+                   disable_color ? "" : WARN_END);
+}
+
+static gchar *
+get_fts_string (GStrv    search_words,
+                gboolean use_or_operator)
+{
+#if HAVE_TRACKER_FTS
+       GString *fts;
+       gint i, len;
+
+       if (disable_fts) {
+               return NULL;
+       }
+
+       if (!search_words) {
+               return NULL;
+       }
+
+       fts = g_string_new ("");
+       len = g_strv_length (search_words);
+
+       for (i = 0; i < len; i++) {
+               gchar *escaped;
+
+               /* Properly escape the input string as it's going to be passed
+                * in a sparql query */
+               escaped = tracker_sparql_escape_string (search_words[i]);
+
+               g_string_append (fts, escaped);
+
+               if (i < len - 1) {
+                       if (use_or_operator) {
+                               g_string_append (fts, " OR ");
+                       } else {
+                               g_string_append (fts, " ");
+                       }
+               }
+
+               g_free (escaped);
+       }
+
+       return g_string_free (fts, FALSE);
+#else
+       /* If FTS support not enabled, always do non-fts searches */
+       return NULL;
+#endif
+}
+
+static inline void
+print_snippet (const gchar *snippet)
+{
+       if (disable_snippets) {
+               return;
+       }
+
+       if (!snippet || *snippet == '\0') {
+               return;
+       } else {
+               gchar *compressed;
+               gchar *p;
+
+               compressed = g_strdup (snippet);
+
+               for (p = compressed;
+                    p && *p != '\0';
+                    p = g_utf8_next_char (p)) {
+                       if (*p == '\r' || *p == '\n') {
+                               /* inline \n and \r */
+                               *p = ' ';
+                       }
+               }
+
+               g_print ("  %s\n", compressed);
+               g_free (compressed);
+       }
+
+       g_print ("\n");
+}
+
+static gboolean
+get_contacts_results (TrackerSparqlConnection *connection,
+                      const gchar             *query,
+                      gint                     search_limit,
+                      gboolean                 details)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No contacts were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Contacts"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       if (details) {
+                               g_print ("  '%s%s%s', %s (%s)\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                        disable_color ? "" : TITLE_END,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        tracker_sparql_cursor_get_string (cursor, 2, NULL));
+                       } else {
+                               g_print ("  '%s%s%s', %s\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                        disable_color ? "" : TITLE_END,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL));
+                       }
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_contacts (TrackerSparqlConnection *connection,
+              GStrv                    search_terms,
+              gboolean                 show_all,
+              gint                     search_offset,
+              gint                     search_limit,
+              gboolean                 use_or_operator,
+              gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       gboolean success;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT tracker:coalesce(nco:fullname(?contact), 
fn:concat(nco:nameFamily(?contact), \" \", nco:nameGiven(?contact)),\"%s\") 
tracker:coalesce(nco:hasEmailAddress(?contact), \"%s\") ?contact "
+                                        "WHERE { "
+                                        "  ?contact a nco:Contact ;"
+                                        "  fts:match \"%s\" ."
+                                        "} "
+                                        "ORDER BY ASC(nco:fullname(?contact)) 
ASC(nco:hasEmailAddress(?contact)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                         _("No name"),
+                                         _("No E-mail address"),
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT tracker:coalesce(nco:fullname(?contact), 
fn:concat(nco:nameFamily(?contact), \" \", nco:nameGiven(?contact)), \"%s\") 
tracker:coalesce(nco:hasEmailAddress(?contact), \"%s\") ?contact "
+                                        "WHERE { "
+                                        "  ?contact a nco:Contact ."
+                                        "} "
+                                        "ORDER BY ASC(nco:fullname(?contact)) 
ASC(nco:hasEmailAddress(?contact)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                         _("No name"),
+                                         _("No E-mail address"),
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_contacts_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_emails_results (TrackerSparqlConnection *connection,
+                    const gchar             *query,
+                    gint                     search_limit,
+                    gboolean                 details)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No emails were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Emails"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s%s%s\n"
+                                "  %s, %s\n",
+                                disable_color ? "" : TITLE_BEGIN,
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                disable_color ? "" : TITLE_END,
+                                tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                tracker_sparql_cursor_get_string (cursor, 2, NULL));
+
+                       print_snippet (tracker_sparql_cursor_get_string (cursor, 3, NULL));
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_emails (TrackerSparqlConnection *connection,
+            GStrv                    search_terms,
+            gboolean                 show_all,
+            gint                     search_offset,
+            gint                     search_limit,
+            gboolean                 use_or_operator,
+            gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       gboolean success;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT nie:url(?email) nmo:receivedDate(?email) 
nmo:messageSubject(?email) fts:snippet(?email, \"%s\", \"%s\") "
+                                        "WHERE { "
+                                        "  ?email a nmo:Email ;"
+                                        "  fts:match \"%s\" ."
+                                        "} "
+                                        "ORDER BY ASC(nmo:messageSubject(?email)) 
ASC(nmo:receivedDate(?email))"
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT nie:url(?email) nmo:receivedDate(?email) 
nmo:messageSubject(?email) "
+                                        "WHERE { "
+                                        "  ?email a nmo:Email ."
+                                        "} "
+                                        "ORDER BY ASC(nmo:messageSubject(?email)) 
ASC(nmo:receivedDate(?email))"
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_emails_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_files_results (TrackerSparqlConnection *connection,
+                   const gchar             *query,
+                   gint                     search_limit,
+                   gboolean                 details)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No files were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Files"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       if (details) {
+                               g_print ("  %s%s%s (%s)\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        disable_color ? "" : TITLE_END,
+                                        tracker_sparql_cursor_get_string (cursor, 0, NULL));
+
+                               if (tracker_sparql_cursor_get_n_columns (cursor) > 2)
+                                       print_snippet (tracker_sparql_cursor_get_string (cursor, 2, NULL));
+                       } else {
+                               g_print ("  %s%s%s\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        disable_color ? "" : TITLE_END);
+
+                               if (tracker_sparql_cursor_get_n_columns (cursor) > 2)
+                                       print_snippet (tracker_sparql_cursor_get_string (cursor, 2, NULL));
+                       }
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_document_files (TrackerSparqlConnection *connection,
+                    GStrv                    search_terms,
+                    gboolean                 show_all,
+                    gint                     search_offset,
+                    gint                     search_limit,
+                    gboolean                 use_or_operator,
+                    gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+       gboolean success;
+
+       show_all_str = show_all ? "" : "?document tracker:available true .";
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?document nie:url(?document) fts:snippet(?document, \"%s\", 
\"%s\") "
+                                        "WHERE { "
+                                        "  ?document a nfo:Document ;"
+                                        "  fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?document)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?document nie:url(?document) "
+                                        "WHERE { "
+                                        "  ?document a nfo:Document ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?document)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_files_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_video_files (TrackerSparqlConnection *connection,
+                 GStrv                    search_terms,
+                 gboolean                 show_all,
+                 gint                     search_offset,
+                 gint                     search_limit,
+                 gboolean                 use_or_operator,
+                 gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+       gboolean success;
+
+       show_all_str = show_all ? "" : "?video tracker:available true . ";
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?video nie:url(?video) fts:snippet(?video, \"%s\", \"%s\") "
+                                        "WHERE { "
+                                        "  ?video a nfo:Video ;"
+                                        "  fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?video)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?video nie:url(?video) "
+                                        "WHERE { "
+                                        "  ?video a nfo:Video ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?video)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_files_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_image_files (TrackerSparqlConnection *connection,
+                 GStrv                    search_terms,
+                 gboolean                 show_all,
+                 gint                     search_offset,
+                 gint                     search_limit,
+                 gboolean                 use_or_operator,
+                 gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+       gboolean success;
+
+       show_all_str = show_all ? "" : "?image tracker:available true . ";
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?image nie:url(?image) fts:snippet(?image, \"%s\", \"%s\") "
+                                        "WHERE { "
+                                        "  ?image a nfo:Image ;"
+                                        "  fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?image)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?image nie:url(?image) "
+                                        "WHERE { "
+                                        "  ?image a nfo:Image ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?image)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_files_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_music_files (TrackerSparqlConnection *connection,
+                 GStrv                    search_terms,
+                 gboolean                 show_all,
+                 gint                     search_offset,
+                 gint                     search_limit,
+                 gboolean                 use_or_operator,
+                 gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+       gboolean success;
+
+       show_all_str = show_all ? "" : "?song tracker:available true . ";
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?song nie:url(?song) fts:snippet(?song, \"%s\", \"%s\")"
+                                        "WHERE { "
+                                        "  ?song a nmm:MusicPiece ;"
+                                        "  fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?song)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?song nie:url(?song) "
+                                        "WHERE { "
+                                        "  ?song a nmm:MusicPiece ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?song)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_files_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_music_artists (TrackerSparqlConnection *connection,
+                   GStrv                    search_terms,
+                   gint                     search_offset,
+                   gint                     search_limit,
+                   gboolean                 use_or_operator,
+                   gboolean                 details)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?artist ?title "
+                                        "WHERE {"
+                                        "  ?artist a nmm:Artist ;"
+                                        "  nmm:artistName ?title ;"
+                                        "  fts:match \"%s\" . "
+                                        "} "
+                                        "ORDER BY ASC(?title) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?artist ?title "
+                                        "WHERE {"
+                                        "  ?artist a nmm:Artist ;"
+                                        "  nmm:artistName ?title . "
+                                        "} "
+                                        "ORDER BY ASC(?title) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No artists were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Artists"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       if (details) {
+                               g_print ("  '%s%s%s' (%s)\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        disable_color ? "" : TITLE_END,
+                                        tracker_sparql_cursor_get_string (cursor, 0, NULL));
+                       } else {
+                               g_print ("  '%s%s%s'\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        disable_color ? "" : TITLE_END);
+                       }
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_music_albums (TrackerSparqlConnection *connection,
+                  GStrv                    search_words,
+                  gint                     search_offset,
+                  gint                     search_limit,
+                  gboolean                 use_or_operator,
+                  gboolean                 details)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+
+       fts = get_fts_string (search_words, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?album nie:title(?album) "
+                                        "WHERE {"
+                                        "  ?album a nmm:MusicAlbum ;"
+                                        "  fts:match \"%s\" ."
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?album)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?album nie:title(?album) "
+                                        "WHERE {"
+                                        "  ?album a nmm:MusicAlbum ."
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?album)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No music was found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Albums"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       if (details) {
+                               g_print ("  '%s%s%s' (%s)\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        disable_color ? "" : TITLE_END,
+                                        tracker_sparql_cursor_get_string (cursor, 0, NULL));
+                       } else {
+                               g_print ("  '%s%s%s'\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 1, NULL),
+                                        disable_color ? "" : TITLE_END);
+                       }
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_bookmarks (TrackerSparqlConnection *connection,
+               GStrv                    search_terms,
+               gint                     search_offset,
+               gint                     search_limit,
+               gboolean                 use_or_operator)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT nie:title(?urn) nie:url(?bookmark) "
+                                        "WHERE {"
+                                        "  ?urn a nfo:Bookmark ;"
+                                        "       nfo:bookmarks ?bookmark ."
+                                        "  ?urn fts:match \"%s\" . "
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?urn)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT nie:title(?urn) nie:url(?bookmark) "
+                                        "WHERE {"
+                                        "  ?urn a nfo:Bookmark ;"
+                                        "       nfo:bookmarks ?bookmark ."
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?urn)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No bookmarks were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Bookmarks"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s%s%s (%s)\n",
+                                disable_color ? "" : TITLE_BEGIN,
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                disable_color ? "" : TITLE_END,
+                                tracker_sparql_cursor_get_string (cursor, 1, NULL));
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_feeds (TrackerSparqlConnection *connection,
+           GStrv                    search_terms,
+           gint                     search_offset,
+           gint                     search_limit,
+           gboolean                 use_or_operator)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?feed nie:title(?feed) "
+                                        "WHERE {"
+                                        "  ?feed a mfo:FeedMessage ;"
+                                        "  fts:match \"%s\" . "
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?feed)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?feed nie:title(?feed) "
+                                        "WHERE {"
+                                        "  ?feed a mfo:FeedMessage ."
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?feed)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No feeds were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Feeds"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s%s%s (%s)\n",
+                                disable_color ? "" : TITLE_BEGIN,
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                disable_color ? "" : TITLE_END,
+                                tracker_sparql_cursor_get_string (cursor, 1, NULL));
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_software (TrackerSparqlConnection *connection,
+              GStrv                    search_terms,
+              gint                     search_offset,
+              gint                     search_limit,
+              gboolean                 use_or_operator)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?soft nie:title(?soft) fts:snippet(?soft, \"%s\", \"%s\") "
+                                        "WHERE {"
+                                        "  ?soft a nfo:Software ;"
+                                        "  fts:match \"%s\" . "
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?soft)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?soft nie:title(?soft) "
+                                        "WHERE {"
+                                        "  ?soft a nfo:Software ."
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?soft)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No software was found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Software"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s%s%s (%s)\n",
+                                disable_color ? "" : TITLE_BEGIN,
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                disable_color ? "" : TITLE_END,
+                                tracker_sparql_cursor_get_string (cursor, 1, NULL));
+                       print_snippet (tracker_sparql_cursor_get_string (cursor, 2, NULL));
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_software_categories (TrackerSparqlConnection *connection,
+                         GStrv                    search_terms,
+                         gint                     search_offset,
+                         gint                     search_limit,
+                         gboolean                 use_or_operator)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?cat nie:title(?cat) "
+                                        "WHERE {"
+                                        "  ?cat a nfo:SoftwareCategory ;"
+                                        "  fts:match \"%s\" . "
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?cat)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?cat nie:title(?cat) "
+                                        "WHERE {"
+                                        "  ?cat a nfo:SoftwareCategory ."
+                                        "} "
+                                        "ORDER BY ASC(nie:title(?cat)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No software categories were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Software Categories"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s%s%s (%s)\n",
+                                disable_color ? "" : TITLE_BEGIN,
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                disable_color ? "" : TITLE_END,
+                                tracker_sparql_cursor_get_string (cursor, 1, NULL));
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+get_files (TrackerSparqlConnection *connection,
+           GStrv                    search_terms,
+           gboolean                 show_all,
+           gint                     search_offset,
+           gint                     search_limit,
+           gboolean                 use_or_operator,
+           gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+       gboolean success;
+
+       show_all_str = show_all ? "" : "?u tracker:available true . ";
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?u ?url "
+                                        "WHERE { "
+                                        "  ?u a nie:InformationElement ;"
+                                        "  nie:url ?url ;"
+                                        "  fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?u)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?u ?url "
+                                        "WHERE { "
+                                        "  ?u a nie:InformationElement ;"
+                                        "     nie:url ?url ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?u)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_files_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_folders (TrackerSparqlConnection *connection,
+             GStrv                    search_terms,
+             gboolean                 show_all,
+             gint                     search_offset,
+             gint                     search_limit,
+             gboolean                 use_or_operator,
+             gboolean                 details)
+{
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+       gboolean success;
+
+       show_all_str = show_all ? "" : "?u tracker:available true . ";
+       fts = get_fts_string (search_terms, use_or_operator);
+
+       if (fts) {
+               query = g_strdup_printf ("SELECT ?u nie:url(?u) "
+                                        "WHERE { "
+                                        "  ?u a nfo:Folder ;"
+                                        "  fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?u)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT ?u nie:url(?u) "
+                                        "WHERE { "
+                                        "  ?u a nfo:Folder ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY ASC(nie:url(?u)) "
+                                        "OFFSET %d "
+                                        "LIMIT %d",
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       success = get_files_results (connection, query, search_limit, details);
+       g_free (query);
+       g_free (fts);
+
+       return success;
+}
+
+static gboolean
+get_all_by_search (TrackerSparqlConnection *connection,
+                   GStrv                    search_words,
+                   gboolean                 show_all,
+                   gint                     search_offset,
+                   gint                     search_limit,
+                   gboolean                 use_or_operator,
+                   gboolean                 details)
+{
+       GError *error = NULL;
+       TrackerSparqlCursor *cursor;
+       gchar *fts;
+       gchar *query;
+       const gchar *show_all_str;
+
+       fts = get_fts_string (search_words, use_or_operator);
+       if (!fts) {
+               return FALSE;
+       }
+
+       show_all_str = show_all ? "" : "?s tracker:available true . ";
+
+       if (details) {
+               query = g_strdup_printf ("SELECT tracker:coalesce (nie:url (?s), ?s) nie:mimeType (?s) ?type 
fts:snippet(?document, \"%s\", \"%s\") "
+                                        "WHERE {"
+                                        "  ?s fts:match \"%s\" ;"
+                                        "  rdf:type ?type ."
+                                        "  %s"
+                                        "} "
+                                        "GROUP BY nie:url(?s) "
+                                        "ORDER BY nie:url(?s) "
+                                        "OFFSET %d LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       } else {
+               query = g_strdup_printf ("SELECT tracker:coalesce (nie:url (?s), ?s) fts:snippet(?document, 
\"%s\", \"%s\") "
+                                        "WHERE {"
+                                        "  ?s fts:match \"%s\" ."
+                                        "  %s"
+                                        "} "
+                                        "ORDER BY nie:url(?s) "
+                                        "OFFSET %d LIMIT %d",
+                                        disable_color ? "" : SNIPPET_BEGIN,
+                                        disable_color ? "" : SNIPPET_END,
+                                        fts,
+                                        show_all_str,
+                                        search_offset,
+                                        search_limit);
+       }
+
+       g_free (fts);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get search results"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No results were found matching your query"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Results"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       if (details) {
+                               const gchar *urn;
+                               const gchar *mime_type;
+                               const gchar *class;
+
+                               g_print ("cols:%d\n", tracker_sparql_cursor_get_n_columns (cursor));
+
+                               urn = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+                               mime_type = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+                               class = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+
+                               if (mime_type && mime_type[0] == '\0') {
+                                       mime_type = NULL;
+                               }
+
+                               if (mime_type) {
+                                       g_print ("  %s%s%s\n"
+                                                "    %s\n"
+                                                "    %s\n",
+                                                disable_color ? "" : TITLE_BEGIN,
+                                                urn,
+                                                disable_color ? "" : TITLE_END,
+                                                mime_type,
+                                                class);
+                               } else {
+                                       g_print ("  %s%s%s\n"
+                                                "    %s\n",
+                                                disable_color ? "" : TITLE_BEGIN,
+                                                urn,
+                                                disable_color ? "" : TITLE_END,
+                                                class);
+                               }
+                               print_snippet (tracker_sparql_cursor_get_string (cursor, 3, NULL));
+                       } else {
+                               g_print ("  %s%s%s\n",
+                                        disable_color ? "" : TITLE_BEGIN,
+                                        tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                        disable_color ? "" : TITLE_END);
+                               print_snippet (tracker_sparql_cursor_get_string (cursor, 1, NULL));
+                       }
+
+                       count++;
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gint
+search_run (void)
+{
+       TrackerSparqlConnection *connection;
+       GError *error = NULL;
+
+       if (disable_fts) {
+               disable_snippets = TRUE;
+       }
+
+#if HAVE_TRACKER_FTS
+       /* Only check stopwords if FTS is enabled */
+       if (terms) {
+               TrackerLanguage *language;
+               gboolean stop_words_found;
+               gchar **p;
+
+               /* Check terms don't have additional quotes */
+               for (p = terms; *p; p++) {
+                       gchar *term = *p;
+                       gint end = strlen (term) - 1;
+
+                       if ((term[0] == '"' && term[end] == '"') ||
+                           (term[0] == '\'' && term[end] == '\'')) {
+                               /* We never have a quote JUST at the end */
+                               term[0] = term[end] = ' ';
+                               g_strstrip (term);
+                       }
+               }
+
+               /* Check if terms are stopwords, and warn if so */
+               language = tracker_language_new (NULL);
+               stop_words_found = FALSE;
+               for (p = terms; *p; p++) {
+                       gchar *down;
+
+                       down = g_utf8_strdown (*p, -1);
+
+                       if (tracker_language_is_stop_word (language, down)) {
+                               g_printerr (_("Search term “%s” is a stop word."),
+                                           down);
+                               g_printerr ("\n");
+
+                               stop_words_found = TRUE;
+                       }
+
+                       g_free (down);
+               }
+
+               if (stop_words_found) {
+                       g_printerr (_("Stop words are common words which "
+                                     "may be ignored during the indexing "
+                                     "process."));
+                       g_printerr ("\n\n");
+               }
+
+               g_object_unref (language);
+       }
+#else
+       disable_snippets = TRUE;
+#endif
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (!connection) {
+               g_printerr ("%s: %s\n",
+                           _("Could not establish a connection to Tracker"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       if (limit <= 0) {
+               /* Default to 10 for snippets because more is not
+                * useful on the screen. The categories are those not
+                * using snippets yet.
+                */
+               if (disable_snippets || !terms ||
+                   (files || folders || contacts || emails ||
+                    music_albums || music_artists || bookmarks ||
+                    feeds)) {
+                       limit = 512;
+               } else {
+                       limit = 10;
+               }
+       }
+
+       if (files) {
+               gboolean success;
+
+               success = get_files (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (folders) {
+               gboolean success;
+
+               success = get_folders (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (music_albums) {
+               gboolean success;
+
+               success = get_music_albums (connection, terms, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (music_artists) {
+               gboolean success;
+
+               success = get_music_artists (connection, terms, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (music_files) {
+               gboolean success;
+
+               success = get_music_files (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (feeds) {
+               gboolean success;
+
+               success = get_feeds (connection, terms, offset, limit, or_operator);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (image_files) {
+               gboolean success;
+
+               success = get_image_files (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (video_files) {
+               gboolean success;
+
+               success = get_video_files (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (document_files) {
+               gboolean success;
+
+               success = get_document_files (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (emails) {
+               gboolean success;
+
+               success = get_emails (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (contacts) {
+               gboolean success;
+
+               success = get_contacts (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (software) {
+               gboolean success;
+
+               success = get_software (connection, terms, offset, limit, or_operator);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (software_categories) {
+               gboolean success;
+
+               success = get_software_categories (connection, terms, offset, limit, or_operator);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (bookmarks) {
+               gboolean success;
+
+               success = get_bookmarks (connection, terms, offset, limit, or_operator);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (terms) {
+               gboolean success;
+
+               success = get_all_by_search (connection, terms, all, offset, limit, or_operator, detailed);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       g_object_unref (connection);
+
+       /* All known options have their own exit points */
+       g_warn_if_reached ();
+
+       return EXIT_FAILURE;
+}
+
+static int
+search_run_default (void)
+{
+       GOptionContext *context;
+       gchar *help;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+       help = g_option_context_get_help (context, FALSE, NULL);
+       g_option_context_free (context);
+       g_printerr ("%s\n", help);
+       g_free (help);
+
+       return EXIT_FAILURE;
+}
+
+static gboolean
+search_options_enabled (void)
+{
+       return SEARCH_OPTIONS_ENABLED ();
+}
+
+int
+tracker_search (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       argv[0] = "tracker search";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (search_options_enabled ()) {
+               return search_run ();
+       }
+
+       return search_run_default ();
+}
diff --git a/src/tracker/tracker-status.c b/src/tracker/tracker-status.c
new file mode 100644
index 000000000..3b25be10c
--- /dev/null
+++ b/src/tracker/tracker-status.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2006, Jamie McCracken <jamiemcc gnome org>
+ * Copyright (C) 2008, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-sparql/tracker-sparql.h>
+#include <libtracker-control/tracker-control.h>
+
+#include "tracker-status.h"
+#include "tracker-config.h"
+
+#define STATUS_OPTIONS_ENABLED()         \
+       (show_stat || \
+        collect_debug_info)
+
+static gboolean show_stat;
+static gboolean show_all;
+static gboolean collect_debug_info;
+static gchar **terms;
+
+static GHashTable *common_rdf_types;
+
+static GOptionEntry entries[] = {
+       { "stat", 'a', 0, G_OPTION_ARG_NONE, &show_stat,
+         N_("Show statistics for current index / data set"),
+         NULL
+       },
+       { "all", 'a', 0, G_OPTION_ARG_NONE, &show_all,
+         N_("Show statistics about ALL RDF classes, not just common ones which is the default (implied by 
search terms)"),
+         NULL
+       },
+       { "collect-debug-info", 0, 0, G_OPTION_ARG_NONE, &collect_debug_info,
+         N_("Collect debug information useful for problem reporting and investigation, results are output to 
terminal"),
+         NULL },
+       { G_OPTION_REMAINING, 0, 0,
+         G_OPTION_ARG_STRING_ARRAY, &terms,
+         N_("search terms"),
+         N_("EXPRESSION") },
+       { NULL }
+};
+
+static gboolean
+get_common_rdf_types (void)
+{
+       const gchar *extractors_dir, *name;
+       GList *files = NULL, *l;
+       GError *error = NULL;
+       GDir *dir;
+
+       if (common_rdf_types) {
+               return TRUE;
+       }
+
+       extractors_dir = g_getenv ("TRACKER_EXTRACTOR_RULES_DIR");
+       if (G_LIKELY (extractors_dir == NULL)) {
+               extractors_dir = TRACKER_EXTRACTOR_RULES_DIR;
+       }
+
+       dir = g_dir_open (extractors_dir, 0, &error);
+
+       if (!dir) {
+               g_error ("Error opening extractor rules directory: %s", error->message);
+               g_error_free (error);
+               return FALSE;
+       }
+
+       common_rdf_types = g_hash_table_new_full (g_str_hash,
+                                                 g_str_equal,
+                                                 g_free,
+                                                 NULL);
+
+       while ((name = g_dir_read_name (dir)) != NULL) {
+               files = g_list_insert_sorted (files, (gpointer) name, (GCompareFunc) g_strcmp0);
+       }
+
+       for (l = files; l; l = l->next) {
+               GKeyFile *key_file;
+               const gchar *name;
+               gchar *path;
+
+               name = l->data;
+
+               if (!g_str_has_suffix (l->data, ".rule")) {
+                       continue;
+               }
+
+               path = g_build_filename (extractors_dir, name, NULL);
+               key_file = g_key_file_new ();
+
+               g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error);
+
+               if (G_UNLIKELY (error)) {
+                       g_clear_error (&error);
+               } else {
+                       gchar **rdf_types;
+                       gsize n_rdf_types;
+
+                       rdf_types = g_key_file_get_string_list (key_file, "ExtractorRule", 
"FallbackRdfTypes", &n_rdf_types, &error);
+
+                       if (G_UNLIKELY (error)) {
+                               g_clear_error (&error);
+                       } else if (rdf_types != NULL) {
+                               gint i;
+
+                               for (i = 0; i < n_rdf_types; i++) {
+                                       const gchar *rdf_type = rdf_types[i];
+
+                                       g_hash_table_insert (common_rdf_types, g_strdup (rdf_type), 
GINT_TO_POINTER(TRUE));
+                               }
+                       }
+
+                       g_strfreev (rdf_types);
+               }
+
+               g_key_file_free (key_file);
+               g_free (path);
+       }
+
+       g_list_free (files);
+       g_dir_close (dir);
+
+       /* Make sure some additional RDF types are shown which are not
+        * fall backs.
+        */
+       g_hash_table_insert (common_rdf_types, g_strdup ("rdfs:Resource"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("rdfs:Class"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("nfo:FileDataObject"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("nfo:Folder"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("nfo:Executable"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("nco:Contact"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("nao:Tag"), GINT_TO_POINTER(TRUE));
+       g_hash_table_insert (common_rdf_types, g_strdup ("tracker:Volume"), GINT_TO_POINTER(TRUE));
+
+       return TRUE;
+}
+
+static int
+status_stat (void)
+{
+       TrackerSparqlConnection *connection;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (!connection) {
+               g_printerr ("%s: %s\n",
+                           _("Could not establish a connection to Tracker"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       cursor = tracker_sparql_connection_statistics (connection, NULL, &error);
+
+       g_object_unref (connection);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get Tracker statistics"),
+                           error->message);
+               g_error_free (error);
+               return EXIT_FAILURE;
+       }
+
+       /* We use search terms on ALL ontologies not just common ones */
+       if (terms && g_strv_length (terms) > 0) {
+               show_all = TRUE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n", _("No statistics available"));
+       } else {
+               GString *output;
+
+               output = g_string_new ("");
+
+               if (!show_all) {
+                       get_common_rdf_types ();
+               }
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       const gchar *rdf_type;
+                       const gchar *rdf_type_count;
+
+                       rdf_type = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+                       rdf_type_count = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+
+                       if (!show_all && !g_hash_table_contains (common_rdf_types, rdf_type)) {
+                               continue;
+                       }
+
+                       if (terms) {
+                               gint i, n_terms;
+                               gboolean show_rdf_type = FALSE;
+
+                               n_terms = g_strv_length (terms);
+
+                               for (i = 0;
+                                    i < n_terms && !show_rdf_type;
+                                    i++) {
+                                       show_rdf_type = g_str_match_string (terms[i], rdf_type, TRUE);
+                               }
+
+                               if (!show_rdf_type) {
+                                       continue;
+                               }
+                       }
+
+                       g_string_append_printf (output,
+                                               "  %s = %s\n",
+                                               rdf_type,
+                                               rdf_type_count);
+               }
+
+               if (output->len > 0) {
+                       g_string_prepend (output, "\n");
+                       /* To translators: This is to say there are no
+                        * statistics found. We use a "Statistics:
+                        * None" with multiple print statements */
+                       g_string_prepend (output, _("Statistics:"));
+               } else {
+                       g_string_append_printf (output,
+                                               "  %s\n", _("None"));
+               }
+
+               g_print ("%s\n", output->str);
+               g_string_free (output, TRUE);
+
+               if (common_rdf_types) {
+                       g_hash_table_unref (common_rdf_types);
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return EXIT_SUCCESS;
+}
+
+static int
+collect_debug (void)
+{
+       /* What to collect?
+        * This is based on information usually requested from maintainers to users.
+        *
+        * 1. Package details, e.g. version.
+        * 2. Disk size, space left, type (SSD/etc)
+        * 3. Size of dataset (tracker-stats), size of databases
+        * 4. Current configuration (libtracker-fts, tracker-miner-fs, tracker-extract)
+        *    All txt files in ~/.cache/
+        * 5. Statistics about data (tracker-stats)
+        */
+
+       GDir *d;
+       gchar *data_dir;
+       gchar *str;
+
+       data_dir = g_build_filename (g_get_user_cache_dir (), "tracker", NULL);
+
+       /* 1. Package details, e.g. version. */
+       g_print ("[Package Details]\n");
+       g_print ("%s: " PACKAGE_VERSION "\n", _("Version"));
+       g_print ("\n\n");
+
+       /* 2. Disk size, space left, type (SSD/etc) */
+       guint64 remaining_bytes;
+       gdouble remaining;
+
+       g_print ("[%s]\n", _("Disk Information"));
+
+       remaining_bytes = tracker_file_system_get_remaining_space (data_dir);
+       str = g_format_size (remaining_bytes);
+
+       remaining = tracker_file_system_get_remaining_space_percentage (data_dir);
+       g_print ("%s: %s (%3.2lf%%)\n",
+                _("Remaining space on database partition"),
+                str,
+                remaining);
+       g_free (str);
+       g_print ("\n\n");
+
+       /* 3. Size of dataset (tracker-stats), size of databases */
+       g_print ("[%s]\n", _("Data Set"));
+
+       for (d = g_dir_open (data_dir, 0, NULL); d != NULL;) {
+               const gchar *f;
+               gchar *path;
+               goffset size;
+
+               f = g_dir_read_name (d);
+               if (!f) {
+                       break;
+               }
+
+               if (g_str_has_suffix (f, ".txt")) {
+                       continue;
+               }
+
+               path = g_build_filename (data_dir, f, NULL);
+               size = tracker_file_get_size (path);
+               str = g_format_size (size);
+
+               g_print ("%s\n%s\n\n", path, str);
+               g_free (str);
+               g_free (path);
+       }
+       g_dir_close (d);
+       g_print ("\n");
+
+       /* 4. Current configuration (libtracker-fts, tracker-miner-fs, tracker-extract)
+        *    All txt files in ~/.cache/
+        */
+       GSList *all, *l;
+
+       g_print ("[%s]\n", _("Configuration"));
+
+       all = tracker_gsettings_get_all (NULL);
+
+       if (all) {
+               for (l = all; l; l = l->next) {
+                       ComponentGSettings *c = l->data;
+                       gchar **keys, **p;
+
+                       if (!c) {
+                               continue;
+                       }
+
+                       keys = g_settings_schema_list_keys (c->schema);
+                       for (p = keys; p && *p; p++) {
+                               GVariant *v;
+                               gchar *printed;
+
+                               v = g_settings_get_value (c->settings, *p);
+                               printed = g_variant_print (v, FALSE);
+                               g_print ("%s.%s: %s\n", c->name, *p, printed);
+                               g_free (printed);
+                               g_variant_unref (v);
+                       }
+               }
+
+               tracker_gsettings_free (all);
+       } else {
+               g_print ("** %s **\n", _("No configuration was found"));
+       }
+       g_print ("\n\n");
+
+       g_print ("[%s]\n", _("States"));
+
+       for (d = g_dir_open (data_dir, 0, NULL); d != NULL;) {
+               const gchar *f;
+               gchar *path;
+               gchar *content = NULL;
+
+               f = g_dir_read_name (d);
+               if (!f) {
+                       break;
+               }
+
+               if (!g_str_has_suffix (f, ".txt")) {
+                       continue;
+               }
+
+               path = g_build_filename (data_dir, f, NULL);
+               if (g_file_get_contents (path, &content, NULL, NULL)) {
+                       /* Special case last-index.txt which is time() dump to file */
+                       if (g_str_has_suffix (path, "last-crawl.txt")) {
+                               guint64 then, now;
+
+                               now = (guint64) time (NULL);
+                               then = g_ascii_strtoull (content, NULL, 10);
+                               str = tracker_seconds_to_string (now - then, FALSE);
+
+                               g_print ("%s\n%s (%s)\n\n", path, content, str);
+                       } else {
+                               g_print ("%s\n%s\n\n", path, content);
+                       }
+                       g_free (content);
+               }
+               g_free (path);
+       }
+       g_dir_close (d);
+       g_print ("\n");
+
+       /* 5. Statistics about data (tracker-stats) */
+       TrackerSparqlConnection *connection;
+       GError *error = NULL;
+
+       g_print ("[%s]\n", _("Data Statistics"));
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (!connection) {
+               g_print ("** %s, %s **\n",
+                        _("No connection available"),
+                        error ? error->message : _("No error given"));
+               g_clear_error (&error);
+       } else {
+               TrackerSparqlCursor *cursor;
+
+               cursor = tracker_sparql_connection_statistics (connection, NULL, &error);
+
+               if (error) {
+                       g_print ("** %s, %s **\n",
+                                _("Could not get statistics"),
+                                error ? error->message : _("No error given"));
+                       g_error_free (error);
+               } else {
+                       if (!cursor) {
+                               g_print ("** %s **\n",
+                                        _("No statistics were available"));
+                       } else {
+                               gint count = 0;
+
+                               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                                       g_print ("%s: %s\n",
+                                                tracker_sparql_cursor_get_string (cursor, 0, NULL),
+                                                tracker_sparql_cursor_get_string (cursor, 1, NULL));
+                                       count++;
+                               }
+
+                               if (count == 0) {
+                                       g_print ("%s\n",
+                                                _("Database is currently empty"));
+                               }
+
+                               g_object_unref (cursor);
+                       }
+               }
+       }
+
+       g_object_unref (connection);
+       g_print ("\n\n");
+
+       g_print ("\n");
+
+       g_free (data_dir);
+
+       return EXIT_SUCCESS;
+}
+
+static int
+status_run (void)
+{
+       if (show_stat) {
+               return status_stat ();
+       }
+
+       if (collect_debug_info) {
+               return collect_debug ();
+       }
+
+       /* All known options have their own exit points */
+       g_warn_if_reached ();
+
+       return EXIT_FAILURE;
+}
+
+static int
+get_file_and_folder_count (int *files,
+                           int *folders)
+{
+       TrackerSparqlConnection *connection;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (files) {
+               *files = 0;
+       }
+
+       if (folders) {
+               *folders = 0;
+       }
+
+       if (!connection) {
+               g_printerr ("%s: %s\n",
+                           _("Could not establish a connection to Tracker"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       if (files) {
+               const gchar query[] =
+                       "\nSELECT COUNT(?file) "
+                       "\nWHERE { "
+                       "\n  ?file a nfo:FileDataObject ;"
+                       "\n        tracker:available true ."
+                       "\n  FILTER (?file != nfo:Folder) "
+                       "\n}";
+
+               cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+               if (cursor)
+                       tracker_sparql_cursor_next (cursor, NULL, &error);
+
+               if (error) {
+                       g_printerr ("%s, %s\n",
+                                   _("Could not get basic status for Tracker"),
+                                   error->message);
+                       g_error_free (error);
+                       return EXIT_FAILURE;
+               }
+
+               *files = tracker_sparql_cursor_get_integer (cursor, 0);
+
+               g_object_unref (cursor);
+       }
+
+       if (folders) {
+               const gchar query[] =
+                       "\nSELECT COUNT(?folders)"
+                       "\nWHERE { "
+                       "\n  ?folders a nfo:Folder ;"
+                       "\n           tracker:available true ."
+                       "\n}";
+
+               cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+               if (error || !tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_printerr ("%s, %s\n",
+                                   _("Could not get basic status for Tracker"),
+                                   error ? error->message : _("No error given"));
+                       g_error_free (error);
+                       return EXIT_FAILURE;
+               }
+
+               *folders = tracker_sparql_cursor_get_integer (cursor, 0);
+
+               g_object_unref (cursor);
+       }
+
+       g_object_unref (connection);
+
+       return EXIT_SUCCESS;
+}
+
+static gboolean
+are_miners_finished (gint *max_remaining_time)
+{
+       TrackerMinerManager *manager;
+       GError *error = NULL;
+       GSList *miners_running;
+       GSList *l;
+       gboolean finished = TRUE;
+       gint _max_remaining_time = 0;
+
+       /* Don't auto-start the miners here */
+       manager = tracker_miner_manager_new_full (FALSE, &error);
+       if (!manager) {
+               g_printerr (_("Could not get status, manager could not be created, %s"),
+                           error ? error->message : _("No error given"));
+               g_printerr ("\n");
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       miners_running = tracker_miner_manager_get_running (manager);
+
+       for (l = miners_running; l; l = l->next) {
+               gchar *status;
+               gdouble progress;
+               gint remaining_time;
+
+               if (!tracker_miner_manager_get_status (manager,
+                                                      l->data,
+                                                      &status,
+                                                      &progress,
+                                                      &remaining_time)) {
+                       continue;
+               }
+
+               g_free (status);
+
+               finished &= progress == 1.0;
+               _max_remaining_time = MAX(remaining_time, _max_remaining_time);
+       }
+
+       g_slist_foreach (miners_running, (GFunc) g_free, NULL);
+       g_slist_free (miners_running);
+
+       if (max_remaining_time) {
+               *max_remaining_time = _max_remaining_time;
+       }
+
+       g_object_unref (manager);
+
+       return finished;
+}
+
+static int
+get_no_args (void)
+{
+       gchar *str;
+       gchar *data_dir;
+       guint64 remaining_bytes;
+       gdouble remaining;
+       gint remaining_time;
+       gint files, folders;
+
+       /* How many files / folders do we have? */
+       if (get_file_and_folder_count (&files, &folders) != 0) {
+               return EXIT_FAILURE;
+       }
+
+       g_print (_("Currently indexed"));
+       g_print (": ");
+       g_print (g_dngettext (NULL,
+                             "%d file",
+                             "%d files",
+                             files),
+                files);
+       g_print (", ");
+       g_print (g_dngettext (NULL,
+                             "%d folders",
+                             "%d folders",
+                             folders),
+                folders);
+       g_print ("\n");
+
+       /* How much space is left? */
+       data_dir = g_build_filename (g_get_user_cache_dir (), "tracker", NULL);
+
+       remaining_bytes = tracker_file_system_get_remaining_space (data_dir);
+       str = g_format_size (remaining_bytes);
+
+       remaining = tracker_file_system_get_remaining_space_percentage (data_dir);
+       g_print ("%s: %s (%3.2lf%%)\n",
+                _("Remaining space on database partition"),
+                str,
+                remaining);
+       g_free (str);
+       g_free (data_dir);
+
+       /* Are we finished indexing? */
+       if (!are_miners_finished (&remaining_time)) {
+               gchar *remaining_time_str;
+
+               remaining_time_str = tracker_seconds_to_string (remaining_time, TRUE);
+
+               g_print ("%s: ", _("Data is still being indexed"));
+               g_print (_("Estimated %s left"), remaining_time_str);
+               g_print ("\n");
+               g_free (remaining_time_str);
+       } else {
+               g_print ("%s\n", _("All data miners are idle, indexing complete"));
+       }
+
+       g_print ("\n\n");
+
+       return EXIT_SUCCESS;
+}
+
+static int
+status_run_default (void)
+{
+       return get_no_args ();
+}
+
+static gboolean
+status_options_enabled (void)
+{
+       return STATUS_OPTIONS_ENABLED ();
+}
+
+int
+tracker_status (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       argv[0] = "tracker status";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (status_options_enabled ()) {
+               return status_run ();
+       }
+
+       return status_run_default ();
+}
diff --git a/src/tracker/tracker-tag.c b/src/tracker/tracker-tag.c
new file mode 100644
index 000000000..a7160f442
--- /dev/null
+++ b/src/tracker/tracker-tag.c
@@ -0,0 +1,1109 @@
+/*
+ * Copyright (C) 2009, Nokia <ivan frade nokia com>
+ * Copyright (C) 2014, Lanedo <martyn lanedo com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <time.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-tag.h"
+
+#define TAG_OPTIONS_ENABLED() \
+       (resources || \
+        add_tag || \
+        remove_tag || \
+        list)
+
+static gint limit = 512;
+static gint offset;
+static gchar **resources;
+static gboolean and_operator;
+static gchar *add_tag;
+static gchar *remove_tag;
+static gchar *description;
+static gboolean *list;
+static gboolean show_resources;
+
+static GOptionEntry entries[] = {
+       { "list", 't', 0, G_OPTION_ARG_NONE, &list,
+         N_("List all tags (using FILTER if specified; FILTER always uses logical OR)"),
+         N_("FILTER"),
+       },
+       { "show-files", 's', 0, G_OPTION_ARG_NONE, &show_resources,
+         N_("Show files associated with each tag (this is only used with --list)"),
+         NULL
+       },
+       { "add", 'a', 0, G_OPTION_ARG_STRING, &add_tag,
+         N_("Add a tag (if FILEs are omitted, TAG is not associated with any files)"),
+         N_("TAG")
+       },
+       { "delete", 'd', 0, G_OPTION_ARG_STRING, &remove_tag,
+         N_("Delete a tag (if FILEs are omitted, TAG is removed for all files)"),
+         N_("TAG")
+       },
+       { "description", 'e', 0, G_OPTION_ARG_STRING, &description,
+         N_("Description for a tag (this is only used with --add)"),
+         N_("STRING")
+       },
+       { "limit", 'l', 0, G_OPTION_ARG_INT, &limit,
+         N_("Limit the number of results shown"),
+         "512"
+       },
+       { "offset", 'o', 0, G_OPTION_ARG_INT, &offset,
+         N_("Offset the results"),
+         "0"
+       },
+       { "and-operator", 'n', 0, G_OPTION_ARG_NONE, &and_operator,
+         N_("Use AND for search terms instead of OR (the default)"),
+         NULL
+       },
+       { G_OPTION_REMAINING, 0, 0,
+         G_OPTION_ARG_FILENAME_ARRAY, &resources,
+         N_("FILE…"),
+         N_("FILE [FILE…]")},
+       { NULL }
+};
+
+static void
+show_limit_warning (void)
+{
+       /* Display '...' so the user thinks there is
+        * more items.
+        */
+       g_print ("  ...\n");
+
+       /* Display warning so the user knows this is
+        * not the WHOLE data set.
+        */
+       g_printerr ("\n%s\n",
+                   _("NOTE: Limit was reached, there are more items in the database not listed here"));
+}
+
+static gchar *
+get_escaped_sparql_string (const gchar *str)
+{
+       GString *sparql;
+
+       sparql = g_string_new ("");
+       g_string_append_c (sparql, '"');
+
+       while (*str != '\0') {
+               gsize len = strcspn (str, "\t\n\r\"\\");
+               g_string_append_len (sparql, str, len);
+               str += len;
+               switch (*str) {
+               case '\t':
+                       g_string_append (sparql, "\\t");
+                       break;
+               case '\n':
+                       g_string_append (sparql, "\\n");
+                       break;
+               case '\r':
+                       g_string_append (sparql, "\\r");
+                       break;
+               case '"':
+                       g_string_append (sparql, "\\\"");
+                       break;
+               case '\\':
+                       g_string_append (sparql, "\\\\");
+                       break;
+               default:
+                       continue;
+               }
+               str++;
+       }
+
+       g_string_append_c (sparql, '"');
+
+       return g_string_free (sparql, FALSE);
+}
+
+static gchar *
+get_filter_string (GStrv        resources,
+                   const gchar *subject,
+                   gboolean     resources_are_urns,
+                   const gchar *tag)
+{
+       GString *filter;
+       gint i, len;
+
+       if (!resources) {
+               return NULL;
+       }
+
+       len = g_strv_length (resources);
+
+       if (len < 1) {
+               return NULL;
+       }
+
+       filter = g_string_new ("");
+
+       g_string_append_printf (filter, "FILTER (");
+
+       if (tag) {
+               g_string_append (filter, "(");
+       }
+
+       for (i = 0; i < len; i++) {
+               g_string_append_printf (filter, "%s = %s%s%s",
+                                       subject,
+                                       resources_are_urns ? "<" : "\"",
+                                       resources[i],
+                                       resources_are_urns ? ">" : "\"");
+
+               if (i < len - 1) {
+                       g_string_append (filter, " || ");
+               }
+       }
+
+       if (tag) {
+               g_string_append_printf (filter, ") && ?t = <%s>", tag);
+       }
+
+       g_string_append (filter, ")");
+
+       return g_string_free (filter, FALSE);
+}
+
+static GStrv
+get_uris (GStrv resources)
+{
+       GStrv uris;
+       gint len, i;
+
+       if (!resources) {
+               return NULL;
+       }
+
+       len = g_strv_length (resources);
+
+       if (len < 1) {
+               return NULL;
+       }
+
+       uris = g_new0 (gchar *, len + 1);
+
+       for (i = 0; resources[i]; i++) {
+               GFile *file;
+
+               file = g_file_new_for_commandline_arg (resources[i]);
+               uris[i] = g_file_get_uri (file);
+               g_object_unref (file);
+       }
+
+       return uris;
+}
+
+static TrackerSparqlCursor *
+get_file_urns (TrackerSparqlConnection *connection,
+              GStrv                    uris,
+              const gchar             *tag)
+{
+       TrackerSparqlCursor *cursor;
+       gchar *query, *filter;
+       GError *error = NULL;
+
+       filter = get_filter_string (uris, "?f", FALSE, tag);
+       query = g_strdup_printf ("SELECT ?urn ?f "
+                                "WHERE { "
+                                "  ?urn "
+                                "    %s "
+                                "    nie:url ?f . "
+                                "  %s "
+                                "}",
+                                tag ? "nao:hasTag ?t ; " : "",
+                                filter ? filter : "");
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+
+       g_free (query);
+       g_free (filter);
+
+       if (error) {
+               g_print ("    %s, %s\n",
+                        _("Could not get file URNs"),
+                        error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       return cursor;
+}
+
+static GStrv
+result_to_strv (TrackerSparqlCursor *cursor,
+                gint                 n_col)
+{
+       GStrv strv;
+       gint count, i;
+
+       if (!cursor) {
+               return NULL;
+       }
+
+       i = 0;
+       count = 0;
+
+       /* Really no other option here, but we iterate the cursor
+        * first to get the length.
+        */
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               count++;
+       }
+
+       strv = g_new0 (gchar *, count + 1);
+
+       tracker_sparql_cursor_rewind (cursor);
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               const gchar *str;
+
+               str = tracker_sparql_cursor_get_string (cursor, n_col, NULL);
+               strv[i++] = g_strdup (str);
+       }
+
+       return strv;
+}
+
+static void
+get_all_tags_show_tag_id (TrackerSparqlConnection *connection,
+                          const gchar             *id)
+{
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       gchar *query;
+
+       /* Get resources associated */
+       query = g_strdup_printf ("SELECT ?uri WHERE {"
+                                "  ?urn a rdfs:Resource; "
+                                "  nie:url ?uri ; "
+                                "  nao:hasTag \"%s\" . "
+                                "}",
+                                id);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("    %s, %s\n",
+                           _("Could not get files related to tag"),
+                           error->message);
+               g_error_free (error);
+               return;
+       }
+
+       if (!cursor) {
+               /* To translators: This is to say there are no
+                * tags found with a particular unique ID. */
+               g_print ("    %s\n", _("None"));
+               return;
+       }
+
+
+       while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+               g_print ("    %s\n", tracker_sparql_cursor_get_string (cursor, 0, NULL));
+       }
+
+       g_object_unref (cursor);
+}
+
+static inline gchar *
+get_filter_in_for_strv (GStrv        resources,
+                        const gchar *subject)
+{
+       gchar *filter, *filter_in;
+
+       /* e.g. '?label IN ("foo", "bar")' */
+       filter_in = g_strjoinv ("\",\"", resources);
+       filter = g_strdup_printf ("FILTER (%s IN (\"%s\"))", subject, filter_in);
+       g_free (filter_in);
+
+       return filter;
+}
+
+static gboolean
+get_all_resources_with_tags (TrackerSparqlConnection *connection,
+                             GStrv                    tags,
+                             gint                     search_offset,
+                             gint                     search_limit)
+{
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       GStrv tag_urns, p;
+       GString *s;
+       gchar *filter, *query;
+
+       if (!tags) {
+               return FALSE;
+       }
+
+       /* First, get matching tags */
+       filter = get_filter_in_for_strv (tags, "?label");
+       query = g_strdup_printf ("SELECT ?t "
+                                "WHERE { "
+                                "  ?t a nao:Tag ;"
+                                "     nao:prefLabel ?label ."
+                                "  %s"
+                                "}",
+                                filter);
+       g_free (filter);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get all tags in the database"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       tag_urns = result_to_strv (cursor, 0);
+       if (!tag_urns) {
+               g_print ("%s\n",
+                        _("No files have been tagged"));
+
+               if (cursor) {
+                       g_object_unref (cursor);
+               }
+
+               return TRUE;
+       }
+
+       s = g_string_new ("");
+
+       for (p = tag_urns; p && *p; p++) {
+               g_string_append_printf (s, "; nao:hasTag <%s>", *p);
+       }
+
+       s = g_string_append (s, " .");
+       filter = g_string_free (s, FALSE);
+       g_strfreev (tag_urns);
+
+       query = g_strdup_printf ("SELECT DISTINCT nie:url(?r) "
+                                "WHERE {"
+                                "  ?r a rdfs:Resource %s"
+                                "} "
+                                "OFFSET %d "
+                                "LIMIT %d",
+                                filter,
+                                search_offset,
+                                search_limit);
+       g_free (filter);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get files for matching tags"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No files were found matching ALL of those tags"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Files"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s\n",
+                                tracker_sparql_cursor_get_string (cursor, 0, NULL));
+                       count++;
+               }
+
+               if (count == 0) {
+                       /* To translators: This is to say there are no
+                        * files found associated with multiple tags, e.g.:
+                        *
+                        *   Files:
+                        *     None
+                        *
+                        */
+                       g_print ("  %s\n", _("None"));
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+
+static gboolean
+get_all_tags (TrackerSparqlConnection *connection,
+              GStrv                    resources,
+              gint                     search_offset,
+              gint                     search_limit,
+              gboolean                 show_resources)
+{
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       gchar *query;
+       gchar *filter = NULL;
+
+       if (resources && g_strv_length (resources) > 0) {
+               filter = get_filter_in_for_strv (resources, "?label");
+       }
+
+       /* You might be asking, why not logical AND here, why
+        * logical OR for FILTER, well, tags can't have
+        * multiple labels is the simple answer.
+        */
+       query = g_strdup_printf ("SELECT ?tag ?label nao:description(?tag) COUNT(?urns) AS urns "
+                                "WHERE {"
+                                "  ?tag a nao:Tag ;"
+                                "  nao:prefLabel ?label ."
+                                "  OPTIONAL {"
+                                "     ?urns nao:hasTag ?tag"
+                                "  } ."
+                                "  %s"
+                                "} "
+                                "GROUP BY ?tag "
+                                "ORDER BY ASC(?label) "
+                                "OFFSET %d "
+                                "LIMIT %d",
+                                filter ? filter : "",
+                                search_offset,
+                                search_limit);
+       g_free (filter);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get all tags"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("%s\n",
+                        _("No tags were found"));
+       } else {
+               gint count = 0;
+
+               g_print ("%s:\n", _("Tags (shown by name)"));
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       const gchar *id;
+                       const gchar *tag;
+                       const gchar *description;
+                       const gchar *resources;
+                       gint n_resources = 0;
+
+                       id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+                       resources = tracker_sparql_cursor_get_string (cursor, 3, NULL);
+                       n_resources = atoi (resources);
+
+                       tag = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+                       description = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+
+                       if (description && *description == '\0') {
+                               description = NULL;
+                       }
+
+                       g_print ("  %s %s%s%s\n",
+                                tag,
+                                description ? "(" : "",
+                                description ? description : "",
+                                description ? ")" : "");
+
+                       if (show_resources && n_resources > 0) {
+                               get_all_tags_show_tag_id (connection, id);
+                       } else {
+                               g_print ("    %s\n", id);
+                               g_print ("    ");
+                               g_print (g_dngettext (NULL,
+                                                     "%d file",
+                                                     "%d files",
+                                                     n_resources),
+                                        n_resources);
+                               g_print ("\n");
+                       }
+
+                       count++;
+               }
+
+               if (count == 0) {
+                       /* To translators: This is to say there are no
+                        * resources found associated with this tag, e.g.:
+                        *
+                        *   Tags (shown by name):
+                        *     None
+                        *
+                        */
+                       g_print ("  %s\n", _("None"));
+               }
+
+               g_print ("\n");
+
+               if (count >= search_limit) {
+                       show_limit_warning ();
+               }
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static void
+print_file_report (TrackerSparqlCursor *cursor,
+                   GStrv                uris,
+                   const gchar         *found_msg,
+                   const gchar         *not_found_msg)
+{
+       gint i;
+
+       if (!cursor || !uris) {
+               g_print ("  %s\n", _("No files were modified"));
+               return;
+       }
+
+       for (i = 0; uris[i]; i++) {
+               gboolean found = FALSE;
+
+               tracker_sparql_cursor_rewind (cursor);
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       const gchar *str;
+
+                       str = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+
+                       if (g_strcmp0 (str, uris[i]) == 0) {
+                               found = TRUE;
+                               break;
+                       }
+               }
+
+               g_print ("  %s: %s\n",
+                        found ? found_msg : not_found_msg,
+                        uris[i]);
+       }
+}
+
+static gboolean
+add_tag_for_urns (TrackerSparqlConnection *connection,
+                  GStrv                    resources,
+                  const gchar             *tag,
+                  const gchar             *description)
+{
+       TrackerSparqlCursor *cursor = NULL;
+       GError *error = NULL;
+       GStrv  uris = NULL, urns_strv = NULL;
+       gchar *tag_escaped;
+       gchar *query;
+
+       tag_escaped = get_escaped_sparql_string (tag);
+
+       if (resources) {
+               uris = get_uris (resources);
+
+               if (!uris) {
+                       return FALSE;
+               }
+
+               cursor = get_file_urns (connection, uris, NULL);
+
+               if (!cursor) {
+                       g_printerr ("%s\n", _("Files do not exist or aren’t indexed"));
+                       g_strfreev (uris);
+                       return FALSE;
+               }
+
+               urns_strv = result_to_strv (cursor, 0);
+
+               if (!urns_strv || g_strv_length (urns_strv) < 1) {
+                       g_printerr ("%s\n", _("Files do not exist or aren’t indexed"));
+                       g_object_unref (cursor);
+                       g_strfreev (uris);
+                       return FALSE;
+               }
+       }
+
+       if (description) {
+               gchar *description_escaped;
+
+               description_escaped = get_escaped_sparql_string (description);
+
+               query = g_strdup_printf ("INSERT { "
+                                        "  _:tag a nao:Tag;"
+                                        "  nao:prefLabel %s ;"
+                                        "  nao:description %s ."
+                                        "} "
+                                        "WHERE {"
+                                        "  OPTIONAL {"
+                                        "     ?tag a nao:Tag ;"
+                                        "     nao:prefLabel %s ."
+                                        "  } ."
+                                        "  FILTER (!bound(?tag)) "
+                                        "}",
+                                        tag_escaped,
+                                        description_escaped,
+                                        tag_escaped);
+
+               g_free (description_escaped);
+       } else {
+               query = g_strdup_printf ("INSERT { "
+                                        "  _:tag a nao:Tag;"
+                                        "  nao:prefLabel %s ."
+                                        "} "
+                                        "WHERE {"
+                                        "  OPTIONAL {"
+                                        "     ?tag a nao:Tag ;"
+                                        "     nao:prefLabel %s ."
+                                        "  } ."
+                                        "  FILTER (!bound(?tag)) "
+                                        "}",
+                                        tag_escaped,
+                                        tag_escaped);
+       }
+
+       tracker_sparql_connection_update (connection, query, 0, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not add tag"),
+                           error->message);
+
+               if (cursor) {
+                       g_object_unref (cursor);
+               }
+
+               g_error_free (error);
+               g_free (tag_escaped);
+               g_strfreev (urns_strv);
+               g_strfreev (uris);
+
+               return FALSE;
+       }
+
+       g_print ("%s\n",
+                _("Tag was added successfully"));
+
+       /* First we check if the tag is already set and only add if it
+        * is, then we add the urns specified to the new tag.
+        */
+       if (urns_strv) {
+               gchar *filter;
+
+               filter = get_filter_string (urns_strv, "?urn", TRUE, NULL);
+
+               /* Add tag to specific urns */
+               query = g_strdup_printf ("INSERT { "
+                                        "  ?urn nao:hasTag ?id "
+                                        "} "
+                                        "WHERE {"
+                                        "  ?urn nie:url ?f ."
+                                        "  ?id nao:prefLabel %s "
+                                        "  %s "
+                                        "}",
+                                        tag_escaped,
+                                        filter ? filter : "");
+
+               tracker_sparql_connection_update (connection, query, 0, NULL, &error);
+               g_strfreev (urns_strv);
+               g_free (filter);
+               g_free (query);
+
+               if (error) {
+                       g_printerr ("%s, %s\n",
+                                   _("Could not add tag to files"),
+                                   error->message);
+                       g_object_unref (cursor);
+                       g_error_free (error);
+                       g_free (tag_escaped);
+                       g_strfreev (uris);
+
+                       return FALSE;
+               }
+
+               print_file_report (cursor, uris, _("Tagged"),
+                                  _("Not tagged, file is not indexed"));
+       }
+
+       g_strfreev (uris);
+       g_free (tag_escaped);
+
+       if (cursor) {
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+remove_tag_for_urns (TrackerSparqlConnection *connection,
+                     GStrv                    resources,
+                     const gchar             *tag)
+{
+       TrackerSparqlCursor *urns_cursor = NULL;
+       GError *error = NULL;
+       gchar *tag_escaped;
+       gchar *query;
+       GStrv uris;
+
+       tag_escaped = get_escaped_sparql_string (tag);
+       uris = get_uris (resources);
+
+       if (uris && *uris) {
+               TrackerSparqlCursor *tag_cursor;
+               gchar *filter;
+               const gchar *urn;
+               GStrv urns_strv;
+
+               /* Get all tags urns */
+               query = g_strdup_printf ("SELECT ?tag "
+                                        "WHERE {"
+                                        "  ?tag a nao:Tag ."
+                                        "  ?tag nao:prefLabel %s "
+                                        "}",
+                                        tag_escaped);
+
+               tag_cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+               g_free (query);
+
+               if (error) {
+                       g_printerr ("%s, %s\n",
+                                   _("Could not get tag by label"),
+                                   error->message);
+                       g_error_free (error);
+                       g_free (tag_escaped);
+                       g_strfreev (uris);
+
+                       return FALSE;
+               }
+
+               if (!tag_cursor || !tracker_sparql_cursor_next (tag_cursor, NULL, NULL)) {
+                       g_print ("%s\n",
+                                _("No tags were found by that name"));
+
+                       g_free (tag_escaped);
+                       g_strfreev (uris);
+
+                       if (tag_cursor) {
+                               g_object_unref (tag_cursor);
+                       }
+
+                       return TRUE;
+               }
+
+               urn = tracker_sparql_cursor_get_string (tag_cursor, 0, NULL);
+               urns_cursor = get_file_urns (connection, uris, urn);
+
+               if (!urns_cursor || !tracker_sparql_cursor_next (urns_cursor, NULL, NULL)) {
+                       g_print ("%s\n",
+                                _("None of the files had this tag set"));
+
+                       g_strfreev (uris);
+                       g_free (tag_escaped);
+                       g_object_unref (tag_cursor);
+
+                       if (urns_cursor) {
+                               g_object_unref (urns_cursor);
+                       }
+
+                       return TRUE;
+               }
+
+               urns_strv = result_to_strv (urns_cursor, 0);
+               filter = get_filter_string (urns_strv, "?urn", TRUE, urn);
+               g_strfreev (urns_strv);
+
+               query = g_strdup_printf ("DELETE { "
+                                        "  ?urn nao:hasTag ?t "
+                                        "} "
+                                        "WHERE { "
+                                        "  ?urn nao:hasTag ?t . "
+                                        "  %s "
+                                        "}",
+                                        filter ? filter : "");
+               g_free (filter);
+
+               g_object_unref (tag_cursor);
+       } else {
+               /* Remove tag completely */
+               query = g_strdup_printf ("DELETE { "
+                                        "  ?tag a nao:Tag "
+                                        "} "
+                                        "WHERE {"
+                                        "  ?tag nao:prefLabel %s "
+                                        "}",
+                                        tag_escaped);
+       }
+
+       g_free (tag_escaped);
+
+       tracker_sparql_connection_update (connection, query, 0, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not remove tag"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       g_print ("%s\n", _("Tag was removed successfully"));
+
+       if (urns_cursor) {
+               print_file_report (urns_cursor, uris,
+                                  _("Untagged"),
+                                  _("File not indexed or already untagged"));
+               g_object_unref (urns_cursor);
+       }
+
+       g_strfreev (uris);
+
+       return TRUE;
+}
+
+static gboolean
+get_tags_by_file (TrackerSparqlConnection *connection,
+                  const gchar             *uri)
+{
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       gchar *query;
+
+       query = g_strdup_printf ("SELECT ?tags ?labels "
+                                "WHERE {"
+                                "  ?urn nao:hasTag ?tags ;"
+                                "  nie:url <%s> ."
+                                "  ?tags a nao:Tag ;"
+                                "  nao:prefLabel ?labels "
+                                "} "
+                                "ORDER BY ASC(?labels)",
+                                uri);
+
+       cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
+       g_free (query);
+
+       if (error) {
+               g_printerr ("%s, %s\n",
+                           _("Could not get all tags"),
+                           error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       if (!cursor) {
+               g_print ("  %s\n",
+                        _("No tags were found"));
+       } else {
+               gint count = 0;
+
+               while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
+                       g_print ("  %s\n", tracker_sparql_cursor_get_string (cursor, 1, NULL));
+                       count++;
+               }
+
+               if (count == 0) {
+                       /* To translators: This is to say there are no
+                        * tags found for a particular file, e.g.:
+                        *
+                        *   /path/to/some/file:
+                        *     None
+                        *
+                        */
+                       g_print ("  %s\n", _("None"));
+               }
+
+               g_print ("\n");
+
+               g_object_unref (cursor);
+       }
+
+       return TRUE;
+}
+
+static int
+tag_run (void)
+{
+       TrackerSparqlConnection *connection;
+       GError *error = NULL;
+
+       connection = tracker_sparql_connection_get (NULL, &error);
+
+       if (!connection) {
+               g_printerr ("%s: %s\n",
+                           _("Could not establish a connection to Tracker"),
+                           error ? error->message : _("No error given"));
+               g_clear_error (&error);
+               return EXIT_FAILURE;
+       }
+
+       if (list) {
+               gboolean success;
+
+               if (G_UNLIKELY (and_operator)) {
+                       success = get_all_resources_with_tags (connection, resources, offset, limit);
+               } else {
+                       success = get_all_tags (connection, resources, offset, limit, show_resources);
+               }
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (add_tag) {
+               gboolean success;
+
+               success = add_tag_for_urns (connection, resources, add_tag, description);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (remove_tag) {
+               gboolean success;
+
+               success = remove_tag_for_urns (connection, resources, remove_tag);
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       if (resources) {
+               gboolean success = TRUE;
+               gchar **p;
+
+               for (p = resources; *p; p++) {
+                       GFile *file;
+                       gchar *uri;
+                       
+                       file = g_file_new_for_commandline_arg (*p);
+                       uri = g_file_get_uri (file);
+                       g_object_unref (file);
+
+                       g_print ("%s\n", uri);
+                       success &= get_tags_by_file (connection, uri);
+
+                       g_free (uri);
+               }
+
+               g_object_unref (connection);
+
+               return success ? EXIT_SUCCESS : EXIT_FAILURE;
+       }
+
+       g_object_unref (connection);
+
+       /* This is a failure because we should have done something.
+        * This code should never be reached in practise.
+        */
+       return EXIT_FAILURE;
+}
+
+static int
+tag_run_default (void)
+{
+       GOptionContext *context;
+       gchar *help;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+       help = g_option_context_get_help (context, TRUE, NULL);
+       g_option_context_free (context);
+       g_printerr ("%s\n", help);
+       g_free (help);
+
+       return EXIT_FAILURE;
+}
+
+static gboolean
+tag_options_enabled (void)
+{
+       return TAG_OPTIONS_ENABLED ();
+}
+
+int
+tracker_tag (int argc, const char **argv)
+{
+       GOptionContext *context;
+       GError *error = NULL;
+       const gchar *failed;
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       argv[0] = "tracker tag";
+
+       if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
+               g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
+               g_error_free (error);
+               g_option_context_free (context);
+               return EXIT_FAILURE;
+       }
+
+       g_option_context_free (context);
+
+       if (!list && show_resources) {
+               failed = _("The --list option is required for --show-files");
+       } else if (and_operator && (!list || !resources)) {
+               failed = _("The --and-operator option can only be used with --list and tag label arguments");
+       } else if (add_tag && remove_tag) {
+               failed = _("Add and delete actions can not be used together");
+       } else if (description && !add_tag) {
+               failed = _("The --description option can only be used with --add");
+       } else {
+               failed = NULL;
+       }
+
+       if (failed) {
+               g_printerr ("%s\n\n", failed);
+               return EXIT_FAILURE;
+       }
+
+       if (tag_options_enabled ()) {
+               return tag_run ();
+       }
+
+       return tag_run_default ();
+}


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