[latexila/wip/latexila-next: 36/55] Build tools: implement the run operations



commit e3fa550912c62f824b2cec22b4e134dc6adde4b9
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu May 8 22:08:47 2014 +0200

    Build tools: implement the run operations
    
    And adds a str_replace() utils function.

 docs/reference/latexila-sections.txt  |    6 +-
 src/liblatexila/latexila-build-job.c  |  380 +++++++++++++++++++++++++++++++++
 src/liblatexila/latexila-build-job.h  |   12 +-
 src/liblatexila/latexila-build-tool.c |  358 ++++++++++++++++++++++++++++++-
 src/liblatexila/latexila-build-tool.h |   12 +-
 src/liblatexila/latexila-build-view.c |    9 +-
 src/liblatexila/latexila-utils.c      |   36 +++
 src/liblatexila/latexila-utils.h      |    4 +
 src/main_window_build_tools.vala      |   22 +-
 tests/test-utils.c                    |   23 ++
 10 files changed, 834 insertions(+), 28 deletions(-)
---
diff --git a/docs/reference/latexila-sections.txt b/docs/reference/latexila-sections.txt
index 8d1703a..6730ec5 100644
--- a/docs/reference/latexila-sections.txt
+++ b/docs/reference/latexila-sections.txt
@@ -67,7 +67,8 @@ latexila_build_tool_add_job
 latexila_build_tool_get_jobs
 latexila_build_tool_get_description
 latexila_build_tool_to_xml
-latexila_build_tool_run
+latexila_build_tool_run_async
+latexila_build_tool_run_finish
 <SUBSECTION Standard>
 LATEXILA_BUILD_TOOL
 LATEXILA_BUILD_TOOL_CLASS
@@ -87,6 +88,8 @@ LatexilaBuildJob
 latexila_build_job_new
 latexila_build_job_clone
 latexila_build_job_to_xml
+latexila_build_job_run_async
+latexila_build_job_run_finish
 <SUBSECTION Standard>
 LATEXILA_BUILD_JOB
 LATEXILA_BUILD_JOB_CLASS
@@ -133,4 +136,5 @@ latexila_build_view_get_type
 latexila_utils_get_shortname
 latexila_utils_replace_home_dir_with_tilde
 latexila_utils_register_icons
+latexila_utils_str_replace
 </SECTION>
diff --git a/src/liblatexila/latexila-build-job.c b/src/liblatexila/latexila-build-job.c
index f6d458b..bd35801 100644
--- a/src/liblatexila/latexila-build-job.c
+++ b/src/liblatexila/latexila-build-job.c
@@ -26,12 +26,23 @@
  */
 
 #include "latexila-build-job.h"
+#include <string.h>
+#include <glib/gi18n.h>
+#include "latexila-build-view.h"
+#include "latexila-utils.h"
 #include "latexila-enum-types.h"
 
 struct _LatexilaBuildJobPrivate
 {
   gchar *command;
   LatexilaPostProcessorType post_processor_type;
+
+  /* Used for running the build job. */
+  GTask *task;
+  GFile *file;
+  LatexilaBuildView *build_view;
+  GtkTreeIter job_title;
+  GNode *build_messages;
 };
 
 enum
@@ -43,6 +54,30 @@ enum
 
 G_DEFINE_TYPE_WITH_PRIVATE (LatexilaBuildJob, latexila_build_job, G_TYPE_OBJECT)
 
