[gnome-builder/wip/gtk4-port: 1470/1774] plugins/shellcmd: add model which can live update




commit 738c95011b0d0e0e315aa699fb1a5cc547513bae
Author: Christian Hergert <chergert redhat com>
Date:   Fri Jun 10 20:58:15 2022 -0700

    plugins/shellcmd: add model which can live update
    
    This way we don't have to requery things to keep them in sync. We can just
    expect that the shellcmd list model will update with GSettings.

 src/plugins/shellcmd/gbp-shellcmd-command-model.c  | 302 +++++++++++++++++++++
 src/plugins/shellcmd/gbp-shellcmd-command-model.h  |  34 +++
 .../shellcmd/gbp-shellcmd-run-command-provider.c   |  54 ++--
 src/plugins/shellcmd/meson.build                   |   1 +
 4 files changed, 356 insertions(+), 35 deletions(-)
---
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-model.c 
b/src/plugins/shellcmd/gbp-shellcmd-command-model.c
new file mode 100644
index 000000000..fbfb27422
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-model.c
@@ -0,0 +1,302 @@
+/* gbp-shellcmd-command-model.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-shellcmd-command-model"
+
+#include "config.h"
+
+#include "gbp-shellcmd-command-model.h"
+#include "gbp-shellcmd-run-command.h"
+
+struct _GbpShellcmdCommandModel
+{
+  GObject      parent_instance;
+  GSettings   *settings;
+  char        *key;
+  GHashTable  *id_to_command;
+  char       **ids;
+  guint        n_items;
+};
+
+enum {
+  PROP_0,
+  PROP_KEY,
+  PROP_SETTINGS,
+  PROP_N_ITEMS,
+  N_PROPS
+};
+
+static gpointer
+gbp_shellcmd_command_model_get_item (GListModel *model,
+                                     guint       position)
+{
+  GbpShellcmdCommandModel *self = (GbpShellcmdCommandModel *)model;
+  GbpShellcmdRunCommand *command;
+  const char *id;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_MODEL (self));
+
+  if (position >= self->n_items)
+    return NULL;
+
+  id = self->ids[position];
+  command = g_hash_table_lookup (self->id_to_command, id);
+
+  if (command == NULL)
+    {
+      g_autofree char *base_path = NULL;
+      g_autofree char *settings_path = NULL;
+
+      g_object_get (self->settings,
+                    "path", &base_path,
+                    NULL);
+      settings_path = g_strconcat (base_path, id, "/", NULL);
+      command = gbp_shellcmd_run_command_new (settings_path);
+      g_hash_table_insert (self->id_to_command, g_strdup (id), command);
+    }
+
+  return g_object_ref (command);
+}
+
+static guint
+gbp_shellcmd_command_model_get_n_items (GListModel *model)
+{
+  return GBP_SHELLCMD_COMMAND_MODEL (model)->n_items;
+}
+
+static GType
+gbp_shellcmd_command_model_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_RUN_COMMAND;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_n_items = gbp_shellcmd_command_model_get_n_items;
+  iface->get_item = gbp_shellcmd_command_model_get_item;
+  iface->get_item_type = gbp_shellcmd_command_model_get_item_type;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpShellcmdCommandModel, gbp_shellcmd_command_model, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_shellcmd_command_model_replace (GbpShellcmdCommandModel  *self,
+                                    char                    **commands)
+{
+  guint old_len;
+  guint new_len;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_MODEL (self));
+  g_assert (self->ids != NULL);
+  g_assert (commands != NULL);
+
+  if (g_strv_equal ((const char * const *)self->ids,
+                    (const char * const *)commands))
+    {
+      g_strfreev (commands);
+      return;
+    }
+
+  old_len = g_strv_length (self->ids);
+  new_len = g_strv_length (commands);
+
+  for (guint i = 0; i < self->n_items; i++)
+    {
+      if (!g_strv_contains ((const char * const *)commands, self->ids[i]))
+        g_hash_table_remove (self->id_to_command, self->ids[i]);
+    }
+
+  g_strfreev (self->ids);
+  self->ids = g_steal_pointer (&commands);
+  self->n_items = new_len;
+
+  g_list_model_items_changed (G_LIST_MODEL (self), 0, old_len, new_len);
+
+  if (old_len != new_len)
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ITEMS]);
+}
+
+static void
+gbp_shellcmd_command_model_settings_changed_cb (GbpShellcmdCommandModel *self,
+                                                const char              *key,
+                                                GSettings               *settings)
+{
+  g_auto(GStrv) commands = NULL;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_MODEL (self));
+  g_assert (ide_str_equal0 (key, self->key));
+  g_assert (G_IS_SETTINGS (settings));
+
+  commands = g_settings_get_strv (settings, key);
+
+  gbp_shellcmd_command_model_replace (self, g_steal_pointer (&commands));
+}
+
+static void
+gbp_shellcmd_command_model_constructed (GObject *object)
+{
+  GbpShellcmdCommandModel *self = (GbpShellcmdCommandModel *)object;
+  g_autofree char *signal_name = NULL;
+  g_auto(GStrv) commands = NULL;
+
+  G_OBJECT_CLASS (gbp_shellcmd_command_model_parent_class)->constructed (object);
+
+  g_assert (self->key != NULL);
+  g_assert (G_IS_SETTINGS (self->settings));
+
+  signal_name = g_strconcat ("changed::", self->key, NULL);
+
+  g_signal_connect_object (self->settings,
+                           signal_name,
+                           G_CALLBACK (gbp_shellcmd_command_model_settings_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->ids = g_settings_get_strv (self->settings, self->key);
+  self->n_items = g_strv_length (self->ids);
+}
+
+static void
+gbp_shellcmd_command_model_dispose (GObject *object)
+{
+  GbpShellcmdCommandModel *self = (GbpShellcmdCommandModel *)object;
+
+  g_clear_pointer (&self->key, g_free);
+  g_clear_pointer (&self->id_to_command, g_hash_table_unref);
+  g_clear_pointer (&self->ids, g_strfreev);
+
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (gbp_shellcmd_command_model_parent_class)->dispose (object);
+}
+
+static void
+gbp_shellcmd_command_model_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  GbpShellcmdCommandModel *self = GBP_SHELLCMD_COMMAND_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_KEY:
+      g_value_set_string (value, self->key);
+      break;
+
+    case PROP_SETTINGS:
+      g_value_set_object (value, self->settings);
+      break;
+
+    case PROP_N_ITEMS:
+      g_value_set_uint (value, self->n_items);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shellcmd_command_model_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  GbpShellcmdCommandModel *self = GBP_SHELLCMD_COMMAND_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_KEY:
+      self->key = g_value_dup_string (value);
+      break;
+
+    case PROP_SETTINGS:
+      self->settings = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shellcmd_command_model_class_init (GbpShellcmdCommandModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = gbp_shellcmd_command_model_constructed;
+  object_class->dispose = gbp_shellcmd_command_model_dispose;
+  object_class->get_property = gbp_shellcmd_command_model_get_property;
+  object_class->set_property = gbp_shellcmd_command_model_set_property;
+
+  properties [PROP_KEY] =
+    g_param_spec_string ("key", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SETTINGS] =
+    g_param_spec_object ("settings", NULL, NULL,
+                         G_TYPE_SETTINGS,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_N_ITEMS] =
+    g_param_spec_uint ("n-items", NULL, NULL,
+                       0, G_MAXUINT, 0,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_shellcmd_command_model_init (GbpShellcmdCommandModel *self)
+{
+  self->id_to_command = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+GbpShellcmdCommandModel *
+gbp_shellcmd_command_model_new (GSettings  *settings,
+                                const char *key)
+{
+  g_autoptr(GSettingsSchema) schema = NULL;
+
+  g_return_val_if_fail (G_SETTINGS (settings), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  g_object_get (settings,
+                "settings-schema", &schema,
+                NULL);
+
+  g_return_val_if_fail (schema != NULL, NULL);
+  g_return_val_if_fail (g_settings_schema_has_key (schema, key), NULL);
+
+  return g_object_new (GBP_TYPE_SHELLCMD_COMMAND_MODEL,
+                       "settings", settings,
+                       "key", key,
+                       NULL);
+}
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-model.h 
b/src/plugins/shellcmd/gbp-shellcmd-command-model.h
new file mode 100644
index 000000000..201c739e0
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-model.h
@@ -0,0 +1,34 @@
+/* gbp-shellcmd-command-model.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHELLCMD_COMMAND_MODEL (gbp_shellcmd_command_model_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShellcmdCommandModel, gbp_shellcmd_command_model, GBP, SHELLCMD_COMMAND_MODEL, 
GObject)
+
+GbpShellcmdCommandModel *gbp_shellcmd_command_model_new (GSettings  *settings,
+                                                         const char *key);
+
+G_END_DECLS
diff --git a/src/plugins/shellcmd/gbp-shellcmd-run-command-provider.c 
b/src/plugins/shellcmd/gbp-shellcmd-run-command-provider.c
index 0193b3620..72e4a393c 100644
--- a/src/plugins/shellcmd/gbp-shellcmd-run-command-provider.c
+++ b/src/plugins/shellcmd/gbp-shellcmd-run-command-provider.c
@@ -25,6 +25,7 @@
 
 #include <libide-threading.h>
 
+#include "gbp-shellcmd-command-model.h"
 #include "gbp-shellcmd-run-command.h"
 #include "gbp-shellcmd-run-command-provider.h"
 
@@ -33,41 +34,17 @@ struct _GbpShellcmdRunCommandProvider
   IdeObject parent_instance;
 };
 
-static void
-gbp_shellcmd_run_command_provider_populate (GListStore *store,
-                                            const char *settings_path)
-{
-  g_autoptr(GSettings) settings = NULL;
-  g_auto(GStrv) run_commands = NULL;
-
-  IDE_ENTRY;
-
-  g_assert (G_IS_LIST_STORE (store));
-  g_assert (settings_path != NULL);
-
-  IDE_TRACE_MSG ("Adding commands to GListStore %p from %s", store, settings_path);
-
-  settings = g_settings_new_with_path ("org.gnome.builder.shellcmd", settings_path);
-  run_commands = g_settings_get_strv (settings, "run-commands");
-
-  for (guint i = 0; run_commands[i]; i++)
-    {
-      g_autofree char *run_settings_path = g_strconcat (settings_path, run_commands[i], "/", NULL);
-      g_autoptr(GbpShellcmdRunCommand) run_command = gbp_shellcmd_run_command_new (run_settings_path);
-
-      g_list_store_append (store, run_command);
-    }
-
-  IDE_EXIT;
-}
-
 static void
 gbp_shellcmd_run_command_provider_list_commands_async (IdeRunCommandProvider *provider,
                                                        GCancellable          *cancellable,
                                                        GAsyncReadyCallback    callback,
                                                        gpointer               user_data)
 {
+  g_autoptr(GbpShellcmdCommandModel) app_commands = NULL;
+  g_autoptr(GbpShellcmdCommandModel) project_commands = NULL;
   g_autoptr(GListStore) store = NULL;
+  g_autoptr(GSettings) app_settings = NULL;
+  g_autoptr(GSettings) project_settings = NULL;
   g_autoptr(IdeTask) task = NULL;
   g_autofree char *project_id = NULL;
   g_autofree char *project_settings_path = NULL;
@@ -81,18 +58,25 @@ gbp_shellcmd_run_command_provider_list_commands_async (IdeRunCommandProvider *pr
   task = ide_task_new (provider, cancellable, callback, user_data);
   ide_task_set_source_tag (task, gbp_shellcmd_run_command_provider_list_commands_async);
 
-  store = g_list_store_new (IDE_TYPE_RUN_COMMAND);
-
-  /* Add project shell commands so they resolve first */
   context = ide_object_get_context (IDE_OBJECT (provider));
   project_id = ide_context_dup_project_id (context);
   project_settings_path = g_strconcat (SHELLCMD_SETTINGS_BASE, "projects/", project_id, "/", NULL);
