1 /* ide-extension-set-adapter.c
2  *
3  * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-extension-set-adapter"
22 
23 #include "config.h"
24 
25 #include <dazzle.h>
26 #include <glib/gi18n.h>
27 #include <stdlib.h>
28 
29 #include "ide-extension-set-adapter.h"
30 #include "ide-extension-util-private.h"
31 
32 struct _IdeExtensionSetAdapter
33 {
34   IdeObject   parent_instance;
35 
36   PeasEngine *engine;
37   gchar      *key;
38   gchar      *value;
39   GHashTable *extensions;
40   GPtrArray  *settings;
41 
42   GType       interface_type;
43 
44   guint       reload_handler;
45 };
46 
47 G_DEFINE_FINAL_TYPE (IdeExtensionSetAdapter, ide_extension_set_adapter, IDE_TYPE_OBJECT)
48 
49 enum {
50   EXTENSIONS_LOADED,
51   EXTENSION_ADDED,
52   EXTENSION_REMOVED,
53   LAST_SIGNAL
54 };
55 
56 enum {
57   PROP_0,
58   PROP_ENGINE,
59   PROP_INTERFACE_TYPE,
60   PROP_KEY,
61   PROP_VALUE,
62   LAST_PROP
63 };
64 
65 static GParamSpec *properties [LAST_PROP];
66 static guint signals [LAST_SIGNAL];
67 
68 static void ide_extension_set_adapter_queue_reload (IdeExtensionSetAdapter *);
69 
70 static gchar *
ide_extension_set_adapter_repr(IdeObject * object)71 ide_extension_set_adapter_repr (IdeObject *object)
72 {
73   IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
74 
75   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
76 
77   return g_strdup_printf ("%s interface=\"%s\" key=\"%s\" value=\"%s\"",
78                           G_OBJECT_TYPE_NAME (self),
79                           g_type_name (self->interface_type),
80                           self->key ?: "",
81                           self->value ?: "");
82 }
83 
84 static void
add_extension(IdeExtensionSetAdapter * self,PeasPluginInfo * plugin_info,PeasExtension * exten)85 add_extension (IdeExtensionSetAdapter *self,
86                PeasPluginInfo         *plugin_info,
87                PeasExtension          *exten)
88 {
89   g_assert (IDE_IS_MAIN_THREAD ());
90   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
91   g_assert (plugin_info != NULL);
92   g_assert (exten != NULL);
93   g_assert (g_type_is_a (G_OBJECT_TYPE (exten), self->interface_type));
94 
95   g_hash_table_insert (self->extensions, plugin_info, exten);
96 
97   /* Ensure that we take the reference in case it's a floating ref */
98   if (G_IS_INITIALLY_UNOWNED (exten) && g_object_is_floating (exten))
99     g_object_ref_sink (exten);
100 
101   /*
102    * If the plugin object turned out to have IdeObject as a
103    * base, make it a child of ourselves, because we're an
104    * IdeObject too and that gives it access to the context.
105    */
106   if (IDE_IS_OBJECT (exten))
107     ide_object_append (IDE_OBJECT (self), IDE_OBJECT (exten));
108 
109   g_signal_emit (self, signals [EXTENSION_ADDED], 0, plugin_info, exten);
110 }
111 
112 static void
remove_extension(IdeExtensionSetAdapter * self,PeasPluginInfo * plugin_info,PeasExtension * exten)113 remove_extension (IdeExtensionSetAdapter *self,
114                   PeasPluginInfo         *plugin_info,
115                   PeasExtension          *exten)
116 {
117   g_autoptr(GObject) hold = NULL;
118 
119   g_assert (IDE_IS_MAIN_THREAD ());
120   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
121   g_assert (plugin_info != NULL);
122   g_assert (exten != NULL);
123   g_assert (self->interface_type == G_TYPE_INVALID ||
124             g_type_is_a (G_OBJECT_TYPE (exten), self->interface_type));
125 
126   hold = g_object_ref (exten);
127 
128   g_hash_table_remove (self->extensions, plugin_info);
129   g_signal_emit (self, signals [EXTENSION_REMOVED], 0, plugin_info, hold);
130 
131   if (IDE_IS_OBJECT (hold))
132     ide_object_destroy (IDE_OBJECT (hold));
133 }
134 
135 static void
ide_extension_set_adapter_enabled_changed(IdeExtensionSetAdapter * self,const gchar * key,GSettings * settings)136 ide_extension_set_adapter_enabled_changed (IdeExtensionSetAdapter *self,
137                                            const gchar            *key,
138                                            GSettings              *settings)
139 {
140   g_assert (IDE_IS_MAIN_THREAD ());
141   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
142   g_assert (key != NULL);
143   g_assert (G_IS_SETTINGS (settings));
144 
145   ide_extension_set_adapter_queue_reload (self);
146 }
147 
148 static void
watch_extension(IdeExtensionSetAdapter * self,PeasPluginInfo * plugin_info,GType interface_type)149 watch_extension (IdeExtensionSetAdapter *self,
150                  PeasPluginInfo         *plugin_info,
151                  GType                   interface_type)
152 {
153   GSettings *settings;
154   gchar *path;
155 
156   g_assert (IDE_IS_MAIN_THREAD ());
157   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
158   g_assert (plugin_info != NULL);
159   g_assert (G_TYPE_IS_INTERFACE (interface_type) || G_TYPE_IS_OBJECT (interface_type));
160 
161   path = g_strdup_printf ("/org/gnome/builder/extension-types/%s/%s/",
162                           peas_plugin_info_get_module_name (plugin_info),
163                           g_type_name (interface_type));
164   settings = g_settings_new_with_path ("org.gnome.builder.extension-type", path);
165 
166   g_ptr_array_add (self->settings, g_object_ref (settings));
167 
168   g_signal_connect_object (settings,
169                            "changed::enabled",
170                            G_CALLBACK (ide_extension_set_adapter_enabled_changed),
171                            self,
172                            G_CONNECT_SWAPPED);
173 
174   g_object_unref (settings);
175   g_free (path);
176 }
177 
178 static void
ide_extension_set_adapter_reload(IdeExtensionSetAdapter * self)179 ide_extension_set_adapter_reload (IdeExtensionSetAdapter *self)
180 {
181   const GList *plugins;
182 
183   g_assert (IDE_IS_MAIN_THREAD ());
184   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
185   g_assert (self->interface_type != G_TYPE_INVALID);
186 
187   while (self->settings->len > 0)
188     {
189       GSettings *settings;
190 
191       settings = g_ptr_array_index (self->settings, self->settings->len - 1);
192       g_signal_handlers_disconnect_by_func (settings,
193                                             ide_extension_set_adapter_enabled_changed,
194                                             self);
195       g_ptr_array_remove_index (self->settings, self->settings->len - 1);
196     }
197 
198   plugins = peas_engine_get_plugin_list (self->engine);
199 
200   for (; plugins; plugins = plugins->next)
201     {
202       PeasPluginInfo *plugin_info = plugins->data;
203       gint priority;
204 
205       if (!peas_plugin_info_is_loaded (plugin_info))
206         continue;
207 
208       if (!peas_engine_provides_extension (self->engine, plugin_info, self->interface_type))
209         continue;
210 
211       watch_extension (self, plugin_info, self->interface_type);
212 
213       if (ide_extension_util_can_use_plugin (self->engine,
214                                              plugin_info,
215                                              self->interface_type,
216                                              self->key,
217                                              self->value,
218                                              &priority))
219         {
220           if (!g_hash_table_contains (self->extensions, plugin_info))
221             {
222               PeasExtension *exten;
223 
224               exten = ide_extension_new (self->engine,
225                                          plugin_info,
226                                          self->interface_type,
227                                          NULL);
228 
229               add_extension (self, plugin_info, exten);
230             }
231         }
232       else
233         {
234           PeasExtension *exten;
235 
236           if ((exten = g_hash_table_lookup (self->extensions, plugin_info)))
237             remove_extension (self, plugin_info, exten);
238         }
239     }
240 
241   g_signal_emit (self, signals [EXTENSIONS_LOADED], 0);
242 }
243 
244 static gboolean
ide_extension_set_adapter_do_reload(gpointer data)245 ide_extension_set_adapter_do_reload (gpointer data)
246 {
247   IdeExtensionSetAdapter *self = data;
248 
249   g_assert (IDE_IS_MAIN_THREAD ());
250   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
251 
252   self->reload_handler = 0;
253 
254   if (self->interface_type != G_TYPE_INVALID)
255     ide_extension_set_adapter_reload (self);
256 
257   return G_SOURCE_REMOVE;
258 }
259 
260 static void
ide_extension_set_adapter_queue_reload(IdeExtensionSetAdapter * self)261 ide_extension_set_adapter_queue_reload (IdeExtensionSetAdapter *self)
262 {
263   g_assert (IDE_IS_MAIN_THREAD ());
264   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
265 
266   g_clear_handle_id (&self->reload_handler, g_source_remove);
267 
268   self->reload_handler = g_idle_add_full (G_PRIORITY_HIGH,
269                                           ide_extension_set_adapter_do_reload,
270                                           self,
271                                           NULL);
272 }
273 
274 static void
ide_extension_set_adapter_load_plugin(IdeExtensionSetAdapter * self,PeasPluginInfo * plugin_info,PeasEngine * engine)275 ide_extension_set_adapter_load_plugin (IdeExtensionSetAdapter *self,
276                                        PeasPluginInfo         *plugin_info,
277                                        PeasEngine             *engine)
278 {
279   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
280   g_assert (plugin_info != NULL);
281   g_assert (PEAS_IS_ENGINE (engine));
282 
283   ide_extension_set_adapter_queue_reload (self);
284 }
285 
286 static void
ide_extension_set_adapter_unload_plugin(IdeExtensionSetAdapter * self,PeasPluginInfo * plugin_info,PeasEngine * engine)287 ide_extension_set_adapter_unload_plugin (IdeExtensionSetAdapter *self,
288                                          PeasPluginInfo         *plugin_info,
289                                          PeasEngine             *engine)
290 {
291   PeasExtension *exten;
292 
293   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
294   g_assert (plugin_info != NULL);
295   g_assert (PEAS_IS_ENGINE (engine));
296 
297   if ((exten = g_hash_table_lookup (self->extensions, plugin_info)))
298     {
299       remove_extension (self, plugin_info, exten);
300       g_hash_table_remove (self->extensions, plugin_info);
301     }
302 }
303 
304 static void
ide_extension_set_adapter_set_engine(IdeExtensionSetAdapter * self,PeasEngine * engine)305 ide_extension_set_adapter_set_engine (IdeExtensionSetAdapter *self,
306                                       PeasEngine             *engine)
307 {
308   g_assert (IDE_IS_MAIN_THREAD ());
309   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
310   g_assert (!engine || PEAS_IS_ENGINE (engine));
311 
312   if (engine == NULL)
313     engine = peas_engine_get_default ();
314 
315   if (g_set_object (&self->engine, engine))
316     {
317       g_signal_connect_object (self->engine, "load-plugin",
318                                G_CALLBACK (ide_extension_set_adapter_load_plugin),
319                                self,
320                                G_CONNECT_AFTER | G_CONNECT_SWAPPED);
321       g_signal_connect_object (self->engine, "unload-plugin",
322                                G_CALLBACK (ide_extension_set_adapter_unload_plugin),
323                                self,
324                                G_CONNECT_SWAPPED);
325       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENGINE]);
326       ide_extension_set_adapter_queue_reload (self);
327     }
328 }
329 
330 static void
ide_extension_set_adapter_set_interface_type(IdeExtensionSetAdapter * self,GType interface_type)331 ide_extension_set_adapter_set_interface_type (IdeExtensionSetAdapter *self,
332                                               GType                   interface_type)
333 {
334   g_assert (IDE_IS_MAIN_THREAD ());
335   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
336   g_assert (G_TYPE_IS_INTERFACE (interface_type) || G_TYPE_IS_OBJECT (interface_type));
337 
338   if (interface_type != self->interface_type)
339     {
340       self->interface_type = interface_type;
341       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INTERFACE_TYPE]);
342       ide_extension_set_adapter_queue_reload (self);
343     }
344 }
345 
346 static void
ide_extension_set_adapter_destroy(IdeObject * object)347 ide_extension_set_adapter_destroy (IdeObject *object)
348 {
349   IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
350   g_autoptr(GHashTable) extensions = NULL;
351   GHashTableIter iter;
352   gpointer key;
353   gpointer value;
354 
355   g_assert (IDE_IS_MAIN_THREAD ());
356   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
357 
358   self->interface_type = G_TYPE_INVALID;
359   g_clear_handle_id (&self->reload_handler, g_source_remove);
360 
361   /*
362    * Steal the extensions so we can be re-entrant safe and not break
363    * any assumptions about extensions being a real pointer.
364    */
365   extensions = g_steal_pointer (&self->extensions);
366   self->extensions = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
367 
368   g_hash_table_iter_init (&iter, extensions);
369 
370   while (g_hash_table_iter_next (&iter, &key, &value))
371     {
372       PeasPluginInfo *plugin_info = key;
373       PeasExtension *exten = value;
374 
375       remove_extension (self, plugin_info, exten);
376       g_hash_table_iter_remove (&iter);
377     }
378 
379   IDE_OBJECT_CLASS (ide_extension_set_adapter_parent_class)->destroy (object);
380 }
381 
382 static void
ide_extension_set_adapter_finalize(GObject * object)383 ide_extension_set_adapter_finalize (GObject *object)
384 {
385   IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
386 
387   while (self->settings->len > 0)
388     {
389       guint i = self->settings->len - 1;
390       GSettings *settings = g_ptr_array_index (self->settings, i);
391 
392       g_signal_handlers_disconnect_by_func (settings,
393                                             ide_extension_set_adapter_enabled_changed,
394                                             self);
395       g_ptr_array_remove_index (self->settings, i);
396     }
397 
398   g_clear_object (&self->engine);
399   g_clear_pointer (&self->key, g_free);
400   g_clear_pointer (&self->value, g_free);
401   g_clear_pointer (&self->extensions, g_hash_table_unref);
402   g_clear_pointer (&self->settings, g_ptr_array_unref);
403 
404   G_OBJECT_CLASS (ide_extension_set_adapter_parent_class)->finalize (object);
405 }
406 
407 static void
ide_extension_set_adapter_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)408 ide_extension_set_adapter_get_property (GObject    *object,
409                                         guint       prop_id,
410                                         GValue     *value,
411                                         GParamSpec *pspec)
412 {
413   IdeExtensionSetAdapter *self = IDE_EXTENSION_SET_ADAPTER (object);
414 
415   switch (prop_id)
416     {
417     case PROP_ENGINE:
418       g_value_set_object (value, ide_extension_set_adapter_get_engine (self));
419       break;
420 
421     case PROP_INTERFACE_TYPE:
422       g_value_set_gtype (value, ide_extension_set_adapter_get_interface_type (self));
423       break;
424 
425     case PROP_KEY:
426       g_value_set_string (value, ide_extension_set_adapter_get_key (self));
427       break;
428 
429     case PROP_VALUE:
430       g_value_set_string (value, ide_extension_set_adapter_get_value (self));
431       break;
432 
433     default:
434       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
435     }
436 }
437 
438 static void
ide_extension_set_adapter_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)439 ide_extension_set_adapter_set_property (GObject      *object,
440                                         guint         prop_id,
441                                         const GValue *value,
442                                         GParamSpec   *pspec)
443 {
444   IdeExtensionSetAdapter *self = IDE_EXTENSION_SET_ADAPTER (object);
445 
446   switch (prop_id)
447     {
448     case PROP_ENGINE:
449       ide_extension_set_adapter_set_engine (self, g_value_get_object (value));
450       break;
451 
452     case PROP_INTERFACE_TYPE:
453       ide_extension_set_adapter_set_interface_type (self, g_value_get_gtype (value));
454       break;
455 
456     case PROP_KEY:
457       ide_extension_set_adapter_set_key (self, g_value_get_string (value));
458       break;
459 
460     case PROP_VALUE:
461       ide_extension_set_adapter_set_value (self, g_value_get_string (value));
462       break;
463 
464     default:
465       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
466     }
467 }
468 
469 static void
ide_extension_set_adapter_class_init(IdeExtensionSetAdapterClass * klass)470 ide_extension_set_adapter_class_init (IdeExtensionSetAdapterClass *klass)
471 {
472   GObjectClass *object_class = G_OBJECT_CLASS (klass);
473   IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
474 
475   object_class->finalize = ide_extension_set_adapter_finalize;
476   object_class->get_property = ide_extension_set_adapter_get_property;
477   object_class->set_property = ide_extension_set_adapter_set_property;
478 
479   i_object_class->destroy = ide_extension_set_adapter_destroy;
480   i_object_class->repr = ide_extension_set_adapter_repr;
481 
482   properties [PROP_ENGINE] =
483     g_param_spec_object ("engine",
484                          "Engine",
485                          "Engine",
486                          PEAS_TYPE_ENGINE,
487                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
488 
489   properties [PROP_INTERFACE_TYPE] =
490     g_param_spec_gtype ("interface-type",
491                         "Interface Type",
492                         "Interface Type",
493                         G_TYPE_OBJECT,
494                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
495 
496   properties [PROP_KEY] =
497     g_param_spec_string ("key",
498                          "Key",
499                          "Key",
500                          NULL,
501                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
502 
503   properties [PROP_VALUE] =
504     g_param_spec_string ("value",
505                          "Value",
506                          "Value",
507                          NULL,
508                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
509 
510   g_object_class_install_properties (object_class, LAST_PROP, properties);
511 
512   signals [EXTENSION_ADDED] =
513     g_signal_new ("extension-added",
514                   G_TYPE_FROM_CLASS (klass),
515                   G_SIGNAL_RUN_LAST,
516                   0,
517                   NULL, NULL, NULL,
518                   G_TYPE_NONE,
519                   2,
520                   PEAS_TYPE_PLUGIN_INFO,
521                   PEAS_TYPE_EXTENSION);
522 
523   signals [EXTENSION_REMOVED] =
524     g_signal_new ("extension-removed",
525                   G_TYPE_FROM_CLASS (klass),
526                   G_SIGNAL_RUN_LAST,
527                   0,
528                   NULL, NULL, NULL,
529                   G_TYPE_NONE,
530                   2,
531                   PEAS_TYPE_PLUGIN_INFO,
532                   PEAS_TYPE_EXTENSION);
533 
534   signals [EXTENSIONS_LOADED] =
535     g_signal_new ("extensions-loaded",
536                   G_TYPE_FROM_CLASS (klass),
537                   G_SIGNAL_RUN_LAST,
538                   0,
539                   NULL, NULL, NULL,
540                   G_TYPE_NONE, 0);
541 }
542 
543 static void
ide_extension_set_adapter_init(IdeExtensionSetAdapter * self)544 ide_extension_set_adapter_init (IdeExtensionSetAdapter *self)
545 {
546   self->settings = g_ptr_array_new_with_free_func (g_object_unref);
547   self->extensions = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
548 }
549 
550 /**
551  * ide_extension_set_adapter_get_engine:
552  *
553  * Gets the #IdeExtensionSetAdapter:engine property.
554  *
555  * Returns: (transfer none): a #PeasEngine.
556  *
557  * Since: 3.32
558  */
559 PeasEngine *
ide_extension_set_adapter_get_engine(IdeExtensionSetAdapter * self)560 ide_extension_set_adapter_get_engine (IdeExtensionSetAdapter *self)
561 {
562   g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
563 
564   return self->engine;
565 }
566 
567 GType
ide_extension_set_adapter_get_interface_type(IdeExtensionSetAdapter * self)568 ide_extension_set_adapter_get_interface_type (IdeExtensionSetAdapter *self)
569 {
570   g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), G_TYPE_INVALID);
571 
572   return self->interface_type;
573 }
574 
575 const gchar *
ide_extension_set_adapter_get_key(IdeExtensionSetAdapter * self)576 ide_extension_set_adapter_get_key (IdeExtensionSetAdapter *self)
577 {
578   g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
579 
580   return self->key;
581 }
582 
583 void
ide_extension_set_adapter_set_key(IdeExtensionSetAdapter * self,const gchar * key)584 ide_extension_set_adapter_set_key (IdeExtensionSetAdapter *self,
585                                    const gchar            *key)
586 {
587   g_return_if_fail (IDE_IS_MAIN_THREAD ());
588   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
589 
590   if (!ide_str_equal0 (self->key, key))
591     {
592       g_free (self->key);
593       self->key = g_strdup (key);
594       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KEY]);
595       ide_extension_set_adapter_queue_reload (self);
596     }
597 }
598 
599 const gchar *
ide_extension_set_adapter_get_value(IdeExtensionSetAdapter * self)600 ide_extension_set_adapter_get_value (IdeExtensionSetAdapter *self)
601 {
602   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
603   g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
604 
605   return self->value;
606 }
607 
608 void
ide_extension_set_adapter_set_value(IdeExtensionSetAdapter * self,const gchar * value)609 ide_extension_set_adapter_set_value (IdeExtensionSetAdapter *self,
610                                      const gchar            *value)
611 {
612   g_return_if_fail (IDE_IS_MAIN_THREAD ());
613   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
614 
615   IDE_TRACE_MSG ("Setting extension adapter %s value to \"%s\"",
616                  g_type_name (self->interface_type),
617                  value ?: "");
618 
619   if (!ide_str_equal0 (self->value, value))
620     {
621       g_free (self->value);
622       self->value = g_strdup (value);
623       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]);
624       ide_extension_set_adapter_queue_reload (self);
625     }
626 }
627 
628 /**
629  * ide_extension_set_adapter_foreach:
630  * @self: an #IdeExtensionSetAdapter
631  * @foreach_func: (scope call): A callback
632  * @user_data: user data for @foreach_func
633  *
634  * Calls @foreach_func for every extension loaded by the extension set.
635  *
636  * Since: 3.32
637  */
638 void
ide_extension_set_adapter_foreach(IdeExtensionSetAdapter * self,IdeExtensionSetAdapterForeachFunc foreach_func,gpointer user_data)639 ide_extension_set_adapter_foreach (IdeExtensionSetAdapter            *self,
640                                    IdeExtensionSetAdapterForeachFunc  foreach_func,
641                                    gpointer                           user_data)
642 {
643   const GList *list;
644 
645   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
646   g_return_if_fail (foreach_func != NULL);
647 
648   /*
649    * Use the ordered list of plugins as it is sorted including any
650    * dependencies of plugins.
651    */
652 
653   list = peas_engine_get_plugin_list (self->engine);
654 
655   for (const GList *iter = list; iter; iter = iter->next)
656     {
657       PeasPluginInfo *plugin_info = iter->data;
658       PeasExtension *exten = g_hash_table_lookup (self->extensions, plugin_info);
659 
660       if (exten != NULL)
661         foreach_func (self, plugin_info, exten, user_data);
662     }
663 }
664 
665 typedef struct
666 {
667   PeasPluginInfo *plugin_info;
668   PeasExtension  *exten;
669   gint            priority;
670 } SortedInfo;
671 
672 static gint
sort_by_priority(gconstpointer a,gconstpointer b)673 sort_by_priority (gconstpointer a,
674                   gconstpointer b)
675 {
676   const SortedInfo *sa = a;
677   const SortedInfo *sb = b;
678 
679   /* Greater values are higher priority */
680 
681   if (sa->priority < sb->priority)
682     return -1;
683   else if (sa->priority > sb->priority)
684     return 1;
685   else
686     return 0;
687 }
688 
689 /**
690  * ide_extension_set_adapter_foreach_by_priority:
691  * @self: an #IdeExtensionSetAdapter
692  * @foreach_func: (scope call): A callback
693  * @user_data: user data for @foreach_func
694  *
695  * Calls @foreach_func for every extension loaded by the extension set.
696  *
697  * Since: 3.32
698  */
699 void
ide_extension_set_adapter_foreach_by_priority(IdeExtensionSetAdapter * self,IdeExtensionSetAdapterForeachFunc foreach_func,gpointer user_data)700 ide_extension_set_adapter_foreach_by_priority (IdeExtensionSetAdapter            *self,
701                                                IdeExtensionSetAdapterForeachFunc  foreach_func,
702                                                gpointer                           user_data)
703 {
704   g_autoptr(GArray) sorted = NULL;
705   g_autofree gchar *prio_key = NULL;
706   GHashTableIter iter;
707   gpointer key;
708   gpointer value;
709 
710   g_return_if_fail (IDE_IS_MAIN_THREAD ());
711   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
712   g_return_if_fail (foreach_func != NULL);
713 
714   if (self->key == NULL)
715     {
716       ide_extension_set_adapter_foreach (self, foreach_func, user_data);
717       return;
718     }
719 
720   prio_key = g_strdup_printf ("%s-Priority", self->key);
721   sorted = g_array_new (FALSE, FALSE, sizeof (SortedInfo));
722 
723   g_hash_table_iter_init (&iter, self->extensions);
724 
725   while (g_hash_table_iter_next (&iter, &key, &value))
726     {
727       PeasPluginInfo *plugin_info = key;
728       PeasExtension *exten = value;
729       const gchar *priostr = peas_plugin_info_get_external_data (plugin_info, prio_key);
730       gint prio = priostr ? atoi (priostr) : 0;
731       SortedInfo info = { plugin_info, exten, prio };
732 
733       g_array_append_val (sorted, info);
734     }
735 
736   g_array_sort (sorted, sort_by_priority);
737 
738   for (guint i = 0; i < sorted->len; i++)
739     {
740       const SortedInfo *info = &g_array_index (sorted, SortedInfo, i);
741 
742       foreach_func (self, info->plugin_info, info->exten, user_data);
743     }
744 }
745 
746 guint
ide_extension_set_adapter_get_n_extensions(IdeExtensionSetAdapter * self)747 ide_extension_set_adapter_get_n_extensions (IdeExtensionSetAdapter *self)
748 {
749   g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), 0);
750 
751   if (self->extensions != NULL)
752     return g_hash_table_size (self->extensions);
753 
754   return 0;
755 }
756 
757 IdeExtensionSetAdapter *
ide_extension_set_adapter_new(IdeObject * parent,PeasEngine * engine,GType interface_type,const gchar * key,const gchar * value)758 ide_extension_set_adapter_new (IdeObject   *parent,
759                                PeasEngine  *engine,
760                                GType        interface_type,
761                                const gchar *key,
762                                const gchar *value)
763 {
764   IdeExtensionSetAdapter *ret;
765 
766   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
767   g_return_val_if_fail (!parent || IDE_IS_OBJECT (parent), NULL);
768   g_return_val_if_fail (!engine || PEAS_IS_ENGINE (engine), NULL);
769   g_return_val_if_fail (G_TYPE_IS_INTERFACE (interface_type) ||
770                         G_TYPE_IS_OBJECT (interface_type), NULL);
771 
772   ret = g_object_new (IDE_TYPE_EXTENSION_SET_ADAPTER,
773                       "engine", engine,
774                       "interface-type", interface_type,
775                       "key", key,
776                       "value", value,
777                       NULL);
778 
779   if (parent != NULL)
780     ide_object_append (parent, IDE_OBJECT (ret));
781 
782   /* If we have a reload queued, just process it immediately so that
783    * there is some determinism in plugin loading.
784    */
785   if (ret->reload_handler != 0)
786     {
787       g_clear_handle_id (&ret->reload_handler, g_source_remove);
788       ide_extension_set_adapter_do_reload (ret);
789     }
790 
791   return ret;
792 }
793 
794 /**
795  * ide_extension_set_adapter_get_extension:
796  * @self: a #IdeExtensionSetAdapter
797  * @plugin_info: a #PeasPluginInfo
798  *
799  * Locates the extension owned by @plugin_info if such extension exists.
800  *
801  * Returns: (transfer none) (nullable): a #PeasExtension or %NULL
802  *
803  * Since: 3.32
804  */
805 PeasExtension *
ide_extension_set_adapter_get_extension(IdeExtensionSetAdapter * self,PeasPluginInfo * plugin_info)806 ide_extension_set_adapter_get_extension (IdeExtensionSetAdapter *self,
807                                          PeasPluginInfo         *plugin_info)
808 {
809   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
810   g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
811   g_return_val_if_fail (plugin_info != NULL, NULL);
812 
813   return g_hash_table_lookup (self->extensions, plugin_info);
814 }
815