[gnome-builder/wip/extensions: 1/4] extensions: add basic extension point api



commit 9c6315e671f56393509a422e272ab1bab720ba74
Author: Christian Hergert <christian hergert me>
Date:   Sat Jun 27 00:05:47 2015 -0700

    extensions: add basic extension point api
    
    This should feel familiar to GIOExtensionPoint but allows us to integrate
    with libpeas. In particular, we want the ability to swap out extension
    points when libpeas plugins are loaded or unloaded.
    
    Consumers can listen to the IdeExtensionPoint::changed signal to be
    notified when the extension point has changed the preferred implementation.
    Such a situation would occur when a new plugin is loaded (or existing
    plugin is unloaded) which provides an implementation at a higher priority
    (indicated by a lower integer value).

 libide/Makefile.am           |    2 +
 libide/ide-extension-point.c |  347 ++++++++++++++++++++++++++++++++++++++++++
 libide/ide-extension-point.h |   40 +++++
 3 files changed, 389 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index b658b24..3ae7e10 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -65,6 +65,8 @@ libide_1_0_la_public_sources = \
        ide-executable.h \
        ide-executer.c \
        ide-executer.h \
+       ide-extension-point.c \
+       ide-extension-point.h \
        ide-file-settings.c \
        ide-file-settings.defs \
        ide-file-settings.h \
