[latexila/wip/build-tools-revamp] PostProcessor and PostProcessorAllOutput (not finished)



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

    PostProcessor and PostProcessorAllOutput (not finished)
    
    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. This big string was split into lines, and then processed.
    
    One exception: the latexmk post-processor worked on the full output with
    a big regex, without splitting the output into lines (splitting the
    lines was delegated to the all-output and latex post-processors, called
    by the latexmk post-processor after extracting the relevant contents
    from the big string).
    
    Next steps:
    - write the latex post-processor (the Vala code works already line by
    line, so working on the stream will use the same algorithm). One
    difference though: the latex post-processor should read the log file,
    not the command output.
    
    - write the latexmk post-processor. Working on the stream (line by line)
    will be more complicated, the big regex cannot be reused (unless the
    command output is constructed to obtain one big string, but I don't want
    this solution, it uses more memory and is thus most probably slower).
    
    - write unit tests!

 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]