#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