diff options
Diffstat (limited to 'e-util/e-plugin.c')
-rw-r--r-- | e-util/e-plugin.c | 850 |
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 |