+static gboolean
+free_build_msg (GNode    *node,
+                gpointer  user_data)
+{
+  latexila_build_msg_free (node->data);
+  return FALSE;
+}
+
+static void
+free_build_messages (GNode *build_messages)
+{
+  if (build_messages != NULL)
+    {
+      g_node_traverse (build_messages,
+                       G_POST_ORDER,
+                       G_TRAVERSE_ALL,
+                       -1,
+                       free_build_msg,
+                       NULL);
+
+      g_node_destroy (build_messages);
+    }
+}
+
 static void
 latexila_build_job_get_property (GObject    *object,
                                  guint       prop_id,
@@ -75,6 +110,9 @@ latexila_build_job_set_property (GObject      *object,
 {
   LatexilaBuildJob *build_job = LATEXILA_BUILD_JOB (object);
 
+  /* The build job can not be modified when it is running. */
+  g_return_if_fail (build_job->priv->task == NULL);
+
   switch (prop_id)
     {
     case PROP_COMMAND:
@@ -95,6 +133,11 @@ latexila_build_job_set_property (GObject      *object,
 static void
 latexila_build_job_dispose (GObject *object)
 {
+  LatexilaBuildJob *build_job = LATEXILA_BUILD_JOB (object);
+
+  g_clear_object (&build_job->priv->task);
+  g_clear_object (&build_job->priv->file);
+  g_clear_object (&build_job->priv->build_view);
 
   G_OBJECT_CLASS (latexila_build_job_parent_class)->dispose (object);
 }
@@ -105,6 +148,7 @@ latexila_build_job_finalize (GObject *object)
   LatexilaBuildJob *build_job = LATEXILA_BUILD_JOB (object);
 
   g_free (build_job->priv->command);
+  free_build_messages (build_job->priv->build_messages);
 
   G_OBJECT_CLASS (latexila_build_job_parent_class)->finalize (object);
 }
@@ -192,3 +236,339 @@ latexila_build_job_to_xml (LatexilaBuildJob *build_job)
                                   latexila_post_processor_get_name_from_type 
(build_job->priv->post_processor_type),
                                   build_job->priv->command != NULL ? build_job->priv->command : "");
 }
+
+static gchar **
+get_command_argv (LatexilaBuildJob  *build_job,
+                  gboolean           for_printing,
+                  GError           **error)
+{
+  gchar **argv;
+  gchar *base_filename;
+  gchar *base_shortname;
+  gint i;
+
+  /* Separate arguments */
+  if (!g_shell_parse_argv (build_job->priv->command, NULL, &argv, error) ||
+      argv == NULL)
+    {
+      return NULL;
+    }
+
+  /* Re-add quotes if needed */
+  if (for_printing)
+    {
+      for (i = 0; argv[i] != NULL; i++)
+        {
+          /* If the argument contains a space, add the quotes. */
+          if (strchr (argv[i], ' ') != NULL)
+            {
+              gchar *new_arg = g_strdup_printf ("\"%s\"", argv[i]);
+              g_free (argv[i]);
+              argv[i] = new_arg;
+            }
+        }
+    }
+
+  /* Replace placeholders */
+  base_filename = g_file_get_basename (build_job->priv->file);
+  base_shortname = latexila_utils_get_shortname (base_filename);
+
+  for (i = 0; argv[i] != NULL; i++)
+    {
+      gchar *new_arg = NULL;
+
+      if (strstr (argv[i], "$filename") != NULL)
+        {
+          new_arg = latexila_utils_str_replace (argv[i], "$filename", base_filename);
+        }
+      else if (strstr (argv[i], "$shortname"))
+        {
+          new_arg = latexila_utils_str_replace (argv[i], "$shortname", base_shortname);
+        }
+      else if (strstr (argv[i], "$view"))
+        {
+          g_warning ("Build job: the '$view' placeholder is deprecated.");
+          new_arg = latexila_utils_str_replace (argv[i], "$view", "xdg-open");
+        }
+
+      if (new_arg != NULL)
+        {
+          g_free (argv[i]);
+          argv[i] = new_arg;
+        }
+    }
+
+  g_free (base_filename);
+  g_free (base_shortname);
+  return argv;
+}
+
+static gchar *
+get_command_name (LatexilaBuildJob *build_job)
+{
+  gchar **argv;
+  gchar *command_name;
+
+  argv = get_command_argv (build_job, TRUE, NULL);
+
+  if (argv == NULL || argv[0] == NULL || argv[0][0] == '\0')
+    {
+      command_name = NULL;
+    }
+  else
+    {
+      command_name = g_strdup (argv[0]);
+    }
+
+  g_strfreev (argv);
+  return command_name;
+}
+
+static void
+display_error (LatexilaBuildJob *build_job,
+               const gchar      *message,
+               GError           *error)
+{
+  LatexilaBuildMsg *build_msg;
+
+  g_assert (error != NULL);
+
+  latexila_build_view_set_title_state (build_job->priv->build_view,
+                                       &build_job->priv->job_title,
+                                       LATEXILA_BUILD_STATE_FAILED);
+
+  build_msg = latexila_build_msg_new ();
+  build_msg->text = (gchar *) message;
+  build_msg->type = LATEXILA_BUILD_MSG_TYPE_ERROR;
+  latexila_build_view_append_single_message (build_job->priv->build_view,
+                                             &build_job->priv->job_title,
+                                             build_msg);
+
+  build_msg->text = g_strdup (error->message);
+  build_msg->type = LATEXILA_BUILD_MSG_TYPE_INFO;
+  latexila_build_view_append_single_message (build_job->priv->build_view,
+                                             &build_job->priv->job_title,
+                                             build_msg);
+
+  /* If the command doesn't seem to be installed, display a more understandable
+   * message.
+   */
+  if (error->domain == G_SPAWN_ERROR &&
+      error->code == G_SPAWN_ERROR_NOENT)
+    {
+      gchar *command_name = get_command_name (build_job);
+
+      if (command_name != NULL)
+        {
+          g_free (build_msg->text);
+          build_msg->text = g_strdup_printf (_("%s doesn't seem to be installed."), command_name);
+
+          latexila_build_view_append_single_message (build_job->priv->build_view,
+                                                     &build_job->priv->job_title,
+                                                     build_msg);
+
+          g_free (command_name);
+        }
+    }
+
+  g_error_free (error);
+  latexila_build_msg_free (build_msg);
+  g_task_return_boolean (build_job->priv->task, FALSE);
+}
+
+/* Returns TRUE on success. */
+static gboolean
+display_command_line (LatexilaBuildJob *build_job)
+{
+  gchar **argv;
+  gchar *command_line;
+  GError *error = NULL;
+
+  argv = get_command_argv (build_job, TRUE, &error);
+
+  if (error != NULL)
+    {
+      build_job->priv->job_title = latexila_build_view_add_job_title (build_job->priv->build_view,
+                                                                      build_job->priv->command,
+                                                                      LATEXILA_BUILD_STATE_FAILED);
+
+      display_error (build_job, "Failed to parse command line:", error);
+      return FALSE;
+    }
+
+  command_line = g_strjoinv (" ", argv);
+
+  build_job->priv->job_title = latexila_build_view_add_job_title (build_job->priv->build_view,
+                                                                  command_line,
+                                                                  LATEXILA_BUILD_STATE_RUNNING);
+
+  g_strfreev (argv);
+  g_free (command_line);
+  return TRUE;
+}
+
+static void
+subprocess_wait_cb (GSubprocess      *subprocess,
+                    GAsyncResult     *result,
+                    LatexilaBuildJob *build_job)
+{
+  LatexilaBuildMsg *msg;
+  gboolean ret;
+  LatexilaBuildState state;
+
+  ret = g_subprocess_wait_finish (subprocess, result, NULL);
+
+  if (!ret)
+    {
+      state = LATEXILA_BUILD_STATE_ABORTED;
+      g_subprocess_force_exit (subprocess);
+    }
+  else if (g_subprocess_get_successful (subprocess))
+    {
+      state = LATEXILA_BUILD_STATE_SUCCEEDED;
+    }
+  else
+    {
+      ret = FALSE;
+      state = LATEXILA_BUILD_STATE_FAILED;
+    }
+
+  msg = latexila_build_msg_new ();
+  msg->text = g_strdup ("build job output");
+  msg->type = LATEXILA_BUILD_MSG_TYPE_INFO;
+
+  latexila_build_view_append_single_message (build_job->priv->build_view,
+                                             &build_job->priv->job_title,
+                                             msg);
+
+  latexila_build_view_set_title_state (build_job->priv->build_view,
+                                       &build_job->priv->job_title,
+                                       state);
+
+  g_task_return_boolean (build_job->priv->task, ret);
+
+  latexila_build_msg_free (msg);
+  g_object_unref (subprocess);
+}
+
+static void
+launch_subprocess (LatexilaBuildJob *build_job)
+{
+  GSubprocessLauncher *launcher;
+  GSubprocess *subprocess;
+  GFile *parent_dir;
+  gchar *working_directory;
+  gchar **argv;
+  GError *error = NULL;
+
+  /* No output for the moment */
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE |
+                                        G_SUBPROCESS_FLAGS_STDERR_SILENCE);
+
+  parent_dir = g_file_get_parent (build_job->priv->file);
+  working_directory = g_file_get_path (parent_dir);
+  g_object_unref (parent_dir);
+
+  g_subprocess_launcher_set_cwd (launcher, working_directory);
+  g_free (working_directory);
+
+  /* The error is already catched in display_command_line(). */
+  argv = get_command_argv (build_job, FALSE, NULL);
+
+  subprocess = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) argv, &error);
+  g_strfreev (argv);
+  g_object_unref (launcher);
+
+  if (error != NULL)
+    {
+      display_error (build_job, "Failed to launch command:", error);
+      return;
+    }
+
+  g_subprocess_wait_async (subprocess,
+                           g_task_get_cancellable (build_job->priv->task),
+                           (GAsyncReadyCallback) subprocess_wait_cb,
+                           build_job);
+}
+
+/**
+ * latexila_build_job_run_async:
+ * @build_job: a build job.
+ * @file: a file.
+ * @build_view: a build view.
+ * @cancellable: a #GCancellable object.
+ * @callback: the callback to call when the operation is finished.
+ * @user_data: the data to pass to the callback function.
+ *
+ * Runs asynchronously the build job on a file with the messages displayed in a
+ * build view. When the operation is finished, @callback will be called. You can
+ * then call latexila_build_job_run_finish().
+ */
+void
+latexila_build_job_run_async (LatexilaBuildJob    *build_job,
+                              GFile               *file,
+                              LatexilaBuildView   *build_view,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  g_return_if_fail (LATEXILA_IS_BUILD_JOB (build_job));
+  g_return_if_fail (G_IS_FILE (file));
+  g_return_if_fail (LATEXILA_IS_BUILD_VIEW (build_view));
+  g_return_if_fail (build_job->priv->task == NULL);
+
+  build_job->priv->task = g_task_new (build_job, cancellable, callback, user_data);
+
+  g_clear_object (&build_job->priv->file);
+  build_job->priv->file = g_object_ref (file);
+
+  g_clear_object (&build_job->priv->build_view);
+  build_job->priv->build_view = g_object_ref (build_view);
+
+  free_build_messages (build_job->priv->build_messages);
+
+  if (!display_command_line (build_job))
+    {
+      return;
+    }
+
+  if (!g_task_return_error_if_cancelled (build_job->priv->task))
+    {
+      launch_subprocess (build_job);
+    }
+}
+
+/**
+ * latexila_build_job_run_finish:
+ * @build_job: a build job.
+ * @result: a #GAsyncResult.
+ *
+ * Finishes the operation started with latexila_build_job_run_async().
+ *
+ * Returns: %TRUE if the build job has run successfully.
+ */
+gboolean
+latexila_build_job_run_finish (LatexilaBuildJob *build_job,
+                               GAsyncResult     *result)
+{
+  GCancellable *cancellable;
+  gboolean succeed;
+
+  g_return_if_fail (g_task_is_valid (result, build_job));
+
+  cancellable = g_task_get_cancellable (G_TASK (result));
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      latexila_build_view_set_title_state (build_job->priv->build_view,
+                                           &build_job->priv->job_title,
+                                           LATEXILA_BUILD_STATE_ABORTED);
+      succeed = FALSE;
+    }
+  else
+    {
+      succeed = g_task_propagate_boolean (G_TASK (result), NULL);
+    }
+
+  g_clear_object (&build_job->priv->task);
+  return succeed;
+}
diff --git a/src/liblatexila/latexila-build-job.h b/src/liblatexila/latexila-build-job.h
index d09da3d..f4bef9b 100644
--- a/src/liblatexila/latexila-build-job.h
+++ b/src/liblatexila/latexila-build-job.h
@@ -20,7 +20,7 @@
 #ifndef __LATEXILA_BUILD_JOB_H__
 #define __LATEXILA_BUILD_JOB_H__
 
