[tracker-miners/sam/index-file-sync: 17/18] cli: Wait for indexing to finish and report status



commit 28fc4a75e035845757ad4938c7a2c281e9c41876
Author: Sam Thursfield <sam afuera me uk>
Date:   Sun Mar 8 19:12:31 2020 +0100

    cli: Wait for indexing to finish and report status
    
    This changes the default behaviour of `tracker index`, which would
    previously tell the tracker-miner-fs to index a file and then exit
    immediately, without giving any feedback to the user about whether
    indexing succeeded or failed.
    
    The default behaviour is now to wait for indexing to complete and
    report any errors which occured. The old behaviour is still available
    with the --no-wait option.
    
    A new --monitor mode is also added, which wraps the IndexFileForProcess
    functionality. It lets users trigger crawling and monitoring of
    locations which aren't configured for automatic indexing.
    
    Fixes https://gitlab.gnome.org/GNOME/tracker/issues/122

 docs/manpages/tracker-index.1                      |  27 +-
 .../tracker-miners-common.h                        |   2 +-
 src/tracker/meson.build                            |   1 +
 src/tracker/tracker-index.c                        | 185 ++++++-
 src/tracker/tracker-indexing-status.c              | 595 +++++++++++++++++++++
 src/tracker/tracker-indexing-status.h              |  49 ++
 src/tracker/tracker-miner-manager.c                | 381 ++++++++-----
 src/tracker/tracker-miner-manager.h                |  71 ++-
 8 files changed, 1120 insertions(+), 191 deletions(-)
---
diff --git a/docs/manpages/tracker-index.1 b/docs/manpages/tracker-index.1
index b8201b7fa..07a9050ad 100644
--- a/docs/manpages/tracker-index.1
+++ b/docs/manpages/tracker-index.1
@@ -5,19 +5,32 @@ tracker-index \- Index content using the Tracker filesystem miner
 
 .SH SYNOPSIS
 .nf
-\fBtracker index\fR <\fIpath\fR> [[\fIpath\fR] ...]
+\fBtracker index\fR <\fIPATH\fR> [[\fIPATH\fR] ...]
+\fBtracker index\fR [\-\-monitor] <\fIDIR\fR> [[\fIDIR\fR] ...]
 .fi
 
 .SH DESCRIPTION
 This command causes the Tracker filesystem miner to process the given
 files or directories.
 
-Usually you should configure the miner to automatically index your
-content, because it will monitor the content locations for changes
-and ensure that the index is kept up to date. Content that is added
-using `tracker index` will not be monitored for changes by default,
-so you can end up with stale data in your search results if you use
-this command.
+Content added using \fBtracker index\fR will not be monitored for changes by
+default, so you can end up with stale data in your search results.
+
+Use \fB\-\-monitor\fR mode to avoid stale data. In this mode, the command will
+not exit until CTRL+C is pressed, and on exit the content will be removed from
+the store again.
+
+It's often more convenient to configure the filesystem miner to index your
+content automatically, rather than using this command.
+
+.SH OPTIONS
+.TP
+.B \-\-no\-wait
+Exit as soon as the files are queued for indexing. Errors during processing
+will not be reported in this mode.
+.TP
+.B \-m, \-\-monitor
+Trigger indexing and wait for CTRL+C, removing the data again on exit.
 
 .SH SEE ALSO
 .BR tracker (1).
diff --git a/src/libtracker-miners-common/tracker-miners-common.h 
b/src/libtracker-miners-common/tracker-miners-common.h
index 01a2ad115..1c605d41e 100644
--- a/src/libtracker-miners-common/tracker-miners-common.h
+++ b/src/libtracker-miners-common/tracker-miners-common.h
@@ -21,7 +21,7 @@
 #define __LIBTRACKER_MINERS_COMMON_H__
 
 #if !defined (__LIBTRACKER_MINERS_COMMON_INSIDE__) && !defined (TRACKER_COMPILATION)
-#error "only <libtracker-miners-common/tracker-common.h> must be included directly."
+#error "only <libtracker-miners-common/tracker-miners-common.h> must be included directly."
 #endif
 
 #include <glib.h>
diff --git a/src/tracker/meson.build b/src/tracker/meson.build
index 1e03cbcf0..e203d46ae 100644
--- a/src/tracker/meson.build
+++ b/src/tracker/meson.build
@@ -9,6 +9,7 @@ modules = [
 ]
 
 common_sources = [
+    'tracker-indexing-status.c',
     'tracker-miner-manager.c',
     'tracker-config.c',
     'tracker-dbus.c',
diff --git a/src/tracker/tracker-index.c b/src/tracker/tracker-index.c
index f0ff904fe..d625a5c23 100644
--- a/src/tracker/tracker-index.c
+++ b/src/tracker/tracker-index.c
@@ -27,6 +27,7 @@
 #endif
 
 #include <glib.h>
+#include <glib-unix.h>
 #include <glib/gi18n.h>
 #include <gio/gio.h>
 #include <locale.h>
@@ -37,62 +38,200 @@
 #include "tracker-miner-manager.h"
 
 static gboolean file_arg;
+static gboolean monitor_mode;
+static gboolean no_wait;
 static gchar **filenames;
 
 static GOptionEntry entries[] = {
        { "file", 'f', 0, G_OPTION_ARG_NONE, &file_arg,
         N_("Does nothing, provided for compatibility with Tracker 2.0"),
         NULL },
+       { "monitor", 'm', 0, G_OPTION_ARG_NONE, &monitor_mode,
+         N_("Trigger indexing and wait for CTRL+C, removing the data again on exit."),
+         NULL },
+       { "no-wait", 'w', 0, G_OPTION_ARG_NONE, &no_wait,
+         N_("Don't wait for processing to complete, exit immediately."),
+         NULL },
        { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
          N_("FILE"),
          N_("FILE") },
        { NULL }
 };
 
+GMainLoop *main_loop;
+
+static void
+print_indexing_status (GFile                 *root,
+                       TrackerIndexingStatus *status)
+{
+       g_autofree gchar *uri = g_file_get_uri (root);
+       g_autoptr(GList) error_list = NULL;
+
+       error_list = tracker_indexing_status_get_errors (status);
+
+       if (tracker_indexing_status_get_n_indexed_files (status) == 1) {
+               if (error_list) {
+                       const gchar *message = error_list->data;
+                       /* TRANSLATORS: %s is a URI. */
+                       g_print (_("File %s could not be indexed:"), uri);
+                       g_print ("%s\n", message);
+               } else {
+                       /* TRANSLATORS: %s is a URI. */
+                       g_print (_("File %s was processed successfully."), uri);
+                       g_print ("\n");
+               }
+       } else {
+               GList *node;
+
+               if (tracker_indexing_status_get_completed (status)) {
+                       /* TRANSLATORS: %s is a URI. */
+                       g_print (_("Indexing of %s completed."), uri);
+                       g_print ("\n");
+               }
+
+               if (error_list) {
+                       for (node = error_list; node; node=node->next) {
+                               const gchar *message = node->data;
+                               g_print ("  * %s\n", message);
+                       }
+               }
+
+               g_print (_("%i files were added to the index."),
+                        tracker_indexing_status_get_n_indexed_files (status));
+               g_print ("\n");
+       }
+
+       g_list_free_full (error_list, g_free);
+}
+
+static void
+index_file_cb (GObject      *source_object,
+               GAsyncResult *res,
+               gpointer      user_data)
+{
+       GMainLoop *loop = user_data;
+
+       tracker_miner_manager_index_file_finish (TRACKER_MINER_MANAGER (source_object), res, NULL);
+
+       g_main_loop_quit (loop);
+}
+
+static gboolean
+g_file_is_directory (GFile *path)
+{
+       g_autoptr(GFileInfo) info = NULL;
+       g_autoptr(GError) error = NULL;
+
+       info = g_file_query_info (path,
+                                 G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error) {
+               g_warning ("Error checking if %s is a directory: %s.",
+                          g_file_peek_path(path), error->message);
+               return FALSE;
+       }
+
+       return (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY);
+}
+
+static gboolean
+sigterm_cb (gpointer user_data)
+{
+       g_message ("Received signal");
+
+       g_main_loop_quit (user_data);
+
+       return G_SOURCE_REMOVE;
+}
+
 static gint
-index_or_reindex_file (void)
+index_run (void)
 {
-       TrackerMinerManager *manager;
-       GError *error = NULL;
+       g_autoptr(TrackerMinerManager) manager;
+       g_autoptr(GMainLoop) main_loop;
+       g_autoptr(GError) error = NULL;
+       gboolean success = TRUE;
        gchar **p;
 
+       /* Check we were only passed directories. IndexFileForProcess doesn't work
+        * for files. */
+       if (monitor_mode) {
+               for (p = filenames; *p; p++) {
+                       g_autoptr(GFile) path = NULL;
+
+                       path = g_file_new_for_commandline_arg (*p);
+
+                       if (!g_file_is_directory (path)) {
+                               g_printerr (_("Could not index file %s: in `--monitor` mode, "
+                                             "only directories can be indexed.\n"),
+                                           g_file_peek_path (path));
+                               return EXIT_FAILURE;
+                       }
+               }
+       }
+
        /* 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;
        }
 
+       main_loop = g_main_loop_new (NULL, 0);
+
        for (p = filenames; *p; p++) {
-               GFile *file;
+               g_autoptr(GFile) file;
+               g_autoptr(TrackerIndexingStatus) status;
 
                file = g_file_new_for_commandline_arg (*p);
-               tracker_miner_manager_index_file (manager, file, NULL, &error);
-
-               if (error) {
-                       g_printerr ("%s: %s\n",
-                                   _("Could not (re)index file"),
-                                   error->message);
-                       g_error_free (error);
-                       return EXIT_FAILURE;
+
+               if (monitor_mode) {
+                       status = tracker_miner_manager_index_file_for_process_async (manager, file, NULL, 
index_file_cb, main_loop);
+               } else {
+                       status = tracker_miner_manager_index_file_async (manager, file, NULL, index_file_cb, 
main_loop);
                }
 
-               g_print ("%s\n", _("(Re)indexing file was successful"));
-               g_object_unref (file);
+               if (no_wait) {
+                       /* We may detect an error straight away, even if we don't wait. */
+                       if (tracker_indexing_status_had_error (status)) {
+                               print_indexing_status (file, status);
+                               success = FALSE;
+                       } else {
+                               g_print ("Successfully enqueued %s for indexing.", *p);
+                               success &= TRUE;
+                       }
+               } else {
+                       /* Run the main loop until the indexing completes, at which point
+                        * index_file_cb() will quit this loop.
+                        */
+                       g_main_loop_run (main_loop);
+
+                       print_indexing_status (file, status);
+
+                       success &= !(tracker_indexing_status_had_error (status));
+               }
        }
 
