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