-#include <glib-object.h>
+#include <gio/gio.h>
 #include "latexila-types.h"
 #include "latexila-post-processor.h"
 
@@ -56,6 +56,16 @@ LatexilaBuildJob *  latexila_build_job_clone                      (LatexilaBuild
 
 gchar *             latexila_build_job_to_xml                     (LatexilaBuildJob *build_job);
 
+void                latexila_build_job_run_async                  (LatexilaBuildJob    *build_job,
+                                                                   GFile               *file,
+                                                                   LatexilaBuildView   *build_view,
+                                                                   GCancellable        *cancellable,
+                                                                   GAsyncReadyCallback  callback,
+                                                                   gpointer             user_data);
+
+gboolean            latexila_build_job_run_finish                 (LatexilaBuildJob *build_job,
+                                                                   GAsyncResult     *result);
+
 G_END_DECLS
 
 #endif /* __LATEXILA_BUILD_JOB_H__ */
diff --git a/src/liblatexila/latexila-build-tool.c b/src/liblatexila/latexila-build-tool.c
index 67b393a..f954e68 100644
--- a/src/liblatexila/latexila-build-tool.c
+++ b/src/liblatexila/latexila-build-tool.c
@@ -29,8 +29,11 @@
  */
 
 #include "latexila-build-tool.h"
+#include <string.h>
+#include <glib/gi18n.h>
 #include "latexila-build-job.h"
 #include "latexila-build-view.h"
+#include "latexila-utils.h"
 
 struct _LatexilaBuildToolPrivate
 {
@@ -44,6 +47,17 @@ struct _LatexilaBuildToolPrivate
   /* A list of LatexilaBuildJob's. */
   GQueue *jobs;
 
+  /* Used for running the build tool. */
+  GTask *task;
+  GFile *file;
+  LatexilaBuildView *build_view;
+  GtkTreeIter main_title;
+  GList *current_job;
+
+  gchar **files_to_open_split;
+  gchar **current_file_to_open; /* Position in files_to_open_split */
+  GtkTreeIter open_file_job_title;
+
   guint enabled : 1;
 };
 
@@ -61,6 +75,10 @@ enum
 
 G_DEFINE_TYPE_WITH_PRIVATE (LatexilaBuildTool, latexila_build_tool, G_TYPE_OBJECT)
 
+/* Prototypes */
+static void run_job (LatexilaBuildTool *build_tool);
+static void open_file (LatexilaBuildTool *build_tool);
+
 static void
 latexila_build_tool_get_property (GObject    *object,
                                   guint       prop_id,
@@ -113,6 +131,9 @@ latexila_build_tool_set_property (GObject      *object,
 {
   LatexilaBuildTool *build_tool = LATEXILA_BUILD_TOOL (object);
 
+  /* The build tool can not be modified when it is running. */
+  g_return_if_fail (build_tool->priv->task == NULL);
+
   switch (prop_id)
     {
     case PROP_LABEL:
@@ -138,6 +159,13 @@ latexila_build_tool_set_property (GObject      *object,
     case PROP_FILES_TO_OPEN:
       g_free (build_tool->priv->files_to_open);
       build_tool->priv->files_to_open = g_value_dup_string (value);
+
+      g_strfreev (build_tool->priv->files_to_open_split);
+      build_tool->priv->files_to_open_split = NULL;
+      if (build_tool->priv->files_to_open != NULL)
+        {
+          build_tool->priv->files_to_open_split = g_strsplit (build_tool->priv->files_to_open, " ", -1);
+        }
       break;
 
     case PROP_ID:
@@ -165,6 +193,10 @@ latexila_build_tool_dispose (GObject *object)
       build_tool->priv->jobs = NULL;
     }
 
+  g_clear_object (&build_tool->priv->task);
+  g_clear_object (&build_tool->priv->file);
+  g_clear_object (&build_tool->priv->build_view);
+
   G_OBJECT_CLASS (latexila_build_tool_parent_class)->dispose (object);
 }
 
@@ -178,6 +210,7 @@ latexila_build_tool_finalize (GObject *object)
   g_free (build_tool->priv->extensions);
   g_free (build_tool->priv->icon);
   g_free (build_tool->priv->files_to_open);
+  g_strfreev (build_tool->priv->files_to_open_split);
 
   G_OBJECT_CLASS (latexila_build_tool_parent_class)->finalize (object);
 }
@@ -362,6 +395,9 @@ latexila_build_tool_add_job (LatexilaBuildTool *build_tool,
   g_return_if_fail (LATEXILA_IS_BUILD_TOOL (build_tool));
   g_return_if_fail (LATEXILA_IS_BUILD_JOB (build_job));
 
+  /* The build tool can not be modified when it is running. */
+  g_return_if_fail (build_tool->priv->task == NULL);
+
   g_queue_push_tail (build_tool->priv->jobs, build_job);
   g_object_ref (build_job);
 }
@@ -431,30 +467,332 @@ latexila_build_tool_to_xml (LatexilaBuildTool *tool)
   return g_string_free (contents, FALSE);
 }
 
+static void
+failed (LatexilaBuildTool *build_tool)
+{
+  GCancellable *cancellable;
+  LatexilaBuildState state;
+
+  cancellable = g_task_get_cancellable (build_tool->priv->task);
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      state = LATEXILA_BUILD_STATE_ABORTED;
+    }
+  else
+    {
+      state = LATEXILA_BUILD_STATE_FAILED;
+    }
+
+  latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                       &build_tool->priv->main_title,
+                                       state);
+
+  g_task_return_boolean (build_tool->priv->task, FALSE);
+}
+
+static void
+query_exists_cb (GFile             *file,
+                 GAsyncResult      *result,
+                 LatexilaBuildTool *build_tool)
+{
+  GFileInfo *info;
+  GCancellable *cancellable;
+  gboolean file_exists;
+  gchar *uri = NULL;
+  GError *error = NULL;
+
+  info = g_file_query_info_finish (file, result, NULL);
+
+  cancellable = g_task_get_cancellable (build_tool->priv->task);
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                           &build_tool->priv->open_file_job_title,
+                                           LATEXILA_BUILD_STATE_ABORTED);
+      failed (build_tool);
+      goto out;
+    }
+
+  file_exists = info != NULL;
+  g_clear_object (&info);
+
+  uri = g_file_get_uri (file);
+
+  if (!file_exists)
+    {
+      LatexilaBuildMsg *msg;
+
+      latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                           &build_tool->priv->open_file_job_title,
+                                           LATEXILA_BUILD_STATE_FAILED);
+
+      msg = latexila_build_msg_new ();
+      msg->text = g_strdup_printf (_("The file '%s' doesn't exist."), uri);
+      msg->type = LATEXILA_BUILD_MSG_TYPE_ERROR;
+
+      latexila_build_view_append_single_message (build_tool->priv->build_view,
+                                                 &build_tool->priv->open_file_job_title,
+                                                 msg);
+
+      latexila_build_msg_free (msg);
+      failed (build_tool);
+      goto out;
+    }
+
+  /* Show URI */
+
+  gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (build_tool->priv->build_view)),
+                uri,
+                GDK_CURRENT_TIME,
+                &error);
+
+  if (error != NULL)
+    {
+      LatexilaBuildMsg *msg;
+
+      latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                           &build_tool->priv->open_file_job_title,
+                                           LATEXILA_BUILD_STATE_FAILED);
+
+      msg = latexila_build_msg_new ();
+      msg->text = g_strdup_printf (_("Failed to open '%s':"), uri);
+      msg->type = LATEXILA_BUILD_MSG_TYPE_ERROR;
+
+      latexila_build_view_append_single_message (build_tool->priv->build_view,
+                                                 &build_tool->priv->open_file_job_title,
+                                                 msg);
+
+      g_free (msg->text);
+      msg->text = g_strdup (error->message);
+      msg->type = LATEXILA_BUILD_MSG_TYPE_INFO;
+
+      latexila_build_view_append_single_message (build_tool->priv->build_view,
+                                                 &build_tool->priv->open_file_job_title,
+                                                 msg);
+
+      latexila_build_msg_free (msg);
+      g_error_free (error);
+
+      failed (build_tool);
+      goto out;
+    }
+
+  latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                       &build_tool->priv->open_file_job_title,
+                                       LATEXILA_BUILD_STATE_SUCCEEDED);
+
+  build_tool->priv->current_file_to_open++;
+  open_file (build_tool);
+
+out:
+  g_object_unref (file);
+  g_free (uri);
+}
+
+static void
+open_file (LatexilaBuildTool *build_tool)
+{
+  const gchar *file_to_open;
+  gchar *filename;
+  gchar *shortname;
+  gchar *uri;
+  gchar *basename;
+  gchar *message;
+  GFile *file;
+
+  while (TRUE)
+    {
+      if (build_tool->priv->current_file_to_open == NULL ||
+          build_tool->priv->current_file_to_open[0] == NULL)
+        {
+          /* Finished */
+          latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                               &build_tool->priv->main_title,
+                                               LATEXILA_BUILD_STATE_SUCCEEDED);
+
+          g_task_return_boolean (build_tool->priv->task, TRUE);
+          return;
+        }
+
+      /* Check if the file to open is an empty string. It happens if there are
+       * two contiguous spaces in priv->files_to_open for example.
+       */
+      if (build_tool->priv->current_file_to_open[0][0] == '\0')
+        {
+          build_tool->priv->current_file_to_open++;
+        }
+      else
+        {
+          break;
+        }
+    }
+
+  file_to_open = build_tool->priv->current_file_to_open[0];
+
+  /* Replace placeholders */
+
+  filename = g_file_get_uri (build_tool->priv->file);
+  shortname = latexila_utils_get_shortname (filename);
+
+  if (strstr (file_to_open, "$filename") != NULL)
+    {
+      uri = latexila_utils_str_replace (file_to_open, "$filename", filename);
+    }
+  else if (strstr (file_to_open, "$shortname") != NULL)
+    {
+      uri = latexila_utils_str_replace (file_to_open, "$shortname", shortname);
+    }
+  else
+    {
+      uri = g_strdup_printf ("file://%s", file_to_open);
+    }
+
+  /* Add job title in the build view */
+
+  basename = g_path_get_basename (uri);
+  message = g_strdup_printf (_("Open %s"), basename);
+
+  build_tool->priv->open_file_job_title = latexila_build_view_add_job_title (build_tool->priv->build_view,
+                                                                             message,
+                                                                             LATEXILA_BUILD_STATE_RUNNING);
+
+  /* Check if the file exists */
+
+  file = g_file_new_for_uri (uri);
+
+  g_file_query_info_async (file,
+                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           g_task_get_cancellable (build_tool->priv->task),
+                           (GAsyncReadyCallback) query_exists_cb,
+                           build_tool);
+
+  g_free (filename);
+  g_free (shortname);
+  g_free (uri);
+  g_free (basename);
+  g_free (message);
+}
+
+static void
+open_files (LatexilaBuildTool *build_tool)
+{
+  build_tool->priv->current_file_to_open = build_tool->priv->files_to_open_split;
+  open_file (build_tool);
+}
+
+static void
+run_job_cb (LatexilaBuildJob  *build_job,
+            GAsyncResult      *result,
+            LatexilaBuildTool *build_tool)
+{
+  gboolean success;
+
+  success = latexila_build_job_run_finish (build_job, result);
+
+  if (success)
+    {
+      build_tool->priv->current_job = build_tool->priv->current_job->next;
+      run_job (build_tool);
+    }
+  else
+    {
+      failed (build_tool);
+    }
+}
+
+static void
+run_job (LatexilaBuildTool *build_tool)
+{
+  LatexilaBuildJob *build_job;
+
+  if (g_task_return_error_if_cancelled (build_tool->priv->task))
+    {
+      return;
+    }
+
+  if (build_tool->priv->current_job == NULL)
+    {
+      open_files (build_tool);
+      return;
+    }
+
+  build_job = build_tool->priv->current_job->data;
+
+  latexila_build_job_run_async (build_job,
+                                build_tool->priv->file,
+                                build_tool->priv->build_view,
+                                g_task_get_cancellable (build_tool->priv->task),
+                                (GAsyncReadyCallback) run_job_cb,
+                                build_tool);
+}
+
 /**
- * latexila_build_tool_run:
+ * latexila_build_tool_run_async:
  * @build_tool: a build tool.
  * @file: a file.
  * @build_view: a build view.
+ * @cancellable: a #GCancellable object.
+ * @callback: the callback to call when the operation is finished.
+ * @user_data: the data to pass to the callback function.
  *
  * Run a build tool on a file with the messages displayed in a build view.
  */
 void
