aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-plugin.c')
-rw-r--r--e-util/e-plugin.c850
1 files changed, 850 insertions, 0 deletions
diff --git a/e-util/e-plugin.c b/e-util/e-plugin.c
new file mode 100644
index 0000000000..52f92cbea9
--- /dev/null
+++ b/e-util/e-plugin.c
@@ -0,0 +1,850 @@
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "e-plugin.h"
+
+/* plugin debug */
+#define pd(x) x
+/* plugin hook debug */
+#define phd(x) x
+
+/*
+<camel-plugin
+ class="com.ximian.camel.plugin.provider:1.0"
+ id="com.ximian.camel.provider.imap:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ factory="camel_imap_provider_new">
+ <name>imap</name>
+ <description>IMAP4 and IMAP4v1 mail store</description>
+ <class-data class="com.ximian.camel.plugin.provider:1.0"
+ protocol="imap"
+ domain="mail"
+ flags="remote,source,storage,ssl"/>
+</camel-plugin>
+
+<camel-plugin
+ class="com.ximian.camel.plugin.sasl:1.0"
+ id="com.ximian.camel.sasl.plain:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelsasl.so"
+ factory="camel_sasl_plain_new">
+ <name>PLAIN</name>
+ <description>SASL PLAIN authentication mechanism</description>
+</camel-plugin>
+*/
+
+static GObjectClass *ep_parent_class;
+static GHashTable *ep_types;
+static GSList *ep_path;
+
+static int
+ep_construct(EPlugin *ep, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ int res = -1;
+
+ ep->domain = e_plugin_xml_prop(root, "domain");
+ ep->name = e_plugin_xml_prop_domain(root, "name", ep->domain);
+
+ printf("creating plugin '%s'\n", ep->name);
+
+ node = root->children;
+ while (node) {
+ if (strcmp(node->name, "hook") == 0) {
+ struct _EPluginHook *hook;
+
+ hook = e_plugin_hook_new(ep, node);
+ if (hook)
+ ep->hooks = g_slist_prepend(ep->hooks, hook);
+ else {
+ char *tmp = xmlGetProp(node, "class");
+
+ g_warning("Plugin '%s' failed to load hook '%s'", ep->name, tmp?tmp:"unknown");
+ if (tmp)
+ xmlFree(tmp);
+ }
+ } else if (strcmp(node->name, "description") == 0) {
+ ep->description = e_plugin_xml_content_domain(node, ep->domain);
+ }
+ node = node->next;
+ }
+ res = 0;
+
+ return res;
+}
+
+static void
+ep_finalise(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ 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);
+
+ ((GObjectClass *)ep_parent_class)->finalize(o);
+}
+
+static void
+ep_init(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ ep->enabled = TRUE;
+}
+
+static void
+ep_class_init(EPluginClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = ep_finalise;
+ klass->construct = ep_construct;
+}
+
+/**
+ * e_plugin_get_type:
+ *
+ * Standard GObject type function. This is only an abstract class, so
+ * you can only use this to subclass EPlugin.
+ *
+ * Return value: The type.
+ **/
+GType
+e_plugin_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ char *path, *col, *p;
+
+ static const GTypeInfo info = {
+ sizeof(EPluginClass), NULL, NULL, (GClassInitFunc)ep_class_init, NULL, NULL,
+ sizeof(EPlugin), 0, (GInstanceInitFunc)ep_init,
+ };
+
+ ep_parent_class = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EPlugin", &info, 0);
+
+ /* Add paths in the environment variable or default global and user specific paths */
+ path = g_strdup(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(), ".eplug", NULL);
+ }
+
+ p = path;
+ while ((col = strchr(p, ':'))) {
+ *col++ = 0;
+ e_plugin_add_load_path(p);
+ p = col;
+ }
+ e_plugin_add_load_path(p);
+ g_free(path);
+ }
+
+ return type;
+}
+
+static int
+ep_load(const char *filename)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ int res = -1;
+ EPlugin *ep;
+
+ doc = xmlParseFile(filename);
+ if (doc == NULL) {
+ return -1;
+ }
+
+ root = xmlDocGetRootElement(doc);
+ if (strcmp(root->name, "e-plugin-list") != 0)
+ goto fail;
+
+ root = root->children;
+ while (root) {
+ if (strcmp(root->name, "e-plugin") == 0) {
+ char *prop;
+ EPluginClass *klass;
+
+ prop = xmlGetProp(root, "type");
+ if (prop == NULL)
+ goto fail;
+
+ klass = g_hash_table_lookup(ep_types, prop);
+ if (klass == NULL) {
+ g_warning("can't find plugin type '%s'\n", prop);
+ xmlFree(prop);
+ goto fail;
+ }
+
+ xmlFree(prop);
+
+ ep = g_object_new(G_TYPE_FROM_CLASS(klass), NULL);
+ if (e_plugin_construct(ep, root) == -1) {
+ g_object_unref(ep);
+ } else {
+ /* ... */
+ }
+ }
+ root = root->next;
+ }
+
+ res = 0;
+fail:
+ xmlFreeDoc(doc);
+ return res;
+}
+
+/**
+ * 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 ~/.eplug 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 char *path)
+{
+ ep_path = g_slist_append(ep_path, g_strdup(path));
+}
+
+/**
+ * e_plugin_load_plugins:
+ *
+ * Scan the search path, looking for plugin definitions, and load them
+ * into memory.
+ *
+ * Return value: Returns -1 if an error occured.
+ **/
+int
+e_plugin_load_plugins(void)
+{
+ GSList *l;
+
+ if (ep_types == NULL) {
+ g_warning("no plugin types defined");
+ return 0;
+ }
+
+
+ for (l = ep_path;l;l = g_slist_next(l)) {
+ DIR *dir;
+ struct dirent *d;
+ char *path = l->data;
+
+ printf("scanning plugin dir '%s'\n", path);
+
+ dir = opendir(path);
+ if (dir == NULL) {
+ g_warning("Could not find plugin path: %s", path);
+ continue;
+ }
+
+ while ( (d = readdir(dir)) ) {
+ if (strlen(d->d_name) > 6
+ && !strcmp(d->d_name + strlen(d->d_name) - 6, ".eplug")) {
+ char * name = g_build_filename(path, d->d_name, NULL);
+
+ ep_load(name);
+ g_free(name);
+ }
+ }
+
+ closedir(dir);
+ }
+
+ return 0;
+}
+
+/**
+ * e_plugin_register_type:
+ * @type: The GObject type of the plugin loader.
+ *
+ * Register a new plugin type with the plugin system. Each type must
+ * subclass EPlugin and must override the type member of the
+ * EPluginClass with a unique name.
+ **/
+void
+e_plugin_register_type(GType type)
+{
+ EPluginClass *klass;
+
+ if (ep_types == NULL)
+ ep_types = g_hash_table_new(g_str_hash, g_str_equal);
+
+ klass = g_type_class_ref(type);
+
+ pd(printf("register plugin type '%s'\n", klass->type));
+
+ g_hash_table_insert(ep_types, (void *)klass->type, klass);
+}
+
+/**
+ * e_plugin_construct:
+ * @ep: An EPlugin derived object.
+ * @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.
+ **/
+int
+e_plugin_construct(EPlugin *ep, xmlNodePtr root)
+{
+ return ((EPluginClass *)G_OBJECT_GET_CLASS(ep))->construct(ep, root);
+}
+
+/**
+ * e_plugin_invoke:
+ * @ep:
+ * @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.
+ **/
+void *
+e_plugin_invoke(EPlugin *ep, const char *name, void *data)
+{
+ if (!ep->enabled)
+ g_warning("Invoking method on disabled plugin");
+
+ return ((EPluginClass *)G_OBJECT_GET_CLASS(ep))->invoke(ep, name, data);
+}
+
+/**
+ * e_plugin_enable:
+ * @ep:
+ * @state:
+ *
+ * Set the enable state of a plugin.
+ *
+ * THIS IS NOT FULLY IMPLEMENTED YET
+ **/
+void
+e_plugin_enable(EPlugin *ep, int state)
+{
+ GSList *l;
+
+ if ((ep->enabled == 0) == (state == 0))
+ return;
+
+ ep->enabled = state;
+ for (l=ep->hooks;l;l = g_slist_next(l)) {
+ EPluginHook *eph = l->data;
+
+ e_plugin_hook_enable(eph, state);
+ }
+}
+
+/**
+ * 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. If GLib isn't using
+ * the system malloc then it must copy the property value.
+ *
+ * Return value: The property, allocated in GLib memory, or NULL if no
+ * such property exists.
+ **/
+char *
+e_plugin_xml_prop(xmlNodePtr node, const char *id)
+{
+ char *p = xmlGetProp(node, id);
+
+ if (g_mem_is_system_malloc()) {
+ return p;
+ } else {
+ char * out = g_strdup(p);
+
+ if (p)
+ xmlFree(p);
+ return out;
+ }
+}
+
+/**
+ * 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.
+ **/
+char *
+e_plugin_xml_prop_domain(xmlNodePtr node, const char *id, const char *domain)
+{
+ char *p, *out;
+
+ p = xmlGetProp(node, 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.
+ **/
+int
+e_plugin_xml_int(xmlNodePtr node, const char *id, int def)
+{
+ char *p = xmlGetProp(node, 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.
+ **/
+char *
+e_plugin_xml_content(xmlNodePtr node)
+{
+ char *p = xmlNodeGetContent(node);
+
+ if (g_mem_is_system_malloc()) {
+ return p;
+ } else {
+ char * 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.
+ **/
+char *
+e_plugin_xml_content_domain(xmlNodePtr node, const char *domain)
+{
+ char *p, *out;
+
+ p = xmlNodeGetContent(node);
+ if (p == NULL)
+ return NULL;
+
+ out = g_strdup(dgettext(domain, p));
+ xmlFree(p);
+
+ return out;
+}
+
+/* ********************************************************************** */
+static void *epl_parent_class;
+
+#define epl ((EPluginLib *)ep)
+
+/* TODO:
+ We need some way to manage lifecycle.
+ We need some way to manage state.
+
+ Maybe just the g module init method will do, or we could add
+ another which returns context.
+
+ There is also the question of per-instance context, e.g. for config
+ pages.
+*/
+
+static void *
+epl_invoke(EPlugin *ep, const char *name, void *data)
+{
+ void *(*cb)(EPlugin *ep, void *data);
+
+ if (epl->module == NULL
+ && (epl->module = g_module_open(epl->location, 0)) == NULL) {
+ g_warning("can't load plugin '%s'", g_module_error());
+ return NULL;
+ }
+
+ if (!g_module_symbol(epl->module, name, (void *)&cb))
+ return NULL;
+
+ return cb(ep, data);
+}
+
+static int
+epl_construct(EPlugin *ep, xmlNodePtr root)
+{
+ if (((EPluginClass *)epl_parent_class)->construct(ep, root) == -1)
+ return -1;
+
+ epl->location = e_plugin_xml_prop(root, "location");
+
+ if (epl->location == NULL)
+ return -1;
+
+ return 0;
+}
+
+static void
+epl_finalise(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ g_free(epl->location);
+
+ if (epl->module)
+ g_module_close(epl->module);
+
+ ((GObjectClass *)epl_parent_class)->finalize(o);
+}
+
+static void
+epl_class_init(EPluginClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = epl_finalise;
+ klass->construct = epl_construct;
+ klass->invoke = epl_invoke;
+ klass->type = "shlib";
+}
+
+/**
+ * e_plugin_lib_get_type:
+ *
+ * Standard GObject function to retrieve the EPluginLib type. Use to
+ * register the type with the plugin system if you want to use shared
+ * library plugins.
+ *
+ * Return value: The EPluginLib type.
+ **/
+GType
+e_plugin_lib_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EPluginLibClass), NULL, NULL, (GClassInitFunc) epl_class_init, NULL, NULL,
+ sizeof(EPluginLib), 0, (GInstanceInitFunc) NULL,
+ };
+
+ epl_parent_class = g_type_class_ref(e_plugin_get_type());
+ type = g_type_register_static(e_plugin_get_type(), "EPluginLib", &info, 0);
+ }
+
+ return type;
+}
+
+/* ********************************************************************** */
+static void *eph_parent_class;
+static GHashTable *eph_types;
+
+static int
+eph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
+{
+ eph->plugin = ep;
+
+ return 0;
+}
+
+static void
+eph_enable(EPluginHook *eph, int state)
+{
+ /* NOOP */
+}
+
+static void
+eph_finalise(GObject *o)
+{
+ ((GObjectClass *)eph_parent_class)->finalize((GObject *)o);
+}
+
+static void
+eph_class_init(EPluginHookClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = eph_finalise;
+ klass->construct = eph_construct;
+ klass->enable = eph_enable;
+}
+
+/**
+ * e_plugin_hook_get_type:
+ *
+ * Standard GObject function to retrieve the EPluginHook type. Since
+ * EPluginHook is an abstract class, this is only used to subclass it.
+ *
+ * Return value: The EPluginHook type.
+ **/
+GType
+e_plugin_hook_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EPluginHookClass), NULL, NULL, (GClassInitFunc) eph_class_init, NULL, NULL,
+ sizeof(EPluginHook), 0, (GInstanceInitFunc) NULL,
+ };
+
+ eph_parent_class = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EPluginHook", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_plugin_hook_new:
+ * @ep: The parent EPlugin this hook belongs to.
+ * @root: The XML node of the root of the hook definition.
+ *
+ * This is a static factory method to instantiate a new EPluginHook
+ * object to represent a plugin hook.
+ *
+ * Return value: The EPluginHook appropriate for the XML definition at
+ * @root. NULL is returned if a syntax error is encountered.
+ **/
+EPluginHook *
+e_plugin_hook_new(EPlugin *ep, xmlNodePtr root)
+{
+ EPluginHookClass *type;
+ char *class;
+ EPluginHook *hook;
+
+ /* FIXME: Keep a list of all plugin hooks */
+
+ if (eph_types == NULL)
+ return NULL;
+
+ class = xmlGetProp(root, "class");
+ if (class == NULL)
+ return NULL;
+
+ type = g_hash_table_lookup(eph_types, class);
+ g_free(class);
+ if (type == NULL)
+ return NULL;
+
+ hook = g_object_new(G_OBJECT_CLASS_TYPE(type), NULL);
+ if (type->construct(hook, ep, root) == -1) {
+ g_object_unref(hook);
+ hook = NULL;
+ }
+
+ return 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, int state)
+{
+ ((EPluginHookClass *)G_OBJECT_GET_CLASS(eph))->enable(eph, state);
+}
+
+/**
+ * e_plugin_hook_register_type:
+ * @type:
+ *
+ * Register a new plugin hook type with the plugin system. Each type
+ * must subclass EPluginHook and must override the id member of the
+ * EPluginHookClass with a unique identification string.
+ **/
+void
+e_plugin_hook_register_type(GType type)
+{
+ EPluginHookClass *klass;
+
+ if (eph_types == NULL)
+ eph_types = g_hash_table_new(g_str_hash, g_str_equal);
+
+ klass = g_type_class_ref(type);
+
+ phd(printf("register plugin hook type '%s'\n", klass->id));
+
+ g_hash_table_insert(eph_types, (void *)klass->id, klass);
+}
+
+/**
+ * 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 struct _EPluginHookTargetKey *map, const char *prop)
+{
+ char *val, *p, *start, c;
+ guint32 mask = 0;
+
+ val = xmlGetProp(root, prop);
+ if (val == NULL)
+ return 0;
+
+ p = val;
+ do {
+ start = p;
+ while (*p && *p != ',')
+ p++;
+ c = *p;
+ *p = 0;
+ if (start != p) {
+ int 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 struct _EPluginHookTargetKey *map, const char *prop)
+{
+ char *val;
+ int i;
+
+ val = xmlGetProp(root, 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;
+}
+
+#if 0
+/*
+ e-mail-format-handler
+ mime_type
+ target
+*/
+struct _EMFormatPlugin {
+ EPlugin plugin;
+
+ char *target;
+ char *mime_type;
+ struct _EMFormatHandler *(*get_handler)(void);
+};
+
+struct _EMFormatPluginClass {
+ EPluginClass plugin_class;
+};
+
+#endif
+
+#if 0
+void em_setup_plugins(void);
+
+void
+em_setup_plugins(void)
+{
+ GType *e_plugin_mono_get_type(void);
+
+ e_plugin_register_type(e_plugin_lib_get_type());
+ e_plugin_register_type(e_plugin_mono_get_type());
+
+ e_plugin_hook_register_type(em_popup_hook_get_type());
+
+ e_plugin_load_plugins(".");
+}
+#endif