-       g_object_unref (manager);
+       if (monitor_mode) {
+               g_print (_("Press CTRL+C to exit and remove the files from the index."));
+               g_print ("\n");
 
-       return EXIT_SUCCESS;
-}
+               g_unix_signal_add (SIGINT, sigterm_cb, main_loop);
+               g_unix_signal_add (SIGTERM, sigterm_cb, main_loop);
 
-static int
-index_run (void)
-{
-       return index_or_reindex_file ();
+               g_main_loop_run (main_loop);
+       } else {
+               g_print (_("Files may not be monitored for changes. Use --monitor mode to "
+                          "avoid stale data being left in your Tracker index."));
+               g_print ("\n");
+       };
+
+       return success;
 }
 
 int
@@ -124,6 +263,8 @@ main (int argc, const char **argv)
 
        if (!filenames || g_strv_length (filenames) < 1) {
                failed = _("Please specify one or more locations to index.");
+       } else if (monitor_mode && no_wait) {
+               failed = _("The --monitor-mode and --no-wait options are incompatible");
        } else {
                failed = NULL;
        }
diff --git a/src/tracker/tracker-indexing-status.c b/src/tracker/tracker-indexing-status.c
new file mode 100644
index 000000000..cadc34c74
--- /dev/null
+++ b/src/tracker/tracker-indexing-status.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2020 Sam Thursfield <sam afuera me uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "tracker-indexing-status.h"
+#include "tracker-miner-manager.h"
+
+typedef struct _TrackerIndexingStatus
+{
+       GObject parent_instance;
+} TrackerIndexingStatus;
+
+typedef struct
+{
+       GRWLock lock;
+
+       TrackerMinerManager *manager;
+       gint signal_id;
+       gint timeout_id;
+       gint timeout;
+
+       TrackerSparqlConnection *miner_fs_sparql;
+       TrackerSparqlStatement *stmt;
+       gint queries;
+
+       GFile *root;
+       GTask *task;
+       gboolean root_is_directory;
+
+       gboolean mining_complete;
+       GList *succeeded;
+       GHashTable *failed;
+       GHashTable *to_extract;
+} TrackerIndexingStatusPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (TrackerIndexingStatus, tracker_indexing_status, G_TYPE_OBJECT)
+
+enum {
+       COMPLETE,
+       LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+/**
+ * tracker_indexing_status_new:
+ *
+ * Create a new #TrackerIndexingStatus.
+ *
+ * Returns: (transfer full): a newly created #TrackerIndexingStatus
+ */
+TrackerIndexingStatus *
+tracker_indexing_status_new (GTask *task,
+                             GFile *root)
+{
+       TrackerIndexingStatus *self;
+       TrackerIndexingStatusPrivate *priv;
+
+       self = g_object_new (TRACKER_TYPE_INDEXING_STATUS, NULL);
+
+       priv = tracker_indexing_status_get_instance_private (self);
+       priv->root = g_object_ref (root);
+       priv->task = g_object_ref (task);
+
+       return self;
+}
+
+static void
+tracker_indexing_status_finalize (GObject *object)
+{
+       TrackerIndexingStatus *self = (TrackerIndexingStatus *)object;
+       TrackerIndexingStatusPrivate *priv = tracker_indexing_status_get_instance_private (self);
+
+       g_clear_object (&priv->manager);
+       g_clear_object (&priv->root);
+       g_clear_object (&priv->task);
+
+       g_clear_object (&priv->miner_fs_sparql);
+       g_clear_object (&priv->stmt);
+
+       g_hash_table_unref (priv->failed);
+       g_hash_table_unref (priv->to_extract);
+       g_list_free_full (priv->succeeded, g_object_unref);
+
+       g_rw_lock_clear (&priv->lock);
+
+       G_OBJECT_CLASS (tracker_indexing_status_parent_class)->finalize (object);
+}
+
+static void
+tracker_indexing_status_class_init (TrackerIndexingStatusClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_indexing_status_finalize;
+
+       /**
+        * TrackerIndexingStatus::complete:
+        * @status: the #TrackerIndexingStatus
+        *
+        * The ::complete signal is fired when indexing of the given location
+        * is finished.
+        **/
+       signals [COMPLETE] =
+               g_signal_new ("complete",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 0);
+}
+
+static void
+tracker_indexing_status_init (TrackerIndexingStatus *self)
+{
+       TrackerIndexingStatusPrivate *priv = tracker_indexing_status_get_instance_private (self);
+
+       g_rw_lock_init (&priv->lock);
+
+       priv->manager = NULL;
+       priv->signal_id = 0;
+
+       priv->mining_complete = FALSE;
+       priv->succeeded = NULL;
+       priv->failed = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, g_free);
+       priv->to_extract = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, 
NULL);
+}
+
+static gboolean
+file_is_same_or_child (GFile *root,
+                       GFile *file)
+{
+       gchar *relative_path = NULL;
+       gboolean is_child;
+
+       if (g_file_equal (root, file))
+               return TRUE;
+
+       relative_path = g_file_get_relative_path (root, file);
+
+       if (relative_path != NULL) {
+               is_child = TRUE;
+       } else  {
+               is_child = FALSE;
+       }
+
+       g_free (relative_path);
+
+       return is_child;
+}
+
+static void
+indexing_complete (TrackerIndexingStatus *status)
+{
+       g_signal_emit (status, signals[COMPLETE], 0);
+}
+
+typedef struct {
+       TrackerIndexingStatus *status;
+       GFile *file;
+} CheckWillBeExtractedData;
+
+static CheckWillBeExtractedData *
+check_will_be_extracted_data_new (TrackerIndexingStatus *status,
+                                  GFile                 *file)
+{
+       CheckWillBeExtractedData *data;
+
+       data = g_slice_new0 (CheckWillBeExtractedData);
+       data->status = g_object_ref (status);
+       data->file = g_object_ref (file);
+
+       return data;
+}
+
+static void
+check_will_be_extracted_data_free (CheckWillBeExtractedData *data)
+{
+       g_clear_object (&data->status);
+       g_clear_object (&data->file);
+       g_slice_free (CheckWillBeExtractedData, data);
+}
+
+static void
+check_will_be_extracted_cb (GObject      *source_object,
+                            GAsyncResult *res,
+                            gpointer      user_data)
+{
+       CheckWillBeExtractedData *data = user_data;
+       TrackerIndexingStatusPrivate *priv;
+       TrackerSparqlCursor *cursor;
+       GError *error = NULL;
+       GCancellable *cancellable;
+
+       priv = tracker_indexing_status_get_instance_private (data->status);
+
+       g_rw_lock_writer_lock (&priv->lock);
+
+       cancellable = g_task_get_cancellable (priv->task);
+
+       cursor = tracker_sparql_statement_execute_finish (TRACKER_SPARQL_STATEMENT (source_object), res, 
&error);
+
+       if (!error) {
+               if (tracker_sparql_cursor_next (cursor, cancellable, &error)) {
+                       if (tracker_sparql_cursor_get_boolean (cursor, 0)) {
+                               /* Extractor will process this file, so we must wait for that. */
+                               g_hash_table_insert (priv->to_extract, g_object_ref (data->file), data->file);
+                       } else {
+                               /* Extractor will not process this file. */
+                               priv->succeeded = g_list_prepend (priv->succeeded, g_object_ref (data->file));
+                       }
+               } else if (!error) {
+                       error = g_error_new (TRACKER_MINER_MANAGER_ERROR, 0, "Internal error: ASK query 
returned no result");
+               }
+       }
+
+       if (error) {
+               gchar *message;
+
+               message = g_strdup_printf ("Internal error: %s", error->message);
+               g_hash_table_insert (priv->failed, g_object_ref (data->file), message);
+       }
+
+       g_rw_lock_writer_unlock (&priv->lock);
+
+       priv->queries -= 1;
+
+       check_will_be_extracted_data_free (data);
+}
+
+static void
+check_will_be_extracted (TrackerIndexingStatus *status,
+                         GFile                 *file,
+                         const gchar           *uri)
+{
+       TrackerIndexingStatusPrivate *priv;
+       CheckWillBeExtractedData *data;
+       GCancellable *cancellable;
+       GError *error = NULL;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+       cancellable = g_task_get_cancellable (priv->task);
+
+       if (priv->stmt == NULL) {
+       /* This list must match the supported_classes list declared in tracker-extract-decorator.c */
+               const gchar *query = "ASK { "
+                                    "    ?r nie:url <~url> ; "
+                                    "        a ?type . "
+                                    "    FILTER (?type IN ( "
+                                    "                nfo:Document,nfo:Audio,nfo:Image,nfo:Video,"
+                                    "                
nfo:FilesystemImage,nmm:Playlist,nfo:SoftwareApplication)"
+                                    "    )"
+                                    "}";
+               priv->stmt = tracker_sparql_connection_query_statement (priv->miner_fs_sparql, query, 
cancellable, &error);
+
+               if (error) {
+                       g_critical ("Failed to prepare SPARQL statement: %s", error->message);
+                       g_clear_error (&error);
+               }
+       }
+
+       data = check_will_be_extracted_data_new (status, file);
+
+       tracker_sparql_statement_bind_string (priv->stmt, "url", uri);
+       tracker_sparql_statement_execute_async (priv->stmt,
+                                               g_task_get_cancellable (priv->task),
+                                               check_will_be_extracted_cb,
+                                               data);
+
+       priv->queries += 1;
+}
+
+/* Call this when you already have the read or write lock. */
+static gboolean
+processing_is_completed (TrackerIndexingStatus *status)
+{
+       TrackerIndexingStatusPrivate *priv;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       if (priv->mining_complete && g_hash_table_size (priv->to_extract) == 0 && priv->queries == 0) {
+               return TRUE;
+       } else {
+               return FALSE;
+       }
+}
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+       TrackerIndexingStatus *status = TRACKER_INDEXING_STATUS (user_data);
+       TrackerIndexingStatusPrivate *priv;
+       GHashTableIter iter;
+       GFile *key;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       g_debug ("Indexing timed out. Setting all unextracted files to failed.");
+
+       g_rw_lock_writer_lock (&priv->lock);
+
+       g_signal_handler_disconnect (priv->manager, priv->signal_id);
+       priv->signal_id = 0;
+       priv->timeout_id = 0;
+
+       g_hash_table_iter_init (&iter, priv->to_extract);
+       while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL)) {
+               g_hash_table_insert (priv->failed, g_object_ref (key),
+                                    g_strdup ("Timed out waiting for extractor"));
+               g_hash_table_iter_remove (&iter);
+       }
+
+       g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) indexing_complete, g_object_ref (status), 
g_object_unref);
+
+       g_rw_lock_writer_unlock (&priv->lock);
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+reset_timeout (TrackerIndexingStatus *status)
+{
+       TrackerIndexingStatusPrivate *priv;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       if (priv->timeout_id) {
+               g_source_remove (priv->timeout_id);
+               priv->timeout_id = 0;
+       }
+
+       priv->timeout_id = g_timeout_add_seconds (priv->timeout, timeout_cb, status);
+}
+
+static void
+file_processed_cb (TrackerMinerManager   *miner_manager,
+                   const gchar           *miner,
+                   const gchar           *uri,
+                   const gboolean         success,
+                   const gchar           *message,
+                   TrackerIndexingStatus *status)
+{
+       GFile *file;
+       TrackerIndexingStatusPrivate *priv;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       file = g_file_new_for_uri (uri);
+
+       if (!file_is_same_or_child (priv->root, file)) {
+               g_object_unref (file);
+               return;
+       }
+
+       g_rw_lock_writer_lock (&priv->lock);
+
+       g_main_context_push_thread_default (g_task_get_context (priv->task));
+
+       if (g_str_equal (miner, TRACKER_MINER_FS_DBUS_NAME)) {
+               if (success) {
+                       g_debug ("%s: miner-fs processed successfully", uri);
+
+                       check_will_be_extracted (status, file, uri);
+               } else {
+                       g_debug ("%s: error from miner-fs: %s", uri, message);
+                       g_hash_table_insert (priv->failed, g_object_ref (file), g_strdup (message));
+               }
+
+               /* We require that the miner-fs returns file-processed for the root after all
+                * of its children are complete.
+                */
+               if (g_file_equal (priv->root, file)) {
+                       priv->mining_complete = TRUE;
+               }
+       } else if (g_str_equal (miner, TRACKER_EXTRACT_DBUS_NAME)) {
+               g_hash_table_remove (priv->to_extract, file);
+
+               if (success) {
+                       g_debug ("%s: extractor processed successfully", uri);
+                       priv->succeeded = g_list_prepend (priv->succeeded, g_object_ref (file));
+               } else {
+                       g_debug ("%s: error from miner-fs: %s", uri, message);
+                       g_hash_table_insert (priv->failed, g_object_ref (file), g_strdup (message));
+               }
+       }
+
+       if (processing_is_completed (status)) {
+               g_debug ("Indexing complete");
+
+               g_signal_handler_disconnect (priv->manager, priv->signal_id);
+               g_source_remove (priv->timeout_id);
+
+               priv->signal_id = 0;
+               priv->timeout_id = 0;
+
+               g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) indexing_complete, g_object_ref 
(status), g_object_unref);
+       }
+
+       reset_timeout (status);
+
+       g_main_context_pop_thread_default (g_task_get_context (priv->task));
+
+       g_rw_lock_writer_unlock (&priv->lock);
+
+       g_object_unref (file);
+}
+
+/**
+ * tracker_indexing_status_start_watching:
+ * @status: a #TrackerIndexingStatus instance
+ * @manager: a #TrackerMinerManager instance
+ * @timeout: a timeout value in seconds
+ * @error: return location for a #GError
+ *
+ * Start monitoring an indexing process using the given
+ * #TrackerMinerManager.
+ *
+ * You should not need to call this function directly, as the
+ * tracker_miner_manager_index_file() family of functions will call it for you.
+ *
+ * In order to avoid hanging, the watch will timeout after @timeout seconds
+ * without a signal from any miner process. Pass 0 for the default timeout
+ * value of 10 seconds.
+ */
+void
+tracker_indexing_status_start_watching (TrackerIndexingStatus *status,
+                                        TrackerMinerManager   *manager,
+                                        guint                  timeout,
+                                        GError               **error)
+{
+       TrackerIndexingStatusPrivate *priv;
+       GError *inner_error = NULL;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       g_return_if_fail (priv->manager == NULL);
+
+       g_rw_lock_writer_lock (&priv->lock);
+
+       priv->manager = g_object_ref (manager);
+
+       priv->signal_id = g_signal_connect_data (G_OBJECT (manager),
+                                                "miner-file-processed",
+                                                 G_CALLBACK (file_processed_cb),
+                                                 g_object_ref (status),
+                                                 (GClosureNotify)g_object_unref,
+                                                 0);
+
+       priv->miner_fs_sparql = tracker_sparql_connection_bus_new (TRACKER_MINER_FS_DBUS_NAME,
+                                                                  NULL,
+                                                                  tracker_miner_manager_get_dbus_connection 
(manager),
+                                                                  &inner_error);
+
+       if (inner_error) {
+               g_propagate_error (error, inner_error);
+       }
+
+       priv->timeout = timeout > 0 ? timeout : 10;
+
+       reset_timeout (status);
+
+       g_rw_lock_writer_unlock (&priv->lock);
+}
+
+/**
+ * tracker_indexing_status_get_completed:
+ * @status: a #TrackerIndexingStatus instance
+ *
+ * Returns: %TRUE if indexing has finished, %FALSE otherwise.
+ */
+gboolean
+tracker_indexing_status_get_completed (TrackerIndexingStatus *status)
+{
+       TrackerIndexingStatusPrivate *priv;
+       guint result;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       g_rw_lock_reader_lock (&priv->lock);
+
+       result = processing_is_completed (status);
+
+       g_rw_lock_reader_unlock (&priv->lock);
+
+       return result;
+}
+
+/**
+ * tracker_indexing_status_get_n_indexed_files:
+ * @status: a #TrackerIndexingStatus instance
+ *
+ * Return the number of files which have been successfully processed.
+ *
+ * Returns: a #guint
+ */
+guint
+tracker_indexing_status_get_n_indexed_files (TrackerIndexingStatus *status)
+{
+       TrackerIndexingStatusPrivate *priv;
+       guint result;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       g_rw_lock_reader_lock (&priv->lock);
+
+       result = g_list_length (priv->succeeded);
+
+       g_rw_lock_reader_unlock (&priv->lock);
+
+       return result;
+}
+
+/**
+ * tracker_indexing_status_get_errors:
+ * @status: a #TrackerIndexingStatus instance
+ *
+ * Return all of the errors encountered so far during indexing.
+ * Each string is formatted with the URI and then the error message,
+ * for example:
+ *
+ *     file:///home/sam/Example.mp3: Could not parse file as MP3.
+ *
+ * Returns: (transfer full): a #GList of strings which you must free.
+ */
+GList *
+tracker_indexing_status_get_errors (TrackerIndexingStatus *status)
+{
+       TrackerIndexingStatusPrivate *priv;
+       GHashTableIter iter;
+       GList *result = NULL;
+       GFile *key;
+       const gchar *value;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       g_rw_lock_reader_lock (&priv->lock);
+
+       g_hash_table_iter_init (&iter, priv->failed);
+       while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value)) {
+               gchar *message;
+               gchar *uri;
+
+               uri = g_file_get_uri (key);
+               message = g_strdup_printf ("%s: %s", uri, value);
+
+               result = g_list_prepend (result, message);
+
+               g_free (uri);
+       }
+
+       g_rw_lock_reader_unlock (&priv->lock);
+
+       return result;
+}
+
+/**
+ * tracker_indexing_status_had_error:
+ * @status: a #TrackerIndexingStatus instance
+ *
+ * Returns: %TRUE if any errors have been encountered during indexing, %FALSE otherwise.
+ */
+gboolean
+tracker_indexing_status_had_error (TrackerIndexingStatus *status)
+{
+       TrackerIndexingStatusPrivate *priv;
+       gboolean result;
+
+       priv = tracker_indexing_status_get_instance_private (status);
+
+       g_rw_lock_reader_lock (&priv->lock);
+
+       result = g_hash_table_size (priv->failed) == 0;
+
+       g_rw_lock_reader_unlock (&priv->lock);
+
+       return result;
+}
diff --git a/src/tracker/tracker-indexing-status.h b/src/tracker/tracker-indexing-status.h
new file mode 100644
index 000000000..8e15f1162
--- /dev/null
+++ b/src/tracker/tracker-indexing-status.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 Sam Thursfield <sam afuera me uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_INDEXING_STATUS_H__
+#define __TRACKER_INDEXING_STATUS_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TrackerMinerManager TrackerMinerManager;
+
+#define TRACKER_TYPE_INDEXING_STATUS (tracker_indexing_status_get_type())
+
+G_DECLARE_FINAL_TYPE (TrackerIndexingStatus, tracker_indexing_status, TRACKER, INDEXING_STATUS, GObject)
+
+TrackerIndexingStatus *tracker_indexing_status_new (GTask *task,
+                                                    GFile *root);
+
+void     tracker_indexing_status_start_watching       (TrackerIndexingStatus *status,
+                                                       TrackerMinerManager   *manager,
+                                                       guint                  timeout,
+                                                       GError               **error);
+
+gboolean       tracker_indexing_status_get_completed        (TrackerIndexingStatus *status);
+guint          tracker_indexing_status_get_n_indexed_files  (TrackerIndexingStatus *status);
+GList *        tracker_indexing_status_get_errors           (TrackerIndexingStatus *status);
+gboolean       tracker_indexing_status_had_error            (TrackerIndexingStatus *status);
+
+
+G_END_DECLS
+
+#endif /* __TRACKER_INDEXING_STATUS_H__ */
diff --git a/src/tracker/tracker-miner-manager.c b/src/tracker/tracker-miner-manager.c
index 8e5b2d2c3..ec5064be2 100644
--- a/src/tracker/tracker-miner-manager.c
+++ b/src/tracker/tracker-miner-manager.c
@@ -50,8 +50,6 @@
 #define METHOD_INDEX_FILE "IndexFile"
 #define METHOD_INDEX_FILE_FOR_PROCESS "IndexFileForProcess"
 