-latexila_build_tool_run (LatexilaBuildTool *build_tool,
-                         GFile             *file,
-                         LatexilaBuildView *build_view)
+latexila_build_tool_run_async (LatexilaBuildTool   *build_tool,
+                               GFile               *file,
+                               LatexilaBuildView   *build_view,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
 {
   g_return_if_fail (LATEXILA_IS_BUILD_TOOL (build_tool));
   g_return_if_fail (G_IS_FILE (file));
   g_return_if_fail (LATEXILA_IS_BUILD_VIEW (build_view));
+  g_return_if_fail (build_tool->priv->task == NULL);
+
+  build_tool->priv->task = g_task_new (build_tool, cancellable, callback, user_data);
+
+  g_clear_object (&build_tool->priv->file);
+  build_tool->priv->file = g_object_ref (file);
+
+  g_clear_object (&build_tool->priv->build_view);
+  build_tool->priv->build_view = g_object_ref (build_view);
 
   latexila_build_view_clear (build_view);
 
-  latexila_build_view_add_main_title (build_view,
-                                      build_tool->priv->label,
-                                      LATEXILA_BUILD_STATE_RUNNING);
+  build_tool->priv->main_title = latexila_build_view_add_main_title (build_view,
+                                                                     build_tool->priv->label,
+                                                                     LATEXILA_BUILD_STATE_RUNNING);
+
+  build_tool->priv->current_job = build_tool->priv->jobs->head;
+  run_job (build_tool);
+}
+
+/**
+ * latexila_build_tool_run_finish:
+ * @build_tool: a build tool.
+ * @result: a #GAsyncResult.
+ *
+ * Finishes the operation started with latexila_build_tool_run_async().
+ */
+void
+latexila_build_tool_run_finish (LatexilaBuildTool *build_tool,
+                                GAsyncResult      *result)
+{
+  GCancellable *cancellable;
+
+  g_return_if_fail (g_task_is_valid (result, build_tool));
+
+  cancellable = g_task_get_cancellable (G_TASK (result));
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      latexila_build_view_set_title_state (build_tool->priv->build_view,
+                                           &build_tool->priv->main_title,
+                                           LATEXILA_BUILD_STATE_ABORTED);
+    }
 
-  latexila_build_view_add_job_title (build_view,
-                                     "job",
-                                     LATEXILA_BUILD_STATE_SUCCEEDED);
+  g_task_propagate_boolean (G_TASK (result), NULL);
+  g_clear_object (&build_tool->priv->task);
 }
