[latexila/wip/build-tools-revamp] PostProcessor and PostProcessorAllOutput



commit 4e36ea24b3207451ac30c12683a317a2ee32158a
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Fri May 23 12:25:30 2014 +0200

    PostProcessor and PostProcessorAllOutput
    
    The post processors work on a GInputStream obtained by the GSubprocess.
    The GInputStream is read asynchronously.
    
    Before, the Vala code worked on the full command output, as one big
    string.

 docs/reference/Makefile.am                         |    4 +-
 docs/reference/latexila-docs.xml                   |    2 +
 docs/reference/latexila-sections.txt               |   40 +++
 src/liblatexila/latexila-build-job.c               |   84 +++---
 src/liblatexila/latexila-build-view.c              |    4 +-
 src/liblatexila/latexila-build-view.h              |    2 +-
 .../latexila-post-processor-all-output.c           |   73 ++---
 src/liblatexila/latexila-post-processor.c          |  308 +++++++++++++++++++-
 src/liblatexila/latexila-post-processor.h          |   23 +-
 9 files changed, 430 insertions(+), 110 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 9d900a7..7f86569 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -53,9 +53,7 @@ EXTRA_HFILES =
 # Header files or dirs to ignore when scanning. Use base file/dir names
 # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h private_code
 IGNORE_HFILES =                                        \
-       latexila-enum-types.h                   \
-       latexila-post-processor.h               \
-       latexila-post-processor-all-output.h
+       latexila-enum-types.h
 
 # Images to copy into HTML directory.
 # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
diff --git a/docs/reference/latexila-docs.xml b/docs/reference/latexila-docs.xml
index 2e09e5f..a1b8a38 100644
--- a/docs/reference/latexila-docs.xml
+++ b/docs/reference/latexila-docs.xml
@@ -17,6 +17,8 @@
     <xi:include href="xml/build-tools-default.xml"/>
     <xi:include href="xml/build-tools-personal.xml"/>
     <xi:include href="xml/build-view.xml"/>
+    <xi:include href="xml/post-processor.xml"/>
+    <xi:include href="xml/post-processor-all-output.xml"/>
     <xi:include href="xml/synctex.xml"/>
     <xi:include href="xml/utils.xml"/>
   </chapter>
diff --git a/docs/reference/latexila-sections.txt b/docs/reference/latexila-sections.txt
index 9c5ce08..1b830d0 100644
--- a/docs/reference/latexila-sections.txt
+++ b/docs/reference/latexila-sections.txt
@@ -131,6 +131,46 @@ latexila_build_view_get_type
 </SECTION>
 
 <SECTION>
+<FILE>post-processor</FILE>
+<TITLE>LatexilaPostProcessor</TITLE>
+LatexilaPostProcessor
+LatexilaPostProcessorType
+latexila_build_messages_free
+latexila_post_processor_get_name_from_type
+latexila_post_processor_get_type_from_name
+latexila_post_processor_process_async
+latexila_post_processor_process_finish
+latexila_post_processor_get_messages
+<SUBSECTION Standard>
+LATEXILA_IS_POST_PROCESSOR
+LATEXILA_IS_POST_PROCESSOR_CLASS
+LATEXILA_POST_PROCESSOR
+LATEXILA_POST_PROCESSOR_CLASS
+LATEXILA_POST_PROCESSOR_GET_CLASS
+LATEXILA_TYPE_POST_PROCESSOR
+LatexilaPostProcessorClass
+LatexilaPostProcessorPrivate
+latexila_post_processor_get_type
+</SECTION>
+
+<SECTION>
+<FILE>post-processor-all-output</FILE>
+<TITLE>LatexilaPostProcessorAllOutput</TITLE>
+LatexilaPostProcessorAllOutput
+latexila_post_processor_all_output_new
+<SUBSECTION Standard>
+LATEXILA_IS_POST_PROCESSOR_ALL_OUTPUT
+LATEXILA_IS_POST_PROCESSOR_ALL_OUTPUT_CLASS
+LATEXILA_POST_PROCESSOR_ALL_OUTPUT
+LATEXILA_POST_PROCESSOR_ALL_OUTPUT_CLASS
+LATEXILA_POST_PROCESSOR_ALL_OUTPUT_GET_CLASS
+LATEXILA_TYPE_POST_PROCESSOR_ALL_OUTPUT
+LatexilaPostProcessorAllOutputClass
+LatexilaPostProcessorAllOutputPrivate
+latexila_post_processor_all_output_get_type
+</SECTION>
+
+<SECTION>
 <FILE>synctex</FILE>
 <TITLE>LatexilaSynctex</TITLE>
 LatexilaSynctex
