gplugin/gplugin

Update the convey plans
default tip
35 hours ago, Gary Kramlich
9d8481feda6e
Update the convey plans

Testing Done:
Ran `convey run all` without issue.

Reviewed at https://reviews.imfreedom.org/r/3150/
/*
* 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.43
*/
struct _GPluginFileSource {
GObject parent;
GPluginManager *manager;
GHashTable *plugin_filenames;
GHashTable *loaders_by_extension;
GNode *root;
GList *error_messages;
};
enum {
PROP_0,
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_clear_list(&file_source->error_messages, (GDestroyNotify)g_free);
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(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(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(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);
}
g_slist_free_full(l, g_object_unref);
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_auto_load(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(info);
/* Since the plugin is now stored in our hash tables we need to
* remove this function's reference to it.
*/
g_object_unref(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_FINAL_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_slist_unref(gpointer data)
{
GSList *slist;
slist = data;
g_slist_free_full(slist, g_object_unref);
}
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,
gplugin_file_source_slist_unref);
/* 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
*/
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
*/
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);
}