diff --git a/libide/ide-extension-point.c b/libide/ide-extension-point.c
new file mode 100644
index 0000000..7ab812a
--- /dev/null
+++ b/libide/ide-extension-point.c
@@ -0,0 +1,347 @@
+/* ide-extension-point.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-extension-point"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-extension-point.h"
+
+struct _IdeExtensionPoint
+{
+  GObject      parent_instance;
+  const gchar *name;
+  GPtrArray   *extensions;
+};
+
+typedef struct
+{
+  const gchar *module_name;
+  GType        type;
+  gint         priority;
+} IdeExtension;
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  LAST_PROP
+};
+
+enum {
+  CHANGED,
+  LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (IdeExtensionPoint, ide_extension_point, G_TYPE_OBJECT)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static GRecMutex   gExtensionsMutex;
+static GHashTable *gExtensions;
+static guint       gSignals [LAST_SIGNAL];
+
+static IdeExtension *
+ide_extension_new (GType type,
+                   gint  priority)
+{
+  IdeExtension *exten;
+  GTypePlugin *plugin;
+
+  exten = g_slice_new0 (IdeExtension);
+  exten->type = type;
+  exten->priority = priority;
+
+  plugin = g_type_get_plugin (type);
+
+  if (PEAS_IS_OBJECT_MODULE (plugin))
+    {
+      gchar *module_name;
+
+      g_object_get (plugin, "module-name", &module_name, NULL);
+      exten->module_name = g_intern_string (module_name);
+      g_free (module_name);
+    }
+
+  return exten;
+}
+
+static void
+ide_extension_free (IdeExtension *exten)
+{
+  g_slice_free (IdeExtension, exten);
+}
+
+static void
+ide_extension_point_changed (IdeExtensionPoint *self)
+{
+  g_assert (IDE_IS_EXTENSION_POINT (self));
+
+  g_print ("cahnged!!!\n");
+
+  g_signal_emit (self, gSignals [CHANGED], 0);
+}
+
+static void
+unload_plugin_cb (PeasEngine     *engine,
+                  PeasPluginInfo *plugin_info,
+                  gpointer        unused)
+{
+  const gchar *module_name;
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+
+  g_assert (PEAS_IS_ENGINE (engine));
+  g_assert (plugin_info != NULL);
+
+  /*
+   * FIXME: Since we don't have loader information, we could possibly have
+   *        two plugins from different loaders with the same module name.
+   */
+
+  module_name = peas_plugin_info_get_module_name (plugin_info);
+
+  g_rec_mutex_lock (&gExtensionsMutex);
+
+  g_hash_table_iter_init (&iter, gExtensions);
+
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      IdeExtensionPoint *point = value;
+      gsize i;
+
+      for (i = 0; i < point->extensions->len; i++)
+        {
+          IdeExtension *exten = g_ptr_array_index (point->extensions, i);
+
+          if (g_strcmp0 (exten->module_name, module_name) == 0)
+            {
+              g_ptr_array_remove_index (point->extensions, i);
+              if (i == 0)
+                ide_extension_point_changed (point);
+              ide_extension_free (exten);
+            }
+        }
+    }
+
+  g_rec_mutex_unlock (&gExtensionsMutex);
+}
+
+static void
+ensure_plugin_signals_locked (void)
+{
+  static gsize initialized;
+
+  if (g_once_init_enter (&initialized))
+    {
+      PeasEngine *engine;
+
+      engine = peas_engine_get_default ();
+      g_signal_connect (engine,
+                        "unload-plugin",
+                        G_CALLBACK (unload_plugin_cb),
+                        NULL);
+      g_once_init_leave (&initialized, TRUE);
+    }
+}
+
+static void
+ide_extension_point_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (ide_extension_point_parent_class)->finalize (object);
+}
+
+static void
+ide_extension_point_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeExtensionPoint *self = IDE_EXTENSION_POINT (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_static_string (value, self->name);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_extension_point_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeExtensionPoint *self = IDE_EXTENSION_POINT (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      self->name = g_intern_string (g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_extension_point_class_init (IdeExtensionPointClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_extension_point_finalize;
+  object_class->get_property = ide_extension_point_get_property;
+  object_class->set_property = ide_extension_point_set_property;
+
+  gParamSpecs [PROP_NAME] =
+    g_param_spec_string("name",
+                        _("Name"),
+                        _("Name"),
+                        NULL,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+  gSignals [CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+ide_extension_point_init (IdeExtensionPoint *self)
+{
+  self->extensions = g_ptr_array_new ();
+}
+
+/**
+ * ide_extension_point_lookup:
+ *
+ * Gets an extension point by name.
+ *
+ * Returns: (transfer none): An #IdeExtensionPoint.
+ */
+IdeExtensionPoint *
+ide_extension_point_lookup (const gchar *name)
+{
+  IdeExtensionPoint *self;
+
+  g_rec_mutex_lock (&gExtensionsMutex);
+
+  ensure_plugin_signals_locked ();
+
+  name = g_intern_string (name);
+
+  if (gExtensions == NULL)
+    gExtensions = g_hash_table_new (NULL, NULL);
+
+  self = g_hash_table_lookup (gExtensions, name);
+
+  if (self == NULL)
+    {
+      self = g_object_new (IDE_TYPE_EXTENSION_POINT,
+                           "name", name,
+                           NULL);
+      g_hash_table_insert (gExtensions, (gchar *)name, self);
+    }
+
+  g_rec_mutex_unlock (&gExtensionsMutex);
+
+  return self;
+}
+
+static gint
+compare_extension (gconstpointer a,
+                   gconstpointer b)
+{
+  const IdeExtension *exta = a;
+  const IdeExtension *extb = b;
+
+  if (exta->priority == extb->priority)
+    return g_strcmp0 (g_type_name (exta->type), g_type_name (extb->type));
+
+  return exta->priority - extb->priority;
+}
+
+void
+ide_extension_point_implement (const gchar *name,
+                               GType        implementation_type,
+                               gint         priority)
+{
+  IdeExtensionPoint *self;
+  IdeExtension *exten;
+  gboolean emit_changed = TRUE;
+
+  g_return_if_fail (name);
+  g_return_if_fail (*name != '\0');
+  g_return_if_fail (implementation_type != G_TYPE_INVALID);
+  g_return_if_fail (G_TYPE_IS_OBJECT (implementation_type));
+
+  self = ide_extension_point_lookup (name);
+  exten = ide_extension_new (implementation_type, priority);
+
+  if (self->extensions->len > 0)
+    {
+      IdeExtension *first = g_ptr_array_index (self->extensions, 0);
+
+      if (first->priority < priority)
+        emit_changed = FALSE;
+    }
+
+  g_ptr_array_add (self->extensions, exten);
+  g_ptr_array_sort (self->extensions, compare_extension);
+
+  if (emit_changed)
+    g_signal_emit (self, gSignals [CHANGED], 0);
+}
+
+/**
+ * ide_extension_point_create:
+ *
+ * Creates a new instance of an extension point based on priority.
+ *
+ * Returns: (transfer full) (nullable) (type GObject.Object): A new #GObject or %NULL.
+ */
+gpointer
+ide_extension_point_create (const gchar *name,
+                            const gchar *first_property,
+                            ...)
+{
+  IdeExtensionPoint *self;
+  gpointer ret = NULL;
+  va_list args;
+
+  self = ide_extension_point_lookup (name);
+
+  if (self->extensions->len > 0)
+    {
+      IdeExtension *exten = g_ptr_array_index (self->extensions, 0);
+
+      va_start (args, first_property);
+      ret = g_object_new_valist (exten->type, first_property, args);
+      va_end (args);
+    }
+
+  return ret;
+}
diff --git a/libide/ide-extension-point.h b/libide/ide-extension-point.h
new file mode 100644
index 0000000..e917a68
--- /dev/null
+++ b/libide/ide-extension-point.h
@@ -0,0 +1,40 @@
+/* ide-extension-point.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_EXTENSION_POINT_H
+#define IDE_EXTENSION_POINT_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EXTENSION_POINT (ide_extension_point_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeExtensionPoint, ide_extension_point, IDE, EXTENSION_POINT, GObject)
+
+IdeExtensionPoint *ide_extension_point_lookup    (const gchar *name);
+void               ide_extension_point_implement (const gchar *name,
+                                                  GType        implementation_type,
+                                                  gint         priority);
+gpointer           ide_extension_point_create    (const gchar *name,
+                                                  const gchar *first_property,
+                                                  ...);
+
+G_END_DECLS
+
+#endif /* IDE_EXTENSION_POINT_H */


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