diff --git a/src/liblatexila/latexila-build-tool.h b/src/liblatexila/latexila-build-tool.h
index 77c6443..32c5dc6 100644
--- a/src/liblatexila/latexila-build-tool.h
+++ b/src/liblatexila/latexila-build-tool.h
@@ -62,9 +62,15 @@ GList *               latexila_build_tool_get_jobs                  (LatexilaBui
 
 gchar *               latexila_build_tool_to_xml                    (LatexilaBuildTool *tool);
 
-void                  latexila_build_tool_run                       (LatexilaBuildTool *build_tool,
-                                                                     GFile             *file,
-                                                                     LatexilaBuildView *build_view);
+void                  latexila_build_tool_run_async                 (LatexilaBuildTool   *build_tool,
+                                                                     GFile               *file,
+                                                                     LatexilaBuildView   *build_view,
+                                                                     GCancellable        *cancellable,
+                                                                     GAsyncReadyCallback  callback,
+                                                                     gpointer             user_data);
+
+void                  latexila_build_tool_run_finish                (LatexilaBuildTool *build_tool,
+                                                                     GAsyncResult      *result);
 
 G_END_DECLS
 
diff --git a/src/liblatexila/latexila-build-view.c b/src/liblatexila/latexila-build-view.c
index 879a291..eb485c5 100644
--- a/src/liblatexila/latexila-build-view.c
+++ b/src/liblatexila/latexila-build-view.c
@@ -565,6 +565,9 @@ latexila_build_view_clear (LatexilaBuildView *build_view)
   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
 
   gtk_tree_view_columns_autosize (GTK_TREE_VIEW (build_view));
+
+  build_view->priv->has_details = FALSE;
+  g_object_notify (G_OBJECT (build_view), "has-details");
 }
 
 static GtkTreeIter
