#include #include #include #include #include "e-plugin.h" /* plugin debug */ #define pd(x) x /* plugin hook debug */ #define phd(x) x /* imap IMAP4 and IMAP4v1 mail store PLAIN SASL PLAIN authentication mechanism */ 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