-#define TRACKER_MINER_DBUS_INTERFACE "org.freedesktop.Tracker1.Miner"
-
 typedef struct TrackerMinerManagerPrivate TrackerMinerManagerPrivate;
 typedef struct MinerData MinerData;
 
@@ -66,6 +64,7 @@ struct MinerData {
        guint progress_signal;
        guint paused_signal;
        guint resumed_signal;
+       guint file_processed_signal;
        guint watch_name_id;
        GObject *manager; /* weak */
 };
@@ -98,6 +97,8 @@ G_DEFINE_TYPE_WITH_CODE (TrackerMinerManager, tracker_miner_manager, G_TYPE_OBJE
                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                 miner_manager_initable_iface_init));
 
+G_DEFINE_QUARK (tracker-miner-manager-error, tracker_miner_manager_error)
+
 enum {
        PROP_0,
        PROP_AUTO_START,
@@ -110,6 +111,7 @@ enum {
        MINER_RESUMED,
        MINER_ACTIVATED,
        MINER_DEACTIVATED,
+       MINER_FILE_PROCESSED,
        LAST_SIGNAL
 };
 
@@ -242,6 +244,32 @@ tracker_miner_manager_class_init (TrackerMinerManagerClass *klass)
                              NULL,
                              G_TYPE_NONE, 1,
                              G_TYPE_STRING);
