/* * This program 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) version 3. * * This program 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 the program; if not, see * * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "e-plugin.h" #include "e-util-private.h" /* plugin debug */ #define pd(x) /* plugin hook debug */ #define phd(x) /* * * imap * IMAP4 and IMAP4v1 mail store * * * * * PLAIN * SASL PLAIN authentication mechanism * */ /* EPlugin stuff */ /* global table of plugin types by pluginclass.type */ static GHashTable *ep_types; /* plugin load path */ static GSList *ep_path; /* global table of plugins by plugin.id */ static GHashTable *ep_plugins; /* the list of disabled plugins from GSettings */ static GSList *ep_disabled; /* All classes which implement EPluginHooks, by class.id */ static GHashTable *eph_types; struct _plugin_doc { struct _plugin_doc *next; struct _plugin_doc *prev; gchar *filename; xmlDocPtr doc; }; enum { EP_PROP_0, EP_PROP_ENABLED }; G_DEFINE_TYPE ( EPlugin, e_plugin, G_TYPE_OBJECT) static gboolean ep_check_enabled (const gchar *id) { /* Return TRUE if 'id' is NOT in the disabled list. */ return !g_slist_find_custom (ep_disabled, id, (GCompareFunc) strcmp); } static void ep_set_enabled (const gchar *id, gint state) { GSettings *settings; GSList *l; GPtrArray *array; /* Bail out if no change to state, when expressed as a boolean: */ if ((state == 0) == (ep_check_enabled (id) == 0)) return; if (state) { GSList *link; link = g_slist_find_custom ( ep_disabled, id, (GCompareFunc) strcmp); if (link != NULL) { g_free (link->data); ep_disabled = g_slist_remove_link (ep_disabled, link); } } else ep_disabled = g_slist_prepend (ep_disabled, g_strdup (id)); settings = g_settings_new ("org.gnome.evolution"); array = g_ptr_array_new (); for (l = ep_disabled; l != NULL; l = l->next) g_ptr_array_add (array, l->data); g_ptr_array_add (array, NULL); g_settings_set_strv ( settings, "disabled-eplugins", (const gchar * const *) array->pdata); g_ptr_array_free (array, TRUE); g_object_unref (settings); } static gint ep_construct (EPlugin *ep, xmlNodePtr root) { xmlNodePtr node; gint res = -1; gchar *localedir; ep->domain = e_plugin_xml_prop(root, "domain"); if (ep->domain && (localedir = e_plugin_xml_prop(root, "localedir"))) { #ifdef G_OS_WIN32 gchar *mapped_localedir = e_util_replace_prefix (EVOLUTION_PREFIX, e_util_get_prefix (), localedir); g_free (localedir); localedir = mapped_localedir; #endif bindtextdomain (ep->domain, localedir); g_free (localedir); } ep->name = e_plugin_xml_prop_domain(root, "name", ep->domain); node = root->children; while (node) { if (strcmp((gchar *)node->name, "hook") == 0) { EPluginHook *hook; EPluginHookClass *type; gchar *class = e_plugin_xml_prop(node, "class"); if (class == NULL) { g_warning ( "Plugin '%s' load failed in '%s', " "missing class property for hook", ep->id, ep->path); goto fail; } if (ep->enabled && eph_types != NULL && (type = g_hash_table_lookup ( eph_types, class)) != NULL) { g_free (class); hook = g_object_new (G_OBJECT_CLASS_TYPE (type), NULL); res = type->construct (hook, ep, node); if (res == -1) { g_warning ( "Plugin '%s' failed to " "load hook", ep->name); g_object_unref (hook); goto fail; } else { ep->hooks = g_slist_append (ep->hooks, hook); } } else { g_free (class); } } else if (strcmp((gchar *)node->name, "description") == 0) { ep->description = e_plugin_xml_content_domain (node, ep->domain); } else if (strcmp((gchar *)node->name, "author") == 0) { gchar *name = e_plugin_xml_prop(node, "name"); gchar *email = e_plugin_xml_prop(node, "email"); if (name || email) { EPluginAuthor *epa = g_malloc0 (sizeof (*epa)); epa->name = name; epa->email = email; ep->authors = g_slist_append (ep->authors, epa); } } node = node->next; } res = 0; fail: return res; } static void ep_enable (EPlugin *ep, gint state) { GSList *iter; ep->enabled = state; for (iter = ep->hooks; iter != NULL; iter = iter->next) { EPluginHook *hook = iter->data; e_plugin_hook_enable (hook, state); } ep_set_enabled (ep->id, state); } static void ep_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case EP_PROP_ENABLED: e_plugin_enable ( E_PLUGIN (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void ep_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EPlugin *ep = E_PLUGIN (object); switch (property_id) { case EP_PROP_ENABLED: g_value_set_boolean (value, ep->enabled); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void ep_finalize (GObject *object) { EPlugin *ep = E_PLUGIN (object); g_free (ep->id); g_free (ep->description); g_free (ep->name); g_free (ep->domain); g_slist_foreach (ep->hooks, (GFunc) g_object_unref, NULL); g_slist_free (ep->hooks); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_plugin_parent_class)->finalize (object); } static void e_plugin_class_init (EPluginClass *class) { GObjectClass *object_class; gchar *path, *col, *p; object_class = G_OBJECT_CLASS (class); object_class->set_property = ep_set_property; object_class->get_property = ep_get_property; object_class->finalize = ep_finalize; class->construct = ep_construct; class->enable = ep_enable; g_object_class_install_property ( object_class, EP_PROP_ENABLED, g_param_spec_boolean ( "enabled", "Enabled", "Whether the plugin is enabled", TRUE, G_PARAM_READWRITE)); /* Add paths in the environment variable or default global * and user specific paths */ path = g_strdup(g_getenv("EVOLUTION_PLUGIN_PATH")); if (path == NULL) { /* Add the global path */ e_plugin_add_load_path (EVOLUTION_PLUGINDIR); path = g_build_filename(g_get_home_dir(), ".eplugins", NULL); } p = path; while ((col = strchr (p, G_SEARCHPATH_SEPARATOR))) { *col++ = 0; e_plugin_add_load_path (p); p = col; } e_plugin_add_load_path (p); g_free (path); } static void e_plugin_init (EPlugin *ep) { ep->enabled = TRUE; } static EPlugin * ep_load_plugin (xmlNodePtr root, struct _plugin_doc *pdoc) { gchar *prop, *id; EPluginClass *class; EPlugin *ep; id = e_plugin_xml_prop(root, "id"); if (id == NULL) { g_warning("Invalid e-plugin entry in '%s': no id", pdoc->filename); return NULL; } if (g_hash_table_lookup (ep_plugins, id)) { g_warning("Plugin '%s' already defined", id); g_free (id); return NULL; } prop = (gchar *)xmlGetProp(root, (const guchar *)"type"); if (prop == NULL) { g_free (id); g_warning("Invalid e-plugin entry in '%s': no type", pdoc->filename); return NULL; } /* If we can't find a plugin, add it to a pending list * which is checked when a new type is registered. */ class = g_hash_table_lookup (ep_types, prop); if (class == NULL) { g_free (id); xmlFree (prop); return NULL; } xmlFree (prop); ep = g_object_new (G_TYPE_FROM_CLASS (class), NULL); ep->id = id; ep->path = g_strdup (pdoc->filename); ep->enabled = ep_check_enabled (id); if (e_plugin_construct (ep, root) == -1) e_plugin_enable (ep, FALSE); g_hash_table_insert (ep_plugins, ep->id, ep); return ep; } static gint ep_load (const gchar *filename, gint load_level) { xmlDocPtr doc; xmlNodePtr root; EPlugin *ep = NULL; struct _plugin_doc *pdoc; doc = e_xml_parse_file (filename); if (doc == NULL) return -1; root = xmlDocGetRootElement (doc); if (strcmp((gchar *)root->name, "e-plugin-list") != 0) { g_warning("No root element: %s", filename); xmlFreeDoc (doc); return -1; } pdoc = g_malloc0 (sizeof (*pdoc)); pdoc->doc = doc; pdoc->filename = g_strdup (filename); for (root = root->children; root; root = root->next) { if (strcmp((gchar *)root->name, "e-plugin") == 0) { gchar *plugin_load_level, *is_system_plugin; plugin_load_level = NULL; plugin_load_level = e_plugin_xml_prop (root, "load_level"); if (plugin_load_level) { if ((atoi (plugin_load_level) == load_level) ) { ep = ep_load_plugin (root, pdoc); if (ep && load_level == 1) e_plugin_invoke ( ep, "load_plugin_type_register_function", NULL); } } else if (load_level == 2) { ep = ep_load_plugin (root, pdoc); } if (ep) { /* README: Maybe we can use load_levels to * achieve the same thing. But it may be * confusing for a plugin writer. */ is_system_plugin = e_plugin_xml_prop (root, "system_plugin"); if (g_strcmp0 (is_system_plugin, "true") == 0) { e_plugin_enable (ep, TRUE); ep->flags |= E_PLUGIN_FLAGS_SYSTEM_PLUGIN; } else ep->flags &= ~E_PLUGIN_FLAGS_SYSTEM_PLUGIN; g_free (is_system_plugin); ep = NULL; } } } xmlFreeDoc (pdoc->doc); g_free (pdoc->filename); g_free (pdoc); return 0; } /** * e_plugin_add_load_path: * @path: The path to add to search for plugins. * * Add a path to be searched when e_plugin_load_plugins() is called. * By default the system plugin directory and ~/.eplugins is used as * the search path unless overriden by the environmental variable * %EVOLUTION_PLUGIN_PATH. * * %EVOLUTION_PLUGIN_PATH is a : separated list of paths to search for * plugin definitions in order. * * Plugin definitions are XML files ending in the extension ".eplug". **/ void e_plugin_add_load_path (const gchar *path) { ep_path = g_slist_append (ep_path, g_strdup (path)); } static void plugin_load_subclass (GType type, GHashTable *hash_table) { EPluginClass *class; class = g_type_class_ref (type); g_hash_table_insert (hash_table, (gpointer) class->type, class); } static void plugin_hook_load_subclass (GType type, GHashTable *hash_table) { EPluginHookClass *hook_class; EPluginHookClass *dupe_class; gpointer key; hook_class = g_type_class_ref (type); /* Sanity check the hook class. */ if (hook_class->id == NULL || *hook_class->id == '\0') { g_warning ( "%s has no hook ID, so skipping", G_OBJECT_CLASS_NAME (hook_class)); g_type_class_unref (hook_class); return; } /* Check for class ID collisions. */ dupe_class = g_hash_table_lookup (hash_table, hook_class->id); if (dupe_class != NULL) { g_warning ( "%s and %s have the same hook " "ID ('%s'), so skipping %s", G_OBJECT_CLASS_NAME (dupe_class), G_OBJECT_CLASS_NAME (hook_class), hook_class->id, G_OBJECT_CLASS_NAME (hook_class)); g_type_class_unref (hook_class); return; } key = (gpointer) hook_class->id; g_hash_table_insert (hash_table, key, hook_class); } /** * e_plugin_load_plugins: * * Scan the search path, looking for plugin definitions, and load them * into memory. * * Return value: Returns -1 if an error occurred. **/ gint e_plugin_load_plugins (void) { GSettings *settings; GSList *l; gchar **strv; gint i; if (eph_types != NULL) return 0; ep_types = g_hash_table_new (g_str_hash, g_str_equal); eph_types = g_hash_table_new (g_str_hash, g_str_equal); ep_plugins = g_hash_table_new (g_str_hash, g_str_equal); /* We require that all GTypes for EPlugin and EPluginHook * subclasses be registered prior to loading any plugins. * It greatly simplifies the loading process. */ e_type_traverse ( E_TYPE_PLUGIN, (ETypeFunc) plugin_load_subclass, ep_types); e_type_traverse ( E_TYPE_PLUGIN_HOOK, (ETypeFunc) plugin_hook_load_subclass, eph_types); settings = g_settings_new ("org.gnome.evolution"); strv = g_settings_get_strv (settings, "disabled-eplugins"); for (i = 0, ep_disabled = NULL; strv[i] != NULL; i++) ep_disabled = g_slist_append (ep_disabled, g_strdup (strv[i])); g_strfreev (strv); g_object_unref (settings); for (i = 0; i < 3; i++) { for (l = ep_path; l; l = g_slist_next (l)) { GDir *dir; const gchar *d; gchar *path = l->data; pd(printf("scanning plugin dir '%s'\n", path)); dir = g_dir_open (path, 0, NULL); if (dir == NULL) { /*g_warning("Could not find plugin path: %s", path);*/ continue; } while ((d = g_dir_read_name (dir))) { if (g_str_has_suffix (d, ".eplug")) { gchar *name; name = g_build_filename (path, d, NULL); ep_load (name, i); g_free (name); } } g_dir_close (dir); } } return 0; } static void ep_list_plugin (gpointer key, gpointer val, gpointer dat) { GSList **l = (GSList **) dat; *l = g_slist_prepend(*l, g_object_ref(val)); } /** * e_plugin_list_plugins: List all plugins. * * Static class method to retrieve a list of all current plugins. They * are listed in no particular order. * * Return value: A GSList of all plugins, they must be * g_object_unref'd and the list freed. **/ GSList * e_plugin_list_plugins (void) { GSList *l = NULL; if (ep_plugins) g_hash_table_foreach (ep_plugins, ep_list_plugin, &l); return l; } /** * e_plugin_construct: * @ep: an #EPlugin * @root: The XML root node of the sub-tree containing the plugin * definition. * * Helper to invoke the construct virtual method. * * Return value: The return from the construct virtual method. **/ gint e_plugin_construct (EPlugin *ep, xmlNodePtr root) { EPluginClass *class; g_return_val_if_fail (E_IS_PLUGIN (ep), -1); class = E_PLUGIN_GET_CLASS (ep); g_return_val_if_fail (class->construct != NULL, -1); return class->construct (ep, root); } /** * e_plugin_invoke: * @ep: an #EPlugin * @name: The name of the function to invoke. The format of this name * will depend on the EPlugin type and its language conventions. * @data: The argument to the function. Its actual type depends on * the hook on which the function resides. It is up to the called * function to get this right. * * Helper to invoke the invoke virtual method. * * Return value: The return of the plugin invocation. **/ gpointer e_plugin_invoke (EPlugin *ep, const gchar *name, gpointer data) { EPluginClass *class; g_return_val_if_fail (E_IS_PLUGIN (ep), NULL); g_return_val_if_fail (name != NULL, NULL); /* Prevent invocation on a disabled plugin. */ g_return_val_if_fail (ep->enabled, NULL); class = E_PLUGIN_GET_CLASS (ep); g_return_val_if_fail (class->invoke != NULL, NULL); return class->invoke (ep, name, data); } /** * e_plugin_get_symbol: * @ep: an #EPlugin * @name: The name of the symbol to fetch. The format of this name * will depend on the EPlugin type and its language conventions. * * Helper to fetch a symbol name from a plugin. * * Return value: the symbol value, or %NULL if not found **/ gpointer e_plugin_get_symbol (EPlugin *ep, const gchar *name) { EPluginClass *class; g_return_val_if_fail (E_IS_PLUGIN (ep), NULL); class = E_PLUGIN_GET_CLASS (ep); g_return_val_if_fail (class->get_symbol != NULL, NULL); return class->get_symbol (ep, name); } /** * e_plugin_enable: * @ep: an #EPlugin * @state: %TRUE to enable, %FALSE to disable * * Set the enable state of a plugin. * * THIS IS NOT FULLY IMPLEMENTED YET **/ void e_plugin_enable (EPlugin *ep, gint state) { EPluginClass *class; g_return_if_fail (E_IS_PLUGIN (ep)); if ((ep->enabled == 0) == (state == 0)) return; class = E_PLUGIN_GET_CLASS (ep); g_return_if_fail (class->enable != NULL); class->enable (ep, state); g_object_notify (G_OBJECT (ep), "enabled"); } /** * e_plugin_get_configure_widget * @ep: an #EPlugin * * Plugin itself should have implemented "e_plugin_lib_get_configure_widget" * function * of prototype EPluginLibGetConfigureWidgetFunc. * * Returns: Configure widget or %NULL **/ GtkWidget * e_plugin_get_configure_widget (EPlugin *ep) { EPluginClass *class; g_return_val_if_fail (E_IS_PLUGIN (ep), NULL); class = E_PLUGIN_GET_CLASS (ep); if (class->get_configure_widget == NULL) return NULL; return class->get_configure_widget (ep); } /** * e_plugin_xml_prop: * @node: An XML node. * @id: The name of the property to retrieve. * * A static helper function to look up a property on an XML node, and * ensure it is allocated in GLib system memory. * * Return value: The property, allocated in GLib memory, or NULL if no * such property exists. **/ gchar * e_plugin_xml_prop (xmlNodePtr node, const gchar *id) { xmlChar *xml_prop; gchar *glib_prop = NULL; xml_prop = xmlGetProp (node, (xmlChar *) id); if (xml_prop != NULL) { glib_prop = g_strdup ((gchar *) xml_prop); xmlFree (xml_prop); } return glib_prop; } /** * e_plugin_xml_prop_domain: * @node: An XML node. * @id: The name of the property to retrieve. * @domain: The translation domain for this string. * * A static helper function to look up a property on an XML node, and * translate it based on @domain. * * Return value: The property, allocated in GLib memory, or NULL if no * such property exists. **/ gchar * e_plugin_xml_prop_domain (xmlNodePtr node, const gchar *id, const gchar *domain) { gchar *p, *out; p = (gchar *) xmlGetProp (node, (const guchar *) id); if (p == NULL) return NULL; out = g_strdup (dgettext (domain, p)); xmlFree (p); return out; } /** * e_plugin_xml_int: * @node: An XML node. * @id: The name of the property to retrieve. * @def: A default value if the property doesn't exist. Can be used * to determine if the property isn't set. * * A static helper function to look up a property on an XML node as an * integer. If the property doesn't exist, then @def is returned as a * default value instead. * * Return value: The value if set, or @def if not. **/ gint e_plugin_xml_int (xmlNodePtr node, const gchar *id, gint def) { gchar *p = (gchar *) xmlGetProp (node, (const guchar *) id); if (p) return atoi (p); else return def; } /** * e_plugin_xml_content: * @node: * * A static helper function to retrieve the entire textual content of * an XML node, and ensure it is allocated in GLib system memory. If * GLib isn't using the system malloc them it must copy the content. * * Return value: The node content, allocated in GLib memory. **/ gchar * e_plugin_xml_content (xmlNodePtr node) { gchar *p = (gchar *) xmlNodeGetContent (node); if (g_mem_is_system_malloc ()) { return p; } else { gchar * out = g_strdup (p); if (p) xmlFree (p); return out; } } /** * e_plugin_xml_content_domain: * @node: * @domain: * * A static helper function to retrieve the entire textual content of * an XML node, and ensure it is allocated in GLib system memory. If * GLib isn't using the system malloc them it must copy the content. * * Return value: The node content, allocated in GLib memory. **/ gchar * e_plugin_xml_content_domain (xmlNodePtr node, const gchar *domain) { gchar *p, *out; p = (gchar *) xmlNodeGetContent (node); if (p == NULL) return NULL; out = g_strdup (dgettext (domain, p)); xmlFree (p); return out; } /* ********************************************************************** */ G_DEFINE_TYPE ( EPluginHook, e_plugin_hook, G_TYPE_OBJECT) static gint eph_construct (EPluginHook *eph, EPlugin *ep, xmlNodePtr root) { eph->plugin = ep; return 0; } static void eph_enable (EPluginHook *eph, gint state) { /* NOOP */ } static void e_plugin_hook_class_init (EPluginHookClass *class) { class->construct = eph_construct; class->enable = eph_enable; } static void e_plugin_hook_init (EPluginHook *hook) { } /** * e_plugin_hook_enable: Set hook enabled state. * @eph: * @state: * * Set the enabled state of the plugin hook. This is called by the * plugin code. * * THIS IS NOT FULY IMEPLEMENTED YET **/ void e_plugin_hook_enable (EPluginHook *eph, gint state) { EPluginHookClass *class; g_return_if_fail (E_IS_PLUGIN_HOOK (eph)); class = E_PLUGIN_HOOK_GET_CLASS (eph); g_return_if_fail (class->enable != NULL); class->enable (eph, state); } /** * e_plugin_hook_mask: * @root: An XML node. * @map: A zero-fill terminated array of EPluginHookTargeKeys used to * map a string with a bit value. * @prop: The property name. * * This is a static helper function which looks up a property @prop on * the XML node @root, and then uses the @map table to convert it into * a bitmask. The property value is a comma separated list of * enumeration strings which are indexed into the @map table. * * Return value: A bitmask representing the inclusive-or of all of the * integer values of the corresponding string id's stored in the @map. **/ guint32 e_plugin_hook_mask (xmlNodePtr root, const EPluginHookTargetKey *map, const gchar *prop) { gchar *val, *p, *start, c; guint32 mask = 0; val = (gchar *) xmlGetProp (root, (const guchar *) prop); if (val == NULL) return 0; p = val; do { start = p; while (*p && *p != ',') p++; c = *p; *p = 0; if (start != p) { gint i; for (i = 0; map[i].key; i++) { if (!strcmp (map[i].key, start)) { mask |= map[i].value; break; } } } *p++ = c; } while (c); xmlFree (val); return mask; } /** * e_plugin_hook_id: * @root: * @map: * @prop: * * This is a static helper function which looks up a property @prop on * the XML node @root, and then uses the @map table to convert it into * an integer. * * This is used as a helper wherever you need to represent an * enumerated value in the XML. * * Return value: If the @prop value is in @map, then the corresponding * integer value, if not, then ~0. **/ guint32 e_plugin_hook_id (xmlNodePtr root, const EPluginHookTargetKey *map, const gchar *prop) { gchar *val; gint i; val = (gchar *) xmlGetProp (root, (const guchar *) prop); if (val == NULL) return ~0; for (i = 0; map[i].key; i++) { if (!strcmp (map[i].key, val)) { xmlFree (val); return map[i].value; } } xmlFree (val); return ~0; }