gplugin/gplugin

90d067db1688
Add option in meson to install helper application

Added two new options in meson that allow choosing whether the helper applications (gplugin-gtk-viewer and gplugin-query) are going to be installed or not

Testing Done:
Compiled a few times switching the two new options between true and false and verifying that the behavior was correct.

Bugs closed: GPLUGIN-129

Reviewed at https://reviews.imfreedom.org/r/159/
/*
* Copyright (C) 2011-2020 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 <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gplugin-native.h>
#include <gplugin/gplugin-core.h>
#include <gplugin/gplugin-file-tree.h>
#include <gplugin/gplugin-manager.h>
#include <gplugin/gplugin-private.h>
/**
* SECTION:gplugin-manager
* @Title: Manager API
* @Short_description: API for managing plugins
*
* The manager is used to manager all plugins in GPlugin. This includes
* loading, unloading, querying, checking for new plugins, and so on.
*/
/**
* GPluginManagerForeachFunc:
* @id: The id of the plugin.
* @plugins: A #GSList of each plugin that has the id @id.
* @data: User data passed to gplugin_manager_foreach().
*
* A callback function for gplugin_manager_foreach().
*/
/******************************************************************************
* Enums
*****************************************************************************/
enum {
SIG_LOADING,
SIG_LOADED,
SIG_LOAD_FAILED,
SIG_UNLOADING,
SIG_UNLOADED,
SIG_UNLOAD_FAILED,
N_SIGNALS,
};
/******************************************************************************
* Structs
*****************************************************************************/
typedef struct {
GObject parent;
GQueue *paths;
GHashTable *plugins;
GHashTable *plugins_filename_view;
GSList *loaders;
GHashTable *loaders_by_extension;
gboolean refresh_needed;
} GPluginManager;
typedef struct {
GObjectClass parent;
void (*append_path)(GPluginManager *manager, const gchar *path);
void (*prepend_path)(GPluginManager *manager, const gchar *path);
void (*remove_path)(GPluginManager *manager, const gchar *path);
void (*remove_paths)(GPluginManager *manager);
GList *(*get_paths)(GPluginManager *manager);
gboolean (
*register_loader)(GPluginManager *manager, GType type, GError **error);
gboolean (*unregister_loader)(
GPluginManager *manager,
GType type,
GError **error);
GSList *(*get_loaders)(GPluginManager *manager);
void (*refresh)(GPluginManager *manager);
void (*foreach)(
GPluginManager *manager,
GPluginManagerForeachFunc func,
gpointer data);
GSList *(*find_plugins)(GPluginManager *manager, const gchar *id);
GSList *(*find_plugins_with_state)(
GPluginManager *manager,
GPluginPluginState state);
GList *(*list_plugins)(GPluginManager *manager);
GSList *(*get_plugin_dependencies)(
GPluginManager *manager,
GPluginPlugin *plugin,
GError **error);
gboolean (*load_plugin)(
GPluginManager *manager,
GPluginPlugin *plugin,
GError **error);
gboolean (*unload_plugin)(
GPluginManager *manager,
GPluginPlugin *plugin,
GError **error);
/* signals */
gboolean (*loading_plugin)(
GObject *manager,
GPluginPlugin *plugin,
GError **error);
void (*loaded_plugin)(GObject *manager, GPluginPlugin *plugin);
void (*load_failed)(GObject *manager, GPluginPlugin *plugin);
gboolean (*unloading_plugin)(
GObject *manager,
GPluginPlugin *plugin,
GError **error);
void (*unloaded_plugin)(GObject *manager, GPluginPlugin *plugin);
void (*unload_plugin_failed)(GObject *manager, GPluginPlugin *plugin);
} GPluginManagerClass;
#define GPLUGIN_TYPE_MANAGER (gplugin_manager_get_type())
#define GPLUGIN_MANAGER(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), GPLUGIN_TYPE_MANAGER, GPluginManager))
#define GPLUGIN_MANAGER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST( \
(klass), \
GPLUGIN_TYPE_MANAGER, \
GPluginManagerClass))
#define GPLUGIN_IS_MANAGER(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), GPLUGIN_TYPE_MANAGER))
#define GPLUGIN_IS_MANAGER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass), GPLUGIN_TYPE_MANAGER))
#define GPLUGIN_MANAGER_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS( \
(obj), \
GPLUGIN_TYPE_MANAGER, \
GPluginManagerClass))
#define GPLUGIN_MANAGER_INSTANCE \
(GPLUGIN_MANAGER(gplugin_manager_get_instance()))
G_DEFINE_TYPE(GPluginManager, gplugin_manager, G_TYPE_OBJECT);
/******************************************************************************
* Globals
*****************************************************************************/
GPluginManager *instance = NULL;
static guint signals[N_SIGNALS] = {
0,
};
const gchar *dependency_pattern =
"^(?P<id>.+?)((?P<op>\\<=|\\<|==|=|\\>=|\\>)(?P<version>.+))?$";
GRegex *dependency_regex = NULL;
/******************************************************************************
* 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,
gpointer v,
G_GNUC_UNUSED gpointer d)
{
GSList *l = NULL;
for(l = (GSList *)v; l; l = l->next) {
if(l->data && G_IS_OBJECT(l->data))
g_object_unref(G_OBJECT(l->data));
}
g_slist_free((GSList *)v);
return TRUE;
}
/*
* gplugin_manager_find_loader_by_type:
* @manager: The #GPluginManager instance.
* @type: The #GType of the loader to find.
*
* Looks up a #GPluginLoader instance by its type.
*
* Returns: (transfer none): The #GPluginLoader instance or %NULL.
*/
static GPluginLoader *
gplugin_manager_find_loader_by_type(GPluginManager *manager, GType type)
{
GSList *l = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
g_return_val_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER), NULL);
for(l = manager->loaders; l; l = l->next) {
if(G_OBJECT_TYPE(l->data) == type) {
return GPLUGIN_LOADER(l->data);
}
}
return NULL;
}
static void
gplugin_manager_foreach_unload_plugin(
gpointer key,
gpointer value,
G_GNUC_UNUSED gpointer data)
{
GList *l = NULL;
gchar *id = (gchar *)key;
for(l = (GList *)value; l; l = l->next) {
GPluginPlugin *plugin = GPLUGIN_PLUGIN(l->data);
GPluginLoader *loader = NULL;
GError *error = NULL;
if(gplugin_plugin_get_state(plugin) != GPLUGIN_PLUGIN_STATE_LOADED) {
continue;
}
loader = gplugin_plugin_get_loader(plugin);
if(!gplugin_loader_unload_plugin(loader, plugin, &error)) {
g_warning(
"failed to unload plugin with id %s: %s",
id,
error ? error->message : "unknown");
g_clear_error(&error);
}
g_object_unref(G_OBJECT(loader));
}
}
static gchar *
gplugin_manager_normalize_path(const gchar *path)
{
if(g_str_has_suffix(path, G_DIR_SEPARATOR_S)) {
return g_strdup(path);
}
return g_strdup_printf("%s%s", path, G_DIR_SEPARATOR_S);
}
static gint
gplugin_manager_compare_paths(gconstpointer a, gconstpointer b)
{
gchar *keya = NULL, *keyb = NULL;
gint r = 0;
keya = g_utf8_collate_key_for_filename((const gchar *)a, -1);
keyb = g_utf8_collate_key_for_filename((const gchar *)b, -1);
r = strcmp(keya, keyb);
g_free(keya);
g_free(keyb);
return r;
}
/******************************************************************************
* Manager implementation
*****************************************************************************/
static void
gplugin_manager_real_append_path(GPluginManager *manager, const gchar *path)
{
GList *l = NULL;
gchar *normalized = NULL;
if(!path) {
return;
}
normalized = gplugin_manager_normalize_path(path);
l = g_queue_find_custom(
manager->paths,
normalized,
gplugin_manager_compare_paths);
if(l == NULL) {
g_queue_push_tail(manager->paths, normalized);
} else {
g_free(normalized);
}
}
static void
gplugin_manager_real_prepend_path(GPluginManager *manager, const gchar *path)
{
GList *l = NULL;
gchar *normalized = NULL;
if(!path) {
return;
}
normalized = gplugin_manager_normalize_path(path);
l = g_queue_find_custom(
manager->paths,
normalized,
gplugin_manager_compare_paths);
if(l == NULL) {
g_queue_push_head(manager->paths, normalized);
} else {
g_free(normalized);
}
}
static void
gplugin_manager_real_remove_path(GPluginManager *manager, const gchar *path)
{
GList *l = NULL;
gchar *normalized = NULL;
g_return_if_fail(path != NULL);
normalized = gplugin_manager_normalize_path(path);
l = g_queue_find_custom(
manager->paths,
normalized,
gplugin_manager_compare_paths);
if(l != NULL) {
g_free(l->data);
g_queue_delete_link(manager->paths, l);
}
g_free(normalized);
}
static void
gplugin_manager_real_remove_paths(GPluginManager *manager)
{
/* g_queue_clear_full was added in 2.60 but we require 2.40 */
g_queue_foreach(manager->paths, (GFunc)g_free, NULL);
g_queue_clear(manager->paths);
}
static GList *
gplugin_manager_real_get_paths(GPluginManager *manager)
{
return manager->paths->head;
}
static gboolean
gplugin_manager_real_register_loader(
GPluginManager *manager,
GType type,
GError **error)
{
GPluginLoader *loader = NULL;
GSList *l = NULL, *exts = NULL;
g_return_val_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER), FALSE);
loader = gplugin_manager_find_loader_by_type(manager, type);
if(GPLUGIN_IS_LOADER(loader)) {
g_set_error(
error,
GPLUGIN_DOMAIN,
0,
_("loader %s was already registered"),
g_type_name(type));
return FALSE;
}
/* Create the loader instance first. If we can't create it, we bail */
loader = g_object_new(type, NULL);
if(!GPLUGIN_IS_LOADER(loader)) {
g_set_error(
error,
GPLUGIN_DOMAIN,
0,
_("failed to create loader instance for %s"),
g_type_name(type));
return FALSE;
}
manager->loaders =
g_slist_prepend(manager->loaders, g_object_ref(G_OBJECT(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) {
if(G_OBJECT_TYPE(ll->data) == type) {
GPluginLoader *old = GPLUGIN_LOADER(ll->data);
existing = g_slist_remove(existing, old);
g_object_unref(G_OBJECT(old));
break;
}
}
existing = g_slist_prepend(existing, g_object_ref(G_OBJECT(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;
/* we remove our initial reference from the loader now to avoid a leak */
g_object_unref(G_OBJECT(loader));
return TRUE;
}
static gboolean
gplugin_manager_real_unregister_loader(
GPluginManager *manager,
GType type,
GError **error)
{
GPluginLoader *loader = NULL;
GSList *l = NULL, *exts = NULL;
g_return_val_if_fail(g_type_is_a(type, GPLUGIN_TYPE_LOADER), FALSE);
loader = gplugin_manager_find_loader_by_type(manager, type);
if(!GPLUGIN_IS_LOADER(loader)) {
g_set_error(
error,
GPLUGIN_DOMAIN,
0,
_("loader %s is not registered"),
g_type_name(type));
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);
/* check if this is not the loader we're looking for */
if(G_OBJECT_TYPE(lo) != type)
continue;
/* at this point, the loader we're at is of the type 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(G_OBJECT(lo));
/* now move to the next extension to check */
break;
}
}
g_slist_free(exts);
manager->loaders = g_slist_remove(manager->loaders, loader);
g_object_unref(G_OBJECT(loader));
return TRUE;
}
static GSList *
gplugin_manager_real_get_loaders(GPluginManager *manager)
{
return g_slist_copy_deep(manager->loaders, (GCopyFunc)g_object_ref, NULL);
}
static void
gplugin_manager_real_refresh(GPluginManager *manager)
{
GNode *root = NULL;
GList *error_messages = NULL, *l = NULL;
gchar *error_message = NULL;
guint errors = 0;
/* build a tree of all possible plugins */
root = gplugin_file_tree_new(manager->paths->head);
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 {
gplugin_plugin_set_state(
plugin,
GPLUGIN_PLUGIN_STATE_LOADED);
}
} else {
/* finally set the plugin state queried */
gplugin_plugin_set_state(
plugin,
GPLUGIN_PLUGIN_STATE_QUERIED);
/* 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(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);
}
static void
gplugin_manager_real_foreach(
GPluginManager *manager,
GPluginManagerForeachFunc func,
gpointer data)
{
GHashTableIter iter;
gpointer id = NULL, plugins = NULL;
g_hash_table_iter_init(&iter, manager->plugins);
while(g_hash_table_iter_next(&iter, &id, &plugins)) {
func((gchar *)id, (GSList *)plugins, data);
}
}
static GSList *
gplugin_manager_real_find_plugins(GPluginManager *manager, const gchar *id)
{
GSList *plugins_list = NULL, *l;
g_return_val_if_fail(id != NULL, NULL);
l = g_hash_table_lookup(manager->plugins, id);
plugins_list = g_slist_copy_deep(l, (GCopyFunc)g_object_ref, NULL);
return plugins_list;
}
static GSList *
gplugin_manager_real_find_plugins_with_state(
GPluginManager *manager,
GPluginPluginState state)
{
GSList *plugins = NULL;
GHashTableIter iter;
gpointer value = NULL;
g_hash_table_iter_init(&iter, manager->plugins);
while(g_hash_table_iter_next(&iter, NULL, &value)) {
GSList *l = NULL;
for(l = (GSList *)value; l != NULL; l = l->next) {
GPluginPlugin *plugin = GPLUGIN_PLUGIN(l->data);
if(gplugin_plugin_get_state(plugin) == state) {
plugins =
g_slist_prepend(plugins, g_object_ref(G_OBJECT(plugin)));
}
}
}
return plugins;
}
static GList *
gplugin_manager_real_list_plugins(GPluginManager *manager)
{
GQueue *queue = g_queue_new();
GList *ret = NULL;
GHashTableIter iter;
gpointer key = NULL;
g_hash_table_iter_init(&iter, manager->plugins);
while(g_hash_table_iter_next(&iter, &key, NULL)) {
g_queue_push_tail(queue, (gchar *)key);
}
ret = g_list_copy(queue->head);
g_queue_free(queue);
return ret;
}
static gboolean
gplugin_manager_load_dependencies(
GPluginPlugin *plugin,
GPluginPluginInfo *info,
GError **error)
{
GSList *dependencies = NULL, *l = NULL;
GError *ourerror = NULL;
gboolean all_loaded = TRUE;
dependencies = gplugin_manager_get_plugin_dependencies(plugin, &ourerror);
if(ourerror != NULL) {
g_propagate_error(error, ourerror);
return FALSE;
}
for(l = dependencies; l != NULL; l = l->next) {
GPluginPlugin *dependency = GPLUGIN_PLUGIN(l->data);
gboolean loaded = FALSE;
loaded = gplugin_manager_load_plugin(dependency, &ourerror);
if(!loaded || ourerror != NULL) {
if(ourerror != NULL) {
g_propagate_error(error, ourerror);
}
all_loaded = FALSE;
break;
}
}
g_slist_free_full(dependencies, g_object_unref);
return all_loaded;
}
static GSList *
gplugin_manager_real_get_plugin_dependencies(
G_GNUC_UNUSED GPluginManager *manager,
GPluginPlugin *plugin,
GError **error)
{
GPluginPluginInfo *info = NULL;
GSList *ret = NULL;
const gchar *const *dependencies = NULL;
gint i = 0;
info = gplugin_plugin_get_info(plugin);
dependencies = gplugin_plugin_info_get_dependencies(info);
g_object_unref(G_OBJECT(info));
if(dependencies == NULL) {
return NULL;
}
for(i = 0; dependencies[i] != NULL; i++) {
gboolean found = FALSE;
gchar **ors = NULL;
gint o = 0;
ors = g_strsplit(dependencies[i], "|", 0);
for(o = 0; ors[o]; o++) {
GMatchInfo *match = NULL;
GSList *matches = NULL;
gchar *oid = NULL, *oop = NULL, *over = NULL;
if(!g_regex_match(dependency_regex, ors[o], 0, &match)) {
continue;
}
/* grab the or'd id, op, and version */
oid = g_match_info_fetch_named(match, "id");
oop = g_match_info_fetch_named(match, "op");
over = g_match_info_fetch_named(match, "version");
/* free the match info */
g_match_info_free(match);
/* now look for a plugin matching the id */
matches = gplugin_manager_find_plugins_with_version(oid, oop, over);
g_free(oid);
g_free(oop);
g_free(over);
if(matches == NULL) {
continue;
}
/* prepend the first found match to our return value */
ret = g_slist_prepend(ret, g_object_ref(matches->data));
g_slist_free_full(matches, g_object_unref);
found = TRUE;
break;
}
g_strfreev(ors);
if(!found) {
g_set_error(
error,
GPLUGIN_DOMAIN,
0,
_("failed to find dependency %s for %s"),
dependencies[i],
gplugin_plugin_info_get_id(info));
g_slist_free_full(ret, g_object_unref);
return NULL;
}
}
return ret;
}
static gboolean
gplugin_manager_real_load_plugin(
GPluginManager *manager,
GPluginPlugin *plugin,
GError **ret_error)
{
GPluginPluginInfo *info = NULL;
GPluginLoader *loader = NULL;
GError *error = NULL;
gboolean ret = TRUE;
g_return_val_if_fail(GPLUGIN_IS_PLUGIN(plugin), FALSE);
/* if the plugin is already loaded there's nothing for us to do */
if(gplugin_plugin_get_state(plugin) == GPLUGIN_PLUGIN_STATE_LOADED)
return TRUE;
/* now try to get the plugin info from the plugin */
info = gplugin_plugin_get_info(plugin);
if(info == NULL) {
gchar *filename = gplugin_plugin_get_filename(plugin);
g_set_error(
ret_error,
GPLUGIN_DOMAIN,
0,
_("Plugin %s did not return value plugin info"),
filename);
g_free(filename);
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_LOAD_FAILED);
return FALSE;
}
if(!gplugin_manager_load_dependencies(plugin, info, ret_error)) {
g_object_unref(G_OBJECT(info));
return FALSE;
}
g_object_unref(G_OBJECT(info));
/* now load the actual plugin */
loader = gplugin_plugin_get_loader(plugin);
if(!GPLUGIN_IS_LOADER(loader)) {
gchar *filename = gplugin_plugin_get_filename(plugin);
g_set_error(
ret_error,
GPLUGIN_DOMAIN,
0,
_("The loader for %s is not a loader. This "
"should not happened!"),
filename);
g_free(filename);
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_LOAD_FAILED);
return FALSE;
}
g_signal_emit(manager, signals[SIG_LOADING], 0, plugin, &error, &ret);
if(!ret) {
/* Set the plugin's error. */
g_object_set(G_OBJECT(plugin), "error", error, NULL);
g_propagate_error(ret_error, error);
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_LOAD_FAILED);
return ret;
}
ret = gplugin_loader_load_plugin(loader, plugin, &error);
if(ret) {
/* If the plugin successfully loaded but returned an error, ignore the
* error.
*/
g_clear_error(&error);
/* Likewise, make sure the plugin's error is set to NULL. */
g_object_set(G_OBJECT(plugin), "error", NULL, NULL);
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_LOADED);
g_signal_emit(manager, signals[SIG_LOADED], 0, plugin);
} else {
/* Set the error on the plugin as well. This has to be before we
* propagate the error, because error is invalidate at that point.
*/
g_object_set(G_OBJECT(plugin), "error", error, NULL);
/* Set the state after the error is set, because people might connect
* to the notify signal on the state property.
*/
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_LOAD_FAILED);
g_signal_emit(manager, signals[SIG_LOAD_FAILED], 0, plugin);
g_propagate_error(ret_error, error);
}
return ret;
}
static gboolean
gplugin_manager_real_unload_plugin(
GPluginManager *manager,
GPluginPlugin *plugin,
GError **ret_error)
{
GPluginLoader *loader = NULL;
GError *error = NULL;
gboolean ret = TRUE;
g_return_val_if_fail(GPLUGIN_IS_PLUGIN(plugin), FALSE);
if(gplugin_plugin_get_state(plugin) != GPLUGIN_PLUGIN_STATE_LOADED)
return TRUE;
loader = gplugin_plugin_get_loader(plugin);
if(!GPLUGIN_IS_LOADER(loader)) {
g_set_error_literal(
ret_error,
GPLUGIN_DOMAIN,
0,
_("Plugin loader is not a loader"));
return FALSE;
}
g_signal_emit(manager, signals[SIG_UNLOADING], 0, plugin, &error, &ret);
if(!ret) {
/* Set the plugin's error. */
g_object_set(G_OBJECT(plugin), "error", error, NULL);
g_propagate_error(ret_error, error);
return ret;
}
ret = gplugin_loader_unload_plugin(loader, plugin, &error);
if(ret) {
/* If the plugin successfully loaded but returned an error, ignore the
* error.
*/
g_clear_error(&error);
/* Like, make sure the plugin's error is set to NULL. */
g_object_set(G_OBJECT(plugin), "error", NULL, NULL);
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_QUERIED);
g_signal_emit(manager, signals[SIG_UNLOADED], 0, plugin);
} else {
/* Set the error on the plugin as well. This has to be before we
* propagate the error, because error is invalidate at that point.
*/
g_object_set(G_OBJECT(plugin), "error", error, NULL);
/* Set the state after the error is set, because people might connect
* to the notify signal on the state property.
*/
gplugin_plugin_set_state(plugin, GPLUGIN_PLUGIN_STATE_UNLOAD_FAILED);
g_signal_emit(manager, signals[SIG_UNLOAD_FAILED], 0, plugin);
g_propagate_error(ret_error, error);
}
return ret;
}
static gboolean
gplugin_manager_loading_cb(
G_GNUC_UNUSED GObject *manager,
G_GNUC_UNUSED GPluginPlugin *plugin,
G_GNUC_UNUSED GError **error)
{
return TRUE;
}
static gboolean
gplugin_manager_unloading_cb(
G_GNUC_UNUSED GObject *manager,
G_GNUC_UNUSED GPluginPlugin *plugin,
G_GNUC_UNUSED GError **error)
{
return TRUE;
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
static void
gplugin_manager_finalize(GObject *obj)
{
GPluginManager *manager = GPLUGIN_MANAGER(obj);
g_queue_free_full(manager->paths, g_free);
manager->paths = NULL;
/* unload all of the loaded plugins */
g_hash_table_foreach(
manager->plugins,
gplugin_manager_foreach_unload_plugin,
NULL);
/* free all the data in the plugins hash table and destroy it */
g_hash_table_foreach_remove(
manager->plugins,
gplugin_manager_remove_list_value,
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_slist_free_full(manager->loaders, g_object_unref);
manager->loaders = NULL;
/* 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);
}
static void
gplugin_manager_class_init(GPluginManagerClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
GPluginManagerClass *manager_class = GPLUGIN_MANAGER_CLASS(klass);
obj_class->finalize = gplugin_manager_finalize;
manager_class->append_path = gplugin_manager_real_append_path;
manager_class->prepend_path = gplugin_manager_real_prepend_path;
manager_class->remove_path = gplugin_manager_real_remove_path;
manager_class->remove_paths = gplugin_manager_real_remove_paths;
manager_class->get_paths = gplugin_manager_real_get_paths;
manager_class->register_loader = gplugin_manager_real_register_loader;
manager_class->unregister_loader = gplugin_manager_real_unregister_loader;
manager_class->get_loaders = gplugin_manager_real_get_loaders;
manager_class->refresh = gplugin_manager_real_refresh;
manager_class->foreach = gplugin_manager_real_foreach;
manager_class->find_plugins = gplugin_manager_real_find_plugins;
manager_class->find_plugins_with_state =
gplugin_manager_real_find_plugins_with_state;
manager_class->list_plugins = gplugin_manager_real_list_plugins;
manager_class->get_plugin_dependencies =
gplugin_manager_real_get_plugin_dependencies;
manager_class->load_plugin = gplugin_manager_real_load_plugin;
manager_class->unload_plugin = gplugin_manager_real_unload_plugin;
manager_class->loading_plugin = gplugin_manager_loading_cb;
manager_class->unloading_plugin = gplugin_manager_unloading_cb;
/* signals */
/**
* GPluginManager::loading-plugin:
* @manager: The #GPluginManager instance. Treat as a #GObject.
* @plugin: The #GPluginPlugin that's about to be loaded.
* @error: Return address for a #GError.
*
* Emitted before @plugin is loaded.
*
* Return FALSE to stop loading
*/
signals[SIG_LOADING] = g_signal_new(
"loading-plugin",
G_OBJECT_CLASS_TYPE(manager_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GPluginManagerClass, loading_plugin),
gplugin_boolean_accumulator,
NULL,
NULL,
G_TYPE_BOOLEAN,
2,
G_TYPE_OBJECT,
G_TYPE_POINTER);
/**
* GPluginManager::loaded-plugin:
* @manager: the #gpluginpluginmanager instance. treat as a #gobject.
* @plugin: the #gpluginplugin that's about to be loaded.
*
* emitted after a plugin is loaded.
*/
signals[SIG_LOADED] = g_signal_new(
"loaded-plugin",
G_OBJECT_CLASS_TYPE(manager_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GPluginManagerClass, loaded_plugin),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
/**
* GPluginManager::load-plugin-failed:
* @manager: The #GPluginPluginManager instance.
* @plugin: The #GPluginPlugin that failed to load.
*
* emitted after a plugin fails to load.
*/
signals[SIG_LOAD_FAILED] = g_signal_new(
"load-plugin-failed",
G_OBJECT_CLASS_TYPE(manager_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GPluginManagerClass, load_failed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
/**
* GPluginManager::unloading-plugin:
* @manager: the #GPluginPluginManager instance. treat as a #GObject.
* @plugin: the #GPluginPlugin that's about to be loaded.
*
* emitted before a plugin is unloaded.
*
* Return FALSE to stop unloading
*/
signals[SIG_UNLOADING] = g_signal_new(
"unloading-plugin",
G_OBJECT_CLASS_TYPE(manager_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GPluginManagerClass, unloading_plugin),
gplugin_boolean_accumulator,
NULL,
NULL,
G_TYPE_BOOLEAN,
2,
G_TYPE_OBJECT,
G_TYPE_POINTER);
/**
* GPluginManager::unloaded-plugin:
* @manager: the #gpluginpluginmanager instance. treat as a #gobject.
* @plugin: the #gpluginplugin that's about to be loaded.
*
* emitted after a plugin is successfully unloaded.
*/
signals[SIG_UNLOADED] = g_signal_new(
"unloaded-plugin",
G_OBJECT_CLASS_TYPE(manager_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GPluginManagerClass, unloaded_plugin),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
/**
* GPluginManager::unload-plugin-failed:
* @manager: The #GPluginManager instance.
* @plugin: The #GPluginPlugin instance that failed to unload.
* @error: A #GError instance.
*
* Emitted when @manager was asked to unload @plugin, but @plugin returned
* %FALSE when its unload function was called.
*/
signals[SIG_UNLOAD_FAILED] = g_signal_new(
"unload-plugin-failed",
G_OBJECT_CLASS_TYPE(manager_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GPluginManagerClass, unload_plugin_failed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
}
static void
gplugin_manager_init(GPluginManager *manager)
{
manager->paths = g_queue_new();
/* the plugins hashtable is keyed on a plugin id and holds a GSList of all
* plugins that share that id.
*/
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);
/* 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);
}
/******************************************************************************
* Private API
*****************************************************************************/
void
gplugin_manager_private_init(gboolean register_native_loader)
{
GError *error = NULL;
if(instance != NULL) {
return;
}
instance = g_object_new(GPLUGIN_TYPE_MANAGER, NULL);
if(register_native_loader) {
if(!gplugin_manager_register_loader(
GPLUGIN_TYPE_NATIVE_LOADER,
&error)) {
if(error != NULL) {
g_error("failed to register loader: %s", error->message);
g_error_free(error);
} else {
g_error("failed to register loader: unknown failure");
}
}
}
dependency_regex = g_regex_new(dependency_pattern, 0, 0, NULL);
}
void
gplugin_manager_private_uninit(void)
{
g_regex_unref(dependency_regex);
g_clear_object(&instance);
}
/******************************************************************************
* API
*****************************************************************************/
/**
* gplugin_manager_append_path:
* @path: A path to add to the end of the plugin search paths.
*
* Adds @path to the end of the list of paths to search for plugins.
*/
void
gplugin_manager_append_path(const gchar *path)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->append_path)
klass->append_path(manager, path);
}
/**
* gplugin_manager_prepend_path:
* @path: A path to add to the beginning of the plugin search paths.
*
* Adds @path to the beginning of the list of paths to search for plugins.
*/
void
gplugin_manager_prepend_path(const gchar *path)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->prepend_path)
klass->prepend_path(manager, path);
}
/**
* gplugin_manager_remove_path:
* @path: A path to remove from the plugin search paths.
*
* Removes @path from the list of paths to search for plugins.
*/
void
gplugin_manager_remove_path(const gchar *path)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->remove_path)
klass->remove_path(manager, path);
}
/**
* gplugin_manager_remove_paths:
*
* Clears all paths that are set to search for plugins.
*/
void
gplugin_manager_remove_paths(void)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->remove_paths)
klass->remove_paths(manager);
}
/**
* gplugin_manager_add_default_paths:
*
* Adds the path that GPlugin was installed to to the plugin search path, as
* well as `${XDG_CONFIG_HOME}/gplugin` so users can install additional loaders
* themselves.
*/
void
gplugin_manager_add_default_paths(void)
{
gchar *path;
path = g_build_filename(PREFIX, LIBDIR, "gplugin", NULL);
gplugin_manager_prepend_path(path);
g_free(path);
path = g_build_filename(g_get_user_config_dir(), "gplugin", NULL);
gplugin_manager_prepend_path(path);
g_free(path);
}
/**
* gplugin_manager_add_app_paths:
* @prefix: The installation prefix for the application.
* @appname: The name of the application whose paths to add.
*
* Adds the application installation path for @appname. This will add
* `@prefix/@appname/plugins` to the list as well as
* `${XDG_CONFIG_HOME}/@appname/plugins`.
*/
void
gplugin_manager_add_app_paths(const gchar *prefix, const gchar *appname)
{
gchar *path;
g_return_if_fail(appname != NULL);
path = g_build_filename(prefix, LIBDIR, appname, NULL);
gplugin_manager_prepend_path(path);
g_free(path);
path = g_build_filename(g_get_user_config_dir(), appname, "plugins", NULL);
gplugin_manager_prepend_path(path);
g_free(path);
}
/**
* gplugin_manager_get_paths:
*
* Gets the list of paths which will be searched for plugins.
*
* Returns: (element-type utf8) (transfer none): The list of paths which will
* be searched for plugins.
*/
GList *
gplugin_manager_get_paths(void)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->get_paths)
return klass->get_paths(manager);
return NULL;
}
/**
* gplugin_manager_register_loader:
* @type: #GType of a #GPluginLoader.
* @error: (out) (nullable): The return address for a #GError.
*
* Registers @type as an available loader.
*
* Returns: %TRUE if the loader was successfully register, %FALSE otherwise
* with @error set.
*/
gboolean
gplugin_manager_register_loader(GType type, GError **error)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->register_loader)
return klass->register_loader(manager, type, error);
g_set_error(
error,
GPLUGIN_DOMAIN,
0,
"register_loader method not implemented");
return FALSE;
}
/**
* gplugin_manager_unregister_loader:
* @type: #GType of a #GPluginLoader.
* @error: (out) (nullable): The return address for a #GError.
*
* Unregisters @type as an available loader.
*
* Returns: %TRUE if the loader was successfully unregistered, %FALSE
* otherwise with @error set.
*/
gboolean
gplugin_manager_unregister_loader(GType type, GError **error)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->unregister_loader)
return klass->unregister_loader(manager, type, error);
g_set_error(
error,
GPLUGIN_DOMAIN,
0,
"unregister_loader method not implemented");
return FALSE;
}
/**
* gplugin_manager_get_loaders:
*
* Returns a list of all registered #GPluginLoader's.
*
* Returns: (element-type GPlugin.Loader) (transfer full): Returns a list of all
* registered loaders.
*/
GSList *
gplugin_manager_get_loaders(void)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->get_loaders)
return klass->get_loaders(manager);
return NULL;
}
/**
* gplugin_manager_refresh:
*
* Forces a refresh of all plugins found in the search paths.
*/
void
gplugin_manager_refresh(void)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->refresh)
klass->refresh(manager);
}
/**
* gplugin_manager_foreach:
* @func: (scope call): The #GPluginManagerForeachFunc to call.
* @data: User data to pass to func.
*
* Calls @func for each plugin that is known.
*/
void
gplugin_manager_foreach(GPluginManagerForeachFunc func, gpointer data)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_if_fail(GPLUGIN_IS_MANAGER(manager));
g_return_if_fail(func != NULL);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->foreach)
klass->foreach(manager, func, data);
}
/**
* gplugin_manager_find_plugins:
* @id: id string of the plugin to find.
*
* Finds all plugins matching @id.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A #GSList of
* referenced #GPluginPlugin's matching @id. Call
* g_slist_free_full() with a `DestroyNotify` of g_object_unref() on
* the returned value when you're done with it.
*/
GSList *
gplugin_manager_find_plugins(const gchar *id)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->find_plugins)
return klass->find_plugins(manager, id);
return NULL;
}
/**
* gplugin_manager_find_plugins_with_version:
* @id: The ID of the plugin to find.
* @op: one of <, <=, =, ==, >=, >.
* @version: The version to compare against.
*
* Similar to gplugin_manager_find_plugins() but only returns plugins whose
* versions match @op and @version. This is primarily used for dependency
* loading where a plugin may depend on a specific range of versions of another
* plugin.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A #GSList of
* referenced #GPluginPlugin's matching @id. Call
* g_slist_free_full() with a `DestroyNotify` of g_object_unref() on
* the returned value when you're done with it.
*/
GSList *
gplugin_manager_find_plugins_with_version(
const gchar *id,
const gchar *op,
const gchar *version)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GSList *plugins = NULL, *filtered = NULL, *l = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
plugins = gplugin_manager_find_plugins(id);
if(op == NULL && version == NULL) {
/* we weren't actually passed an operator and a version so just return
* the list we have based on the id.
*/
return plugins;
}
for(l = plugins; l; l = l->next) {
GPluginPlugin *plugin = GPLUGIN_PLUGIN(l->data);
GPluginPluginInfo *info = NULL;
const gchar *found_version = NULL;
gint result = 0;
gboolean keep = FALSE;
/* get the plugin's version from it's info */
info = gplugin_plugin_get_info(plugin);
found_version = gplugin_plugin_info_get_version(info);
/* now compare the version of the plugin to passed in version. This
* should be done in this order so it's easier to track the operators.
* IE: we want to keep the inequality the same.
*/
result = gplugin_version_compare(found_version, version);
/* we need to keep info around until we're done using found_version */
g_object_unref(G_OBJECT(info));
if(result < 0) {
keep = (g_strcmp0(op, "<") == 0 || g_strcmp0(op, "<=") == 0);
} else if(result == 0) {
keep =
(g_strcmp0(op, "=") == 0 || g_strcmp0(op, "==") == 0 ||
g_strcmp0(op, "<=") == 0 || g_strcmp0(op, ">=") == 0);
} else if(result > 0) {
keep = (g_strcmp0(op, ">") == 0 || g_strcmp0(op, ">=") == 0);
}
if(keep) {
filtered =
g_slist_prepend(filtered, g_object_ref(G_OBJECT(plugin)));
}
}
g_slist_free_full(plugins, g_object_unref);
return g_slist_reverse(filtered);
}
/**
* gplugin_manager_find_plugins_with_state:
* @state: The #GPluginPluginState to look for.
*
* Finds all plugins that currently have a state of @state.
*
* Returns: (element-type GPlugin.Plugin) (transfer full): A #GSList of
* referenced #GPluginPlugin's whose state is @state. Call
* g_slist_free_full() with a `DestroyNotify` of g_object_unref() on
* the returned value when you're done with it.
*/
GSList *
gplugin_manager_find_plugins_with_state(GPluginPluginState state)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->find_plugins_with_state)
return klass->find_plugins_with_state(manager, state);
return NULL;
}
/**
* gplugin_manager_find_plugin:
* @id: The id of the plugin to find.
*
* Finds the first plugin matching @id. This function uses
* #gplugin_manager_find_plugins and returns the first plugin in the
* list.
*
* Return value: (transfer full): A referenced #GPluginPlugin instance or NULL
* if no plugin matching @id was found.
*/
GPluginPlugin *
gplugin_manager_find_plugin(const gchar *id)
{
GSList *plugins_list = NULL;
GPluginPlugin *plugin = NULL;
g_return_val_if_fail(id != NULL, NULL);
plugins_list = gplugin_manager_find_plugins(id);
if(plugins_list == NULL)
return NULL;
plugin = GPLUGIN_PLUGIN(g_object_ref(G_OBJECT(plugins_list->data)));
g_slist_free_full(plugins_list, g_object_unref);
return plugin;
}
/**
* gplugin_manager_find_plugin_with_newest_version:
* @id: The id of the plugin to find.
*
* Calls gplugin_manager_find_plugins() with @id, and then returns the plugins
* with the highest version number or %NULL if no plugins with @id are found.
*
* Returns: (transfer full): The #GPluginPlugin with an id of @id that has the
* highest version number, or %NULL if no plugins were found with @id.
*/
GPluginPlugin *
gplugin_manager_find_plugin_with_newest_version(const gchar *id)
{
GPluginPlugin *plugin_a = NULL;
GPluginPluginInfo *info_a = NULL;
const gchar *version_a = NULL;
GSList *l = NULL;
g_return_val_if_fail(id != NULL, NULL);
l = gplugin_manager_find_plugins(id);
for(; l != NULL; l = g_slist_delete_link(l, l)) {
GPluginPlugin *plugin_b = NULL;
GPluginPluginInfo *info_b = NULL;
const gchar *version_b = NULL;
gint cmp = 0;
if(!GPLUGIN_IS_PLUGIN(l->data)) {
continue;
}
plugin_b = GPLUGIN_PLUGIN(l->data);
info_b = gplugin_plugin_get_info(plugin_b);
/* If this is the first plugin we've found, set the plugin_a values and
* continue.
*/
if(!GPLUGIN_IS_PLUGIN(plugin_a)) {
plugin_a = plugin_b;
info_a = info_b;
version_a = gplugin_plugin_info_get_version(info_a);
continue;
}
/* At this point, we've seen another plugin, so we need to compare
* their versions.
*/
version_b = gplugin_plugin_info_get_version(info_b);
cmp = gplugin_version_compare(version_a, version_b);
if(cmp < 0) {
/* plugin_b has a newer version, so set the plugin_a pointers to
* the plugin_b pointers as well as the version pointers.
*/
g_set_object(&plugin_a, plugin_b);
g_set_object(&info_a, info_b);
version_a = version_b;
}
/* Clean up the plugin_b pointers. */
g_clear_object(&plugin_b);
g_clear_object(&info_b);
}
g_clear_object(&info_a);
return plugin_a;
}
/**
* gplugin_manager_get_plugin_dependencies:
* @plugin: The #GPluginPlugin whose dependencies to get.
* @error: (out) (nullable): Return address for a #GError.
*
* Returns a list of all the #GPluginPlugin's that @plugin depends on.
*
* Return value: (element-type GPlugin.Plugin) (transfer full): A #GSList of
* #GPluginPlugin's that @plugin depends on, or %NULL on error
* with @error set. Call g_slist_free_full() with a
* `DestroyNotify` of g_object_unref() on the returned value when
* you're done with it.
*/
GSList *
gplugin_manager_get_plugin_dependencies(GPluginPlugin *plugin, GError **error)
{
GPluginManager *manager = NULL;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_PLUGIN(plugin), NULL);
manager = GPLUGIN_MANAGER_INSTANCE;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->get_plugin_dependencies) {
return klass->get_plugin_dependencies(manager, plugin, error);
}
return NULL;
}
/**
* gplugin_manager_load_plugin:
* @plugin: #GPluginPlugin instance.
* @error: (out) (nullable): return location for a #GError or %NULL.
*
* Loads @plugin and all of its dependencies. If a dependency can not be
* loaded, @plugin will not be loaded either. However, any other plugins that
* @plugin depends on that were loaded from this call, will not be unloaded.
*
* Return value: %TRUE if @plugin was loaded successfully or already loaded,
* %FALSE otherwise.
*/
gboolean
gplugin_manager_load_plugin(GPluginPlugin *plugin, GError **error)
{
GPluginManager *manager = NULL;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_PLUGIN(plugin), FALSE);
manager = GPLUGIN_MANAGER_INSTANCE;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->load_plugin)
return klass->load_plugin(manager, plugin, error);
return FALSE;
}
/**
* gplugin_manager_unload_plugin:
* @plugin: #GPluginPlugin instance.
* @error: (out) (nullable): Return location for a #GError or %NULL.
*
* Unloads @plugin. If @plugin has dependencies, they are not unloaded.
*
* Returns: %TRUE if @plugin was unloaded successfully or not loaded, %FALSE
* otherwise.
*/
gboolean
gplugin_manager_unload_plugin(GPluginPlugin *plugin, GError **error)
{
GPluginManager *manager = NULL;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_PLUGIN(plugin), FALSE);
manager = GPLUGIN_MANAGER_INSTANCE;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), FALSE);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->unload_plugin)
return klass->unload_plugin(manager, plugin, error);
return FALSE;
}
/**
* gplugin_manager_list_plugins:
*
* Returns a #GList of all plugin id's. Each id should be queried directly
* for more information.
*
* Return value: (element-type utf8) (transfer container): A #GList of each
* unique plugin id.
*/
GList *
gplugin_manager_list_plugins(void)
{
GPluginManager *manager = GPLUGIN_MANAGER_INSTANCE;
GPluginManagerClass *klass = NULL;
g_return_val_if_fail(GPLUGIN_IS_MANAGER(manager), NULL);
klass = GPLUGIN_MANAGER_GET_CLASS(manager);
if(klass && klass->list_plugins)
return klass->list_plugins(manager);
return NULL;
}
/**
* gplugin_manager_get_instance:
*
* Returns a #GObject that is the instance of the plugin manager that is being
* used.
*
* This is provided so that signals can be connected and should not be tinkered
* with in any way.
*
* Returns: (transfer none): The #GObject that is the instance of the plugin
* manager.
*/
GObject *
gplugin_manager_get_instance(void)
{
if(instance)
return G_OBJECT(instance);
return NULL;
}