@@ -711,7 +714,11 @@ latexila_build_view_append_single_message (LatexilaBuildView *build_view,
                       COLUMN_LINE_STR, line_str,
                       -1);
 
-  g_object_unref (file);
+  if (file != NULL)
+    {
+      g_object_unref (file);
+    }
+
   g_free (path);
   g_free (basename);
   g_free (line_str);
diff --git a/src/liblatexila/latexila-utils.c b/src/liblatexila/latexila-utils.c
index 3f6d93d..8c06b8a 100644
--- a/src/liblatexila/latexila-utils.c
+++ b/src/liblatexila/latexila-utils.c
@@ -201,3 +201,39 @@ latexila_utils_register_icons (void)
 
   g_strfreev (icon_files);
 }
+
+/**
+ * latexila_utils_str_replace:
+ * @string: a string
+ * @search: the search string
+ * @replacement: the replacement string
+ *
+ * Replaces all occurences of @search by @replacement.
+ *
+ * Returns: A newly allocated string with the replacements. Free with g_free().
+ */
+gchar *
+latexila_utils_str_replace (const gchar *string,
+                            const gchar *search,
+                            const gchar *replacement)
+{
+  gchar **chunks;
+  gchar *ret;
+
+  g_return_val_if_fail (string != NULL, NULL);
+  g_return_val_if_fail (search != NULL, NULL);
+  g_return_val_if_fail (replacement != NULL, NULL);
+
+  chunks = g_strsplit (string, search, -1);
+  if (chunks != NULL && chunks[0] != NULL)
+    {
+      ret = g_strjoinv (replacement, chunks);
+    }
+  else
+    {
+      ret = g_strdup (string);
+    }
+
+  g_strfreev (chunks);
+  return ret;
+}
diff --git a/src/liblatexila/latexila-utils.h b/src/liblatexila/latexila-utils.h
index cf88567..852a6f0 100644
--- a/src/liblatexila/latexila-utils.h
+++ b/src/liblatexila/latexila-utils.h
@@ -30,6 +30,10 @@ gchar *         latexila_utils_replace_home_dir_with_tilde      (const gchar *fi
 
 void            latexila_utils_register_icons                   (void);
 
+gchar *         latexila_utils_str_replace                      (const gchar *string,
+                                                                 const gchar *search,
+                                                                 const gchar *replacement);
+
 G_END_DECLS
 
 #endif /* __LATEXILA_UTILS_H__ */
diff --git a/src/main_window_build_tools.vala b/src/main_window_build_tools.vala
index 4f3e79e..c870507 100644
--- a/src/main_window_build_tools.vala
+++ b/src/main_window_build_tools.vala
@@ -57,6 +57,7 @@ public class MainWindowBuildTools
     private UIManager _ui_manager;
     private Latexila.BuildView _build_view;
     private BottomPanel _bottom_panel;
+    private Cancellable? _cancellable;
 
     private Gtk.ActionGroup _static_action_group;
     private Gtk.ActionGroup _dynamic_action_group;
@@ -355,13 +356,13 @@ public class MainWindowBuildTools
         stop_exec.sensitive = true;
 
         File main_file = active_doc.get_main_file ();
-        tool.run (main_file, _build_view);
-        /* TODO port this code. */
-        /*
-        _build_tool_runner = new BuildToolRunner (tool, main_file, _build_view);
-        _build_tool_runner.finished.connect (() => stop_exec.sensitive = false);
-        _build_tool_runner.run ();
-        */
+        _cancellable = new Cancellable ();
+        tool.run_async.begin (main_file, _build_view, _cancellable, (obj, result) =>
+        {
+            tool.run_async.end (result);
+            _cancellable = null;
+            stop_exec.sensitive = false;
+        });
     }
 
     private void connect_toggle_actions ()
