gplugin/gplugin

5dfb747dafa8
Parents 7cd3caa3f514
Children acb56ed64e56
Move the existing refresh code to GPluginFileSource

I was hoping this would required a bit less tweaking, but it's functionally the
same as the unit tests pass, but things got weird because we're using the
GObject life cycle to mimic stuff we just had scoped differently in the preivous
version.

Testing Done:
Ran the unittests, pulled some hair out, complained on stream...

Reviewed at https://reviews.imfreedom.org/r/1788/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin/gplugin-file-source.c Sun Sep 25 00:45:47 2022 -0500
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2011-2022 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gplugin/gplugin-file-source.h>
+
+#include <gplugin/gplugin-file-tree.h>
+#include <gplugin/gplugin-private.h>
+
+/**
+ * GPluginFileSource:
+ *
+ * A [iface@GPlugin.Source] that will query plugins on disk.
+ *
+ * Since: 0.39.0
+ */
+
+struct _GPluginFileSource {
+ GObject parent;
+
+ GPluginManager *manager;
+
+ GHashTable *plugin_filenames;
+ GHashTable *loaders_by_extension;
+ GNode *root;
+
+ GList *error_messages;
+};
+
+enum {
+ PROP_ZERO,
+ PROP_MANAGER,
+ N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {
+ NULL,
+};
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static guint
+gplugin_file_source_str_hash(gconstpointer v)
+{
+ if(v == NULL) {
+ return g_str_hash("");
+ }
+
+ return g_str_hash(v);
+}
+
+static void
+gplugin_file_source_set_manager(
+ GPluginFileSource *source,
+ GPluginManager *manager)
+{
+ if(g_set_object(&source->manager, manager)) {
+ g_object_notify_by_pspec(G_OBJECT(source), properties[PROP_MANAGER]);
+ }
+}
+
+static GPluginManager *
+gplugin_file_source_get_manager(GPluginFileSource *source)
+{
+ return source->manager;
+}
+
+static void
+gplugin_file_source_add_loader(GPluginFileSource *source, GPluginLoader *loader)
+{
+ GSList *exts = NULL;
+ const gchar *loader_id = NULL;
+
+ loader_id = gplugin_loader_get_id(loader);
+
+ exts = gplugin_loader_get_supported_extensions(loader);
+ while(exts != NULL) {
+ GSList *loaders = NULL;
+ const gchar *extension = exts->data;
+
+ /* Grab any existing loaders that are registered for this extension so
+ * that we can prepend our loader. But before we add ours, we remove any
+ * old copies we might have of ours.
+ */
+ loaders = g_hash_table_lookup(source->loaders_by_extension, extension);
+ for(GSList *ll = loaders; ll != NULL; ll = ll->next) {
+ GPluginLoader *existing = ll->data;
+ const gchar *existing_id = gplugin_loader_get_id(existing);
+
+ if(g_str_equal(loader_id, existing_id)) {
+ loaders = g_slist_remove(loaders, existing);
+
+ g_clear_object(&existing);
+
+ break;
+ }
+ }
+
+ loaders = g_slist_prepend(loaders, g_object_ref(loader));
+
+ /* Now insert the updated slist back into the hash table */
+ g_hash_table_insert(
+ source->loaders_by_extension,
+ g_strdup(extension),
+ loaders);
+
+ /* Move exts to the next one. */
+ exts = g_slist_delete_link(exts, exts);
+ }
+}
+
+static void
+gplugin_file_source_remove_loader(
+ GPluginFileSource *source,
+ GPluginLoader *loader)
+{
+ GSList *exts = NULL;
+ const gchar *loader_id = NULL;
+
+ loader_id = gplugin_loader_get_id(loader);
+
+ exts = gplugin_loader_get_supported_extensions(loader);
+ while(exts != NULL) {
+ GSList *loaders = NULL;
+ const gchar *extension = exts->data;
+
+ loaders = g_hash_table_lookup(source->loaders_by_extension, extension);
+ for(GSList *ll = loaders; ll != NULL; ll = ll->next) {
+ GPluginLoader *existing = ll->data;
+ const gchar *existing_id = gplugin_loader_get_id(existing);
+
+ if(g_str_equal(loader_id, existing_id)) {
+ loaders = g_slist_remove(loaders, existing);
+
+ if(loaders == NULL) {
+ g_hash_table_remove(
+ source->loaders_by_extension,
+ extension);
+ } else {
+ g_hash_table_insert(
+ source->loaders_by_extension,
+ g_strdup(extension),
+ loaders);
+ }
+
+ g_clear_object(&existing);
+
+ break;
+ }
+ }
+
+ /* Move exts to the next one. */
+ exts = g_slist_delete_link(exts, exts);
+ }
+}
+
+static void
+gplugin_file_source_update_loaders(GPluginFileSource *source)
+{
+ GList *loaders = NULL;
+
+ loaders = gplugin_manager_get_loaders(source->manager);
+ while(loaders != NULL) {
+ GPluginLoader *loader = loaders->data;
+
+ gplugin_file_source_add_loader(source, loader);
+
+ loaders = g_list_delete_link(loaders, loaders);
+ }
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+gplugin_file_source_loader_registered_cb(
+ G_GNUC_UNUSED GPluginManager *manager,
+ GPluginLoader *loader,
+ gpointer data)
+{
+ gplugin_file_source_add_loader(data, loader);
+}
+
+static void
+gplugin_file_source_loader_unregistered_cb(
+ G_GNUC_UNUSED GPluginManager *manager,
+ GPluginLoader *loader,
+ gpointer data)
+{
+ gplugin_file_source_remove_loader(data, loader);
+}
+
+/******************************************************************************
+ * GPluginSource implementation
+ *****************************************************************************/
+static gboolean
+gplugin_file_source_scan(GPluginSource *source)
+{
+ GPluginFileSource *file_source = GPLUGIN_FILE_SOURCE(source);
+ gboolean refresh = FALSE;
+ gint errors = 0;
+
+ /* Clear any error messages from our last scan. */
+ g_list_free_full(file_source->error_messages, (GDestroyNotify)g_free);
+ file_source->error_messages = NULL;
+
+ for(GNode *dir = file_source->root->children; dir; dir = dir->next) {
+ GPluginFileTreeEntry *e = dir->data;
+ GNode *file = NULL;
+ const gchar *path = e->filename;
+
+ for(file = dir->children; file; file = file->next) {
+ GPluginPlugin *plugin = NULL;
+ GPluginLoader *loader = NULL;
+ GError *error = NULL;
+ GSList *l = NULL;
+ gchar *filename = NULL;
+
+ e = (GPluginFileTreeEntry *)file->data;
+
+ /* Build the path and see if we need to probe it! */
+ filename = g_build_filename(path, e->filename, NULL);
+ plugin =
+ g_hash_table_lookup(file_source->plugin_filenames, filename);
+
+ if(plugin && GPLUGIN_IS_PLUGIN(plugin)) {
+ GPluginPluginState state = gplugin_plugin_get_state(plugin);
+
+ /* The plugin is in our "view", check its state. If it's
+ * queried or loaded, move on to the next one.
+ */
+ if(state == GPLUGIN_PLUGIN_STATE_QUERIED ||
+ state == GPLUGIN_PLUGIN_STATE_LOADED) {
+ g_free(filename);
+ continue;
+ }
+ }
+
+ /* grab the list of loaders for this extension */
+ l = g_hash_table_lookup(
+ file_source->loaders_by_extension,
+ e->extension);
+ for(; l; l = l->next) {
+ if(!GPLUGIN_IS_LOADER(l->data)) {
+ continue;
+ }
+
+ loader = GPLUGIN_LOADER(l->data);
+
+ /* Try to probe the plugin with the current loader */
+ plugin = gplugin_loader_query_plugin(loader, filename, &error);
+
+ /* Check the GError, if it's set, output its message and
+ * try the next loader.
+ */
+ if(error) {
+ gchar *error_message = NULL;
+
+ errors++;
+
+ error_message = g_strdup_printf(
+ "failed to query '%s' with loader '%s': %s",
+ filename,
+ G_OBJECT_TYPE_NAME(loader),
+ error->message);
+
+ file_source->error_messages = g_list_prepend(
+ file_source->error_messages,
+ error_message);
+
+ g_clear_error(&error);
+ g_clear_object(&plugin);
+
+ loader = NULL;
+
+ continue;
+ }
+
+ /* if the plugin instance is good, then break out of this
+ * loop.
+ */
+ if(GPLUGIN_IS_PLUGIN(plugin)) {
+ break;
+ }
+
+ g_object_unref(G_OBJECT(plugin));
+
+ loader = NULL;
+ }
+
+ /* check if our plugin instance is good. If it's not good we
+ * don't need to do anything but free the filename which we'll
+ * do later.
+ */
+ if(GPLUGIN_IS_PLUGIN(plugin)) {
+ /* We have a good plugin, huzzah! We need to add it to our hash
+ * table.
+ */
+
+ gchar *real_filename = gplugin_plugin_get_filename(plugin);
+
+ /* We also need the GPluginPluginInfo for a bunch of stuff. */
+ GPluginPluginInfo *info = gplugin_plugin_get_info(plugin);
+
+ const gchar *id = gplugin_plugin_info_get_id(info);
+ GSList *l = NULL, *ll = NULL;
+ gboolean seen = FALSE;
+
+ /* Throw a warning if the info->id is NULL. */
+ if(id == NULL) {
+ gchar *error_message = NULL;
+
+ error_message = g_strdup_printf(
+ "plugin %s has a NULL id",
+ real_filename);
+
+ g_free(real_filename);
+ g_object_unref(G_OBJECT(info));
+
+ file_source->error_messages = g_list_prepend(
+ file_source->error_messages,
+ error_message);
+
+ continue;
+ }
+
+ /* Now insert into our hash table. */
+ g_hash_table_replace(
+ file_source->plugin_filenames,
+ real_filename,
+ g_object_ref(G_OBJECT(plugin)));
+
+ /* Grab the list of plugins with our id and prepend the new
+ * plugin to it before updating it.
+ */
+ l = gplugin_manager_find_plugins(file_source->manager, id);
+ for(ll = l; ll; ll = ll->next) {
+ GPluginPlugin *splugin = GPLUGIN_PLUGIN(ll->data);
+ gchar *sfilename = gplugin_plugin_get_filename(splugin);
+
+ if(!g_strcmp0(real_filename, sfilename)) {
+ seen = TRUE;
+ }
+
+ g_free(sfilename);
+ }
+ if(!seen) {
+ gplugin_manager_add_plugin(
+ file_source->manager,
+ id,
+ plugin);
+ }
+
+ /* Check if the plugin is supposed to be loaded on query, and
+ * if so, load it.
+ */
+ if(gplugin_plugin_info_get_load_on_query(info)) {
+ GError *error = NULL;
+ gboolean loaded;
+
+ loaded = gplugin_loader_load_plugin(loader, plugin, &error);
+
+ if(!loaded) {
+ gchar *error_message = NULL;
+
+ error_message = g_strdup_printf(
+ "failed to load %s during query: %s",
+ filename,
+ (error) ? error->message : "unknown");
+
+ file_source->error_messages = g_list_prepend(
+ file_source->error_messages,
+ error_message);
+
+ errors++;
+
+ g_clear_error(&error);
+ }
+ } else {
+ if(errors > 0) {
+ refresh = TRUE;
+ }
+ }
+
+ g_object_unref(G_OBJECT(info));
+
+ /* Since the plugin is now stored in our hash tables we need to
+ * remove this function's reference to it.
+ */
+ g_object_unref(G_OBJECT(plugin));
+ }
+
+ g_free(filename);
+ }
+ }
+
+ return refresh;
+}
+
+static void
+gplugin_file_source_source_iface_init(GPluginSourceInterface *iface)
+{
+ iface->scan = gplugin_file_source_scan;
+}
+
+/******************************************************************************
+ * GObject implementation
+ *****************************************************************************/
+G_DEFINE_TYPE_WITH_CODE(
+ GPluginFileSource,
+ gplugin_file_source,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(
+ GPLUGIN_TYPE_SOURCE,
+ gplugin_file_source_source_iface_init))
+
+static void
+gplugin_file_source_get_property(
+ GObject *obj,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GPluginFileSource *source = GPLUGIN_FILE_SOURCE(obj);
+
+ switch(param_id) {
+ case PROP_MANAGER:
+ g_value_set_object(value, gplugin_file_source_get_manager(source));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gplugin_file_source_set_property(
+ GObject *obj,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GPluginFileSource *source = GPLUGIN_FILE_SOURCE(obj);
+
+ switch(param_id) {
+ case PROP_MANAGER:
+ gplugin_file_source_set_manager(source, g_value_get_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ }
+}
+
+static void
+gplugin_file_source_dispose(GObject *obj)
+{
+ GPluginFileSource *source = GPLUGIN_FILE_SOURCE(obj);
+
+ g_clear_object(&source->manager);
+
+ G_OBJECT_CLASS(gplugin_file_source_parent_class)->dispose(obj);
+}
+
+static void
+gplugin_file_source_constructed(GObject *obj)
+{
+ GPluginFileSource *source = GPLUGIN_FILE_SOURCE(obj);
+ GList *paths = NULL;
+
+ G_OBJECT_CLASS(gplugin_file_source_parent_class)->constructed(obj);
+
+ /* The plugin_filenames hash table is keyed on the filename of the plugin
+ * with a value of the plugin itself.
+ */
+ source->plugin_filenames =
+ g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
+
+ /* The loaders_by_extension hash table is keyed on the supported extensions
+ * of the loader. Which means that a loader that supports multiple
+ * extensions will be in the table multiple times.
+ *
+ * We deal with collisions by using a GSList for the value which will hold
+ * references to instances of the actual loaders.
+ *
+ * Storing this in this method allows us to quickly figure out which loader
+ * to use by the filename and helps us to avoid iterating the loaders table
+ * again and again.
+ */
+ source->loaders_by_extension = g_hash_table_new_full(
+ gplugin_file_source_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ /* Connect to the loader-registered and loader-unregistered signals so we
+ * can keep our loaders_by_extension hash table up to date.
+ */
+ g_signal_connect_object(
+ source->manager,
+ "loader-registered",
+ G_CALLBACK(gplugin_file_source_loader_registered_cb),
+ source,
+ 0);
+ g_signal_connect_object(
+ source->manager,
+ "loader-unregistered",
+ G_CALLBACK(gplugin_file_source_loader_unregistered_cb),
+ source,
+ 0);
+
+ gplugin_file_source_update_loaders(source);
+
+ /* Get the paths from the manager and create our initial file tree. */
+ paths = gplugin_manager_get_paths(source->manager);
+ source->root = gplugin_file_tree_new(paths);
+}
+
+static void
+gplugin_file_source_finalize(GObject *obj)
+{
+ GPluginFileSource *source = GPLUGIN_FILE_SOURCE(obj);
+
+ g_clear_pointer(&source->root, gplugin_file_tree_free);
+ g_clear_pointer(&source->plugin_filenames, g_hash_table_destroy);
+ g_clear_pointer(&source->loaders_by_extension, g_hash_table_destroy);
+
+ while(source->error_messages != NULL) {
+ gchar *error_message = source->error_messages->data;
+
+ g_warning("%s", error_message);
+
+ g_free(error_message);
+
+ source->error_messages =
+ g_list_delete_link(source->error_messages, source->error_messages);
+ }
+
+ G_OBJECT_CLASS(gplugin_file_source_parent_class)->finalize(obj);
+}
+
+static void
+gplugin_file_source_init(G_GNUC_UNUSED GPluginFileSource *source)
+{
+}
+
+static void
+gplugin_file_source_class_init(GPluginFileSourceClass *klass)
+{
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->get_property = gplugin_file_source_get_property;
+ obj_class->set_property = gplugin_file_source_set_property;
+ obj_class->constructed = gplugin_file_source_constructed;
+ obj_class->dispose = gplugin_file_source_dispose;
+ obj_class->finalize = gplugin_file_source_finalize;
+
+ /**
+ * GPluginFileSource::manager:
+ *
+ * The [class@GPlugin.Manager] that this source is working for.
+ *
+ * Since: 0.39.0
+ */
+ properties[PROP_MANAGER] = g_param_spec_object(
+ "manager",
+ "manager",
+ "The manager this source is working for.",
+ GPLUGIN_TYPE_MANAGER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * gplugin_file_source_new:
+ * @manager: The [class@GPlugin.Manager] instance.
+ *
+ * Creates a [iface@GPlugin.Source] that will query plugins on disk using the
+ * paths from @manager.
+ *
+ * Returns: (transfer full): The new source.
+ *
+ * Since: 0.39.0
+ */
+GPluginSource *
+gplugin_file_source_new(GPluginManager *manager)
+{
+ g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
+
+ return g_object_new(GPLUGIN_TYPE_FILE_SOURCE, "manager", manager, NULL);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin/gplugin-file-source.h Sun Sep 25 00:45:47 2022 -0500
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011-2022 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef GPLUGIN_FILE_SOURCE_H
+#define GPLUGIN_FILE_SOURCE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gplugin/gplugin-manager.h>
+#include <gplugin/gplugin-source.h>
+
+G_BEGIN_DECLS
+
+#define GPLUGIN_TYPE_FILE_SOURCE (gplugin_file_source_get_type())
+G_DECLARE_FINAL_TYPE(
+ GPluginFileSource,
+ gplugin_file_source,
+ GPLUGIN,
+ FILE_SOURCE,
+ GObject)
+
+GPluginSource *gplugin_file_source_new(GPluginManager *manager);
+
+G_END_DECLS
+
+#endif /* GPLUGIN_FILE_SOURCE_H */
--- a/gplugin/gplugin-manager.c Sun Sep 25 00:06:03 2022 -0500
+++ b/gplugin/gplugin-manager.c Sun Sep 25 00:45:47 2022 -0500
@@ -22,10 +22,12 @@
#include <glib/gi18n-lib.h>
#include <gplugin/gplugin-core.h>
+#include <gplugin/gplugin-file-source.h>
#include <gplugin/gplugin-file-tree.h>
#include <gplugin/gplugin-manager.h>
#include <gplugin/gplugin-native-loader.h>
#include <gplugin/gplugin-private.h>
+#include <gplugin/gplugin-source.h>
/**
* GPluginManagerForeachFunc:
@@ -70,10 +72,8 @@
GQueue *paths;
GHashTable *plugins;
- GHashTable *plugins_filename_view;
GHashTable *loaders;
- GHashTable *loaders_by_extension;
gboolean refresh_needed;
};
@@ -95,15 +95,6 @@
/******************************************************************************
* Helpers
*****************************************************************************/
-static guint
-gplugin_manager_str_hash(gconstpointer v)
-{
- if(v == NULL)
- return g_str_hash("");
-
- return g_str_hash(v);
-}
-
static gboolean
gplugin_manager_remove_list_value(
G_GNUC_UNUSED gpointer k,
@@ -290,19 +281,9 @@
NULL);
g_clear_pointer(&manager->plugins, g_hash_table_destroy);
- /* destroy the filename view */
- g_clear_pointer(&manager->plugins_filename_view, g_hash_table_destroy);
-
/* clean up our list of loaders */
g_clear_pointer(&manager->loaders, g_hash_table_destroy);
- /* free all the data in the loaders hash table and destroy it */
- g_hash_table_foreach_remove(
- manager->loaders_by_extension,
- gplugin_manager_remove_list_value,
- NULL);
- g_clear_pointer(&manager->loaders_by_extension, g_hash_table_destroy);
-
/* call the base class's destructor */
G_OBJECT_CLASS(gplugin_manager_parent_class)->finalize(obj);
}
@@ -497,31 +478,8 @@
manager->plugins =
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
- /* the filename view is hash table keyed on the filename of the plugin with
- * a value of the plugin itself.
- */
- manager->plugins_filename_view =
- g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
-
manager->loaders =
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
-
- /* The loaders_by_extension hash table is keyed on the supported extensions
- * of the loader. Which means that a loader that supports multiple
- * extensions will be in the table multiple times.
- *
- * We deal with collisions by using a GSList for the value which will hold
- * references to instances of the actual loaders.
- *
- * Storing this in this method allows us to quickly figure out which loader
- * to use by the filename and helps us to avoid iterating the loaders table
- * again and again.
- */
- manager->loaders_by_extension = g_hash_table_new_full(
- gplugin_manager_str_hash,
- g_str_equal,
- g_free,
- NULL);
}
/******************************************************************************
@@ -806,7 +764,6 @@
GError **error)
{
GPluginLoader *found = NULL;
- GSList *l = NULL, *exts = NULL;
const gchar *id = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
@@ -826,39 +783,6 @@
g_hash_table_insert(manager->loaders, g_strdup(id), g_object_ref(loader));
- exts = gplugin_loader_get_supported_extensions(loader);
- for(l = exts; l; l = l->next) {
- GSList *existing = NULL, *ll = NULL;
- const gchar *ext = (const gchar *)l->data;
-
- /* grab any existing loaders that are registered for this type so that
- * we can prepend our loader. But before we add ours, we remove any
- * old copies we might have of ours.
- */
- existing = g_hash_table_lookup(manager->loaders_by_extension, ext);
- for(ll = existing; ll; ll = ll->next) {
- GPluginLoader *iter = GPLUGIN_LOADER(ll->data);
- const gchar *ext_id = gplugin_loader_get_id(iter);
-
- if(g_str_equal(id, ext_id)) {
- existing = g_slist_remove(existing, iter);
-
- g_object_unref(iter);
-
- break;
- }
- }
-
- existing = g_slist_prepend(existing, g_object_ref(loader));
-
- /* Now insert the updated slist back into the hash table */
- g_hash_table_insert(
- manager->loaders_by_extension,
- g_strdup(ext),
- existing);
- }
- g_slist_free(exts);
-
/* make a note that we need to refresh */
manager->refresh_needed = TRUE;
@@ -884,7 +808,6 @@
GPluginLoader *loader,
GError **error)
{
- GSList *l = NULL, *exts = NULL;
const gchar *id = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
@@ -904,47 +827,6 @@
return FALSE;
}
- exts = gplugin_loader_get_supported_extensions(loader);
- for(l = exts; l; l = l->next) {
- GSList *los = NULL;
- GSList *ll = NULL;
- const gchar *ext = NULL;
-
- ext = (const gchar *)exts->data;
- los = g_hash_table_lookup(manager->loaders_by_extension, ext);
-
- for(ll = los; ll; ll = ll->next) {
- GPluginLoader *lo = GPLUGIN_LOADER(ll->data);
- const gchar *lo_id = gplugin_loader_get_id(lo);
-
- /* check if this is not the loader we're looking for */
- if(!g_str_equal(id, lo_id)) {
- continue;
- }
-
- /* At this point, we're at the loader that we're removing. So we'll
- * remove it from the los SList. Then if the SList is empty, we
- * remove it from the hash table, otherwise we just update it.
- */
- los = g_slist_remove(los, lo);
- if(los) {
- g_hash_table_insert(
- manager->loaders_by_extension,
- g_strdup(ext),
- los);
- } else {
- g_hash_table_remove(manager->loaders_by_extension, ext);
- }
-
- /* kill our ref to the loader */
- g_object_unref(lo);
-
- /* now move to the next extension to check */
- break;
- }
- }
- g_slist_free(exts);
-
/* Temporarily add a reference to loader so we can emit the signal if it
* was removed from our table correctly.
*/
@@ -985,231 +867,23 @@
void
gplugin_manager_refresh(GPluginManager *manager)
{
- GNode *root = NULL;
- GList *error_messages = NULL, *l = NULL;
- gchar *error_message = NULL;
- guint errors = 0;
+ GPluginSource *file_source = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
- /* build a tree of all possible plugins */
- root = gplugin_file_tree_new(manager->paths->head);
+ file_source = gplugin_file_source_new(manager);
manager->refresh_needed = TRUE;
while(manager->refresh_needed) {
- GNode *dir = NULL;
-
- if(error_messages) {
- for(l = error_messages; l; l = l->next)
- g_free(l->data);
- g_list_free(error_messages);
- error_messages = NULL;
- }
-
manager->refresh_needed = FALSE;
- for(dir = root->children; dir; dir = dir->next) {
- GPluginFileTreeEntry *e = dir->data;
- GNode *file = NULL;
- const gchar *path = e->filename;
-
- for(file = dir->children; file; file = file->next) {
- GPluginPlugin *plugin = NULL;
- GPluginLoader *loader = NULL;
- GError *error = NULL;
- GSList *l = NULL;
- gchar *filename = NULL;
-
- e = (GPluginFileTreeEntry *)file->data;
-
- /* Build the path and see if we need to probe it! */
- filename = g_build_filename(path, e->filename, NULL);
- plugin = g_hash_table_lookup(
- manager->plugins_filename_view,
- filename);
-
- if(plugin && GPLUGIN_IS_PLUGIN(plugin)) {
- GPluginPluginState state = gplugin_plugin_get_state(plugin);
-
- /* The plugin is in our "view", check its state. If it's
- * queried or loaded, move on to the next one.
- */
- if(state == GPLUGIN_PLUGIN_STATE_QUERIED ||
- state == GPLUGIN_PLUGIN_STATE_LOADED) {
- g_free(filename);
- continue;
- }
- }
-
- /* grab the list of loaders for this extension */
- l = g_hash_table_lookup(
- manager->loaders_by_extension,
- e->extension);
- for(; l; l = l->next) {
- if(!GPLUGIN_IS_LOADER(l->data)) {
- continue;
- }
-
- loader = GPLUGIN_LOADER(l->data);
-
- /* Try to probe the plugin with the current loader */
- plugin =
- gplugin_loader_query_plugin(loader, filename, &error);
-
- /* Check the GError, if it's set, output its message and
- * try the next loader.
- */
- if(error) {
- errors++;
-
- error_message = g_strdup_printf(
- _("failed to query '%s' with "
- "loader '%s': %s"),
- filename,
- G_OBJECT_TYPE_NAME(loader),
- error->message);
- error_messages =
- g_list_prepend(error_messages, error_message);
-
- g_error_free(error);
- error = NULL;
-
- loader = NULL;
-
- continue;
- }
-
- /* if the plugin instance is good, then break out of this
- * loop.
- */
- if(GPLUGIN_IS_PLUGIN(plugin)) {
- break;
- }
-
- g_object_unref(G_OBJECT(plugin));
-
- loader = NULL;
- }
-
- /* check if our plugin instance is good. If it's not good we
- * don't need to do anything but free the filename which we'll
- * do later.
- */
- if(GPLUGIN_IS_PLUGIN(plugin)) {
- /* we have a good plugin, huzzah! We need to add it to our
- * "view" as well as the main plugin hash table.
- */
-
- /* we want the internal filename from the plugin to avoid
- * duplicate memory, so we need to grab it for the "view".
- */
- gchar *real_filename = gplugin_plugin_get_filename(plugin);
-
- /* we also need the GPluginPluginInfo to get the plugin's
- * ID for the key in our main hash table.
- */
- GPluginPluginInfo *info = gplugin_plugin_get_info(plugin);
-
- const gchar *id = gplugin_plugin_info_get_id(info);
- GSList *l = NULL, *ll = NULL;
- gboolean seen = FALSE;
-
- /* throw a warning if the info->id is NULL */
- if(id == NULL) {
- error_message = g_strdup_printf(
- _("Plugin %s has a NULL id."),
- real_filename);
- g_free(real_filename);
- g_object_unref(G_OBJECT(info));
-
- error_messages =
- g_list_prepend(error_messages, error_message);
-
- continue;
- }
-
- /* now insert into our view */
- g_hash_table_replace(
- manager->plugins_filename_view,
- real_filename,
- g_object_ref(G_OBJECT(plugin)));
-
- /* Grab the list of plugins with our id and prepend the new
- * plugin to it before updating it.
- */
- l = g_hash_table_lookup(manager->plugins, id);
- for(ll = l; ll; ll = ll->next) {
- GPluginPlugin *splugin = GPLUGIN_PLUGIN(ll->data);
- gchar *sfilename = gplugin_plugin_get_filename(splugin);
-
- if(!g_strcmp0(real_filename, sfilename))
- seen = TRUE;
-
- g_free(sfilename);
- }
- if(!seen) {
- l = g_slist_prepend(l, g_object_ref(plugin));
- g_hash_table_insert(manager->plugins, g_strdup(id), l);
- }
-
- /* check if the plugin is supposed to be loaded on query,
- * and if so, load it.
- */
- if(gplugin_plugin_info_get_load_on_query(info)) {
- GError *error = NULL;
- gboolean loaded;
-
- loaded =
- gplugin_loader_load_plugin(loader, plugin, &error);
-
- if(!loaded) {
- error_message = g_strdup_printf(
- _("failed to load %s during query: %s"),
- filename,
- (error) ? error->message : _("Unknown"));
- error_messages =
- g_list_prepend(error_messages, error_message);
-
- errors++;
-
- g_error_free(error);
- }
- } else {
- /* if errors is greater than 0 set
- * manager->refresh_needed to TRUE.
- */
- if(errors > 0) {
- errors = 0;
- manager->refresh_needed = TRUE;
- }
- }
-
- g_object_unref(G_OBJECT(info));
-
- /* since the plugin is now stored in our hash tables we
- * need to remove this function's reference to it.
- */
- g_object_unref(G_OBJECT(plugin));
- }
-
- g_free(filename);
- }
+ if(gplugin_source_scan(file_source)) {
+ manager->refresh_needed = TRUE;
}
}
- if(error_messages) {
- error_messages = g_list_reverse(error_messages);
- for(l = error_messages; l; l = l->next) {
- g_warning("%s", (gchar *)l->data);
- g_free(l->data);
- }
-
- g_list_free(error_messages);
- }
-
- /* free the file tree */
- gplugin_file_tree_free(root);
+ g_clear_object(&file_source);
}
/**
@@ -1813,6 +1487,34 @@
}
/**
+ * gplugin_manager_add_plugin:
+ * @manager: The instance.
+ * @id: The id of the plugin to add.
+ * @plugin: The plugin to add.
+ *
+ * Adds @plugin to @manager with @id. This should only be called by
+ * [iface@GPlugin.Source] implementations.
+ *
+ * Since: 0.39.0
+ */
+void
+gplugin_manager_add_plugin(
+ GPluginManager *manager,
+ const gchar *id,
+ GPluginPlugin *plugin)
+{
+ GSList *plugins = NULL;
+
+ g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(GPLUGIN_IS_PLUGIN(plugin));
+
+ plugins = g_hash_table_lookup(manager->plugins, id);
+ plugins = g_slist_prepend(plugins, g_object_ref(plugin));
+ g_hash_table_insert(manager->plugins, g_strdup(id), plugins);
+}
+
+/**
* gplugin_manager_get_default:
*
* Gets the default plugin manager in GPlugin.
--- a/gplugin/gplugin-private.h Sun Sep 25 00:06:03 2022 -0500
+++ b/gplugin/gplugin-private.h Sun Sep 25 00:45:47 2022 -0500
@@ -24,6 +24,7 @@
* it.
*/
#define GPLUGIN_GLOBAL_HEADER_INSIDE
+#include <gplugin/gplugin-manager.h>
#include <gplugin/gplugin-plugin-info.h>
#include <gplugin/gplugin-plugin.h>
#undef GPLUGIN_GLOBAL_HEADER_INSIDE
@@ -39,6 +40,12 @@
const GValue *handler_return,
gpointer data);
+G_GNUC_INTERNAL
+void gplugin_manager_add_plugin(
+ GPluginManager *manager,
+ const gchar *id,
+ GPluginPlugin *plugin);
+
G_END_DECLS
#endif /* GPLUGIN_PRIVATE_H */
--- a/gplugin/meson.build Sun Sep 25 00:06:03 2022 -0500
+++ b/gplugin/meson.build Sun Sep 25 00:45:47 2022 -0500
@@ -34,11 +34,13 @@
]
GPLUGIN_PRIVATE_HEADERS = [
+ 'gplugin-file-source.h',
'gplugin-file-tree.h',
'gplugin-source.h',
]
GPLUGIN_PRIVATE_SOURCES = [
+ 'gplugin-file-source.c',
'gplugin-file-tree.c',
'gplugin-source.c',
]
--- a/gplugin/tests/test-native-loader.c Sun Sep 25 00:06:03 2022 -0500
+++ b/gplugin/tests/test-native-loader.c Sun Sep 25 00:45:47 2022 -0500
@@ -80,7 +80,7 @@
{
g_test_trap_subprocess("/loaders/native/error/query/subprocess", 0, 0);
- g_test_trap_assert_failed();
+ g_test_trap_assert_stderr("*expected error*");
}
/******************************************************************************