+
+       /**
+        * TrackerMinerManager::miner-file-processed:
+        * @manager: the #TrackerMinerManager
+        * @miner: miner reference
+        * @file: URI of the file processed
+        * @status: Successfully indexed the resource.
+        * @message: Error or warning message, if any.
+        *
+        * The ::miner-file-processed signal will be emitted whenever a
+        * miner successfully processes a resource.
+        *
+        * Since: 3.0
+        **/
+       signals [MINER_FILE_PROCESSED] =
+               g_signal_new ("miner-file-processed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (TrackerMinerManagerClass, miner_file_processed),
+                             NULL, NULL,
+                             NULL,
+                             G_TYPE_NONE, 4,
+                             G_TYPE_STRING,
+                             G_TYPE_STRING,
+                             G_TYPE_BOOLEAN,
+                             G_TYPE_STRING);
 }
 
 static void
@@ -389,11 +417,32 @@ miner_resumed (GDBusConnection *connection,
                gpointer         user_data)
 {
        MinerData *data = user_data;
+
        if (data->manager) {
                g_signal_emit (data->manager, signals[MINER_RESUMED], 0, data->dbus_name);
        }
 }
 
+static void
+miner_file_processed (GDBusConnection *connection,
+                      const gchar     *sender_name,
+                      const gchar     *object_path,
+                      const gchar     *interface_name,
+                      const gchar     *signal_name,
+                      GVariant        *parameters,
+                      gpointer         user_data)
+{
+       MinerData *data = user_data;
+       const gchar *url = NULL;
+       gboolean status = FALSE;
+       const gchar *message = NULL;
+
+       g_variant_get (parameters, "(&sbs)", &url, &status, &message);
+       if (data->manager) {
+               g_signal_emit (data->manager, signals[MINER_FILE_PROCESSED], 0, data->dbus_name, url, status, 
message);
+       }
+}
+
 static void
 data_manager_weak_notify (gpointer user_data, GObject *old_object)
 {
@@ -502,6 +551,17 @@ miner_manager_initable_init (GInitable     *initable,
                                                                           data,
                                                                           NULL);
 
+               data->file_processed_signal = g_dbus_connection_signal_subscribe (priv->connection,
+                                                                                 data->dbus_name,
+                                                                                 
TRACKER_MINER_DBUS_INTERFACE,
+                                                                                 "FileProcessed",
+                                                                                 data->dbus_path,
+                                                                                 NULL,
+                                                                                 G_DBUS_SIGNAL_FLAGS_NONE,
+                                                                                 miner_file_processed,
+                                                                                 data,
+                                                                                 NULL);
+
                g_hash_table_insert (priv->miner_proxies, proxy, g_strdup (data->dbus_name));
 
                data->watch_name_id = g_bus_watch_name (TRACKER_IPC_BUS,
@@ -1387,58 +1447,121 @@ tracker_miner_manager_get_description (TrackerMinerManager *manager,
 }
 
 /**
- * tracker_miner_manager_error_quark:
+ * tracker_miner_manager_get_dbus_connection:
+ * @manager: a #TrackerMinerManager
  *
- * Returns: the #GQuark used to identify miner manager errors in
- * GError structures.
+ * Returns the #GDBusConnection.
  *
- * Since: 0.8
+ * Returns: (transfer none): a #GDBusConnection
+ *
+ * Since: 3.0
  **/
-GQuark
-tracker_miner_manager_error_quark (void)
+GDBusConnection *
+tracker_miner_manager_get_dbus_connection (TrackerMinerManager *manager)
 {
-       static GQuark error_quark = 0;
+       TrackerMinerManagerPrivate *priv;
 
-       if (G_UNLIKELY (error_quark == 0)) {
-               error_quark = g_quark_from_static_string ("tracker-miner-manager-error-quark");
-       }
+       priv = tracker_miner_manager_get_instance_private (manager);
 
-       return error_quark;
+       return priv->connection;
 }
 
+typedef struct {
+       GTask *task;
+       TrackerIndexingStatus *status;
 
-static gboolean
-miner_manager_index_file_sync (TrackerMinerManager *manager,
-                               const gchar         *method_name,
-                               GFile               *file,
-                               GCancellable        *cancellable,
-                               GError             **error)
+       GFile *root;
+       gboolean for_process;
+
+       GMainLoop *main_loop;
+} TrackerIndexingTaskData;
+
+static TrackerIndexingTaskData *
+tracker_indexing_task_data_new (GTask                 *task,
+                                TrackerIndexingStatus *status,
+                                GFile                 *root,
+                                gboolean               for_process)
+{
+       TrackerIndexingTaskData *data;
+
+       data = g_slice_new0 (TrackerIndexingTaskData);
+
+       data->status = g_object_ref (status);
+       data->task = g_object_ref (task);
+       data->root = g_object_ref (root);
+       data->for_process = for_process;
+
+       data->main_loop = g_main_loop_new (NULL, 0);
+
+       return data;
+}
+
+static void
+tracker_indexing_task_data_free (TrackerIndexingTaskData *data)
+{
+       g_clear_object (&data->status);
+       g_clear_object (&data->root);
+       g_clear_object (&data->task);
+       g_main_loop_unref (data->main_loop);
+       g_slice_free (TrackerIndexingTaskData, data);
+}
+
+static void
+tracker_indexing_task_complete_cb (TrackerIndexingStatus *status,
+                                   gpointer               user_data)
+{
+       TrackerIndexingTaskData *data = user_data;
+       GError *error;
+
+       if (tracker_indexing_status_had_error (status)) {
+               error = g_error_new (TRACKER_MINER_MANAGER_ERROR,
+                                    TRACKER_MINER_MANAGER_ERROR_INDEXING_ERROR,
+                                    "One or more errors were encountered during indexing.");
+               g_task_return_error (data->task, error);
+       } else {
+               g_task_return_boolean (data->task, TRUE);
+       }
+
+       g_main_loop_quit (data->main_loop);
+}
+
+static void
+tracker_indexing_task_run (GTask        *task,
+                           gpointer      source_object,
+                           gpointer      task_data,
+                           GCancellable *cancellable)
 {
+       TrackerMinerManager *manager = source_object;
+       TrackerIndexingTaskData *data = task_data;
        TrackerMinerManagerPrivate *priv;
        gchar *uri;
        GVariant *v;
-       GError *new_error = NULL;
+       GError *error = NULL;
+       const gchar *method_name;
 
-       if (!g_file_query_exists (file, cancellable)) {
-               g_set_error_literal (error,
-                                    TRACKER_MINER_MANAGER_ERROR,
-                                    TRACKER_MINER_MANAGER_ERROR_NOENT,
-                                    "File or directory does not exist");
-               return FALSE;
-       }
+       priv = tracker_miner_manager_get_instance_private (manager);
+
+       method_name = data->for_process ? METHOD_INDEX_FILE_FOR_PROCESS : METHOD_INDEX_FILE;
 
        if (!tracker_miner_manager_is_active (manager,
                                              "org.freedesktop.Tracker1.Miner.Files")) {
-               g_set_error_literal (error,
-                                    TRACKER_MINER_MANAGER_ERROR,
+               error = g_error_new (TRACKER_MINER_MANAGER_ERROR,
                                     TRACKER_MINER_MANAGER_ERROR_NOT_AVAILABLE,
                                     "Filesystem miner is not active");
-               return FALSE;
+               g_task_return_error (task, error);
+               return;
        }
 
-       priv = tracker_miner_manager_get_instance_private (manager);
+       g_signal_connect (data->status, "complete", G_CALLBACK (tracker_indexing_task_complete_cb), data);
+
+       tracker_indexing_status_start_watching (data->status, manager, 0, &error);
+
+       if (error) {
+               g_task_return_error (task, error);
+               return;
+       }
 
-       uri = g_file_get_uri (file);
+       uri = g_file_get_uri (data->root);
 
        v = g_dbus_connection_call_sync (priv->connection,
                                         "org.freedesktop.Tracker1.Miner.Files",
@@ -1450,105 +1573,118 @@ miner_manager_index_file_sync (TrackerMinerManager *manager,
                                         G_DBUS_CALL_FLAGS_NONE,
                                         -1,
                                         cancellable,
-                                        &new_error);
+                                        &error);
 
        g_free (uri);
 
-       if (new_error) {
-               g_propagate_error (error, new_error);
-               return FALSE;
+       if (error) {
+               g_task_return_error (task, error);
+               return;
        }
 
        g_variant_unref (v);
 
-       return TRUE;
-}
-
-static void
-miner_manager_index_file_thread (GTask *task,
-                                 gpointer source_object,
-                                 gpointer task_data,
-                                 GCancellable *cancellable)
-{
-       TrackerMinerManager *manager = source_object;
-       GFile *file = task_data;
-       GError *error = NULL;
+       g_main_loop_run (data->main_loop);
 
-       miner_manager_index_file_sync (manager, METHOD_INDEX_FILE,
-                                      file, cancellable, &error);
-       if (error != NULL) {
-               g_task_return_error (task, error);
-       } else {
-               g_task_return_boolean (task, TRUE);
-       }
+       return;
 }
 
 /**
  * tracker_miner_manager_index_file:
  * @manager: a #TrackerMinerManager
- * @file: a URL valid in GIO of a file to give to the miner for processing
+ * @file: a #GFile instance of a file or directory
  * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @error: (out callee-allocates) (transfer full) (allow-none): return location for errors
+ * @error: return location for errors
  *
- * Tells the filesystem miner to start indexing the @file.
+ * Instructs Tracker to index the location pointed to by @file, and waits
+ * until indexing is completely finished.
  *
- * On failure @error will be set.
+ * The @error will be set if an error is encountered.
  *
- * Returns: %TRUE on success, otherwise %FALSE.
+ * Returns: (transfer full): a new #TrackerIndexingStatus instance
  *
  * Since: 2.0
  **/
-gboolean
+TrackerIndexingStatus *
 tracker_miner_manager_index_file (TrackerMinerManager  *manager,
                                   GFile                *file,
                                   GCancellable         *cancellable,
                                   GError              **error)
 {
-       g_return_val_if_fail (TRACKER_IS_MINER_MANAGER (manager), FALSE);
-       g_return_val_if_fail (G_IS_FILE (file), FALSE);
-       g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+       GTask *task;
+       TrackerIndexingStatus *status;
+       TrackerIndexingTaskData *data;
+
+       task = g_task_new (manager, cancellable, NULL, NULL);
+
+       status = tracker_indexing_status_new (task, file);
+
+       data = tracker_indexing_task_data_new (task, status, file, FALSE);
+       g_task_set_task_data (task, data, (GDestroyNotify) tracker_indexing_task_data_free);
 
-       return miner_manager_index_file_sync (manager, METHOD_INDEX_FILE,
-                                             file, cancellable, error);
+       g_task_run_in_thread_sync (G_TASK (task), tracker_indexing_task_run);
+
+       /* Result doesn't matter, the data is in the TrackerIndexingStatus object. */
+       g_task_propagate_boolean (G_TASK (task), error);
+
+       return status;
 }
 
 /**
  * tracker_miner_manager_index_file_async:
  * @manager: a #TrackerMinerManager
- * @file: a URL valid in GIO of a file to give to the miner for processing
+ * @file: a #GFile instance of a file or directory
  * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
+ * @callback: (scope async): a #GAsyncReadyCallback to call when indexing is complete.
  * @user_data: the data to pass to the callback function
  *
- * Tells the filesystem miner to start indexing the @file. Once the message has been sent,
- * @callback will be called. You can then call tracker_miner_manager_index_file_finish()
- * to get the result.
+ * Instructs Tracker to index the location pointed to by @file.
+ *
+ * The @callback will be called when the indexing is completed. You can then
+ * call tracker_miner_manager_index_file_finish() to get the result.
+ *
+ * You can monitor the progress of the indexing using the returned
+ * #TrackerIndexingStatus instance.
+ *
+ * Returns: (transfer full): a new #TrackerIndexingStatus instance
  *
  * Since: 0.16
  **/
-void
+TrackerIndexingStatus *
 tracker_miner_manager_index_file_async (TrackerMinerManager *manager,
                                         GFile               *file,
                                         GCancellable        *cancellable,
                                         GAsyncReadyCallback  callback,
                                         gpointer             user_data)
 {
-       GTask *task = g_task_new (manager, cancellable, callback, user_data);
-       g_task_set_task_data (task, g_object_ref (file), (GDestroyNotify) g_object_unref);
-       g_task_run_in_thread (task, miner_manager_index_file_thread);
-       g_object_unref (task);
+       GTask *task;
+       TrackerIndexingStatus *status;
+       TrackerIndexingTaskData *data;
+
+       task = g_task_new (manager, cancellable, callback, user_data);
+
+       status = tracker_indexing_status_new (task, file);
+
+       data = tracker_indexing_task_data_new (task, status, file, FALSE);
+       g_task_set_task_data (task, data, (GDestroyNotify) tracker_indexing_task_data_free);
+
+       g_task_run_in_thread (task, tracker_indexing_task_run);
+
+       return status;
 }
 
 /**
  * tracker_miner_manager_index_file_finish:
  * @manager: a #TrackerMinerManager
  * @result: a #GAsyncResult
- * @error: (out callee-allocates) (transfer full) (allow-none): return location for errors
+ * @error: return location for errors
  *
  * Finishes a request to index a file. See tracker_miner_manager_index_file_async()
  *
  * On failure @error will be set.
  *
+ * The results of indexing are available through the #TrackerIndexingStatus API.
+ *
  * Returns: %TRUE on success, otherwise %FALSE.
  *
  * Since: 0.16
@@ -1561,31 +1697,12 @@ tracker_miner_manager_index_file_finish (TrackerMinerManager *manager,
        return g_task_propagate_boolean (G_TASK (result), error);
 }
 
-static void
-miner_manager_index_file_for_process_thread (GTask        *task,
-                                             gpointer      source_object,
-                                             gpointer      task_data,
-                                             GCancellable *cancellable)
-{
-       TrackerMinerManager *manager = source_object;
-       GFile *file = task_data;
-       GError *error = NULL;
-
-       miner_manager_index_file_sync (manager, METHOD_INDEX_FILE_FOR_PROCESS,
-                                      file, cancellable, &error);
-       if (error != NULL) {
-               g_task_return_error (task, error);
-       } else {
-               g_task_return_boolean (task, TRUE);
-       }
-}
-
 /**
  * tracker_miner_manager_index_file_for_process:
  * @manager: a #TrackerMinerManager
- * @file: a URL valid in GIO of a file to give to the miner for processing
+ * @file: a #GFile instance of a file or directory
  * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @error: (out callee-allocates) (transfer full) (allow-none): return location for errors
+ * @error: return location for errors
  *
  * This function operates exactly the same way as
  * tracker_miner_manager_index_file() with the exception that if the
@@ -1593,33 +1710,43 @@ miner_manager_index_file_for_process_thread (GTask        *task,
  * for cases where the calling process wants to tie the indexing
  * operation closely to its own lifetime.
  *
- * On failure @error will be set.
+ * The @error will be set if an error is encountered while creating the task.
  *
- * Returns: %TRUE on success, otherwise %FALSE.
+ * Returns: (transfer full): a new #TrackerIndexingStatus instance
  *
  * Since: 1.10
  **/
-gboolean
+TrackerIndexingStatus *
 tracker_miner_manager_index_file_for_process (TrackerMinerManager  *manager,
                                               GFile                *file,
                                               GCancellable         *cancellable,
                                               GError              **error)
 {
-       g_return_val_if_fail (TRACKER_IS_MINER_MANAGER (manager), FALSE);
-       g_return_val_if_fail (G_IS_FILE (file), FALSE);
-       g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+       GTask *task;
+       TrackerIndexingStatus *status;
+       TrackerIndexingTaskData *data;
+
+       task = g_task_new (manager, cancellable, NULL, NULL);
+
+       status = tracker_indexing_status_new (task, file);
 
-       return miner_manager_index_file_sync (manager, METHOD_INDEX_FILE_FOR_PROCESS,
-                                             file, cancellable, error);
+       data = tracker_indexing_task_data_new (task, status, file, TRUE);
+       g_task_set_task_data (task, data, (GDestroyNotify) tracker_indexing_task_data_free);
+
+       g_task_run_in_thread_sync (G_TASK (task), tracker_indexing_task_run);
+
+       /* Result doesn't matter, the data is in the TrackerIndexingStatus object. */
+       g_task_propagate_boolean (G_TASK (task), error);
+
+       return status;
 }
 
 /**
  * tracker_miner_manager_index_file_for_process_async:
  * @manager: a #TrackerMinerManager
- * @file: a URL valid in GIO of a file to give to the miner for processing
+ * @file: a #GFile instance of a file or directory
  * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
+ * @callback: (scope async): a #GAsyncReadyCallback to call when indexing is complete.
  * @user_data: the data to pass to the callback function
  *
  * This function operates exactly the same way as
@@ -1628,13 +1755,15 @@ tracker_miner_manager_index_file_for_process (TrackerMinerManager  *manager,
  * for cases where the calling process wants to tie the indexing
  * operation closely to its own lifetime.
  *
- * When the operation is finished, @callback will be called. You can
- * then call tracker_miner_manager_index_file_for_process_finish() to
- * get the result of the operation.
+ * The @callback will be called when the indexing is completed. You can then
+ * call tracker_miner_manager_index_file_finish() to get the result.
+ *
+ * You can monitor the progress of the indexing using the returned
+ * #TrackerIndexingStatus instance.
  *
  * Since: 1.10
  **/
-void
+TrackerIndexingStatus *
 tracker_miner_manager_index_file_for_process_async (TrackerMinerManager *manager,
                                                     GFile               *file,
                                                     GCancellable        *cancellable,
@@ -1642,27 +1771,33 @@ tracker_miner_manager_index_file_for_process_async (TrackerMinerManager *manager
                                                     gpointer             user_data)
 {
        GTask *task;
-
-       g_return_if_fail (TRACKER_IS_MINER_MANAGER (manager));
-       g_return_if_fail (G_IS_FILE (file));
-       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+       TrackerIndexingStatus *status;
+       TrackerIndexingTaskData *data;
 
        task = g_task_new (manager, cancellable, callback, user_data);
-       g_task_set_task_data (task, g_object_ref (file), (GDestroyNotify) g_object_unref);
-       g_task_run_in_thread (task, miner_manager_index_file_for_process_thread);
-       g_object_unref (task);
+
+       status = tracker_indexing_status_new (task, file);
+
+       data = tracker_indexing_task_data_new (task, status, file, TRUE);
+       g_task_set_task_data (task, data, (GDestroyNotify) tracker_indexing_task_data_free);
+
+       g_task_run_in_thread (G_TASK (task), tracker_indexing_task_run);
+
+       return status;
 }
 
 /**
  * tracker_miner_manager_index_file_for_process_finish:
  * @manager: a #TrackerMinerManager
  * @result: a #GAsyncResult
- * @error: (out callee-allocates) (transfer full) (allow-none): return location for errors
+ * @error: return location for errors
  *
  * Finishes a request to index a file. See tracker_miner_manager_index_file_for_process_async()
  *
  * On failure @error will be set.
  *
+ * The results of indexing are available through the #TrackerIndexingStatus API.
+ *
  * Returns: %TRUE on success, otherwise %FALSE.
  *
  * Since: 1.10
@@ -1672,9 +1807,5 @@ tracker_miner_manager_index_file_for_process_finish (TrackerMinerManager  *manag
                                                      GAsyncResult         *result,
                                                      GError              **error)
 {
-       g_return_val_if_fail (TRACKER_IS_MINER_MANAGER (manager), FALSE);
-       g_return_val_if_fail (g_task_is_valid (result, manager), FALSE);;
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
        return g_task_propagate_boolean (G_TASK (result), error);
 }
diff --git a/src/tracker/tracker-miner-manager.h b/src/tracker/tracker-miner-manager.h
index de77b48e0..a9893834d 100644
--- a/src/tracker/tracker-miner-manager.h
+++ b/src/tracker/tracker-miner-manager.h
@@ -26,18 +26,18 @@
 
 #include <gio/gio.h>
 
+#include <tracker-indexing-status.h>
+
 G_BEGIN_DECLS
 
 #define TRACKER_TYPE_MINER_MANAGER         (tracker_miner_manager_get_type())
-#define TRACKER_MINER_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER_MANAGER, 
TrackerMinerManager))
-#define TRACKER_MINER_MANAGER_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    TRACKER_TYPE_MINER_MANAGER, 
TrackerMinerManagerClass))
-#define TRACKER_IS_MINER_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER_MANAGER))
-#define TRACKER_IS_MINER_MANAGER_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    TRACKER_TYPE_MINER_MANAGER))
-#define TRACKER_MINER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  TRACKER_TYPE_MINER_MANAGER, 
TrackerMinerManagerClass))
+G_DECLARE_DERIVABLE_TYPE (TrackerMinerManager, tracker_miner_manager, TRACKER, MINER_MANAGER, GObject)
 
 #define TRACKER_MINER_MANAGER_ERROR tracker_miner_manager_error_quark ()
 
-typedef struct _TrackerMinerManager TrackerMinerManager;
+#define TRACKER_MINER_DBUS_INTERFACE "org.freedesktop.Tracker1.Miner"
+#define TRACKER_MINER_FS_DBUS_NAME "org.freedesktop.Tracker1.Miner.Files"
+#define TRACKER_EXTRACT_DBUS_NAME "org.freedesktop.Tracker1.Miner.Extract"
 
 /**
  * TrackerMinerManagerError:
@@ -45,6 +45,8 @@ typedef struct _TrackerMinerManager TrackerMinerManager;
  * is not active and can so can not be used.
  * @TRACKER_MINER_MANAGER_ERROR_NOENT: The resource that the
  * miner is handling (for example a file or URI) does not exist.
+ * @TRACKER_MINER_MANAGER_ERROR_INDEXING_ERROR: One or more errors
+ * were encountered during indexing.
  *
  * Enumeration values used in errors returned by the
  * #TrackerMinerManager API.
@@ -53,18 +55,10 @@ typedef struct _TrackerMinerManager TrackerMinerManager;
  **/
 typedef enum {
        TRACKER_MINER_MANAGER_ERROR_NOT_AVAILABLE,
-       TRACKER_MINER_MANAGER_ERROR_NOENT
+       TRACKER_MINER_MANAGER_ERROR_NOENT,
+       TRACKER_MINER_MANAGER_ERROR_INDEXING_ERROR
 } TrackerMinerManagerError;
 
-/**
- * TrackerMinerManager:
- *
- * Object to query and control miners.
- **/
-struct _TrackerMinerManager {
-       GObject parent_instance;
-};
-
 /**
  * TrackerMinerManagerClass:
  * @miner_progress: The progress signal for all miners including name,
@@ -75,8 +69,9 @@ struct _TrackerMinerManager {
  * indicates the miner is available on d-bus.
  * @miner_deactivated: The deactivate for all miners which indicates
  * the miner is no longer available on d-bus.
+ * @miner_file_processed: Status update for a single file or unit.
  **/
-typedef struct {
+struct _TrackerMinerManagerClass {
        GObjectClass parent_class;
 
        void (* miner_progress)    (TrackerMinerManager *manager,
@@ -91,7 +86,11 @@ typedef struct {
                                    const gchar         *miner_name);
        void (* miner_deactivated) (TrackerMinerManager *manager,
                                    const gchar         *miner_name);
-} TrackerMinerManagerClass;
+       void (* miner_file_processed) (TrackerMinerManager *manager,
+                                      const gchar         *uri,
+                                      const gboolean      *status,
+                                      const gchar         *message);
+};
 
 GType                tracker_miner_manager_get_type           (void) G_GNUC_CONST;
 GQuark               tracker_miner_manager_error_quark        (void) G_GNUC_CONST;
@@ -127,31 +126,31 @@ const gchar *        tracker_miner_manager_get_display_name   (TrackerMinerManag
                                                                const gchar          *miner);
 const gchar *        tracker_miner_manager_get_description    (TrackerMinerManager  *manager,
                                                                const gchar          *miner);
+GDBusConnection *    tracker_miner_manager_get_dbus_connection (TrackerMinerManager *manager);
 
-gboolean             tracker_miner_manager_index_file          (TrackerMinerManager  *manager,
-                                                                GFile                *file,
+TrackerIndexingStatus * tracker_miner_manager_index_file       (TrackerMinerManager  *manager,               
                                                 GFile                *file,
                                                                 GCancellable         *cancellable,
                                                                 GError              **error);
-void                 tracker_miner_manager_index_file_async    (TrackerMinerManager  *manager,
+TrackerIndexingStatus * tracker_miner_manager_index_file_async (TrackerMinerManager  *manager,
                                                                 GFile                *file,
                                                                 GCancellable         *cancellable,
                                                                 GAsyncReadyCallback   callback,
                                                                 gpointer              user_data);
-gboolean             tracker_miner_manager_index_file_finish   (TrackerMinerManager  *manager,
-                                                                GAsyncResult         *result,
-                                                                GError              **error);
-gboolean             tracker_miner_manager_index_file_for_process        (TrackerMinerManager  *manager,
-                                                                          GFile                *file,
-                                                                          GCancellable         *cancellable,
-                                                                          GError              **error);
-void                 tracker_miner_manager_index_file_for_process_async  (TrackerMinerManager  *manager,
-                                                                          GFile                *file,
-                                                                          GCancellable         *cancellable,
-                                                                          GAsyncReadyCallback   callback,
-                                                                          gpointer              user_data);
-gboolean             tracker_miner_manager_index_file_for_process_finish (TrackerMinerManager  *manager,
-                                                                          GAsyncResult         *result,
-                                                                          GError              **error);
+gboolean                tracker_miner_manager_index_file_finish        (TrackerMinerManager  *manager,
+                                                                        GAsyncResult         *result,
+                                                                        GError              **error);
+TrackerIndexingStatus * tracker_miner_manager_index_file_for_process   (TrackerMinerManager  *manager,
+                                                                        GFile                *file,
+                                                                        GCancellable         *cancellable,
+                                                                        GError              **error);
+TrackerIndexingStatus * tracker_miner_manager_index_file_for_process_async  (TrackerMinerManager  *manager,
+                                                                             GFile                *file,
+                                                                             GCancellable         
*cancellable,
+                                                                             GAsyncReadyCallback   callback,
+                                                                             gpointer              
user_data);
+gboolean                tracker_miner_manager_index_file_for_process_finish (TrackerMinerManager  *manager,
+                                                                             GAsyncResult         *result,
+                                                                             GError              **error);
 
 G_END_DECLS
 


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