[gnome-builder/wip/mwleeds/ide-config-provider: 13/14] flatpak: writeback manifest changes to disk



commit c8be3a7d418c520f02e53274cb3f0473788db3cf
Author: Matthew Leeds <mleeds redhat com>
Date:   Tue Jan 24 17:28:57 2017 -0600

    flatpak: writeback manifest changes to disk
    
    When the user changes build preferences in the Builder UI, those changes
    should propagate back to the manifest file on the disk. This commit
    accomplishes that in a somewhat hack-ish way (by reading the file
    line-by-line and making changes where necessary) so that formatting,
    comments, etc. are preserved that would be lost if JsonBuilder and
    JsonGenerator were used instead.

 .../flatpak/gbp-flatpak-configuration-provider.c   |  514 +++++++++++++++++++-
 1 files changed, 513 insertions(+), 1 deletions(-)
---
diff --git a/plugins/flatpak/gbp-flatpak-configuration-provider.c 
b/plugins/flatpak/gbp-flatpak-configuration-provider.c
index 00edc4a..9152d1b 100644
--- a/plugins/flatpak/gbp-flatpak-configuration-provider.c
+++ b/plugins/flatpak/gbp-flatpak-configuration-provider.c
@@ -30,12 +30,17 @@
 #include "gbp-flatpak-configuration-provider.h"
 #include "gbp-flatpak-configuration.h"
 
+#define WRITEBACK_TIMEOUT_SECS 2
+
 struct _GbpFlatpakConfigurationProvider
 {
   GObject                  parent_instance;
   IdeConfigurationManager *manager;
   GCancellable            *cancellable;
   GPtrArray               *configurations;
+
+  gulong                   writeback_handler;
+  guint                    change_count;
 };
 
 typedef struct
