1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <sys/types.h>
21 #include <string.h>
22 
23 #include <glib/gi18n.h>
24 
25 #include <libebackend/libebackend.h>
26 
27 #include "e-plugin.h"
28 #include "e-util-private.h"
29 #include "e-misc-utils.h"
30 
31 /* plugin debug */
32 #define pd(x)
33 /* plugin hook debug */
34 #define phd(x)
35 
36 /*
37  * <camel-plugin
38  *   class="org.gnome.camel.plugin.provider:1.0"
39  *   id="org.gnome.camel.provider.imap:1.0"
40  *   type="shlib"
41  *   location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
42  *   factory="camel_imap_provider_new">
43  *  <name>imap</name>
44  *  <description>IMAP4 and IMAP4v1 mail store</description>
45  *  <class-data class="org.gnome.camel.plugin.provider:1.0"
46  *    protocol="imap"
47  *    domain="mail"
48  *    flags="remote,source,storage,ssl"/>
49  * </camel-plugin>
50  *
51  * <camel-plugin
52  *   class="org.gnome.camel.plugin.sasl:1.0"
53  *   id="org.gnome.camel.sasl.plain:1.0"
54  *   type="shlib"
55  *   location="/opt/gnome2/lib/camel/1.0/libcamelsasl.so"
56  *   factory="camel_sasl_plain_new">
57  *  <name>PLAIN</name>
58  *  <description>SASL PLAIN authentication mechanism</description>
59  * </camel-plugin>
60 */
61 
62 /* EPlugin stuff */
63 
64 /* global table of plugin types by pluginclass.type */
65 static GHashTable *ep_types;
66 /* global table of plugins by plugin.id */
67 static GHashTable *ep_plugins;
68 /* the list of disabled plugins from GSettings */
69 static GSList *ep_disabled;
70 
71 /* All classes which implement EPluginHooks, by class.id */
72 static GHashTable *eph_types;
73 
74 struct _plugin_doc {
75 	struct _plugin_doc *next;
76 	struct _plugin_doc *prev;
77 
78 	gchar *filename;
79 	xmlDocPtr doc;
80 };
81 
82 enum {
83 	EP_PROP_0,
84 	EP_PROP_ENABLED
85 };
86 
G_DEFINE_TYPE(EPlugin,e_plugin,G_TYPE_OBJECT)87 G_DEFINE_TYPE (
88 	EPlugin,
89 	e_plugin,
90 	G_TYPE_OBJECT)
91 
92 static gboolean
93 ep_check_enabled (const gchar *id)
94 {
95 	/* Return TRUE if 'id' is NOT in the disabled list. */
96 	return !g_slist_find_custom (ep_disabled, id, (GCompareFunc) strcmp);
97 }
98 
99 static void
ep_set_enabled(const gchar * id,gint state)100 ep_set_enabled (const gchar *id,
101                 gint state)
102 {
103 	GSettings *settings;
104 	GSList *link;
105 	GPtrArray *array;
106 
107 	/* Bail out if no change to state, when expressed as a boolean: */
108 	if ((state == 0) == (ep_check_enabled (id) == 0))
109 		return;
110 
111 	if (state) {
112 		GSList *link;
113 
114 		link = g_slist_find_custom (
115 			ep_disabled, id, (GCompareFunc) strcmp);
116 		if (link != NULL) {
117 			g_free (link->data);
118 			ep_disabled = g_slist_remove_link (ep_disabled, link);
119 		}
120 	} else
121 		ep_disabled = g_slist_prepend (ep_disabled, g_strdup (id));
122 
123 	settings = e_util_ref_settings ("org.gnome.evolution");
124 	array = g_ptr_array_new ();
125 	for (link = ep_disabled; link != NULL; link = link->next)
126 		g_ptr_array_add (array, link->data);
127 	g_ptr_array_add (array, NULL);
128 	g_settings_set_strv (
129 		settings, "disabled-eplugins",
130 		(const gchar * const *) array->pdata);
131 	g_ptr_array_free (array, TRUE);
132 	g_object_unref (settings);
133 }
134 
135 static gint
ep_construct(EPlugin * ep,xmlNodePtr root)136 ep_construct (EPlugin *ep,
137               xmlNodePtr root)
138 {
139 	xmlNodePtr node;
140 	gint res = -1;
141 	gchar *localedir;
142 
143 	ep->domain = e_plugin_xml_prop (root, "domain");
144 	if (ep->domain
145 	    && (localedir = e_plugin_xml_prop (root, "localedir"))) {
146 #ifdef G_OS_WIN32
147 		gchar *mapped_localedir =
148 			e_util_replace_prefix (
149 				EVOLUTION_PREFIX,
150 				e_util_get_prefix (),
151 				localedir);
152 		g_free (localedir);
153 		localedir = mapped_localedir;
154 #endif
155 		bindtextdomain (ep->domain, localedir);
156 		g_free (localedir);
157 	}
158 
159 	ep->name = e_plugin_xml_prop_domain (root, "name", ep->domain);
160 
161 	node = root->children;
162 	while (node) {
163 		if (strcmp ((gchar *) node->name, "hook") == 0) {
164 			EPluginHook *hook;
165 			EPluginHookClass *type;
166 			gchar *class = e_plugin_xml_prop (node, "class");
167 
168 			if (class == NULL) {
169 				g_warning (
170 					"Plugin '%s' load failed in '%s', "
171 					"missing class property for hook",
172 					ep->id, ep->path);
173 				goto fail;
174 			}
175 
176 			if (ep->enabled
177 			    && eph_types != NULL
178 				&& (type = g_hash_table_lookup (
179 					eph_types, class)) != NULL) {
180 				g_free (class);
181 				hook = g_object_new (G_OBJECT_CLASS_TYPE (type), NULL);
182 				res = type->construct (hook, ep, node);
183 				if (res == -1) {
184 					g_warning (
185 						"Plugin '%s' failed to "
186 						"load hook", ep->name);
187 					g_object_unref (hook);
188 					goto fail;
189 				} else {
190 					ep->hooks = g_slist_append (ep->hooks, hook);
191 				}
192 			} else {
193 				g_free (class);
194 			}
195 		} else if (strcmp ((gchar *) node->name, "description") == 0) {
196 			ep->description =
197 				e_plugin_xml_content_domain (node, ep->domain);
198 		} else if (strcmp ((gchar *) node->name, "author") == 0) {
199 			gchar *name = e_plugin_xml_prop (node, "name");
200 			gchar *email = e_plugin_xml_prop (node, "email");
201 
202 			if (name || email) {
203 				EPluginAuthor *epa = g_malloc0 (sizeof (*epa));
204 
205 				epa->name = name;
206 				epa->email = email;
207 				ep->authors = g_slist_append (ep->authors, epa);
208 			}
209 		}
210 		node = node->next;
211 	}
212 	res = 0;
213 fail:
214 	return res;
215 }
216 
217 static void
ep_enable(EPlugin * ep,gint state)218 ep_enable (EPlugin *ep,
219            gint state)
220 {
221 	GSList *iter;
222 
223 	ep->enabled = state;
224 	for (iter = ep->hooks; iter != NULL; iter = iter->next) {
225 		EPluginHook *hook = iter->data;
226 		e_plugin_hook_enable (hook, state);
227 	}
228 
229 	ep_set_enabled (ep->id, state);
230 }
231 
232 static void
ep_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)233 ep_set_property (GObject *object,
234                  guint property_id,
235                  const GValue *value,
236                  GParamSpec *pspec)
237 {
238 	switch (property_id) {
239 		case EP_PROP_ENABLED:
240 			e_plugin_enable (
241 				E_PLUGIN (object),
242 				g_value_get_boolean (value));
243 			return;
244 	}
245 
246 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
247 }
248 
249 static void
ep_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)250 ep_get_property (GObject *object,
251                  guint property_id,
252                  GValue *value,
253                  GParamSpec *pspec)
254 {
255 	EPlugin *ep = E_PLUGIN (object);
256 
257 	switch (property_id) {
258 		case EP_PROP_ENABLED:
259 			g_value_set_boolean (value, ep->enabled);
260 			return;
261 	}
262 
263 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
264 }
265 
266 static void
ep_finalize(GObject * object)267 ep_finalize (GObject *object)
268 {
269 	EPlugin *ep = E_PLUGIN (object);
270 
271 	g_free (ep->id);
272 	g_free (ep->description);
273 	g_free (ep->name);
274 	g_free (ep->domain);
275 
276 	g_slist_foreach (ep->hooks, (GFunc) g_object_unref, NULL);
277 	g_slist_free (ep->hooks);
278 
279 	/* Chain up to parent's finalize() method. */
280 	G_OBJECT_CLASS (e_plugin_parent_class)->finalize (object);
281 }
282 
283 static void
e_plugin_class_init(EPluginClass * class)284 e_plugin_class_init (EPluginClass *class)
285 {
286 	GObjectClass *object_class;
287 
288 	object_class = G_OBJECT_CLASS (class);
289 	object_class->set_property = ep_set_property;
290 	object_class->get_property = ep_get_property;
291 	object_class->finalize = ep_finalize;
292 
293 	class->construct = ep_construct;
294 	class->enable = ep_enable;
295 
296 	g_object_class_install_property (
297 		object_class,
298 		EP_PROP_ENABLED,
299 		g_param_spec_boolean (
300 			"enabled",
301 			"Enabled",
302 			"Whether the plugin is enabled",
303 			TRUE,
304 			G_PARAM_READWRITE));
305 }
306 
307 static void
e_plugin_init(EPlugin * ep)308 e_plugin_init (EPlugin *ep)
309 {
310 	ep->enabled = TRUE;
311 }
312 
313 static EPlugin *
ep_load_plugin(xmlNodePtr root,struct _plugin_doc * pdoc)314 ep_load_plugin (xmlNodePtr root,
315                 struct _plugin_doc *pdoc)
316 {
317 	gchar *prop, *id;
318 	EPluginClass *class;
319 	EPlugin *ep;
320 
321 	id = e_plugin_xml_prop (root, "id");
322 	if (id == NULL) {
323 		g_warning ("Invalid e-plugin entry in '%s': no id", pdoc->filename);
324 		return NULL;
325 	}
326 
327 	if (g_hash_table_lookup (ep_plugins, id)) {
328 		g_warning ("Plugin '%s' already defined", id);
329 		g_free (id);
330 		return NULL;
331 	}
332 
333 	prop = (gchar *) xmlGetProp (root, (const guchar *)"type");
334 	if (prop == NULL) {
335 		g_free (id);
336 		g_warning ("Invalid e-plugin entry in '%s': no type", pdoc->filename);
337 		return NULL;
338 	}
339 
340 	/* If we can't find a plugin, add it to a pending list
341 	 * which is checked when a new type is registered. */
342 	class = g_hash_table_lookup (ep_types, prop);
343 	if (class == NULL) {
344 		g_free (id);
345 		xmlFree (prop);
346 		return NULL;
347 	}
348 	xmlFree (prop);
349 
350 	ep = g_object_new (G_TYPE_FROM_CLASS (class), NULL);
351 	ep->id = id;
352 	ep->path = g_strdup (pdoc->filename);
353 	ep->enabled = ep_check_enabled (id);
354 	if (e_plugin_construct (ep, root) == -1)
355 		e_plugin_enable (ep, FALSE);
356 	g_hash_table_insert (ep_plugins, ep->id, ep);
357 
358 	return ep;
359 }
360 
361 static gint
ep_load(const gchar * filename,gint load_level)362 ep_load (const gchar *filename,
363          gint load_level)
364 {
365 	xmlDocPtr doc;
366 	xmlNodePtr root;
367 	EPlugin *ep = NULL;
368 	struct _plugin_doc *pdoc;
369 
370 	doc = e_xml_parse_file (filename);
371 	if (doc == NULL)
372 		return -1;
373 
374 	root = xmlDocGetRootElement (doc);
375 	if (strcmp ((gchar *) root->name, "e-plugin-list") != 0) {
376 		g_warning ("No <e-plugin-list> root element: %s", filename);
377 		xmlFreeDoc (doc);
378 		return -1;
379 	}
380 
381 	pdoc = g_malloc0 (sizeof (*pdoc));
382 	pdoc->doc = doc;
383 	pdoc->filename = g_strdup (filename);
384 
385 	for (root = root->children; root; root = root->next) {
386 		if (strcmp ((gchar *) root->name, "e-plugin") == 0) {
387 			gchar *plugin_load_level, *is_system_plugin;
388 
389 			plugin_load_level = NULL;
390 			plugin_load_level = e_plugin_xml_prop (root, "load_level");
391 			if (plugin_load_level) {
392 				if ((atoi (plugin_load_level) == load_level)) {
393 					ep = ep_load_plugin (root, pdoc);
394 
395 					if (ep && load_level == 1)
396 						e_plugin_invoke (
397 							ep, "load_plugin_type_register_function", NULL);
398 				}
399 			} else if (load_level == 2) {
400 				ep = ep_load_plugin (root, pdoc);
401 			}
402 
403 			if (ep) {
404 				/* README: Maybe we can use load_levels to
405 				 * achieve the same thing.  But it may be
406 				 * confusing for a plugin writer. */
407 				is_system_plugin =
408 					e_plugin_xml_prop (root, "system_plugin");
409 				if (g_strcmp0 (is_system_plugin, "true") == 0) {
410 					e_plugin_enable (ep, TRUE);
411 					ep->flags |= E_PLUGIN_FLAGS_SYSTEM_PLUGIN;
412 				} else
413 					ep->flags &= ~E_PLUGIN_FLAGS_SYSTEM_PLUGIN;
414 				g_free (is_system_plugin);
415 
416 				ep = NULL;
417 			}
418 		}
419 	}
420 
421 	xmlFreeDoc (pdoc->doc);
422 	g_free (pdoc->filename);
423 	g_free (pdoc);
424 
425 	return 0;
426 }
427 
428 static void
plugin_load_subclass(GType type,GHashTable * hash_table)429 plugin_load_subclass (GType type,
430                       GHashTable *hash_table)
431 {
432 	EPluginClass *class;
433 
434 	class = g_type_class_ref (type);
435 	g_hash_table_insert (hash_table, (gpointer) class->type, class);
436 }
437 
438 static void
plugin_hook_load_subclass(GType type,GHashTable * hash_table)439 plugin_hook_load_subclass (GType type,
440                            GHashTable *hash_table)
441 {
442 	EPluginHookClass *hook_class;
443 	EPluginHookClass *dupe_class;
444 	gpointer key;
445 
446 	hook_class = g_type_class_ref (type);
447 
448 	/* Sanity check the hook class. */
449 	if (hook_class->id == NULL || *hook_class->id == '\0') {
450 		g_warning (
451 			"%s has no hook ID, so skipping",
452 			G_OBJECT_CLASS_NAME (hook_class));
453 		g_type_class_unref (hook_class);
454 		return;
455 	}
456 
457 	/* Check for class ID collisions. */
458 	dupe_class = g_hash_table_lookup (hash_table, hook_class->id);
459 	if (dupe_class != NULL) {
460 		g_warning (
461 			"%s and %s have the same hook "
462 			"ID ('%s'), so skipping %s",
463 			G_OBJECT_CLASS_NAME (dupe_class),
464 			G_OBJECT_CLASS_NAME (hook_class),
465 			hook_class->id,
466 			G_OBJECT_CLASS_NAME (hook_class));
467 		g_type_class_unref (hook_class);
468 		return;
469 	}
470 
471 	key = (gpointer) hook_class->id;
472 	g_hash_table_insert (hash_table, key, hook_class);
473 }
474 
475 static void
e_plugin_traverse_directory(const gchar * dirname,gint index)476 e_plugin_traverse_directory (const gchar *dirname,
477 			     gint index)
478 {
479 	GDir *dir;
480 	const gchar *d;
481 
482 	pd (printf ("scanning plugin dir '%s'\n", dirname));
483 
484 	dir = g_dir_open (dirname, 0, NULL);
485 
486 	if (!dir)
487 		return;
488 
489 	while ((d = g_dir_read_name (dir))) {
490 		if (g_str_has_suffix  (d, ".eplug")) {
491 			gchar *name;
492 
493 			name = g_build_filename (dirname, d, NULL);
494 			ep_load (name, index);
495 			g_free (name);
496 		}
497 	}
498 
499 	g_dir_close (dir);
500 }
501 
502 /**
503  * e_plugin_load_plugins:
504  *
505  * Scan the search path, looking for plugin definitions, and load them
506  * into memory.
507  *
508  * Return value: Returns -1 if an error occurred.
509  **/
510 gint
e_plugin_load_plugins(void)511 e_plugin_load_plugins (void)
512 {
513 	GSettings *settings;
514 	GPtrArray *variants;
515 	gchar **strv;
516 	gint i;
517 
518 	if (eph_types != NULL)
519 		return 0;
520 
521 	ep_types = g_hash_table_new (g_str_hash, g_str_equal);
522 	eph_types = g_hash_table_new (g_str_hash, g_str_equal);
523 	ep_plugins = g_hash_table_new (g_str_hash, g_str_equal);
524 
525 	/* We require that all GTypes for EPlugin and EPluginHook
526 	 * subclasses be registered prior to loading any plugins.
527 	 * It greatly simplifies the loading process. */
528 	e_type_traverse (
529 		E_TYPE_PLUGIN, (ETypeFunc)
530 		plugin_load_subclass, ep_types);
531 	e_type_traverse (
532 		E_TYPE_PLUGIN_HOOK, (ETypeFunc)
533 		plugin_hook_load_subclass, eph_types);
534 
535 	settings = e_util_ref_settings ("org.gnome.evolution");
536 	strv = g_settings_get_strv (settings, "disabled-eplugins");
537 	for (i = 0, ep_disabled = NULL; strv[i] != NULL; i++)
538 		ep_disabled = g_slist_append (ep_disabled, g_strdup (strv[i]));
539 	g_strfreev (strv);
540 	g_object_unref (settings);
541 
542 	variants = e_util_get_directory_variants (EVOLUTION_PLUGINDIR, EVOLUTION_PREFIX, TRUE);
543 
544 	for (i = 0; i < 3; i++) {
545 		if (variants) {
546 			guint jj;
547 
548 			for (jj = 0; jj < variants->len; jj++) {
549 				const gchar *dirname = g_ptr_array_index (variants, jj);
550 
551 				if (dirname && *dirname)
552 					e_plugin_traverse_directory (dirname, i);
553 			}
554 		} else {
555 			e_plugin_traverse_directory (EVOLUTION_PLUGINDIR, i);
556 		}
557 	}
558 
559 	if (variants)
560 		g_ptr_array_unref (variants);
561 
562 	return 0;
563 }
564 
565 static void
ep_list_plugin(gpointer key,gpointer val,gpointer dat)566 ep_list_plugin (gpointer key,
567                 gpointer val,
568                 gpointer dat)
569 {
570 	GSList **l = (GSList **) dat;
571 
572 	*l = g_slist_prepend(*l, g_object_ref(val));
573 }
574 
575 /**
576  * e_plugin_list_plugins: List all plugins.
577  *
578  * Static class method to retrieve a list of all current plugins.  They
579  * are listed in no particular order.
580  *
581  * Return value: A GSList of all plugins, they must be
582  * g_object_unref'd and the list freed.
583  **/
584 GSList *
e_plugin_list_plugins(void)585 e_plugin_list_plugins (void)
586 {
587 	GSList *l = NULL;
588 
589 	if (ep_plugins)
590 		g_hash_table_foreach (ep_plugins, ep_list_plugin, &l);
591 
592 	return l;
593 }
594 
595 /**
596  * e_plugin_construct:
597  * @plugin: an #EPlugin
598  * @root: The XML root node of the sub-tree containing the plugin
599  * definition.
600  *
601  * Helper to invoke the construct virtual method.
602  *
603  * Return value: The return from the construct virtual method.
604  **/
605 gint
e_plugin_construct(EPlugin * plugin,xmlNodePtr root)606 e_plugin_construct (EPlugin *plugin,
607                     xmlNodePtr root)
608 {
609 	EPluginClass *class;
610 
611 	g_return_val_if_fail (E_IS_PLUGIN (plugin), -1);
612 
613 	class = E_PLUGIN_GET_CLASS (plugin);
614 	g_return_val_if_fail (class != NULL, -1);
615 	g_return_val_if_fail (class->construct != NULL, -1);
616 
617 	return class->construct (plugin, root);
618 }
619 
620 /**
621  * e_plugin_invoke:
622  * @plugin: an #EPlugin
623  * @name: The name of the function to invoke. The format of this name
624  * will depend on the EPlugin type and its language conventions.
625  * @data: The argument to the function. Its actual type depends on
626  * the hook on which the function resides. It is up to the called
627  * function to get this right.
628  *
629  * Helper to invoke the invoke virtual method.
630  *
631  * Return value: The return of the plugin invocation.
632  **/
633 gpointer
e_plugin_invoke(EPlugin * plugin,const gchar * name,gpointer data)634 e_plugin_invoke (EPlugin *plugin,
635                  const gchar *name,
636                  gpointer data)
637 {
638 	EPluginClass *class;
639 
640 	g_return_val_if_fail (E_IS_PLUGIN (plugin), NULL);
641 	g_return_val_if_fail (name != NULL, NULL);
642 
643 	/* Prevent invocation on a disabled plugin. */
644 	g_return_val_if_fail (plugin->enabled, NULL);
645 
646 	class = E_PLUGIN_GET_CLASS (plugin);
647 	g_return_val_if_fail (class != NULL, NULL);
648 	g_return_val_if_fail (class->invoke != NULL, NULL);
649 
650 	return class->invoke (plugin, name, data);
651 }
652 
653 /**
654  * e_plugin_get_symbol:
655  * @plugin: an #EPlugin
656  * @name: The name of the symbol to fetch. The format of this name
657  * will depend on the EPlugin type and its language conventions.
658  *
659  * Helper to fetch a symbol name from a plugin.
660  *
661  * Return value: the symbol value, or %NULL if not found
662  **/
663 gpointer
e_plugin_get_symbol(EPlugin * plugin,const gchar * name)664 e_plugin_get_symbol (EPlugin *plugin,
665                      const gchar *name)
666 {
667 	EPluginClass *class;
668 
669 	g_return_val_if_fail (E_IS_PLUGIN (plugin), NULL);
670 
671 	class = E_PLUGIN_GET_CLASS (plugin);
672 	g_return_val_if_fail (class != NULL, NULL);
673 	g_return_val_if_fail (class->get_symbol != NULL, NULL);
674 
675 	return class->get_symbol (plugin, name);
676 }
677 
678 /**
679  * e_plugin_enable:
680  * @plugin: an #EPlugin
681  * @state: %TRUE to enable, %FALSE to disable
682  *
683  * Set the enable state of a plugin.
684  *
685  * THIS IS NOT FULLY IMPLEMENTED YET
686  **/
687 void
e_plugin_enable(EPlugin * plugin,gint state)688 e_plugin_enable (EPlugin *plugin,
689                  gint state)
690 {
691 	EPluginClass *class;
692 
693 	g_return_if_fail (E_IS_PLUGIN (plugin));
694 
695 	if ((plugin->enabled == 0) == (state == 0))
696 		return;
697 
698 	class = E_PLUGIN_GET_CLASS (plugin);
699 	g_return_if_fail (class != NULL);
700 	g_return_if_fail (class->enable != NULL);
701 
702 	class->enable (plugin, state);
703 
704 	g_object_notify (G_OBJECT (plugin), "enabled");
705 }
706 
707 /**
708  * e_plugin_get_configure_widget
709  * @plugin: an #EPlugin
710  *
711  * Plugin itself should have implemented "e_plugin_lib_get_configure_widget"
712  * function * of prototype EPluginLibGetConfigureWidgetFunc.
713  *
714  * Returns: Configure widget or %NULL
715  **/
716 GtkWidget *
e_plugin_get_configure_widget(EPlugin * plugin)717 e_plugin_get_configure_widget (EPlugin *plugin)
718 {
719 	EPluginClass *class;
720 
721 	g_return_val_if_fail (E_IS_PLUGIN (plugin), NULL);
722 
723 	class = E_PLUGIN_GET_CLASS (plugin);
724 	g_return_val_if_fail (class != NULL, NULL);
725 
726 	if (class->get_configure_widget == NULL)
727 		return NULL;
728 
729 	return class->get_configure_widget (plugin);
730 }
731 
732 /**
733  * e_plugin_xml_prop:
734  * @node: An XML node.
735  * @id: The name of the property to retrieve.
736  *
737  * A static helper function to look up a property on an XML node, and
738  * ensure it is allocated in GLib system memory.
739  *
740  * Return value: The property, allocated in GLib memory, or NULL if no
741  * such property exists.
742  **/
743 gchar *
e_plugin_xml_prop(xmlNodePtr node,const gchar * id)744 e_plugin_xml_prop (xmlNodePtr node,
745                    const gchar *id)
746 {
747 	xmlChar *xml_prop;
748 	gchar *glib_prop = NULL;
749 
750 	xml_prop = xmlGetProp (node, (xmlChar *) id);
751 
752 	if (xml_prop != NULL) {
753 		glib_prop = g_strdup ((gchar *) xml_prop);
754 		xmlFree (xml_prop);
755 	}
756 
757 	return glib_prop;
758 }
759 
760 /**
761  * e_plugin_xml_prop_domain:
762  * @node: An XML node.
763  * @id: The name of the property to retrieve.
764  * @domain: The translation domain for this string.
765  *
766  * A static helper function to look up a property on an XML node, and
767  * translate it based on @domain.
768  *
769  * Return value: The property, allocated in GLib memory, or NULL if no
770  * such property exists.
771  **/
772 gchar *
e_plugin_xml_prop_domain(xmlNodePtr node,const gchar * id,const gchar * domain)773 e_plugin_xml_prop_domain (xmlNodePtr node,
774                           const gchar *id,
775                           const gchar *domain)
776 {
777 	gchar *p, *out;
778 
779 	p = (gchar *) xmlGetProp (node, (const guchar *) id);
780 	if (p == NULL)
781 		return NULL;
782 
783 	out = g_strdup (dgettext (domain, p));
784 	xmlFree (p);
785 
786 	return out;
787 }
788 
789 /**
790  * e_plugin_xml_int:
791  * @node: An XML node.
792  * @id: The name of the property to retrieve.
793  * @def: A default value if the property doesn't exist.  Can be used
794  * to determine if the property isn't set.
795  *
796  * A static helper function to look up a property on an XML node as an
797  * integer.  If the property doesn't exist, then @def is returned as a
798  * default value instead.
799  *
800  * Return value: The value if set, or @def if not.
801  **/
802 gint
e_plugin_xml_int(xmlNodePtr node,const gchar * id,gint def)803 e_plugin_xml_int (xmlNodePtr node,
804                   const gchar *id,
805                   gint def)
806 {
807 	gchar *p = (gchar *) xmlGetProp (node, (const guchar *) id);
808 
809 	if (p)
810 		return atoi (p);
811 	else
812 		return def;
813 }
814 
815 /**
816  * e_plugin_xml_content:
817  * @node:
818  *
819  * A static helper function to retrieve the entire textual content of
820  * an XML node, and ensure it is allocated in GLib system memory.  If
821  * GLib isn't using the system malloc them it must copy the content.
822  *
823  * Return value: The node content, allocated in GLib memory.
824  **/
825 gchar *
e_plugin_xml_content(xmlNodePtr node)826 e_plugin_xml_content (xmlNodePtr node)
827 {
828 	gchar *p = (gchar *) xmlNodeGetContent (node);
829 
830 	if (g_mem_is_system_malloc ()) {
831 		return p;
832 	} else {
833 		gchar * out = g_strdup (p);
834 
835 		if (p)
836 			xmlFree (p);
837 		return out;
838 	}
839 }
840 
841 /**
842  * e_plugin_xml_content_domain:
843  * @node:
844  * @domain:
845  *
846  * A static helper function to retrieve the entire textual content of
847  * an XML node, and ensure it is allocated in GLib system memory.  If
848  * GLib isn't using the system malloc them it must copy the content.
849  *
850  * Return value: The node content, allocated in GLib memory.
851  **/
852 gchar *
e_plugin_xml_content_domain(xmlNodePtr node,const gchar * domain)853 e_plugin_xml_content_domain (xmlNodePtr node,
854                              const gchar *domain)
855 {
856 	gchar *p, *out;
857 
858 	p = (gchar *) xmlNodeGetContent (node);
859 	if (p == NULL)
860 		return NULL;
861 
862 	out = g_strdup (dgettext (domain, p));
863 	xmlFree (p);
864 
865 	return out;
866 }
867 
868 /* ********************************************************************** */
869 
G_DEFINE_TYPE(EPluginHook,e_plugin_hook,G_TYPE_OBJECT)870 G_DEFINE_TYPE (
871 	EPluginHook,
872 	e_plugin_hook,
873 	G_TYPE_OBJECT)
874 
875 static gint
876 plugin_hook_construct (EPluginHook *plugin_hook,
877                        EPlugin *plugin,
878                        xmlNodePtr root)
879 {
880 	plugin_hook->plugin = plugin;
881 
882 	return 0;
883 }
884 
885 static void
plugin_hook_enable(EPluginHook * plugin_hook,gint state)886 plugin_hook_enable (EPluginHook *plugin_hook,
887                     gint state)
888 {
889 	/* NOOP */
890 }
891 
892 static void
e_plugin_hook_class_init(EPluginHookClass * class)893 e_plugin_hook_class_init (EPluginHookClass *class)
894 {
895 	class->construct = plugin_hook_construct;
896 	class->enable = plugin_hook_enable;
897 }
898 
899 static void
e_plugin_hook_init(EPluginHook * hook)900 e_plugin_hook_init (EPluginHook *hook)
901 {
902 }
903 
904 /**
905  * e_plugin_hook_enable:
906  * @plugin_hook:
907  * @state:
908  *
909  * Set the enabled state of the plugin hook.  This is called by the
910  * plugin code.
911  *
912  * THIS IS NOT FULY IMEPLEMENTED YET
913  **/
914 void
e_plugin_hook_enable(EPluginHook * plugin_hook,gint state)915 e_plugin_hook_enable (EPluginHook *plugin_hook,
916                       gint state)
917 {
918 	EPluginHookClass *class;
919 
920 	g_return_if_fail (E_IS_PLUGIN_HOOK (plugin_hook));
921 
922 	class = E_PLUGIN_HOOK_GET_CLASS (plugin_hook);
923 	g_return_if_fail (class != NULL);
924 	g_return_if_fail (class->enable != NULL);
925 
926 	class->enable (plugin_hook, state);
927 }
928 
929 /**
930  * e_plugin_hook_mask:
931  * @root: An XML node.
932  * @map: A zero-fill terminated array of EPluginHookTargeKeys used to
933  * map a string with a bit value.
934  * @prop: The property name.
935  *
936  * This is a static helper function which looks up a property @prop on
937  * the XML node @root, and then uses the @map table to convert it into
938  * a bitmask.  The property value is a comma separated list of
939  * enumeration strings which are indexed into the @map table.
940  *
941  * Return value: A bitmask representing the inclusive-or of all of the
942  * integer values of the corresponding string id's stored in the @map.
943  **/
944 guint32
e_plugin_hook_mask(xmlNodePtr root,const EPluginHookTargetKey * map,const gchar * prop)945 e_plugin_hook_mask (xmlNodePtr root,
946                     const EPluginHookTargetKey *map,
947                     const gchar *prop)
948 {
949 	gchar *val, *p, *start, c;
950 	guint32 mask = 0;
951 
952 	val = (gchar *) xmlGetProp (root, (const guchar *) prop);
953 	if (val == NULL)
954 		return 0;
955 
956 	p = val;
957 	do {
958 		start = p;
959 		while (*p && *p != ',')
960 			p++;
961 		c = *p;
962 		*p = 0;
963 		if (start != p) {
964 			gint i;
965 
966 			for (i = 0; map[i].key; i++) {
967 				if (!strcmp (map[i].key, start)) {
968 					mask |= map[i].value;
969 					break;
970 				}
971 			}
972 		}
973 		*p++ = c;
974 	} while (c);
975 
976 	xmlFree (val);
977 
978 	return mask;
979 }
980 
981 /**
982  * e_plugin_hook_id:
983  * @root:
984  * @map:
985  * @prop:
986  *
987  * This is a static helper function which looks up a property @prop on
988  * the XML node @root, and then uses the @map table to convert it into
989  * an integer.
990  *
991  * This is used as a helper wherever you need to represent an
992  * enumerated value in the XML.
993  *
994  * Return value: If the @prop value is in @map, then the corresponding
995  * integer value, if not, then ~0.
996  **/
997 guint32
e_plugin_hook_id(xmlNodePtr root,const EPluginHookTargetKey * map,const gchar * prop)998 e_plugin_hook_id (xmlNodePtr root,
999                   const EPluginHookTargetKey *map,
1000                   const gchar *prop)
1001 {
1002 	gchar *val;
1003 	gint i;
1004 
1005 	val = (gchar *) xmlGetProp (root, (const guchar *) prop);
1006 	if (val == NULL)
1007 		return ~0;
1008 
1009 	for (i = 0; map[i].key; i++) {
1010 		if (!strcmp (map[i].key, val)) {
1011 			xmlFree (val);
1012 			return map[i].value;
1013 		}
1014 	}
1015 
1016 	xmlFree (val);
1017 
1018 	return ~0;
1019 }
1020