@@ -408,11 +409,8 @@ public class MainWindowBuildTools
 
     public void on_stop_execution ()
     {
-        /* TODO port this code. */
-        /*
-        return_if_fail (_build_tool_runner != null);
-        _build_tool_runner.abort ();
-        */
+        return_if_fail (_cancellable != null);
+        _cancellable.cancel ();
     }
 
     public void on_clean ()
diff --git a/tests/test-utils.c b/tests/test-utils.c
index 9072428..4bfff00 100644
--- a/tests/test-utils.c
+++ b/tests/test-utils.c
@@ -63,6 +63,28 @@ test_replace_home_dir_with_tilde (void)
   g_free (after);
 }
 
+static void
+test_str_replace (void)
+{
+  gchar *result;
+
+  result = latexila_utils_str_replace ("$filename", "$filename", "blah");
+  g_assert_cmpstr (result, ==, "blah");
+  g_free (result);
+
+  result = latexila_utils_str_replace ("$shortname.pdf", "$shortname", "blah");
+  g_assert_cmpstr (result, ==, "blah.pdf");
+  g_free (result);
+
+  result = latexila_utils_str_replace ("abcdabcd", "ab", "r");
+  g_assert_cmpstr (result, ==, "rcdrcd");
+  g_free (result);
+
+  result = latexila_utils_str_replace ("abcd", "ef", "r");
+  g_assert_cmpstr (result, ==, "abcd");
+  g_free (result);
+}
+
 gint
 main (gint    argc,
       gchar **argv)
@@ -71,6 +93,7 @@ main (gint    argc,
 
   g_test_add_func ("/utils/get-shortname", test_get_shortname);
   g_test_add_func ("/utils/replace-home-dir-with-tilde", test_replace_home_dir_with_tilde);
+  g_test_add_func ("/utils/str-replace", test_str_replace);
 
   return g_test_run ();
 }



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