[tracker-miners/wip/carlosg/cli-split: 2/23] tracker: Restore CLI subcommands relevant to tracker-miners
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [tracker-miners/wip/carlosg/cli-split: 2/23] tracker: Restore CLI subcommands relevant to tracker-miners
- Date: Sun, 29 Dec 2019 21:48:38 +0000 (UTC)
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]