You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/lib/goffice/split/plugin.c

1858 lines
50 KiB

/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Support for dynamically-loaded Gnumeric plugin components.
*
* Authors:
* Old plugin engine:
* Tom Dyas (tdyas@romulus.rutgers.edu)
* Dom Lachowicz (dominicl@seas.upenn.edu)
* New plugin engine:
* Zbigniew Chyla (cyba@gnome.pl)
*/
#include <config.h>
#include <glib/gi18n.h>
#include "gnumeric.h"
#include "plugin.h"
#include "gui-util.h"
#include "gutils.h"
#include "command-context.h"
#include "file.h"
//#include "workbook.h"
//#include "workbook-view.h"
#include "error-info.h"
#include "plugin-loader.h"
#include "plugin-loader-module.h"
#include "plugin-service.h"
#include "xml-io.h"
#include "gnumeric-gconf.h"
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <locale.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <gmodule.h>
#include <application.h>
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlmemory.h>
#include <gsf/gsf-impl-utils.h>
#include <glib-object.h>
#define PLUGIN_INFO_FILE_NAME "plugin.xml"
#define PLUGIN_ID_VALID_CHARS "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
#define BUILTIN_LOADER_MODULE_ID "Gnumeric_Builtin:module"
static GHashTable *plugins_marked_for_deactivation_hash = NULL;
static GSList *available_plugins = NULL;
static GHashTable *available_plugins_id_hash = NULL;
static GHashTable *loader_services = NULL;
static void plugin_get_loader_if_needed (GnmPlugin *pinfo, ErrorInfo **ret_error);
static void plugin_info_read (GnmPlugin *pinfo, const gchar *dir_name, ErrorInfo **ret_error);
static void gnm_plugin_load_base (GnmPlugin *plugin, ErrorInfo **ret_error);
/*
* GnmPlugin
*/
typedef struct {
gchar *plugin_id;
GnmPlugin *plugin; /* don't use directly */
gboolean force_load;
} PluginDependency;
struct _GnmPlugin {
GTypeModule parent_instance;
gboolean has_full_info;
gchar *dir_name;
gchar *id;
gchar *name;
gchar *description;
gboolean require_explicit_enabling;
gboolean is_active;
gint use_refcount;
GSList *dependencies;
gchar *loader_id;
GHashTable *loader_attrs;
GnmPluginLoader *loader;
GSList *services;
char *saved_textdomain;
};
typedef struct _GnmPluginClass GnmPluginClass;
struct _GnmPluginClass {
GTypeModuleClass parent_class;
/* signals */
void (*state_changed) (GnmPluginClass *gpc);
void (*can_deactivate_changed) (GnmPluginClass *gpc);
};
enum {
STATE_CHANGED,
CAN_DEACTIVATE_CHANGED,
LAST_SIGNAL
};
static guint gnm_plugin_signals[LAST_SIGNAL];
static GObjectClass *parent_class = NULL;
static void plugin_dependency_free (gpointer data);
static void
gnm_plugin_init (GObject *obj)
{
GnmPlugin *plugin = GNM_PLUGIN (obj);
plugin->id = NULL;
plugin->dir_name = NULL;
plugin->has_full_info = FALSE;
plugin->saved_textdomain = NULL;
plugin->require_explicit_enabling = FALSE;
}
static void
gnm_plugin_finalize (GObject *obj)
{
GnmPlugin *plugin = GNM_PLUGIN (obj);
g_free (plugin->id);
plugin->id = NULL;
g_free (plugin->dir_name);
plugin->dir_name = NULL;
if (plugin->has_full_info) {
plugin->has_full_info = FALSE;
g_free (plugin->name);
g_free (plugin->description);
gnm_slist_free_custom (plugin->dependencies, plugin_dependency_free);
g_free (plugin->loader_id);
if (plugin->loader_attrs != NULL) {
g_hash_table_destroy (plugin->loader_attrs);
}
if (plugin->loader != NULL) {
g_object_unref (plugin->loader);
}
gnm_slist_free_custom (plugin->services, g_object_unref);
}
g_free (plugin->saved_textdomain);
plugin->saved_textdomain = NULL;
parent_class->finalize (obj);
}
static gboolean
gnm_plugin_type_module_load (GTypeModule *module)
{
GnmPlugin *plugin = GNM_PLUGIN (module);
ErrorInfo *ignored_error;
g_return_val_if_fail (plugin->is_active, FALSE);
gnm_plugin_load_base (plugin, &ignored_error);
if (ignored_error != NULL) {
error_info_print (ignored_error);
error_info_free (ignored_error);
return FALSE;
}
gnm_plugin_use_ref (plugin);
return TRUE;
}
static void
gnm_plugin_type_module_unload (GTypeModule *module)
{
GnmPlugin *plugin = GNM_PLUGIN (module);
g_return_if_fail (plugin->is_active);
gnm_plugin_use_unref (plugin);
}
static void
gnm_plugin_class_init (GObjectClass *gobject_class)
{
GTypeModuleClass *type_module_class = G_TYPE_MODULE_CLASS (gobject_class);
parent_class = g_type_class_peek_parent (gobject_class);
gobject_class->finalize = gnm_plugin_finalize;
type_module_class->load = gnm_plugin_type_module_load;
type_module_class->unload = gnm_plugin_type_module_unload;
gnm_plugin_signals[STATE_CHANGED] = g_signal_new (
"state_changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GnmPluginClass, state_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
gnm_plugin_signals[CAN_DEACTIVATE_CHANGED] = g_signal_new (
"can_deactivate_changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GnmPluginClass, can_deactivate_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
GSF_CLASS (GnmPlugin, gnm_plugin, gnm_plugin_class_init, gnm_plugin_init,
G_TYPE_TYPE_MODULE)
static GnmPlugin *
plugin_info_new_from_xml (const gchar *dir_name, ErrorInfo **ret_error)
{
GnmPlugin *plugin;
ErrorInfo *error;
GNM_INIT_RET_ERROR_INFO (ret_error);
plugin = g_object_new (GNM_PLUGIN_TYPE, NULL);
plugin_info_read (plugin, dir_name, &error);
if (error == NULL) {
plugin->has_full_info = TRUE;
} else {
*ret_error = error;
g_object_unref (plugin);
plugin = NULL;
}
return plugin;
}
static GnmPlugin *
plugin_info_new_with_id_and_dir_name_only (const gchar *id, const gchar *dir_name)
{
GnmPlugin *plugin;
plugin = g_object_new (GNM_PLUGIN_TYPE, NULL);
g_type_module_set_name (G_TYPE_MODULE (plugin), id);
plugin->id = g_strdup (id);
plugin->dir_name = g_strdup (dir_name);
plugin->has_full_info = FALSE;
return plugin;
}
/*
* PluginFileState - information about plugin.xml files used in previous
* and current Gnumeric session.
*/
typedef struct {
gchar *dir_name;
gchar *file_state;
gchar *plugin_id;
enum {PLUGIN_OLD_UNUSED, PLUGIN_OLD_USED, PLUGIN_NEW} age;
} PluginFileState;
static gboolean plugin_file_state_hash_changed;
static GHashTable *plugin_file_state_dir_hash;
static gchar *
get_file_state_as_string (const gchar *file_name)
{
struct stat st;
if (stat (file_name, &st) == -1) {
return NULL;
}
return g_strdup_printf (
"%ld:%ld:%ld:%ld",
(long int) st.st_dev, (long int) st.st_ino,
(long int) st.st_size, (long int) st.st_mtime);
}
static gchar *
plugin_file_state_as_string (PluginFileState *state)
{
return g_strdup_printf ("%s|%s|%s", state->plugin_id, state->file_state,
state->dir_name);
}
static PluginFileState *
plugin_file_state_from_string (const gchar *str)
{
PluginFileState *state;
gchar **strv;
strv = g_strsplit (str, "|", 3);
if (strv[0] == NULL || strv[1] == NULL || strv[2] == NULL) {
g_strfreev (strv);
return NULL;
}
state = g_new (PluginFileState, 1);
state->plugin_id = strv[0];
state->file_state = strv[1];
state->dir_name = strv[2];
state->age = PLUGIN_OLD_UNUSED;
g_free (strv);
return state;
}
static void
plugin_file_state_free (gpointer data)
{
PluginFileState *state = data;
g_free (state->dir_name);
g_free (state->file_state);
g_free (state->plugin_id);
g_free (state);
}
/* --- */
static gboolean
plugin_info_read_full_info_if_needed_error_info (GnmPlugin *pinfo, ErrorInfo **ret_error)
{
ErrorInfo *read_error;
gchar *old_id, *old_dir_name;
GNM_INIT_RET_ERROR_INFO (ret_error);
if (pinfo->has_full_info) {
return TRUE;
}
old_id = pinfo->id;
old_dir_name = pinfo->dir_name;
plugin_info_read (pinfo, old_dir_name, &read_error);
if (read_error == NULL && strcmp (pinfo->id, old_id) == 0) {
/* id and dir_name pointers are guaranteed to be valid during plugin's lifetime */
g_free (pinfo->id);
g_free (pinfo->dir_name);
pinfo->id = old_id;
pinfo->dir_name = old_dir_name;
pinfo->has_full_info = TRUE;
} else {
plugin_message (1, "Can't read plugin.xml file for %s.\n", old_id);
if (read_error == NULL) {
read_error = error_info_new_printf (
_("File contains plugin info with invalid id (%s), expected %s."),
pinfo->id, old_id);
}
*ret_error = error_info_new_str_with_details (
_("Couldn't read plugin info from file."),
read_error);
g_free (old_id);
g_free (old_dir_name);
}
return *ret_error == NULL;
}
static gboolean
plugin_info_read_full_info_if_needed (GnmPlugin *pinfo)
{
ErrorInfo *error;
if (plugin_info_read_full_info_if_needed_error_info (pinfo, &error)) {
return TRUE;
} else {
g_warning ("plugin_info_read_full_info_if_needed: couldn't read plugin info from file.");
error_info_print (error);
error_info_free (error);
return FALSE;
}
}
/*
* Accessor functions
*/
/**
* gnm_plugin_get_textdomain:
* @plugin : The plugin
*
* Returns plugin's textdomain for use with textdomain(3) and d*gettext(3)
* functions.
*/
const gchar *
gnm_plugin_get_textdomain (GnmPlugin *plugin)
{
g_return_val_if_fail (IS_GNM_PLUGIN (plugin), NULL);
if (plugin->saved_textdomain == NULL) {
plugin->saved_textdomain = g_strconcat ("gnumeric__", plugin->id, NULL);
}
return plugin->saved_textdomain;
}
/**
* gnm_plugin_is_active:
* @pinfo : The plugin
*
* Returns : TRUE if @plugin is active and FALSE otherwise.
*/
gboolean
gnm_plugin_is_active (GnmPlugin *plugin)
{
g_return_val_if_fail (IS_GNM_PLUGIN (plugin), FALSE);
if (!plugin->has_full_info) {
return FALSE;
}
return plugin->is_active;
}
/**
* gnm_plugin_get_dir_name:
* @plugin : The plugin
*
* Returns the name of the directory in which @plugin is located.
* Returned string is != NULL and stays valid during @plugin's lifetime.
*/
const gchar *
gnm_plugin_get_dir_name (GnmPlugin *pinfo)
{
g_return_val_if_fail (IS_GNM_PLUGIN (pinfo), NULL);
return pinfo->dir_name;
}
/**
* gnm_plugin_get_id:
* @plugin : The plugin
*
* Returns the ID of @plugin (unique string used for idenfification of
* plugin).
* Returned string is != NULL and stays valid during @plugin's lifetime.
*/
const gchar *
gnm_plugin_get_id (GnmPlugin *pinfo)
{
g_return_val_if_fail (IS_GNM_PLUGIN (pinfo), NULL);
return pinfo->id;
}
/**
* gnm_plugin_get_name:
* @plugin : The plugin
*
* Returns textual name of @plugin. If the real name is not available
* for some reason, automatically generated string will be returned.
* Returned string is != NULL and stays valid during @plugin's lifetime.
*/
const gchar *
gnm_plugin_get_name (GnmPlugin *pinfo)
{
g_return_val_if_fail (IS_GNM_PLUGIN (pinfo), NULL);
if (!plugin_info_read_full_info_if_needed (pinfo)) {
return _("Unknown name");
}
return pinfo->name;
}
/**
* gnm_plugin_get_description:
* @plugin : The plugin
*
* Returns textual description of @plugin or NULL if description is not
* available.
* Returned string stays valid during @plugin's lifetime.
*/
const gchar *
gnm_plugin_get_description (GnmPlugin *pinfo)
{
g_return_val_if_fail (IS_GNM_PLUGIN (pinfo), NULL);
if (!plugin_info_read_full_info_if_needed (pinfo)) {
return NULL;
}
return pinfo->description;
}
/**
* gnm_plugin_is_loaded:
* @pinfo : The plugin
*
* Returns : TRUE if @plugin is loaded and FALSE otherwise.
*/
gboolean
gnm_plugin_is_loaded (GnmPlugin *pinfo)
{
g_return_val_if_fail (IS_GNM_PLUGIN (pinfo), FALSE);
if (!pinfo->has_full_info) {
return FALSE;
}
return pinfo->loader != NULL &&
gnm_plugin_loader_is_base_loaded (pinfo->loader);
}
/* - */
/**
* plugins_register_loader:
* @loader_id : Loader's id
* @service : Plugin service of type "plugin_loader"
*
* Registers new type of plugin loader identified by @loader_id (identifier
* consists of loader's plugin id and service id concatenated using colon).
* All requests to create new loader object of this type will be passed to
* @service.
* This function is intended for use by GnmPluginService objects.
*/
void
plugins_register_loader (const gchar *loader_id, GnmPluginService *service)
{
g_return_if_fail (loader_id != NULL);
g_return_if_fail (service != NULL);
g_hash_table_insert (loader_services, g_strdup (loader_id), service);
}
/**
* plugins_unregister_loader:
* @loader_id : Loader's id
*
* Unregisters a type of plugin loader identified by @loader_id. After
* callingthis function Gnumeric will be unable to load plugins supported
* by the specified loader.
* This function is intended for use by GnmPluginService objects.
*/
void
plugins_unregister_loader (const gchar *loader_id)
{
g_return_if_fail (loader_id != NULL);
g_hash_table_remove (loader_services, loader_id);
}
static GType
get_loader_type_by_id (const gchar *id_str, ErrorInfo **ret_error)
{
GnmPluginService *loader_service;
ErrorInfo *error;
GType loader_type;
g_return_val_if_fail (id_str != NULL, G_TYPE_NONE);
GNM_INIT_RET_ERROR_INFO (ret_error);
if (strcmp (id_str, BUILTIN_LOADER_MODULE_ID) == 0) {
return TYPE_GNM_PLUGIN_LOADER_MODULE;
}
loader_service = g_hash_table_lookup (loader_services, id_str);
if (loader_service == NULL) {
*ret_error = error_info_new_printf (
_("Unsupported loader type \"%s\"."),
id_str);
return G_TYPE_NONE;
}
loader_type = plugin_service_plugin_loader_generate_type (
loader_service, &error);
if (error != NULL) {
*ret_error = error_info_new_printf (
_("Error while preparing loader \"%s\"."),
id_str);
error_info_add_details (*ret_error, error);
return G_TYPE_NONE;
}
return loader_type;
}
static GnmPlugin *
plugin_dependency_get_plugin (PluginDependency *dep)
{
g_return_val_if_fail (dep != NULL, NULL);
if (dep->plugin == NULL)
dep->plugin = plugins_get_plugin_by_id (dep->plugin_id);
return dep->plugin;
}
static GSList *
plugin_info_read_dependency_list (xmlNode *tree)
{
GSList *dependency_list = NULL;
xmlNode *node;
g_return_val_if_fail (tree != NULL, NULL);
g_return_val_if_fail (strcmp (tree->name, "dependencies") == 0, NULL);
for (node = tree->xmlChildrenNode; node != NULL; node = node->next) {
if (strcmp (node->name, "dep_plugin") == 0) {
gchar *plugin_id;
plugin_id = xmlGetProp (node, (xmlChar *)"id");
if (plugin_id != NULL) {
PluginDependency *dep;
dep = g_new (PluginDependency, 1);
dep->plugin_id = plugin_id;
dep->plugin = NULL;
if (!xml_node_get_bool (node, "force_load", &(dep->force_load)))
dep->force_load = FALSE;
GNM_SLIST_PREPEND (dependency_list, dep);
}
}
}
return g_slist_reverse (dependency_list);
}
static GSList *
plugin_info_read_service_list (GnmPlugin *plugin, xmlNode *tree, ErrorInfo **ret_error)
{
GSList *service_list = NULL;
GSList *error_list = NULL;
xmlNode *node;
gint i;
g_return_val_if_fail (tree != NULL, NULL);
node = e_xml_get_child_by_name (tree, (xmlChar *)"services");
if (node == NULL)
return NULL;
node = node->xmlChildrenNode;
for (i = 0; node != NULL; i++, node = node->next) {
if (strcmp (node->name, "service") == 0) {
GnmPluginService *service;
ErrorInfo *service_error;
service = plugin_service_new (plugin, node, &service_error);
if (service != NULL) {
g_assert (service_error == NULL);
GNM_SLIST_PREPEND (service_list, service);
} else {
ErrorInfo *error;
error = error_info_new_printf (
_("Error while reading service #%d info."),
i);
error_info_add_details (error, service_error);
GNM_SLIST_PREPEND (error_list, error);
}
}
}
if (error_list != NULL) {
GNM_SLIST_REVERSE (error_list);
*ret_error = error_info_new_from_error_list (error_list);
gnm_slist_free_custom (service_list, g_object_unref);
return NULL;
} else {
return g_slist_reverse (service_list);
}
}
static GHashTable *
plugin_info_read_loader_attrs (xmlNode *tree)
{
xmlNode *node;
GHashTable *hash;
g_return_val_if_fail (tree != NULL, NULL);
g_return_val_if_fail (strcmp (tree->name, "loader") == 0, NULL);
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
for (node = tree->xmlChildrenNode; node != NULL; node = node->next) {
if (strcmp (node->name, "attribute") == 0) {
gchar *name, *value;
name = xmlGetProp (node, (xmlChar *)"name");
if (name != NULL) {
if (g_hash_table_lookup (hash, name) == NULL) {
value = xmlGetProp (node, (xmlChar *)"value");
g_hash_table_insert (hash, name, value);
} else {
g_warning ("Duplicated \"%s\" attribute in plugin.xml file.", name);
g_free (name);
}
}
}
}
return hash;
}
static void
plugin_dependency_free (gpointer data)
{
PluginDependency *dep = data;
g_return_if_fail (dep != NULL);
g_free (dep->plugin_id);
g_free (dep);
}
static void
plugin_info_read (GnmPlugin *plugin, const gchar *dir_name, ErrorInfo **ret_error)
{
gchar *file_name;
xmlDocPtr doc;
gchar *id, *name, *description;
xmlNode *tree, *information_node, *dependencies_node, *loader_node;
GSList *dependency_list;
gchar *loader_id;
GHashTable *loader_attrs;
gboolean require_explicit_enabling = FALSE;
g_return_if_fail (IS_GNM_PLUGIN (plugin));
g_return_if_fail (dir_name != NULL);
GNM_INIT_RET_ERROR_INFO (ret_error);
file_name = g_build_filename (dir_name, PLUGIN_INFO_FILE_NAME, NULL);
doc = xmlParseFile (file_name);
if (doc == NULL || doc->xmlRootNode == NULL || strcmp (doc->xmlRootNode->name, "plugin") != 0) {
if (access (file_name, R_OK) != 0) {
*ret_error = error_info_new_printf (
_("Can't read plugin info file (\"%s\")."),
file_name);
} else {
*ret_error = error_info_new_printf (
_("File \"%s\" is not valid plugin info file."),
file_name);
}
g_free (file_name);
xmlFreeDoc (doc);
return;
}
tree = doc->xmlRootNode;
id = xmlGetProp (tree, (xmlChar *)"id");
information_node = e_xml_get_child_by_name (tree, (xmlChar *)"information");
if (information_node != NULL) {
xmlNode *node;
xmlChar *val;
node = e_xml_get_child_by_name_by_lang (information_node, "name");
if (node != NULL) {
val = xmlNodeGetContent (node);
name = g_strdup ((gchar *)val);
xmlFree (val);
} else
name = NULL;
node = e_xml_get_child_by_name_by_lang (information_node, "description");
if (node != NULL) {
val = xmlNodeGetContent (node);
description = g_strdup ((gchar *)val);
xmlFree (val);
} else
description = NULL;
if (e_xml_get_child_by_name (information_node, (xmlChar const *)"require_explicit_enabling"))
require_explicit_enabling = TRUE;
} else {
name = NULL;
description = NULL;
}
dependencies_node = e_xml_get_child_by_name (tree, (xmlChar *)"dependencies");
if (dependencies_node != NULL) {
dependency_list = plugin_info_read_dependency_list (dependencies_node);
} else {
dependency_list = NULL;
}
loader_node = e_xml_get_child_by_name (tree, (xmlChar *)"loader");
if (loader_node != NULL) {
char *p;
loader_id = xmlGetProp (loader_node, (xmlChar *)"type");
if (loader_id != NULL && (p = strchr (loader_id, ':')) != NULL) {
loader_attrs = plugin_info_read_loader_attrs (loader_node);
if (strcmp (loader_id, BUILTIN_LOADER_MODULE_ID) != 0) {
PluginDependency *dep;
/* Add loader's plugin to the list of dependencies */
dep = g_new (PluginDependency, 1);
dep->plugin_id = g_strndup (loader_id, p - loader_id);
dep->plugin = NULL;
dep->force_load = FALSE;
GNM_SLIST_PREPEND (dependency_list, dep);
}
} else {
loader_id = NULL;
loader_attrs = NULL;
}
} else {
loader_id = NULL;
loader_attrs = NULL;
}
if (id != NULL && name != NULL && loader_id != NULL &&
id[strspn (id, PLUGIN_ID_VALID_CHARS)] == '\0') {
ErrorInfo *services_error = NULL;
g_type_module_set_name (G_TYPE_MODULE (plugin), id);
plugin->dir_name = g_strdup (dir_name);
plugin->id = id;
plugin->name = name;
plugin->description = description;
plugin->require_explicit_enabling = require_explicit_enabling;
plugin->is_active = FALSE;
plugin->use_refcount = 0;
plugin->dependencies = dependency_list;
plugin->loader_id = loader_id;
plugin->loader_attrs = loader_attrs;
plugin->loader = NULL;
plugin->services = plugin_info_read_service_list (plugin, tree, &services_error);
if (services_error != NULL) {
*ret_error = error_info_new_printf (
_("Errors while reading services for plugin with id=\"%s\"."),
id);
error_info_add_details (*ret_error, services_error);
} else if (plugin->services == NULL)
*ret_error = error_info_new_printf (
_("No services defined for plugin with id=\"%s\"."),
id);
else
plugin_message (4, "Read plugin.xml file for %s.\n", plugin->id);
} else {
if (id != NULL) {
GSList *error_list = NULL;
if (id[strspn (id, PLUGIN_ID_VALID_CHARS)] != '\0') {
GNM_SLIST_PREPEND (error_list, error_info_new_printf (
_("Plugin id contains invalid characters (%s)."), id));
}
if (name == NULL) {
GNM_SLIST_PREPEND (error_list, error_info_new_str (
_("Unknown plugin name.")));
}
if (loader_id == NULL) {
GNM_SLIST_PREPEND (error_list, error_info_new_printf (
_("No loader defined or loader id invalid for plugin with id=\"%s\"."), id));
}
g_assert (error_list != NULL);
GNM_SLIST_REVERSE (error_list);
*ret_error = error_info_new_from_error_list (error_list);
} else
*ret_error = error_info_new_str (_("Plugin has no id."));
gnm_slist_free_custom (dependency_list, plugin_dependency_free);
g_free (plugin->loader_id);
if (plugin->loader_attrs != NULL)
g_hash_table_destroy (plugin->loader_attrs);
g_free (id);
g_free (name);
g_free (description);
}
g_free (file_name);
xmlFreeDoc (doc);
}
static void
plugin_get_loader_if_needed (GnmPlugin *pinfo, ErrorInfo **ret_error)
{
GType loader_type;
ErrorInfo *error = NULL;
g_return_if_fail (IS_GNM_PLUGIN (pinfo));
GNM_INIT_RET_ERROR_INFO (ret_error);
if (!plugin_info_read_full_info_if_needed_error_info (pinfo, ret_error)) {
return;
}
if (pinfo->loader != NULL) {
return;
}
loader_type = get_loader_type_by_id (pinfo->loader_id, &error);
if (error == NULL) {
GnmPluginLoader *loader;
ErrorInfo *error;
loader = GNM_PLUGIN_LOADER (g_object_new (loader_type, NULL));
gnm_plugin_loader_set_attributes (loader, pinfo->loader_attrs, &error);
if (error == NULL) {
pinfo->loader = loader;
gnm_plugin_loader_set_plugin (loader, pinfo);
} else {
g_object_unref (loader);
loader = NULL;
*ret_error = error_info_new_printf (
_("Error initializing plugin loader (\"%s\")."),
pinfo->loader_id);
error_info_add_details (*ret_error, error);
}
} else {
*ret_error = error;
}
}
/**
* gnm_plugin_activate:
* @plugin : The plugin
* @ret_error : Pointer used to report errors
*
* Activates @plugin together with all its dependencies.
* In case of error the plugin won't be activated and detailed error
* information will be returned using @ret_error.
*/
void
gnm_plugin_activate (GnmPlugin *pinfo, ErrorInfo **ret_error)
{
GSList *error_list = NULL;
GSList *l;
gint i;
static GSList *activate_stack = NULL;
g_return_if_fail (IS_GNM_PLUGIN (pinfo));
GNM_INIT_RET_ERROR_INFO (ret_error);
if (g_slist_find (activate_stack, pinfo) != NULL) {
*ret_error = error_info_new_str (
_("Detected cyclic plugin dependencies."));
return;
}
if (!plugin_info_read_full_info_if_needed_error_info (pinfo, ret_error)) {
return;
}
if (pinfo->is_active) {
return;
}
/* Activate plugin dependencies */
GNM_SLIST_PREPEND (activate_stack, pinfo);
GNM_SLIST_FOREACH (pinfo->dependencies, PluginDependency, dep,
GnmPlugin *dep_plugin;
dep_plugin = plugin_dependency_get_plugin (dep);
if (dep_plugin != NULL) {
ErrorInfo *dep_error;
gnm_plugin_activate (dep_plugin, &dep_error);
if (dep_error != NULL) {
ErrorInfo *new_error;
new_error = error_info_new_printf (
_("Couldn't activate plugin with id=\"%s\"."), dep->plugin_id);
error_info_add_details (new_error, dep_error);
GNM_SLIST_PREPEND (error_list, new_error);
}
} else {
GNM_SLIST_PREPEND (error_list, error_info_new_printf (
_("Couldn't find plugin with id=\"%s\"."), dep->plugin_id));
}
);
g_assert (activate_stack != NULL && activate_stack->data == pinfo);
activate_stack = g_slist_delete_link (activate_stack, activate_stack);
if (error_list != NULL) {
*ret_error = error_info_new_str (
_("Error while activating plugin dependencies."));
error_info_add_details_list (*ret_error, error_list);
return;
}
for (l = pinfo->services, i = 0; l != NULL; l = l->next, i++) {
GnmPluginService *service = l->data;
ErrorInfo *service_error;
plugin_service_activate (service, &service_error);
if (service_error != NULL) {
ErrorInfo *error;
error = error_info_new_printf (
_("Error while activating plugin service #%d."), i);
error_info_add_details (error, service_error);
GNM_SLIST_PREPEND (error_list, error);
}
}
if (error_list != NULL) {
*ret_error = error_info_new_from_error_list (error_list);
/* FIXME - deactivate activated services */
return;
}
GNM_SLIST_FOREACH (pinfo->dependencies, PluginDependency, dep,
gnm_plugin_use_ref (plugin_dependency_get_plugin (dep));
);
pinfo->is_active = TRUE;
g_signal_emit (G_OBJECT (pinfo), gnm_plugin_signals[STATE_CHANGED], 0);
}
/**
* gnm_plugin_deactivate:
* @plugin : The plugin
* @ret_error : Pointer used to report errors
*
* Dectivates @plugin. Its dependencies will NOT be automatically
* deactivated.
* In case of error the plugin won't be deactivated and detailed error
* information will be returned using @ret_error.
*/
void
gnm_plugin_deactivate (GnmPlugin *pinfo, ErrorInfo **ret_error)
{
GSList *error_list = NULL;
GSList *l;
gint i;
g_return_if_fail (IS_GNM_PLUGIN (pinfo));
GNM_INIT_RET_ERROR_INFO (ret_error);
if (!pinfo->has_full_info || !pinfo->is_active) {
return;
}
if (pinfo->use_refcount > 0) {
*ret_error = error_info_new_str ("Plugin is still in use.");
return;
}
for (l = pinfo->services, i = 0; l != NULL; l = l->next, i++) {
GnmPluginService *service = l->data;
ErrorInfo *service_error;
plugin_service_deactivate (service, &service_error);
if (service_error != NULL) {
ErrorInfo *error;
error = error_info_new_printf (
_("Error while deactivating plugin service #%d."), i);
error_info_add_details (error, service_error);
GNM_SLIST_PREPEND (error_list, error);
}
}
if (error_list != NULL) {
*ret_error = error_info_new_from_error_list (error_list);
/* FIXME - some services are still active (or broken) */
} else {
pinfo->is_active = FALSE;
GNM_SLIST_FOREACH (pinfo->dependencies, PluginDependency, dep,
gnm_plugin_use_unref (plugin_dependency_get_plugin (dep));
);
if (pinfo->loader != NULL) {
g_object_unref (pinfo->loader);
pinfo->loader = NULL;
}
}
g_signal_emit (G_OBJECT (pinfo), gnm_plugin_signals[STATE_CHANGED], 0);
}
/**
* gnm_plugin_can_deactivate:
* @pinfo : The plugin
*
* Tells if the plugin can be deactivated using gnm_plugin_deactivate.
*
* Returns : TRUE if @plugin can be deactivated and FALSE otherwise.
*/
gboolean
gnm_plugin_can_deactivate (GnmPlugin *pinfo)
{
g_return_val_if_fail (IS_GNM_PLUGIN (pinfo), FALSE);
if (!pinfo->is_active) {
return FALSE;
}
if (!plugin_info_read_full_info_if_needed (pinfo)) {
return FALSE;
}
return pinfo->use_refcount == 0;
}
static void
gnm_plugin_load_base (GnmPlugin *plugin, ErrorInfo **ret_error)
{
ErrorInfo *error;
GSList *error_list = NULL;
static GSList *load_stack = NULL;
GNM_INIT_RET_ERROR_INFO (ret_error);
if (g_slist_find (load_stack, plugin) != NULL) {
*ret_error = error_info_new_str (
_("Detected cyclic plugin dependencies."));
return;
}
if (gnm_plugin_is_loaded (plugin)) {
return;
}
if (!plugin_info_read_full_info_if_needed_error_info (plugin, ret_error)) {
return;
}
plugin_get_loader_if_needed (plugin, &error);
if (error != NULL) {
*ret_error = error_info_new_str_with_details (
_("Cannot load plugin loader."),
error);
return;
}
/* Load plugin dependencies */
GNM_SLIST_PREPEND (load_stack, plugin);
GNM_SLIST_FOREACH (plugin->dependencies, PluginDependency, dep,
GnmPlugin *dep_plugin;
ErrorInfo *dep_error;
if (!dep->force_load) {
continue;
}
dep_plugin = plugin_dependency_get_plugin (dep);
if (dep_plugin != NULL) {
plugin_get_loader_if_needed (dep_plugin, &dep_error);
if (dep_error == NULL) {
gnm_plugin_load_base (dep_plugin, &dep_error);
} else {
dep_error = error_info_new_str_with_details (
_("Cannot load plugin loader."),
dep_error);
}
if (dep_error != NULL) {
ErrorInfo *new_error;
new_error = error_info_new_printf (
_("Couldn't load plugin with id=\"%s\"."), dep->plugin_id);
error_info_add_details (new_error, dep_error);
GNM_SLIST_PREPEND (error_list, new_error);
}
} else {
GNM_SLIST_PREPEND (error_list, error_info_new_printf (
_("Couldn't find plugin with id=\"%s\"."), dep->plugin_id));
}
);
g_assert (load_stack != NULL && load_stack->data == plugin);
load_stack = g_slist_delete_link (load_stack, load_stack);
if (error_list != NULL) {
*ret_error = error_info_new_str (
_("Error while loading plugin dependencies."));
error_info_add_details_list (*ret_error, error_list);
return;
}
gnm_plugin_loader_load_base (plugin->loader, &error);
if (error != NULL) {
*ret_error = error;
return;
}
g_signal_emit (G_OBJECT (plugin), gnm_plugin_signals[STATE_CHANGED], 0);
}
/**
* gnm_plugin_load_service:
* @pinfo : The plugin
* @service : Plugin service
* @ret_error : Pointer used to report errors
*
* Loads base part of the plugin if is not loaded and then loads given
* plugin service (prepares necessary part of the plugin for direct use).
* This function is intended for use by GnmPluginService objects.
*/
void
gnm_plugin_load_service (GnmPlugin *pinfo, GnmPluginService *service, ErrorInfo **ret_error)
{
g_return_if_fail (IS_GNM_PLUGIN (pinfo));
g_return_if_fail (service != NULL);
GNM_INIT_RET_ERROR_INFO (ret_error);
gnm_plugin_load_base (pinfo, ret_error);
if (*ret_error != NULL) {
return;
}
gnm_plugin_loader_load_service (pinfo->loader, service, ret_error);
}
/**
* gnm_plugin_unload_service:
* @pinfo : The plugin
* @service : Plugin service
* @ret_error : Pointer used to report errors
*
* ...
* This function is intended for use by GnmPluginService objects.
*/
void
gnm_plugin_unload_service (GnmPlugin *pinfo, GnmPluginService *service, ErrorInfo **ret_error)
{
g_return_if_fail (IS_GNM_PLUGIN (pinfo));
g_return_if_fail (pinfo->loader != NULL);
g_return_if_fail (service != NULL);
GNM_INIT_RET_ERROR_INFO (ret_error);
if (!plugin_info_read_full_info_if_needed_error_info (pinfo, ret_error)) {
return;
}
gnm_plugin_loader_unload_service (pinfo->loader, service, ret_error);
}
/**
* gnm_plugin_use_ref:
* @plugin : The plugin
*/
void
gnm_plugin_use_ref (GnmPlugin *plugin)
{
g_return_if_fail (IS_GNM_PLUGIN (plugin));
g_return_if_fail (plugin->is_active);
plugin->use_refcount++;
if (plugin->use_refcount == 1) {
g_signal_emit (G_OBJECT (plugin), gnm_plugin_signals[CAN_DEACTIVATE_CHANGED], 0);
}
}
/**
* gnm_plugin_use_unref:
* @plugin : The plugin
*/
void
gnm_plugin_use_unref (GnmPlugin *plugin)
{
g_return_if_fail (IS_GNM_PLUGIN (plugin));
g_return_if_fail (plugin->is_active);
g_return_if_fail (plugin->use_refcount > 0);
plugin->use_refcount--;
if (plugin->use_refcount == 0) {
g_signal_emit (G_OBJECT (plugin), gnm_plugin_signals[CAN_DEACTIVATE_CHANGED], 0);
}
}
/**
* gnm_plugin_get_dependencies_ids:
* @plugin : The plugin
*
* Returns the list of identifiers of plugins that @plugin depends on.
* All these plugins will be automatically activated before activating
* the @plugin itself.
* The caller must free the returned list together with the strings it
* points to (use gnm_slist_free_custom (list, g_free) to do this).
*/
GSList *
gnm_plugin_get_dependencies_ids (GnmPlugin *plugin)
{
GSList *list = NULL;
GNM_SLIST_FOREACH (plugin->dependencies, PluginDependency, dep,
GNM_SLIST_PREPEND (list, g_strdup (dep->plugin_id));
);
return g_slist_reverse (list);
}
/**
* gnm_plugin_get_services:
* @plugin : The plugin
*
*/
GSList *
gnm_plugin_get_services (GnmPlugin *plugin)
{
g_return_val_if_fail (IS_GNM_PLUGIN (plugin), NULL);
return plugin->services;
}
/*
* May return NULL without errors (is XML file doesn't exist)
*/
static GnmPlugin *
plugin_info_read_for_dir (const gchar *dir_name, ErrorInfo **ret_error)
{
GnmPlugin *pinfo = NULL;
gchar *file_name;
gchar *file_state;
PluginFileState *state;
ErrorInfo *plugin_error;
g_return_val_if_fail (dir_name != NULL, NULL);
GNM_INIT_RET_ERROR_INFO (ret_error);
file_name = g_build_filename (dir_name, PLUGIN_INFO_FILE_NAME, NULL);
file_state = get_file_state_as_string (file_name);
if (file_state == NULL) {
g_free (file_name);
return NULL;
}
state = g_hash_table_lookup (plugin_file_state_dir_hash, dir_name);
if (state != NULL && strcmp (state->file_state, file_state) == 0) {
pinfo = plugin_info_new_with_id_and_dir_name_only (state->plugin_id, state->dir_name);
state->age = PLUGIN_OLD_USED;
} else if ((pinfo = plugin_info_new_from_xml (dir_name, &plugin_error)) != NULL) {
g_assert (plugin_error == NULL);
if (state == NULL) {
state = g_new (PluginFileState, 1);
state->dir_name = g_strdup (dir_name);
state->file_state = g_strdup (file_state);
state->plugin_id = g_strdup (gnm_plugin_get_id (pinfo));
state->age = PLUGIN_NEW;
g_hash_table_insert (plugin_file_state_dir_hash, state->dir_name, state);
} else {
if (strcmp (state->plugin_id, pinfo->id) == 0) {
state->age = PLUGIN_OLD_USED;
} else {
state->age = PLUGIN_NEW;
}
g_free (state->file_state);
g_free (state->plugin_id);
state->file_state = g_strdup (file_state);
state->plugin_id = g_strdup (gnm_plugin_get_id (pinfo));
}
plugin_file_state_hash_changed = TRUE;
} else {
*ret_error = error_info_new_printf (
_("Errors occurred while reading plugin informations from file \"%s\"."),
file_name);
error_info_add_details (*ret_error, plugin_error);
}
g_free (file_name);
g_free (file_state);
return pinfo;
}
/*
* May return partial list and some error info.
*/
static GSList *
plugin_info_list_read_for_subdirs_of_dir (const gchar *dir_name, ErrorInfo **ret_error)
{
GSList *plugin_info_list = NULL;
GDir *dir;
char const *d_name;
GSList *error_list = NULL;
g_return_val_if_fail (dir_name != NULL, NULL);
GNM_INIT_RET_ERROR_INFO (ret_error);
dir = g_dir_open (dir_name, 0, NULL);
if (dir == NULL)
return NULL;
while ((d_name = g_dir_read_name (dir)) != NULL) {
gchar *full_entry_name;
ErrorInfo *error = NULL;
GnmPlugin *pinfo;
if (strcmp (d_name, ".") == 0 || strcmp (d_name, "..") == 0)
continue;
full_entry_name = g_build_filename (dir_name, d_name, NULL);
pinfo = plugin_info_read_for_dir (full_entry_name, &error);
if (pinfo != NULL) {
GNM_SLIST_PREPEND (plugin_info_list, pinfo);
}
if (error != NULL) {
GNM_SLIST_PREPEND (error_list, error);
}
g_free (full_entry_name);
}
if (error_list != NULL) {
GNM_SLIST_REVERSE (error_list);
*ret_error = error_info_new_from_error_list (error_list);
}
g_dir_close (dir);
return g_slist_reverse (plugin_info_list);
}
/*
* May return partial list and some error info.
*/
static GSList *
plugin_info_list_read_for_subdirs_of_dir_list (GSList *dir_list, ErrorInfo **ret_error)
{
GSList *plugin_info_list = NULL;
GSList *dir_iterator;
GSList *error_list = NULL;
GNM_INIT_RET_ERROR_INFO (ret_error);
for (dir_iterator = dir_list; dir_iterator != NULL; dir_iterator = dir_iterator->next) {
gchar *dir_name;
ErrorInfo *error = NULL;
GSList *dir_plugin_info_list;
dir_name = (gchar *) dir_iterator->data;
dir_plugin_info_list = plugin_info_list_read_for_subdirs_of_dir (dir_name, &error);
if (error != NULL) {
GNM_SLIST_PREPEND (error_list, error);
}
if (dir_plugin_info_list != NULL) {
GNM_SLIST_CONCAT (plugin_info_list, dir_plugin_info_list);
}
}
if (error_list != NULL) {
GNM_SLIST_REVERSE (error_list);
*ret_error = error_info_new_from_error_list (error_list);
}
return plugin_info_list;
}
static GSList *
gnumeric_extra_plugin_dirs (void)
{
GSList *extra_dirs;
gchar const *plugin_path_env;
extra_dirs = gnm_string_slist_copy (gnm_app_prefs->plugin_extra_dirs);
plugin_path_env = g_getenv ("GNUMERIC_PLUGIN_PATH");
if (plugin_path_env != NULL) {
GNM_SLIST_CONCAT (extra_dirs, gnm_strsplit_to_slist (plugin_path_env, ":"));
}
return extra_dirs;
}
/*
* May return partial list and some error info.
*/
static GSList *
plugin_info_list_read_for_all_dirs (ErrorInfo **ret_error)
{
GSList *dir_list;
GSList *plugin_info_list;
ErrorInfo *error;
GNM_INIT_RET_ERROR_INFO (ret_error);
dir_list = gnm_slist_create (gnm_sys_plugin_dir (),
gnm_usr_plugin_dir (),
NULL);
GNM_SLIST_CONCAT (dir_list, gnumeric_extra_plugin_dirs ());
plugin_info_list = plugin_info_list_read_for_subdirs_of_dir_list (dir_list, &error);
g_slist_foreach (dir_list, (GFunc)g_free, NULL);
g_slist_free (dir_list);
*ret_error = error;
return plugin_info_list;
}
/**
* plugin_db_activate_plugin_list:
* @plugins : The list of plugins
* @ret_error : Pointer used to report errors
*
* Activates all plugins in the list. If some of the plugins cannot be
* activated, the function reports this via @ret_error (errors don't
* affect plugins activated successfully).
*/
void
plugin_db_activate_plugin_list (GSList *plugins, ErrorInfo **ret_error)
{
GSList *error_list = NULL;
GNM_INIT_RET_ERROR_INFO (ret_error);
GNM_SLIST_FOREACH (plugins, GnmPlugin, pinfo,
ErrorInfo *error;
gnm_plugin_activate (pinfo, &error);
if (error != NULL) {
ErrorInfo *new_error;
new_error = error_info_new_printf (
_("Couldn't activate plugin \"%s\" (ID: %s)."),
pinfo->name, pinfo->id);
error_info_add_details (new_error, error);
GNM_SLIST_PREPEND (error_list, new_error);
}
);
if (error_list != NULL) {
GNM_SLIST_REVERSE (error_list);
*ret_error = error_info_new_from_error_list (error_list);
}
}
/**
* plugin_db_deactivate_plugin_list:
* @plugins : The list of plugins
* @ret_error : Pointer used to report errors
*
* Deactivates all plugins in the list. If some of the plugins cannot be
* deactivated, the function reports this via @ret_error (errors don't
* affect plugins deactivated successfully).
*/
void
plugin_db_deactivate_plugin_list (GSList *plugins, ErrorInfo **ret_error)
{
GSList *error_list = NULL;
GNM_INIT_RET_ERROR_INFO (ret_error);
GNM_SLIST_FOREACH (plugins, GnmPlugin, pinfo,
ErrorInfo *error;
gnm_plugin_deactivate (pinfo, &error);
if (error != NULL) {
ErrorInfo *new_error;
new_error = error_info_new_printf (
_("Couldn't deactivate plugin \"%s\" (ID: %s)."),
pinfo->name, pinfo->id);
error_info_add_details (new_error, error);
GNM_SLIST_PREPEND (error_list, new_error);
}
);
if (error_list != NULL) {
GNM_SLIST_REVERSE (error_list);
*ret_error = error_info_new_from_error_list (error_list);
}
}
/**
* plugins_get_available_plugins:
*
* Returns the list of available plugins. The returned value must not be
* freed and stays valid until calling plugins_rescan or plugins_shutdown.
*/
GSList *
plugins_get_available_plugins (void)
{
return available_plugins;
}
/**
* plugins_get_plugin_by_id:
* @plugin_id : String containing plugin ID
*
* Returns GnmPlugin object for plugin with ID equal to @plugin_id or NULL
* if there's no plugin available with given id.
* Function returns "borrowed" reference, use g_object_ref if you want to
* be sure that plugin won't disappear.
*/
GnmPlugin *
plugins_get_plugin_by_id (const gchar *plugin_id)
{
g_return_val_if_fail (plugin_id != NULL, NULL);
return g_hash_table_lookup (available_plugins_id_hash, plugin_id);
}
/**
* plugin_db_mark_plugin_for_deactivation:
* ...
*/
void
plugin_db_mark_plugin_for_deactivation (GnmPlugin *pinfo, gboolean mark)
{
g_return_if_fail (IS_GNM_PLUGIN (pinfo));
if (mark) {
if (plugins_marked_for_deactivation_hash == NULL) {
plugins_marked_for_deactivation_hash = g_hash_table_new (&g_str_hash, &g_str_equal);
}
g_hash_table_insert (plugins_marked_for_deactivation_hash, pinfo->id, pinfo);
} else {
if (plugins_marked_for_deactivation_hash != NULL) {
g_hash_table_remove (plugins_marked_for_deactivation_hash, pinfo->id);
}
}
}
/**
* plugin_db_is_plugin_marked_for_deactivation:
* ...
*/
gboolean
plugin_db_is_plugin_marked_for_deactivation (GnmPlugin *pinfo)
{
return plugins_marked_for_deactivation_hash != NULL &&
g_hash_table_lookup (plugins_marked_for_deactivation_hash, pinfo->id) != NULL;
}
static void
ghf_set_state_old_unused (gpointer key, gpointer value, gpointer unused)
{
PluginFileState *state = value;
state->age = PLUGIN_OLD_UNUSED;
}
/**
* plugins_rescan:
* @ret_error : Pointer used to report errors
* @ret_new_plugins : Optional pointer to return list of new plugins
*
*
*/
void
plugins_rescan (ErrorInfo **ret_error, GSList **ret_new_plugins)
{
GSList *error_list = NULL;
ErrorInfo *error;
GSList *new_available_plugins;
GHashTable *new_available_plugins_id_hash;
GSList *removed_plugins = NULL, *added_plugins = NULL, *still_active_ids = NULL;
GNM_INIT_RET_ERROR_INFO (ret_error);
/* re-read plugins list from disk */
g_hash_table_foreach (plugin_file_state_dir_hash, ghf_set_state_old_unused, NULL);
new_available_plugins = plugin_info_list_read_for_all_dirs (&error);
if (error != NULL) {
GNM_SLIST_PREPEND (error_list, error_info_new_str_with_details (
_("Errors while reading info about available plugins."), error));
}
/* Find and (try to) deactivate not any longer available plugins */
new_available_plugins_id_hash = g_hash_table_new (g_str_hash, g_str_equal);
GNM_SLIST_FOREACH (new_available_plugins, GnmPlugin, plugin,
g_hash_table_insert (
new_available_plugins_id_hash, (char *) gnm_plugin_get_id (plugin), plugin);
);
GNM_SLIST_FOREACH (available_plugins, GnmPlugin, plugin,
GnmPlugin *found_plugin;
found_plugin = g_hash_table_lookup (
new_available_plugins_id_hash, gnm_plugin_get_id (plugin));
if (found_plugin == NULL ||
strcmp (gnm_plugin_get_dir_name (found_plugin),
gnm_plugin_get_dir_name (plugin)) != 0) {
GNM_SLIST_PREPEND (removed_plugins, plugin);
}
);
g_hash_table_destroy (new_available_plugins_id_hash);
plugin_db_deactivate_plugin_list (removed_plugins, &error);
if (error != NULL) {
GNM_SLIST_PREPEND (error_list, error_info_new_str_with_details (
_("Errors while deactivating plugins that are no longer on disk."), error));
}
GNM_SLIST_FOREACH (removed_plugins, GnmPlugin, plugin,
if (gnm_plugin_is_active (plugin)) {
GNM_SLIST_PREPEND (still_active_ids, (char *) gnm_plugin_get_id (plugin));
} else {
GNM_SLIST_REMOVE (available_plugins, plugin);
g_hash_table_remove (available_plugins_id_hash, gnm_plugin_get_id (plugin));
g_object_unref (plugin);
}
);
g_slist_free (removed_plugins);
if (still_active_ids != NULL) {
GString *s;
s = g_string_new (still_active_ids->data);
GNM_SLIST_FOREACH (still_active_ids->next, char, id,
g_string_append (s, ", ");
g_string_append (s, id);
);
GNM_SLIST_PREPEND (error_list, error_info_new_printf (
_("The following plugins are no longer on disk but are still active:\n"
"%s.\nYou should restart Gnumeric now."), s->str));
g_string_free (s, TRUE);
gnm_slist_free_custom (still_active_ids, g_free);
}
/* Find previously not available plugins */
GNM_SLIST_FOREACH (new_available_plugins, GnmPlugin, plugin,
GnmPlugin *old_plugin;
old_plugin = g_hash_table_lookup (
available_plugins_id_hash, gnm_plugin_get_id (plugin));
if (old_plugin == NULL) {
GNM_SLIST_PREPEND (added_plugins, plugin);
g_object_ref (plugin);
}
);
gnm_slist_free_custom (new_available_plugins, g_object_unref);
if (ret_new_plugins != NULL) {
*ret_new_plugins = g_slist_copy (added_plugins);
}
GNM_SLIST_FOREACH (added_plugins, GnmPlugin, plugin,
g_hash_table_insert (
available_plugins_id_hash, (char *) gnm_plugin_get_id (plugin), plugin);
);
GNM_SLIST_CONCAT (available_plugins, added_plugins);
/* handle errors */
if (error_list != NULL) {
*ret_error = error_info_new_from_error_list (g_slist_reverse (error_list));
}
}
static void
ghf_collect_new_plugins (gpointer ignored,
PluginFileState *s, GSList **plugin_list)
{
if (s->age == PLUGIN_NEW) {
GnmPlugin *plugin = plugins_get_plugin_by_id (s->plugin_id);
if (plugin != NULL && !plugin->require_explicit_enabling)
GNM_SLIST_PREPEND (*plugin_list, plugin);
}
}
/**
* plugins_init:
* @context : #GnmCmdContext used to report errors
*
* Initializes the plugin subsystem. Don't call this function more than
* once.
*/
void
plugins_init (GnmCmdContext *context)
{
GSList *error_list = NULL;
ErrorInfo *error;
GSList *plugin_list;
gnm_time_counter_push ();
loader_services = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
/* initialize hash table with information about known plugin.xml files */
plugin_file_state_dir_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, plugin_file_state_free);
GNM_SLIST_FOREACH (gnm_app_prefs->plugin_file_states, char, state_str,
PluginFileState *state;
state = plugin_file_state_from_string (state_str);
if (state != NULL)
g_hash_table_insert (plugin_file_state_dir_hash, state->dir_name, state);
);
plugin_file_state_hash_changed = FALSE;
/* collect information about the available plugins */
available_plugins = plugin_info_list_read_for_all_dirs (&error);
available_plugins_id_hash = g_hash_table_new (g_str_hash, g_str_equal);
GNM_SLIST_FOREACH (available_plugins, GnmPlugin, plugin,
g_hash_table_insert (
available_plugins_id_hash,
(gpointer) gnm_plugin_get_id (plugin), plugin);
);
if (error != NULL) {
GNM_SLIST_PREPEND (error_list, error_info_new_str_with_details (
_("Errors while reading info about available plugins."), error));
}
/* get descriptors for all previously active plugins */
plugin_list = NULL;
GNM_SLIST_FOREACH (gnm_app_prefs->active_plugins, char, plugin_id,
GnmPlugin *plugin = plugins_get_plugin_by_id (plugin_id);
if (plugin != NULL)
GNM_SLIST_PREPEND (plugin_list, plugin);
);
/* get descriptors for new plugins */
if (gnm_app_prefs->activate_new_plugins)
{
g_hash_table_foreach (
plugin_file_state_dir_hash,
(GHFunc) ghf_collect_new_plugins,
&plugin_list);
}
plugin_list = g_slist_reverse (plugin_list);
plugin_db_activate_plugin_list (plugin_list, &error);
g_slist_free (plugin_list);
if (error != NULL) {
GNM_SLIST_PREPEND (error_list, error_info_new_str_with_details (
_("Errors while activating plugins."), error));
}
/* report initialization errors */
if (error_list != NULL) {
GNM_SLIST_REVERSE (error_list);
error = error_info_new_str_with_details_list (
_("Errors while initializing plugin system."),
error_list);
gnm_cmd_context_error_info (context, error);
error_info_free (error);
}
plugin_message (4, "plugins_init() time: %fs\n", gnm_time_counter_pop ());
}
static void
ghf_collect_used_plugin_state_strings (gpointer key, gpointer value, gpointer user_data)
{
PluginFileState *state = value;
GSList **strings = user_data;
if (state->age != PLUGIN_OLD_UNUSED) {
GNM_SLIST_PREPEND (*strings, plugin_file_state_as_string (state));
}
}
/**
* gnm_plugin_try_unref
*
* Unref plugin object if it is legal to destroy it. Destruction is
* not legal if a type or interface has been registered for it. "Once
* a GTypeModule is initialized, it must exist forever" - docs of
* g_type_module_unuse().
*/
static void
gnm_plugin_try_unref (gpointer plugin)
{
GTypeModule *module = G_TYPE_MODULE (plugin);
if (!module->type_infos && !module->interface_infos) {
g_object_unref (plugin);
}
}
/**
* plugins_shutdown:
*
* Shuts down the plugin subsystem. Call this function only once before
* exiting the application. Some plugins may be left active or in broken
* state, so calling plugins_init again will NOT work properly.
*/
void
plugins_shutdown (void)
{
GSList *active_list = NULL, *used_plugin_state_strings = NULL;
ErrorInfo *ignored_error;
/* save active plugins list */
GNM_SLIST_FOREACH (available_plugins, GnmPlugin, plugin,
if (gnm_plugin_is_active (plugin) &&
!plugin_db_is_plugin_marked_for_deactivation (plugin)) {
GNM_SLIST_PREPEND (active_list, (gpointer) gnm_plugin_get_id (plugin));
}
);
active_list = g_slist_reverse (active_list);
gnm_gconf_set_active_plugins (active_list);
g_slist_free (active_list);
if (plugins_marked_for_deactivation_hash != NULL) {
g_hash_table_destroy (plugins_marked_for_deactivation_hash);
}
/* deactivate all plugins */
plugin_db_deactivate_plugin_list (available_plugins, &ignored_error);
error_info_free (ignored_error);
/* update information stored in gconf database
* about known plugin.xml files and destroy hash table */
g_hash_table_foreach (
plugin_file_state_dir_hash,
ghf_collect_used_plugin_state_strings,
&used_plugin_state_strings);
if (plugin_file_state_hash_changed ||
g_hash_table_size (plugin_file_state_dir_hash) != g_slist_length (used_plugin_state_strings)) {
gnm_gconf_set_plugin_file_states (used_plugin_state_strings);
plugin_message (5, "Plugin cache changed\n");
} else
gnm_slist_free_custom (used_plugin_state_strings, g_free);
g_hash_table_destroy (plugin_file_state_dir_hash);
g_hash_table_destroy (loader_services);
g_hash_table_destroy (available_plugins_id_hash);
gnm_slist_free_custom (available_plugins, gnm_plugin_try_unref);
go_conf_sync ();
}
void
plugin_message (gint level, const gchar *format, ...)
{
#ifdef PLUGIN_DEBUG
va_list args;
if (level <= PLUGIN_DEBUG) {
va_start (args, format);
vprintf (format, args);
va_end (args);
}
#endif
}