-  gbp_shellcmd_run_command_provider_populate (store, project_settings_path);
 
-  /* Then application-wide commands for lower priority */
-  gbp_shellcmd_run_command_provider_populate (store, SHELLCMD_SETTINGS_BASE);
+  app_settings = g_settings_new_with_path ("org.gnome.builder.shellcmd", SHELLCMD_SETTINGS_BASE);
+  project_settings = g_settings_new_with_path ("org.gnome.builder.shellcmd", project_settings_path);
+
+  app_commands = gbp_shellcmd_command_model_new (app_settings, "run-commands");
+  project_commands = gbp_shellcmd_command_model_new (project_settings, "run-commands");
+
+  store = g_list_store_new (G_TYPE_LIST_MODEL);
+  g_list_store_append (store, project_commands);
+  g_list_store_append (store, app_commands);
 
-  ide_task_return_pointer (task, g_steal_pointer (&store), g_object_unref);
+  ide_task_return_pointer (task,
+                           g_object_new (GTK_TYPE_FLATTEN_LIST_MODEL,
+                                         "model", store,
+                                         NULL),
+                           g_object_unref);
 
   IDE_EXIT;
 }
diff --git a/src/plugins/shellcmd/meson.build b/src/plugins/shellcmd/meson.build
index 75abde37c..17fa70330 100644
--- a/src/plugins/shellcmd/meson.build
+++ b/src/plugins/shellcmd/meson.build
@@ -2,6 +2,7 @@ if get_option('plugin_shellcmd')
 
 plugins_sources += files([
   'shellcmd-plugin.c',
+  'gbp-shellcmd-command-model.c',
   'gbp-shellcmd-list.c',
   'gbp-shellcmd-preferences-addin.c',
   'gbp-shellcmd-run-command.c',


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