@@ -58,6 +63,503 @@ G_DEFINE_TYPE_EXTENDED (GbpFlatpakConfigurationProvider, gbp_flatpak_configurati
 static void gbp_flatpak_configuration_provider_load (IdeConfigurationProvider *provider, 
IdeConfigurationManager *manager);
 static void gbp_flatpak_configuration_provider_unload (IdeConfigurationProvider *provider, 
IdeConfigurationManager *manager);
 
+static void
+gbp_flatpak_configuration_provider_save_worker (GTask        *task,
+                                                gpointer      source_object,
+                                                gpointer      task_data,
+                                                GCancellable *cancellable)
+{
+  GbpFlatpakConfigurationProvider *self = source_object;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  self->change_count = 0;
+
+  if (self->configurations == NULL)
+    IDE_EXIT;
+
+  for (guint i = 0; i < self->configurations->len; i++)
+    {
+      g_autoptr(GFileInputStream) file_stream = NULL;
+      g_autoptr(GDataInputStream) data_stream = NULL;
+      g_autoptr(GRegex) runtime_regex = NULL;
+      g_autoptr(GRegex) build_options_regex = NULL;
+      g_autoptr(GRegex) config_opts_regex = NULL;
+      g_autoptr(GRegex) primary_module_regex = NULL;
+      g_autoptr(GPtrArray) new_lines = NULL;
+      g_autoptr(GBytes) bytes = NULL;
+      g_auto(GStrv) new_config_opts = NULL;
+      g_auto(GStrv) new_runtime_parts = NULL;
+      g_auto(GStrv) new_environ = NULL;
+      g_autofree gchar *primary_module_regex_str = NULL;
+      g_autofree gchar *primary_module_right_curly_brace = NULL;
+      g_autofree gchar *right_curly_brace_line = NULL;
+      g_autofree gchar *primary_module_indent = NULL;
+      g_autofree gchar *build_options_indent = NULL;
+      g_autofree gchar *config_opt_indent = NULL;
+      g_autofree gchar *array_prefix = NULL;
+      g_autofree gchar *new_config_opts_string = NULL;
+      const gchar *primary_module;
+      const gchar *new_runtime_id;
+      const gchar *config_prefix;
+      const gchar *new_prefix;
+      gchar *json_string;
+      gchar *new_runtime_name;
+      GFile *manifest;
+      gboolean in_config_opts_array;
+      gboolean in_primary_module;
+      gboolean in_build_options;
+      gboolean config_opts_replaced;
+      gboolean build_options_replaced;
+      guint opts_per_line;
+      guint nested_curly_braces;
+
+      GbpFlatpakConfiguration *configuration = (GbpFlatpakConfiguration *)g_ptr_array_index 
(self->configurations, i);
+
+      manifest = gbp_flatpak_configuration_get_manifest (configuration);
+      if (manifest == NULL)
+        continue;
+
+      primary_module = gbp_flatpak_configuration_get_primary_module (configuration);
+      if (primary_module == NULL)
+        {
+          g_warning ("Flatpak manifest configuration has no primary module set");
+          continue;
+        }
+
+      file_stream = g_file_read (manifest, NULL, &error);
+      if (file_stream == NULL)
+        {
+          g_task_return_error (task, error);
+          IDE_EXIT;
+        }
+
+      data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream));
+
+      runtime_regex = g_regex_new ("^\\s*\"runtime\"\\s*:\\s*\"(?<id>.+)\",$", 0, 0, NULL);
+      build_options_regex = g_regex_new ("^\\s*\"build-options\"\\s*:\\s*{$", 0, 0, NULL);
+      config_opts_regex = g_regex_new ("^(\\s*\"config-opts\"\\s*:\\s*\\[\\s*).+$", 0, 0, NULL);
+      primary_module_regex_str = g_strdup_printf ("^(\\s*)\"name\"\\s*:\\s*\"%s\",$", primary_module);
+      primary_module_regex = g_regex_new (primary_module_regex_str, 0, 0, NULL);
+
+      new_runtime_id = ide_configuration_get_runtime_id (IDE_CONFIGURATION (configuration));
+      if (g_str_has_prefix (new_runtime_id, "flatpak:"))
+        {
+          new_runtime_parts = g_strsplit (new_runtime_id + 8, "/", 3);
+          if (new_runtime_parts[0] != NULL)
+            new_runtime_name = new_runtime_parts[0];
+        }
+
+      new_config_opts_string = g_strdup (ide_configuration_get_config_opts (IDE_CONFIGURATION 
(configuration)));
+      if (!ide_str_empty0 (new_config_opts_string))
+        {
+          new_config_opts = g_strsplit (g_strstrip (new_config_opts_string), " ", 0);
+        }
+
+      new_environ = ide_configuration_get_environ (IDE_CONFIGURATION (configuration));
+
+      config_prefix = ide_configuration_get_prefix (IDE_CONFIGURATION (configuration));
+      new_prefix = (g_strcmp0 (config_prefix, "/app") != 0) ? config_prefix : "";
+
+      /**
+       * XXX: The following code, which parses parts of the manifest file and edits
+       * it to match the options chosen by the user in Builder's interface, assumes
+       * that the JSON is "pretty" (meaning it has lots of whitespace and newlines),
+       * which is not technically a requirement for JSON but a de facto standard used
+       * by developers.
+       */
+      new_lines = g_ptr_array_new_with_free_func (g_free);
+      in_config_opts_array = FALSE;
+      in_primary_module = FALSE;
+      in_build_options = FALSE;
+      config_opts_replaced = FALSE;
+      build_options_replaced = FALSE;
+      nested_curly_braces = 0;
+      for (;;)
+        {
+          gchar *line;
+
+          line = g_data_input_stream_read_line_utf8 (data_stream, NULL, NULL, &error);
+          if (error != NULL)
+            {
+              g_task_return_error (task, error);
+              IDE_EXIT;
+            }
+          if (line == NULL)
+            break;
+
+          if (!in_primary_module)
+            {
+              g_autoptr(GMatchInfo) match_info = NULL;
+              g_regex_match (primary_module_regex, line, 0, &match_info);
+              if (g_match_info_matches (match_info))
+                {
+                  gchar *previous_line;
+                  g_auto(GStrv) previous_line_parts = NULL;
+
+                  in_primary_module = TRUE;
+                  primary_module_indent = g_match_info_fetch (match_info, 1);
+
+                  /* Replace '}' with '{' in the last line to get the right indentation */
+                  previous_line = (gchar *)g_ptr_array_index (new_lines, new_lines->len - 1);
+                  previous_line_parts = g_strsplit (previous_line, "{", 0);
+                  primary_module_right_curly_brace = g_strjoinv ("}", previous_line_parts);
+                }
+            }
+
+          /* Replace the runtime with the user-chosen one */
+          if (!ide_str_empty0 (new_runtime_name))
+            {
+              g_autoptr(GMatchInfo) match_info = NULL;
+              g_regex_match (runtime_regex, line, 0, &match_info);
+              if (g_match_info_matches (match_info))
+                {
+                  gchar *old_runtime_ptr;
+                  gchar *new_line;
+                  g_autofree gchar *id = NULL;
+                  id = g_match_info_fetch_named (match_info, "id");
+                  old_runtime_ptr = g_strstr_len (line, -1, id);
+                  *old_runtime_ptr = '\0';
+                  new_line = g_strdup_printf ("%s%s\",", line, new_runtime_name);
+                  g_free (line);
+                  line = new_line;
+                }
+            }
+
+          /* Update the environment variables */
+          if (!in_build_options && !build_options_replaced)
+            {
+              g_autoptr(GMatchInfo) match_info = NULL;
+              g_regex_match (build_options_regex, line, 0, &match_info);
+              if (g_match_info_matches (match_info))
+                {
+                  in_build_options = TRUE;
+                }
+            }
+          else if (in_build_options)
+            {
+              if (g_strstr_len (line, -1, "{") != NULL)
+                nested_curly_braces++;
+              if (g_strstr_len (line, -1, "}") == NULL)
+                {
+                  if (build_options_indent == NULL)
+                    {
+                      g_autoptr(GRegex) build_options_internal_regex = NULL;
+                      g_autoptr(GMatchInfo) match_info = NULL;
+                      build_options_internal_regex = g_regex_new ("^(\\s*)\".+\"\\s*:.*$", 0, 0, NULL);
+                      g_regex_match (build_options_internal_regex, line, 0, &match_info);
+                      if (g_match_info_matches (match_info))
+                        {
+                          build_options_indent = g_match_info_fetch (match_info, 1);
+                        }
+                    }
+                  g_free (line);
+                  continue;
+                }
+              else
+                {
+                  if (nested_curly_braces > 0)
+                    {
+                      nested_curly_braces--;
+                      g_free (line);
+                      continue;
+                    }
+                  else
+                    {
+                      /* We're at the closing curly brace for build-options */
+                      guint num_env;
+                      num_env = g_strv_length (new_environ);
+                      if (num_env > 0)
+                        {
+                          g_autofree gchar *cflags_line = NULL;
+                          g_autofree gchar *cxxflags_line = NULL;
+                          g_autoptr(GPtrArray) env_lines = NULL;
+                          if (build_options_indent == NULL)
+                            build_options_indent = g_strdup ("        ");
+                          for (guint j = 0; new_environ[j]; j++)
+                            {
+                              g_auto(GStrv) line_parts = NULL;
+                              line_parts = g_strsplit (new_environ[j], "=", 2);
+                              if (g_strcmp0 (line_parts[0], "CFLAGS") == 0)
+                                cflags_line = g_strdup_printf ("%s\"cflags\": \"%s\"",
+                                                               build_options_indent,
+                                                               line_parts[1]);
+                              else if (g_strcmp0 (line_parts[0], "CXXFLAGS") == 0)
+                                cxxflags_line = g_strdup_printf ("%s\"cxxflags\": \"%s\"",
+                                                                 build_options_indent,
+                                                                 line_parts[1]);
+                              else
+                                {
+                                  if (env_lines == NULL)
+                                    {
+                                      env_lines = g_ptr_array_new_with_free_func (g_free);
+                                      g_ptr_array_add (env_lines, g_strdup_printf ("%s\"env\": {", 
build_options_indent));
+                                    }
+                                  g_ptr_array_add (env_lines, g_strdup_printf ("%s    \"%s\": \"%s\"",
+                                                                               build_options_indent,
+                                                                               line_parts[0],
+                                                                               line_parts[1]));
+                                }
+                            }
+                          if (cflags_line != NULL)
+                            {
+                              gchar *line_ending;
+                              line_ending = (!ide_str_empty0 (new_prefix) || cxxflags_line != NULL || 
env_lines != NULL) ? "," : "";
+                              g_ptr_array_add (new_lines, g_strdup_printf ("%s%s", cflags_line, 
line_ending));
+                            }
+                          if (cxxflags_line != NULL)
+                            {
+                              gchar *line_ending;
+                              line_ending = (!ide_str_empty0 (new_prefix) || env_lines != NULL) ? "," : "";
+                              g_ptr_array_add (new_lines, g_strdup_printf ("%s%s", cxxflags_line, 
line_ending));
+                            }
+                          if (!ide_str_empty0 (new_prefix))
+                            {
+                              gchar *line_ending;
+                              line_ending = (env_lines != NULL) ? "," : "";
+                              g_ptr_array_add (new_lines, g_strdup_printf ("%s\"prefix\": \"%s\"%s",
+                                                                           build_options_indent,
+                                                                           new_prefix,
+                                                                           line_ending));
+                            }
+                          if (env_lines != NULL)
+                            {
+                              g_ptr_array_add (env_lines, g_strdup_printf ("%s}", build_options_indent));
+                              for (guint j = 0; j < env_lines->len; j++)
+                                {
+                                  gchar *env_line;
+                                  gchar *line_ending;
+                                  line_ending = (j > 0 && j < env_lines->len - 2) ? "," : "";
+                                  env_line = (gchar *)g_ptr_array_index (env_lines, j);
+                                  g_ptr_array_add (new_lines, g_strdup_printf ("%s%s", env_line, 
line_ending));
+                                }
+                            }
+                        }
+                       in_build_options = FALSE;
+                       build_options_replaced = TRUE;
+                    }
+                }
+            }
+
+          if (in_primary_module)
+            {
+              g_autoptr(GMatchInfo) match_info = NULL;
+
+              /* Check if we're at the end of the module and haven't seen a config-opts property */
+              if (g_str_has_prefix (line, primary_module_right_curly_brace))
+                {
+                  in_primary_module = FALSE;
+                  if (!config_opts_replaced && new_config_opts != NULL)
+                    {
+                      gchar *previous_line;
+
+                      previous_line = (gchar *)g_ptr_array_remove_index (new_lines, new_lines->len - 1);
+                      g_ptr_array_add (new_lines, g_strdup_printf ("%s,", previous_line));
+                      right_curly_brace_line = line;
+                      line = g_strdup_printf ("%s\"config-opts\": []", primary_module_indent);
+                    }
+                }
+
+              /* Update the list of configure options, or omit it entirely */
+              g_regex_match (config_opts_regex, line, 0, &match_info);
+              if (g_match_info_matches (match_info) || in_config_opts_array)
+                {
+                  gchar *right_bracket;
+
+                  right_bracket = g_strstr_len (line, -1, "]");
+                  if (g_match_info_matches (match_info))
+                    {
+                      array_prefix = g_match_info_fetch (match_info, 1);
+                      if (right_bracket != NULL)
+                        {
+                          /*
+                           * Ensure that all options will be on one line,
+                           * even if there are more than before
+                           */
+                          if (new_config_opts == NULL)
+                            opts_per_line = 1;
+                          else
+                            opts_per_line = g_strv_length (new_config_opts);
+                        }
+                      else
+                        {
+                          in_config_opts_array = TRUE;
+                          if (new_config_opts == NULL)
+                            opts_per_line = 1;
+                          else
+                            opts_per_line = (g_strv_length (g_strsplit (line, "\"", 0)) - 3) / 2;
+                          g_free (line);
+                          continue;
+                        }
+                    }
+                  if (right_bracket == NULL)
+                    {
+                      in_config_opts_array = TRUE;
+                      config_opt_indent = g_strsplit (line, "\"", 0)[0];
+                      g_free (line);
+                      continue;
+                    }
+
+                  /* At this point it's either a single line or we're on the last line */
+                  in_config_opts_array = FALSE;
+                  config_opts_replaced = TRUE;
+                  if (new_config_opts == NULL)
+                    {
+                      g_free (line);
+                      line = g_strdup_printf ("%s],", array_prefix);
+                    }
+                  else
+                    {
+                      gchar *array_suffix;
+                      array_suffix = *(right_bracket - 1) == ' ' ? " ]" : "]";
+                      if (config_opt_indent == NULL)
+                        {
+                          g_auto(GStrv) line_parts = NULL;
+                          line_parts = g_strsplit (line, "\"", 0);
+                          config_opt_indent = g_strdup (line_parts[0]);
+                        }
+                      for (guint j = 0; g_strv_length (new_config_opts) > j; j += opts_per_line)
+                        {
+                          g_autoptr(GPtrArray) config_opts_subset = NULL;
+                          g_autofree gchar *opts_this_line = NULL;
+                          gchar *prefix;
+                          gchar *suffix;
+                          gchar *new_line;
+
+                          prefix = (j == 0) ? array_prefix : config_opt_indent;
+                          suffix = (g_strv_length (new_config_opts) <= j + opts_per_line) ? array_suffix : 
"";
+                          config_opts_subset = g_ptr_array_new ();
+                          for (guint k = 0; k < opts_per_line && new_config_opts[j+k]; ++k)
+                            g_ptr_array_add (config_opts_subset, new_config_opts[j+k]);
+                          g_ptr_array_add (config_opts_subset, NULL);
+                          opts_this_line = g_strjoinv ("\", \"", (gchar **)config_opts_subset->pdata);
+
+                          new_line = g_strdup_printf ("%s\"%s\"%s,", prefix, opts_this_line, suffix);
+                          g_ptr_array_add (new_lines, new_line);
+                        }
+                      g_free (line);
+                      continue;
+                    }
+                }
+            }
+
+          g_ptr_array_add (new_lines, line);
+          if (right_curly_brace_line != NULL)
+            {
+              g_ptr_array_add (new_lines, right_curly_brace_line);
+              right_curly_brace_line = NULL;
+            }
+        }
+
+      /* Ensure there's a newline at the end of the file */
+      g_ptr_array_add (new_lines, g_strdup (""));
+      g_ptr_array_add (new_lines, NULL);
+      json_string = g_strjoinv ("\n", (gchar **)new_lines->pdata);
+      bytes = g_bytes_new_take (json_string, strlen (json_string));
+
+      if (!g_file_replace_contents (manifest,
+                                    g_bytes_get_data (bytes, NULL),
+                                    g_bytes_get_size (bytes),
+                                    NULL,
+                                    FALSE,
+                                    G_FILE_CREATE_NONE,
+                                    NULL,
+                                    cancellable,
+                                    &error))
+        {
+          g_task_return_error (task, error);
+          IDE_EXIT;
+        }
+    }
+
+  IDE_EXIT;
+}
+
+void
+gbp_flatpak_configuration_provider_save_async (IdeConfigurationProvider *provider,
+                                               GCancellable             *cancellable,
+                                               GAsyncReadyCallback       callback,
+                                               gpointer                  user_data)
+{
+  GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
+  g_autoptr(GTask) task = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (self->change_count == 0)
+    g_task_return_boolean (task, TRUE);
+  else
+    g_task_run_in_thread (task, gbp_flatpak_configuration_provider_save_worker);
+
+  IDE_EXIT;
+}
+
+gboolean
+gbp_flatpak_configuration_provider_save_finish (IdeConfigurationProvider  *provider,
+                                                GAsyncResult              *result,
+                                                GError                   **error)
+{
+  GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
+
+  g_return_val_if_fail (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+gbp_flatpak_configuration_provider_do_writeback (gpointer data)
+{
+  GbpFlatpakConfigurationProvider *self = data;
+
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+
+  self->writeback_handler = 0;
+
+  gbp_flatpak_configuration_provider_save_async (IDE_CONFIGURATION_PROVIDER (self), NULL, NULL, NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gbp_flatpak_configuration_provider_queue_writeback (GbpFlatpakConfigurationProvider *self)
+{
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+
+  IDE_ENTRY;
+
+  if (self->writeback_handler != 0)
+    g_source_remove (self->writeback_handler);
+
+  self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
+                                                   gbp_flatpak_configuration_provider_do_writeback,
+                                                   self);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_flatpak_configuration_provider_changed (GbpFlatpakConfigurationProvider *self,
+                                            IdeConfiguration *configuration)
+{
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
+
+  self->change_count++;
+
+  gbp_flatpak_configuration_provider_queue_writeback (self);
+}
+
 static gboolean
 contains_id (GPtrArray   *ar,
              const gchar *id)
@@ -461,6 +963,12 @@ gbp_flatpak_configuration_provider_load_manifests (GbpFlatpakConfigurationProvid
       if (manifest->config_opts != NULL)
         ide_configuration_set_config_opts (IDE_CONFIGURATION (configuration), manifest->config_opts);
 
+      g_signal_connect_object (configuration,
+                               "changed",
+                               G_CALLBACK (gbp_flatpak_configuration_provider_changed),
+                               self,
+                               G_CONNECT_SWAPPED);
+
       g_ptr_array_add (configurations, configuration);
     }
 
@@ -566,9 +1074,11 @@ gbp_flatpak_configuration_provider_unload (IdeConfigurationProvider *provider,
   g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
   g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
 
+  ide_clear_source (&self->writeback_handler);
+
   if (self->configurations != NULL)
     {
-      for (guint i= 0; i < self->configurations->len; i++)
+      for (guint i = 0; i < self->configurations->len; i++)
         {
           IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
 
@@ -602,4 +1112,6 @@ configuration_provider_iface_init (IdeConfigurationProviderInterface *iface)
 {
   iface->load = gbp_flatpak_configuration_provider_load;
   iface->unload = gbp_flatpak_configuration_provider_unload;
+  iface->save_async = gbp_flatpak_configuration_provider_save_async;
+  iface->save_finish = gbp_flatpak_configuration_provider_save_finish;
 }


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