diff --git a/src/liblatexila/latexila-build-job.c b/src/liblatexila/latexila-build-job.c
index bd35801..641fc99 100644
--- a/src/liblatexila/latexila-build-job.c
+++ b/src/liblatexila/latexila-build-job.c
@@ -29,6 +29,7 @@
 #include <string.h>
 #include <glib/gi18n.h>
 #include "latexila-build-view.h"
+#include "latexila-post-processor-all-output.h"
 #include "latexila-utils.h"
 #include "latexila-enum-types.h"
 
@@ -42,7 +43,7 @@ struct _LatexilaBuildJobPrivate
   GFile *file;
   LatexilaBuildView *build_view;
   GtkTreeIter job_title;
-  GNode *build_messages;
+  LatexilaPostProcessor *post_processor;
 };
 
 enum
@@ -54,30 +55,6 @@ 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,
@@ -138,6 +115,7 @@ latexila_build_job_dispose (GObject *object)
   g_clear_object (&build_job->priv->task);
   g_clear_object (&build_job->priv->file);
   g_clear_object (&build_job->priv->build_view);
+  g_clear_object (&build_job->priv->post_processor);
 
   G_OBJECT_CLASS (latexila_build_job_parent_class)->dispose (object);
 }
@@ -148,7 +126,6 @@ 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);
 }
@@ -408,11 +385,27 @@ display_command_line (LatexilaBuildJob *build_job)
 }
 
 static void
+post_processor_cb (LatexilaPostProcessor *pp,
+                   GAsyncResult          *result,
+                   LatexilaBuildJob      *build_job)
+{
+  const GNode *messages;
+
+  latexila_post_processor_process_finish (pp, result);
+
+  messages = latexila_post_processor_get_messages (pp);
+
+  latexila_build_view_append_messages (build_job->priv->build_view,
+                                       &build_job->priv->job_title,
+                                       messages,
+                                       TRUE);
+}
+
+static void
 subprocess_wait_cb (GSubprocess      *subprocess,
                     GAsyncResult     *result,
                     LatexilaBuildJob *build_job)
 {
-  LatexilaBuildMsg *msg;
   gboolean ret;
   LatexilaBuildState state;
 
@@ -433,21 +426,11 @@ subprocess_wait_cb (GSubprocess      *subprocess,
       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);
 }
 
@@ -461,9 +444,16 @@ launch_subprocess (LatexilaBuildJob *build_job)
   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);
+  if (build_job->priv->post_processor_type == LATEXILA_POST_PROCESSOR_TYPE_NO_OUTPUT)
+    {
+      launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE |
+                                            G_SUBPROCESS_FLAGS_STDERR_SILENCE);
+    }
+  else
+    {
+      launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
+                                            G_SUBPROCESS_FLAGS_STDERR_MERGE);
+    }
 
   parent_dir = g_file_get_parent (build_job->priv->file);
   working_directory = g_file_get_path (parent_dir);
@@ -485,6 +475,18 @@ launch_subprocess (LatexilaBuildJob *build_job)
       return;
     }
 
+  if (build_job->priv->post_processor_type != LATEXILA_POST_PROCESSOR_TYPE_NO_OUTPUT)
+    {
+      g_clear_object (&build_job->priv->post_processor);
+      build_job->priv->post_processor = latexila_post_processor_all_output_new ();
+
+      latexila_post_processor_process_async (build_job->priv->post_processor,
+                                             g_subprocess_get_stdout_pipe (subprocess),
+                                             g_task_get_cancellable (build_job->priv->task),
+                                             (GAsyncReadyCallback) post_processor_cb,
+                                             build_job);
+    }
+
   g_subprocess_wait_async (subprocess,
                            g_task_get_cancellable (build_job->priv->task),
                            (GAsyncReadyCallback) subprocess_wait_cb,
@@ -525,7 +527,7 @@ latexila_build_job_run_async (LatexilaBuildJob    *build_job,
   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);
+  g_clear_object (&build_job->priv->post_processor);
 
   if (!display_command_line (build_job))
     {
diff --git a/src/liblatexila/latexila-build-view.c b/src/liblatexila/latexila-build-view.c
index eb485c5..9e74780 100644
--- a/src/liblatexila/latexila-build-view.c
+++ b/src/liblatexila/latexila-build-view.c
@@ -738,10 +738,10 @@ latexila_build_view_append_single_message (LatexilaBuildView *build_view,
 void
 latexila_build_view_append_messages (LatexilaBuildView *build_view,
                                      GtkTreeIter       *parent,
-                                     GNode             *messages,
+                                     const GNode       *messages,
                                      gboolean           expand)
 {
-  GNode *node;
+  const GNode *node;
 
   for (node = messages; node != NULL; node = node->next)
     {
diff --git a/src/liblatexila/latexila-build-view.h b/src/liblatexila/latexila-build-view.h
index c85b7cd..bc4245e 100644
--- a/src/liblatexila/latexila-build-view.h
+++ b/src/liblatexila/latexila-build-view.h
@@ -139,7 +139,7 @@ GtkTreeIter           latexila_build_view_append_single_message     (LatexilaBui
 
 void                  latexila_build_view_append_messages           (LatexilaBuildView *build_view,
                                                                      GtkTreeIter       *parent,
-                                                                     GNode             *messages,
+                                                                     const GNode       *messages,
                                                                      gboolean           expand);
 
 void                  latexila_build_view_remove_children           (LatexilaBuildView *build_view,
diff --git a/src/liblatexila/latexila-post-processor-all-output.c 
b/src/liblatexila/latexila-post-processor-all-output.c
index f4bf671..392ab44 100644
--- a/src/liblatexila/latexila-post-processor-all-output.c
+++ b/src/liblatexila/latexila-post-processor-all-output.c
@@ -17,74 +17,60 @@
  * along with LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/**
+ * SECTION:post-processor-all-output
+ * @title: LatexilaPostProcessorAllOutput
+ * @short_description: all-output post-processor
+ *
+ * A post-processor that keeps all the output. Nothing is filtered.
+ */
+
 #include "latexila-post-processor-all-output.h"
+#include "latexila-build-view.h"
 
 struct _LatexilaPostProcessorAllOutputPrivate
 {
-  GSList *messages;
+  GNode *messages;
+  GNode *last_message;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (LatexilaPostProcessorAllOutput, latexila_post_processor_all_output, 
LATEXILA_TYPE_POST_PROCESSOR)
 
 static void
-latexila_post_processor_all_output_process (LatexilaPostProcessor *post_processor,
-                                            const gchar           *output)
+latexila_post_processor_all_output_process_lines (LatexilaPostProcessor  *post_processor,
+                                                  gchar                 **lines)
 {
   LatexilaPostProcessorAllOutput *pp = LATEXILA_POST_PROCESSOR_ALL_OUTPUT (post_processor);
-  gchar **lines;
-  gchar **l;
-
-  lines = g_strsplit (output, "\n", 0);
-
-  for (l = lines; l != NULL && *l != NULL; l++)
-    {
-      pp->priv->messages = g_slist_prepend (pp->priv->messages, *l);
-    }
+  gint i;
 
-  /* Generally a single \n is present at the end of the output, so an empty line
-   * is added to the list. But we don't want to display it.
-   * TODO check if it is still the case in C.
-   */
-#if 0
-  if (pp->priv->messages != NULL)
+  for (i = 0; lines != NULL && lines[i] != NULL; i++)
     {
-      gchar *line = pp->priv->messages->data;
-      g_assert (line != NULL);
+      LatexilaBuildMsg *msg = latexila_build_msg_new ();
+      msg->text = lines[i];
+      msg->type = LATEXILA_BUILD_MSG_TYPE_INFO;
 
-      if (line[0] == '\0')
-        {
-          GSList *removed_element = pp->priv->messages;
-
-          pp->priv->messages = g_slist_remove_link (pp->priv->messages, pp->priv->messages);
-
-          g_slist_free_full (removed_element, g_free);
-        }
+      pp->priv->last_message = g_node_insert_data_after (pp->priv->messages,
+                                                         pp->priv->last_message,
+                                                         msg);
     }
-#endif
 
-  pp->priv->messages = g_slist_reverse (pp->priv->messages);
-
-  /* Do not use g_strfreev() because the strings are reused in the list. */
   g_free (lines);
 }
 
-static GSList *
+static const GNode *
 latexila_post_processor_all_output_get_messages (LatexilaPostProcessor *post_processor)
 {
   LatexilaPostProcessorAllOutput *pp = LATEXILA_POST_PROCESSOR_ALL_OUTPUT (post_processor);
 
-  return pp->priv->messages;
+  return pp->priv->messages->children;
 }
 
 static void
 latexila_post_processor_all_output_finalize (GObject *object)
 {
-  LatexilaPostProcessorAllOutputPrivate *priv;
-
-  priv = latexila_post_processor_all_output_get_instance_private (LATEXILA_POST_PROCESSOR_ALL_OUTPUT 
(object));
+  LatexilaPostProcessorAllOutput *pp = LATEXILA_POST_PROCESSOR_ALL_OUTPUT (object);
 
-  g_slist_free_full (priv->messages, g_free);
-  priv->messages = NULL;
+  latexila_build_messages_free (pp->priv->messages);
 
   G_OBJECT_CLASS (latexila_post_processor_all_output_parent_class)->finalize (object);
 }
@@ -97,7 +83,7 @@ latexila_post_processor_all_output_class_init (LatexilaPostProcessorAllOutputCla
 
   object_class->finalize = latexila_post_processor_all_output_finalize;
 
-  post_processor_class->process = latexila_post_processor_all_output_process;
+  post_processor_class->process_lines = latexila_post_processor_all_output_process_lines;
   post_processor_class->get_messages = latexila_post_processor_all_output_get_messages;
 }
 
@@ -105,8 +91,15 @@ static void
 latexila_post_processor_all_output_init (LatexilaPostProcessorAllOutput *pp)
 {
   pp->priv = latexila_post_processor_all_output_get_instance_private (pp);
+
+  pp->priv->messages = g_node_new (NULL);
 }
 
+/**
+ * latexila_post_processor_all_output_new:
+ *
+ * Returns: a new #LatexilaPostProcessorAllOutput object.
+ */
 LatexilaPostProcessor *
 latexila_post_processor_all_output_new (void)
 {
diff --git a/src/liblatexila/latexila-post-processor.c b/src/liblatexila/latexila-post-processor.c
index 19e2b40..a5a461b 100644
--- a/src/liblatexila/latexila-post-processor.c
+++ b/src/liblatexila/latexila-post-processor.c
@@ -17,10 +17,36 @@
  * along with LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/**
+ * SECTION:post-processor
+ * @title: LatexilaPostProcessor
+ * @short_description: post-processor base class
+ *
+ * When running a build tool, a post-processor is used to filter the output to
+ * display only the relevant messages. It can be the output of a build job
+ * command, or a log file, etc.
+ */
+
 #include "latexila-post-processor.h"
+#include "latexila-build-view.h"
+
+#define BUFFER_SIZE 4096
 
 struct _LatexilaPostProcessorPrivate
 {
+  GTask *task;
+  GInputStream *stream;
+
+  /* "+1" so we can nul-terminate the buffer. */
+  gchar buffer[BUFFER_SIZE + 1];
+
+  /* The @buffer is split by lines. But since the stream is read with a fixed
+   * size (BUFFER_SIZE), the last string returned by g_strsplit() is stored in
+   * @line_buffer. When the next block is read, the first line returned is
+   * appended to @line_buffer to have the whole line.
+   */
+  GString *line_buffer;
+
   guint has_details : 1;
   guint show_details : 1;
 };
@@ -34,6 +60,9 @@ enum
 
 G_DEFINE_TYPE_WITH_PRIVATE (LatexilaPostProcessor, latexila_post_processor, G_TYPE_OBJECT)
 
+/* Prototypes */
+static void read_stream (LatexilaPostProcessor *pp);
+
 /**
  * latexila_post_processor_get_type_from_name:
  * @name: the name of the post-processor.
@@ -102,6 +131,36 @@ latexila_post_processor_get_name_from_type (LatexilaPostProcessorType type)
     }
 }
 
+static gboolean
+free_build_msg (GNode    *node,
+                gpointer  user_data)
+{
+  latexila_build_msg_free (node->data);
+  return FALSE;
+}
+
+/**
+ * latexila_build_messages_free:
+ * @build_messages: a tree of #LatexilaBuildMsg's.
+ *
+ * Frees a tree of #LatexilaBuildMsg's.
+ */
+void
+latexila_build_messages_free (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_post_processor_get_property (GObject    *object,
                                       guint       prop_id,
@@ -151,17 +210,39 @@ latexila_post_processor_set_property (GObject      *object,
 }
 
 static void
-latexila_post_processor_process_default (LatexilaPostProcessor *pp,
-                                         const gchar           *output)
+latexila_post_processor_dispose (GObject *object)
 {
-  g_return_if_fail (LATEXILA_IS_POST_PROCESSOR (pp));
+  LatexilaPostProcessor *pp = LATEXILA_POST_PROCESSOR (object);
+
+  g_clear_object (&pp->priv->task);
+  g_clear_object (&pp->priv->stream);
+
+  G_OBJECT_CLASS (latexila_post_processor_parent_class)->dispose (object);
 }
 
-static GSList *
-latexila_post_processor_get_messages_default (LatexilaPostProcessor *pp)
+static void
+latexila_post_processor_finalize (GObject *object)
 {
-  g_return_val_if_fail (LATEXILA_IS_POST_PROCESSOR (pp), NULL);
+  LatexilaPostProcessor *pp = LATEXILA_POST_PROCESSOR (object);
 
+  if (pp->priv->line_buffer != NULL)
+    {
+      g_string_free (pp->priv->line_buffer, TRUE);
+    }
+
+  G_OBJECT_CLASS (latexila_post_processor_parent_class)->finalize (object);
+}
+
+static void
+latexila_post_processor_process_lines_default (LatexilaPostProcessor  *pp,
+                                               gchar                 **lines)
+{
+  g_strfreev (lines);
+}
+
+static const GNode *
+latexila_post_processor_get_messages_default (LatexilaPostProcessor *pp)
+{
   return NULL;
 }
 
@@ -172,8 +253,10 @@ latexila_post_processor_class_init (LatexilaPostProcessorClass *klass)
 
   object_class->get_property = latexila_post_processor_get_property;
   object_class->set_property = latexila_post_processor_set_property;
+  object_class->dispose = latexila_post_processor_dispose;
+  object_class->finalize = latexila_post_processor_finalize;
 
-  klass->process = latexila_post_processor_process_default;
+  klass->process_lines = latexila_post_processor_process_lines_default;
   klass->get_messages = latexila_post_processor_get_messages_default;
 
   g_object_class_install_property (object_class,
@@ -203,22 +286,219 @@ latexila_post_processor_init (LatexilaPostProcessor *pp)
   pp->priv = latexila_post_processor_get_instance_private (pp);
 }
 
-LatexilaPostProcessor *
-latexila_post_processor_new (void)
+static void
+read_stream_cb (GInputStream          *stream,
+                GAsyncResult          *result,
+                LatexilaPostProcessor *pp)
+{
+  gssize bytes_read;
+  GCancellable *cancellable;
+  gchar **lines;
+  GError *error = NULL;
+
+  bytes_read = g_input_stream_read_finish (stream, result, &error);
+
+  cancellable = g_task_get_cancellable (pp->priv->task);
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      if (error != NULL)
+        {
+          g_error_free (error);
+        }
+
+      g_task_return_boolean (pp->priv->task, FALSE);
+      return;
+    }
+
+  if (error != NULL)
+    {
+      g_warning ("Error while reading the post-processor stream: %s", error->message);
+      g_error_free (error);
+      g_task_return_boolean (pp->priv->task, FALSE);
+      return;
+    }
+
+  /* End of stream reached, process line_buffer. */
+  if (bytes_read == 0)
+    {
+      /* Generally a single \n is present at the end of the stream, so an empty
+       * line is present in line_buffer. But we don't want to display it in the
+       * build view.
+       */
+      if (pp->priv->line_buffer != NULL &&
+          pp->priv->line_buffer->str != NULL &&
+          pp->priv->line_buffer->str[0] != '\0')
+        {
+          lines = g_new (gchar *, 2);
+          lines[0] = g_string_free (pp->priv->line_buffer, FALSE);
+          lines[1] = NULL;
+
+          pp->priv->line_buffer = NULL;
+
+          LATEXILA_POST_PROCESSOR_GET_CLASS (pp)->process_lines (pp, lines);
+        }
+
+      /* finished! */
+      g_task_return_boolean (pp->priv->task, TRUE);
+      return;
+    }
+
+  pp->priv->buffer[bytes_read] = '\0';
+
+  lines = g_strsplit (pp->priv->buffer, "\n", 0);
+  g_assert (lines != NULL);
+  g_assert (lines[0] != NULL);
+
+  if (pp->priv->line_buffer != NULL)
+    {
+      /* Merge line_buffer and the first line */
+      g_string_append (pp->priv->line_buffer, lines[0]);
+    }
+
+  /* If a second line exists, we can call process_lines().
+   * The first line must be replaced by the contents of line_buffer.
+   * And the last line must go to line_buffer.
+   */
+  if (lines[1] != NULL)
+    {
+      gint last_line;
+
+      if (pp->priv->line_buffer != NULL)
+        {
+          g_free (lines[0]);
+          lines[0] = g_string_free (pp->priv->line_buffer, FALSE);
+          pp->priv->line_buffer = NULL;
+        }
+
+      for (last_line = 1; lines[last_line+1] != NULL; last_line++);
+
+      pp->priv->line_buffer = g_string_new (lines[last_line]);
+      g_free (lines[last_line]);
+      lines[last_line] = NULL;
+
+      LATEXILA_POST_PROCESSOR_GET_CLASS (pp)->process_lines (pp, lines);
+    }
+  else
+    {
+      /* If not already done above, put the first line to line_buffer. */
+      if (pp->priv->line_buffer == NULL)
+        {
+          pp->priv->line_buffer = g_string_new (lines[0]);
+        }
+
+      g_strfreev (lines);
+    }
+
+  /* Unfortunately for the computer, it is not finished. */
+  read_stream (pp);
+}
+
+static void
+read_stream (LatexilaPostProcessor *pp)
 {
-  return g_object_new (LATEXILA_TYPE_POST_PROCESSOR, NULL);
+  g_input_stream_read_async (pp->priv->stream,
+                             &pp->priv->buffer,
+                             BUFFER_SIZE,
+                             G_PRIORITY_DEFAULT,
+                             g_task_get_cancellable (pp->priv->task),
+                             (GAsyncReadyCallback) read_stream_cb,
+                             pp);
 }
 
+/**
+ * latexila_post_processor_process_async:
+ * @pp: a post-processor.
+ * @stream: the input stream to process.
+ * @cancellable: a #GCancellable.
+ * @callback: the callback to call when the operation is finished.
+ * @user_data: the data to pass to the callback.
+ *
+ * Asynchronously process an input stream. The input stream can for example come
+ * from the output of a command launched with #GSubprocess, or it can be the
+ * input stream of a file (e.g. the LaTeX log file), etc.
+ *
+ * @callback will be called when the operation is finished. You can then call
+ * latexila_post_processor_process_finish().
+ */
 void
-latexila_post_processor_process (LatexilaPostProcessor *pp,
-                                 const gchar           *output)
+latexila_post_processor_process_async (LatexilaPostProcessor *pp,
+                                       GInputStream          *stream,
+                                       GCancellable          *cancellable,
+                                       GAsyncReadyCallback    callback,
+                                       gpointer               user_data)
 {
   g_return_if_fail (LATEXILA_IS_POST_PROCESSOR (pp));
+  g_return_if_fail (G_IS_INPUT_STREAM (stream));
+  g_return_if_fail (G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (pp->priv->task == NULL);
 
-  LATEXILA_POST_PROCESSOR_GET_CLASS (pp)->process (pp, output);
+  pp->priv->task = g_task_new (pp, cancellable, callback, user_data);
+  pp->priv->stream = g_object_ref (stream);
+
+  if (pp->priv->line_buffer != NULL)
+    {
+      g_string_free (pp->priv->line_buffer, TRUE);
+      pp->priv->line_buffer = NULL;
+    }
+
+  read_stream (pp);
 }
 
-GSList *
+/**
+ * latexila_post_processor_process_finish:
+ * @pp: a post-processor.
+ * @result: a #GAsyncResult.
+ *
+ * Finishes an operation started with latexila_post_processor_process_async().
+ * After calling this function, you can get the filtered messages with
+ * latexila_post_processor_get_messages().
+ */
+void
+latexila_post_processor_process_finish (LatexilaPostProcessor *pp,
+                                        GAsyncResult          *result)
+{
+  g_return_if_fail (g_task_is_valid (result, pp));
+
+  g_task_propagate_boolean (G_TASK (result), NULL);
+
+  g_clear_object (&pp->priv->task);
+  g_clear_object (&pp->priv->stream);
+
+  if (pp->priv->line_buffer != NULL)
+    {
+      g_string_free (pp->priv->line_buffer, TRUE);
+    }
+}
+
+/**
+ * latexila_post_processor_get_messages:
+ * @pp: a post-processor.
+ *
+ * Gets the filtered messages. Call this function only after calling
+ * latexila_post_processor_process_finish().
+ *
+ * Another solution would have been to pass the #LatexilaBuildView to the
+ * post-processor, so the filtered messages can directly be outputed to the
+ * build view as they come. But some post-processors don't know what to output
+ * directly. The latexmk post-processor can have a simplified output with only
+ * the LaTeX messages, in which case the detailed messages are also available,
+ * but this can be known only when all the stream has been processed.
+ *
+ * Obviously if the build view is passed to the post-processor, the latexmk
+ * post-processor can output its messages only at the end. But another reason to
+ * not pass the build view is for the unit tests. It is easier for the unit
+ * tests to check the returned #GNode than analyzing a #GtkTreeView. Of course
+ * it would be possible to keep also the messages in a #GNode and have this
+ * function only for the unit tests, but it takes more memory (unless a custom
+ * #GtkTreeModel is implemented), or another function is needed to configure
+ * whether a #GNode is kept in memory or not... It becomes a little too
+ * complicated, and doesn't really worth the effort as most users use latexmk.
+ *
+ * The current solution is "good enough".
+ *
+ * Returns: the tree of filtered messages.
+ */
+const GNode *
 latexila_post_processor_get_messages (LatexilaPostProcessor *pp)
 {
   g_return_val_if_fail (LATEXILA_IS_POST_PROCESSOR (pp), NULL);
diff --git a/src/liblatexila/latexila-post-processor.h b/src/liblatexila/latexila-post-processor.h
index 51e5bab..3f244f9 100644
--- a/src/liblatexila/latexila-post-processor.h
+++ b/src/liblatexila/latexila-post-processor.h
@@ -20,8 +20,7 @@
 #ifndef __LATEXILA_POST_PROCESSOR_H__
 #define __LATEXILA_POST_PROCESSOR_H__
 
-#include <glib.h>
-#include <glib-object.h>
+#include <gio/gio.h>
 #include "latexila-types.h"
 
 G_BEGIN_DECLS
@@ -66,10 +65,10 @@ struct _LatexilaPostProcessorClass
 {
   GObjectClass parent_class;
 
-  void (* process) (LatexilaPostProcessor *post_processor,
-                    const gchar           *output);
+  void (* process_lines) (LatexilaPostProcessor  *pp,
+                          gchar                 **lines);
 
-  GSList * (* get_messages) (LatexilaPostProcessor *post_processor);
+  const GNode * (* get_messages) (LatexilaPostProcessor *pp);
 };
 
 GType                   latexila_post_processor_get_type              (void) G_GNUC_CONST;
@@ -79,12 +78,18 @@ gboolean                latexila_post_processor_get_type_from_name    (const gch
 
 const gchar *           latexila_post_processor_get_name_from_type    (LatexilaPostProcessorType type);
 
-LatexilaPostProcessor * latexila_post_processor_new                   (void);
+void                    latexila_build_messages_free                  (GNode *build_messages);
 
-void                    latexila_post_processor_process               (LatexilaPostProcessor *post_processor,
-                                                                       const gchar           *output);
+void                    latexila_post_processor_process_async         (LatexilaPostProcessor *pp,
+                                                                       GInputStream          *stream,
+                                                                       GCancellable          *cancellable,
+                                                                       GAsyncReadyCallback    callback,
+                                                                       gpointer               user_data);
 
-GSList *                latexila_post_processor_get_messages          (LatexilaPostProcessor 
*post_processor);
+void                    latexila_post_processor_process_finish        (LatexilaPostProcessor *pp,
+                                                                       GAsyncResult          *result);
+
+const GNode *           latexila_post_processor_get_messages          (LatexilaPostProcessor *pp);
 
 G_END_DECLS
 


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