evolution-3.6.4/filter/e-rule-context.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
   2 /*
   3  * This program is free software; you can redistribute it and/or
   4  * modify it under the terms of the GNU Lesser General Public
   5  * License as published by the Free Software Foundation; either
   6  * version 2 of the License, or (at your option) version 3.
   7  *
   8  * This program is distributed in the hope that it will be useful,
   9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11  * Lesser General Public License for more details.
  12  *
  13  * You should have received a copy of the GNU Lesser General Public
  14  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  15  *
  16  *
  17  * Authors:
  18  *		Not Zed <notzed@lostzed.mmc.com.au>
  19  *      Jeffrey Stedfast <fejj@ximian.com>
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  *
  23  */
  24 
  25 #ifdef HAVE_CONFIG_H
  26 #include <config.h>
  27 #endif
  28 
  29 #include <string.h>
  30 #include <sys/types.h>
  31 #include <sys/stat.h>
  32 #include <unistd.h>
  33 #include <fcntl.h>
  34 #include <errno.h>
  35 
  36 #include <glib/gstdio.h>
  37 
  38 #include <gtk/gtk.h>
  39 
  40 #include <glib/gi18n.h>
  41 
  42 #include <libedataserver/libedataserver.h>
  43 
  44 #include "libevolution-utils/e-alert-dialog.h"
  45 #include "libevolution-utils/e-xml-utils.h"
  46 
  47 #include "e-filter-code.h"
  48 #include "e-filter-color.h"
  49 #include "e-filter-datespec.h"
  50 #include "e-filter-file.h"
  51 #include "e-filter-input.h"
  52 #include "e-filter-int.h"
  53 #include "e-filter-option.h"
  54 #include "e-filter-rule.h"
  55 #include "e-rule-context.h"
  56 
  57 #define E_RULE_CONTEXT_GET_PRIVATE(obj) \
  58 	(G_TYPE_INSTANCE_GET_PRIVATE \
  59 	((obj), E_TYPE_RULE_CONTEXT, ERuleContextPrivate))
  60 
  61 struct _ERuleContextPrivate {
  62 	gint frozen;
  63 };
  64 
  65 enum {
  66 	RULE_ADDED,
  67 	RULE_REMOVED,
  68 	CHANGED,
  69 	LAST_SIGNAL
  70 };
  71 
  72 static guint signals[LAST_SIGNAL];
  73 
  74 struct _revert_data {
  75 	GHashTable *rules;
  76 	gint rank;
  77 };
  78 
  79 G_DEFINE_TYPE (
  80 	ERuleContext,
  81 	e_rule_context,
  82 	G_TYPE_OBJECT)
  83 
  84 static void
  85 rule_context_set_error (ERuleContext *context,
  86                         gchar *error)
  87 {
  88 	g_free (context->error);
  89 	context->error = error;
  90 }
  91 
  92 static void
  93 new_rule_response (GtkWidget *dialog,
  94                    gint button,
  95                    ERuleContext *context)
  96 {
  97 	if (button == GTK_RESPONSE_OK) {
  98 		EFilterRule *rule = g_object_get_data ((GObject *) dialog, "rule");
  99 		gchar *user = g_object_get_data ((GObject *) dialog, "path");
 100 		EAlert *alert = NULL;
 101 
 102 		if (!e_filter_rule_validate (rule, &alert)) {
 103 			e_alert_run_dialog (GTK_WINDOW (dialog), alert);
 104 			g_object_unref (alert);
 105 			return;
 106 		}
 107 
 108 		if (e_rule_context_find_rule (context, rule->name, rule->source)) {
 109 			e_alert_run_dialog_for_args ((GtkWindow *) dialog,
 110 						     "filter:bad-name-notunique",
 111 						     rule->name, NULL);
 112 
 113 			return;
 114 		}
 115 
 116 		g_object_ref (rule);
 117 		e_rule_context_add_rule (context, rule);
 118 		if (user)
 119 			e_rule_context_save (context, user);
 120 	}
 121 
 122 	gtk_widget_destroy (dialog);
 123 }
 124 
 125 static void
 126 revert_rule_remove (gpointer key,
 127                     EFilterRule *rule,
 128                     ERuleContext *context)
 129 {
 130 	e_rule_context_remove_rule (context, rule);
 131 	g_object_unref (rule);
 132 }
 133 
 134 static void
 135 revert_source_remove (gpointer key,
 136                       struct _revert_data *rest_data,
 137                       ERuleContext *context)
 138 {
 139 	g_hash_table_foreach (
 140 		rest_data->rules, (GHFunc) revert_rule_remove, context);
 141 	g_hash_table_destroy (rest_data->rules);
 142 	g_free (rest_data);
 143 }
 144 
 145 static guint
 146 source_hashf (const gchar *a)
 147 {
 148 	return (a != NULL) ? g_str_hash (a) : 0;
 149 }
 150 
 151 static gint
 152 source_eqf (const gchar *a,
 153             const gchar *b)
 154 {
 155 	return (g_strcmp0 (a, b) == 0);
 156 }
 157 
 158 static void
 159 free_part_set (struct _part_set_map *map)
 160 {
 161 	g_free (map->name);
 162 	g_free (map);
 163 }
 164 
 165 static void
 166 free_rule_set (struct _rule_set_map *map)
 167 {
 168 	g_free (map->name);
 169 	g_free (map);
 170 }
 171 
 172 static void
 173 rule_context_finalize (GObject *obj)
 174 {
 175 	ERuleContext *context =(ERuleContext *) obj;
 176 
 177 	g_list_foreach (context->rule_set_list, (GFunc) free_rule_set, NULL);
 178 	g_list_free (context->rule_set_list);
 179 	g_hash_table_destroy (context->rule_set_map);
 180 
 181 	g_list_foreach (context->part_set_list, (GFunc) free_part_set, NULL);
 182 	g_list_free (context->part_set_list);
 183 	g_hash_table_destroy (context->part_set_map);
 184 
 185 	g_free (context->error);
 186 
 187 	g_list_foreach (context->parts, (GFunc) g_object_unref, NULL);
 188 	g_list_free (context->parts);
 189 
 190 	g_list_foreach (context->rules, (GFunc) g_object_unref, NULL);
 191 	g_list_free (context->rules);
 192 
 193 	G_OBJECT_CLASS (e_rule_context_parent_class)->finalize (obj);
 194 }
 195 
 196 static gint
 197 rule_context_load (ERuleContext *context,
 198                    const gchar *system,
 199                    const gchar *user)
 200 {
 201 	xmlNodePtr set, rule, root;
 202 	xmlDocPtr systemdoc, userdoc;
 203 	struct _part_set_map *part_map;
 204 	struct _rule_set_map *rule_map;
 205 
 206 	rule_context_set_error (context, NULL);
 207 
 208 	systemdoc = e_xml_parse_file (system);
 209 	if (systemdoc == NULL) {
 210 		gchar * err_msg;
 211 
 212 		err_msg = g_strdup_printf (
 213 			"Unable to load system rules '%s': %s",
 214 			system, g_strerror (errno));
 215 		g_warning ("%s: %s", G_STRFUNC, err_msg);
 216 		rule_context_set_error (context, err_msg);
 217 		/* no need to free err_msg here */
 218 		return -1;
 219 	}
 220 
 221 	root = xmlDocGetRootElement (systemdoc);
 222 	if (root == NULL || strcmp ((gchar *) root->name, "filterdescription")) {
 223 		gchar * err_msg;
 224 
 225 		err_msg = g_strdup_printf (
 226 			"Unable to load system rules '%s': "
 227 			"Invalid format", system);
 228 		g_warning ("%s: %s", G_STRFUNC, err_msg);
 229 		rule_context_set_error (context, err_msg);
 230 		/* no need to free err_msg here */
 231 		xmlFreeDoc (systemdoc);
 232 		return -1;
 233 	}
 234 	/* doesn't matter if this doens't exist */
 235 	userdoc = NULL;
 236 	if (g_file_test (user, G_FILE_TEST_IS_REGULAR))
 237 		userdoc = e_xml_parse_file (user);
 238 
 239 	/* now parse structure */
 240 	/* get rule parts */
 241 	set = root->children;
 242 	while (set) {
 243 		part_map = g_hash_table_lookup (context->part_set_map, set->name);
 244 		if (part_map) {
 245 			rule = set->children;
 246 			while (rule) {
 247 				if (!strcmp ((gchar *) rule->name, "part")) {
 248 					EFilterPart *part =
 249 						E_FILTER_PART (g_object_new (
 250 						part_map->type, NULL, NULL));
 251 
 252 					if (e_filter_part_xml_create (part, rule, context) == 0) {
 253 						part_map->append (context, part);
 254 					} else {
 255 						g_object_unref (part);
 256 						g_warning ("Cannot load filter part");
 257 					}
 258 				}
 259 				rule = rule->next;
 260 			}
 261 		} else if ((rule_map = g_hash_table_lookup (
 262 				context->rule_set_map, set->name))) {
 263 			rule = set->children;
 264 			while (rule) {
 265 				if (!strcmp ((gchar *) rule->name, "rule")) {
 266 					EFilterRule *part =
 267 						E_FILTER_RULE (g_object_new (
 268 						rule_map->type, NULL, NULL));
 269 
 270 					if (e_filter_rule_xml_decode (part, rule, context) == 0) {
 271 						part->system = TRUE;
 272 						rule_map->append (context, part);
 273 					} else {
 274 						g_object_unref (part);
 275 						g_warning ("Cannot load filter part");
 276 					}
 277 				}
 278 				rule = rule->next;
 279 			}
 280 		}
 281 		set = set->next;
 282 	}
 283 
 284 	/* now load actual rules */
 285 	if (userdoc) {
 286 		root = xmlDocGetRootElement (userdoc);
 287 		set = root ? root->children : NULL;
 288 		while (set) {
 289 			rule_map = g_hash_table_lookup (context->rule_set_map, set->name);
 290 			if (rule_map) {
 291 				rule = set->children;
 292 				while (rule) {
 293 					if (!strcmp ((gchar *) rule->name, "rule")) {
 294 						EFilterRule *part =
 295 							E_FILTER_RULE (g_object_new (
 296 							rule_map->type, NULL, NULL));
 297 
 298 						if (e_filter_rule_xml_decode (part, rule, context) == 0) {
 299 							rule_map->append (context, part);
 300 						} else {
 301 							g_object_unref (part);
 302 							g_warning ("Cannot load filter part");
 303 						}
 304 					}
 305 					rule = rule->next;
 306 				}
 307 			}
 308 			set = set->next;
 309 		}
 310 	}
 311 
 312 	xmlFreeDoc (userdoc);
 313 	xmlFreeDoc (systemdoc);
 314 
 315 	return 0;
 316 }
 317 
 318 static gint
 319 rule_context_save (ERuleContext *context,
 320                    const gchar *user)
 321 {
 322 	xmlDocPtr doc;
 323 	xmlNodePtr root, rules, work;
 324 	GList *l;
 325 	EFilterRule *rule;
 326 	struct _rule_set_map *map;
 327 	gint ret;
 328 
 329 	doc = xmlNewDoc ((xmlChar *)"1.0");
 330 	/* FIXME: set character encoding to UTF-8? */
 331 	root = xmlNewDocNode (doc, NULL, (xmlChar *)"filteroptions", NULL);
 332 	xmlDocSetRootElement (doc, root);
 333 	l = context->rule_set_list;
 334 	while (l) {
 335 		map = l->data;
 336 		rules = xmlNewDocNode (doc, NULL, (xmlChar *) map->name, NULL);
 337 		xmlAddChild (root, rules);
 338 		rule = NULL;
 339 		while ((rule = map->next (context, rule, NULL))) {
 340 			if (!rule->system) {
 341 				work = e_filter_rule_xml_encode (rule);
 342 				xmlAddChild (rules, work);
 343 			}
 344 		}
 345 		l = g_list_next (l);
 346 	}
 347 
 348 	ret = e_xml_save_file (user, doc);
 349 
 350 	xmlFreeDoc (doc);
 351 
 352 	return ret;
 353 }
 354 
 355 static gint
 356 rule_context_revert (ERuleContext *context,
 357                      const gchar *user)
 358 {
 359 	xmlNodePtr set, rule;
 360 	/*struct _part_set_map *part_map;*/
 361 	struct _rule_set_map *rule_map;
 362 	struct _revert_data *rest_data;
 363 	GHashTable *source_hash;
 364 	xmlDocPtr userdoc;
 365 	EFilterRule *frule;
 366 
 367 	rule_context_set_error (context, NULL);
 368 
 369 	userdoc = e_xml_parse_file (user);
 370 	if (userdoc == NULL)
 371 		/* clear out anythign we have? */
 372 		return 0;
 373 
 374 	source_hash = g_hash_table_new (
 375 		(GHashFunc) source_hashf,
 376 		(GCompareFunc) source_eqf);
 377 
 378 	/* setup stuff we have now */
 379 	/* Note that we assume there is only 1 set of rules in a given rule context,
 380 	 * although other parts of the code dont assume this */
 381 	frule = NULL;
 382 	while ((frule = e_rule_context_next_rule (context, frule, NULL))) {
 383 		rest_data = g_hash_table_lookup (source_hash, frule->source);
 384 		if (rest_data == NULL) {
 385 			rest_data = g_malloc0 (sizeof (*rest_data));
 386 			rest_data->rules = g_hash_table_new (g_str_hash, g_str_equal);
 387 			g_hash_table_insert (source_hash, frule->source, rest_data);
 388 		}
 389 		g_hash_table_insert (rest_data->rules, frule->name, frule);
 390 	}
 391 
 392 	/* make what we have, match what we load */
 393 	set = xmlDocGetRootElement (userdoc);
 394 	set = set ? set->children : NULL;
 395 	while (set) {
 396 		rule_map = g_hash_table_lookup (context->rule_set_map, set->name);
 397 		if (rule_map) {
 398 			rule = set->children;
 399 			while (rule) {
 400 				if (!strcmp ((gchar *) rule->name, "rule")) {
 401 					EFilterRule *part =
 402 						E_FILTER_RULE (g_object_new (
 403 						rule_map->type, NULL, NULL));
 404 
 405 					if (e_filter_rule_xml_decode (part, rule, context) == 0) {
 406 						/* Use the revert data to keep
 407 						 * track of the right rank of
 408 						 * this rule part. */
 409 						rest_data = g_hash_table_lookup (source_hash, part->source);
 410 						if (rest_data == NULL) {
 411 							rest_data = g_malloc0 (sizeof (*rest_data));
 412 							rest_data->rules = g_hash_table_new (
 413 								g_str_hash,
 414 								g_str_equal);
 415 							g_hash_table_insert (
 416 								source_hash,
 417 								part->source,
 418 								rest_data);
 419 						}
 420 						frule = g_hash_table_lookup (
 421 							rest_data->rules,
 422 							part->name);
 423 						if (frule) {
 424 							if (context->priv->frozen == 0 &&
 425 							    !e_filter_rule_eq (frule, part))
 426 								e_filter_rule_copy (frule, part);
 427 
 428 							g_object_unref (part);
 429 							e_rule_context_rank_rule (
 430 								context, frule,
 431 								frule->source,
 432 								rest_data->rank);
 433 							g_hash_table_remove (rest_data->rules, frule->name);
 434 						} else {
 435 							e_rule_context_add_rule (context, part);
 436 							e_rule_context_rank_rule (
 437 								context,
 438 								part,
 439 								part->source,
 440 								rest_data->rank);
 441 						}
 442 						rest_data->rank++;
 443 					} else {
 444 						g_object_unref (part);
 445 						g_warning ("Cannot load filter part");
 446 					}
 447 				}
 448 				rule = rule->next;
 449 			}
 450 		}
 451 		set = set->next;
 452 	}
 453 
 454 	xmlFreeDoc (userdoc);
 455 
 456 	/* remove any we still have that weren't in the file */
 457 	g_hash_table_foreach (source_hash, (GHFunc) revert_source_remove, context);
 458 	g_hash_table_destroy (source_hash);
 459 
 460 	return 0;
 461 }
 462 
 463 static EFilterElement *
 464 rule_context_new_element (ERuleContext *context,
 465                           const gchar *type)
 466 {
 467 	if (!strcmp (type, "string")) {
 468 		return (EFilterElement *) e_filter_input_new ();
 469 	} else if (!strcmp (type, "address")) {
 470 		/* FIXME: temporary ... need real address type */
 471 		return (EFilterElement *) e_filter_input_new_type_name (type);
 472 	} else if (!strcmp (type, "code")) {
 473 		return (EFilterElement *) e_filter_code_new (FALSE);
 474 	} else if (!strcmp (type, "rawcode")) {
 475 		return (EFilterElement *) e_filter_code_new (TRUE);
 476 	} else if (!strcmp (type, "colour")) {
 477 		return (EFilterElement *) e_filter_color_new ();
 478 	} else if (!strcmp (type, "optionlist")) {
 479 		return (EFilterElement *) e_filter_option_new ();
 480 	} else if (!strcmp (type, "datespec")) {
 481 		return (EFilterElement *) e_filter_datespec_new ();
 482 	} else if (!strcmp (type, "command")) {
 483 		return (EFilterElement *) e_filter_file_new_type_name (type);
 484 	} else if (!strcmp (type, "file")) {
 485 		return (EFilterElement *) e_filter_file_new_type_name (type);
 486 	} else if (!strcmp (type, "integer")) {
 487 		return (EFilterElement *) e_filter_int_new ();
 488 	} else if (!strcmp (type, "regex")) {
 489 		return (EFilterElement *) e_filter_input_new_type_name (type);
 490 	} else if (!strcmp (type, "completedpercent")) {
 491 		return (EFilterElement *) e_filter_int_new_type (
 492 			"completedpercent", 0,100);
 493 	} else {
 494 		g_warning ("Unknown filter type '%s'", type);
 495 		return NULL;
 496 	}
 497 }
 498 
 499 static void
 500 e_rule_context_class_init (ERuleContextClass *class)
 501 {
 502 	GObjectClass *object_class;
 503 
 504 	g_type_class_add_private (class, sizeof (ERuleContextPrivate));
 505 
 506 	object_class = G_OBJECT_CLASS (class);
 507 	object_class->finalize = rule_context_finalize;
 508 
 509 	class->load = rule_context_load;
 510 	class->save = rule_context_save;
 511 	class->revert = rule_context_revert;
 512 	class->new_element = rule_context_new_element;
 513 
 514 	signals[RULE_ADDED] = g_signal_new (
 515 		"rule-added",
 516 		E_TYPE_RULE_CONTEXT,
 517 		G_SIGNAL_RUN_LAST,
 518 		G_STRUCT_OFFSET (ERuleContextClass, rule_added),
 519 		NULL,
 520 		NULL,
 521 		g_cclosure_marshal_VOID__POINTER,
 522 		G_TYPE_NONE, 1,
 523 		G_TYPE_POINTER);
 524 
 525 	signals[RULE_REMOVED] = g_signal_new (
 526 		"rule-removed",
 527 		E_TYPE_RULE_CONTEXT,
 528 		G_SIGNAL_RUN_LAST,
 529 		G_STRUCT_OFFSET (ERuleContextClass, rule_removed),
 530 		NULL,
 531 		NULL,
 532 		g_cclosure_marshal_VOID__POINTER,
 533 		G_TYPE_NONE, 1,
 534 		G_TYPE_POINTER);
 535 
 536 	signals[CHANGED] = g_signal_new (
 537 		"changed",
 538 		E_TYPE_RULE_CONTEXT,
 539 		G_SIGNAL_RUN_LAST,
 540 		G_STRUCT_OFFSET (ERuleContextClass, changed),
 541 		NULL,
 542 		NULL,
 543 		g_cclosure_marshal_VOID__VOID,
 544 		G_TYPE_NONE, 0);
 545 }
 546 
 547 static void
 548 e_rule_context_init (ERuleContext *context)
 549 {
 550 	context->priv = E_RULE_CONTEXT_GET_PRIVATE (context);
 551 
 552 	context->part_set_map = g_hash_table_new (g_str_hash, g_str_equal);
 553 	context->rule_set_map = g_hash_table_new (g_str_hash, g_str_equal);
 554 
 555 	context->flags = E_RULE_CONTEXT_GROUPING;
 556 }
 557 
 558 /**
 559  * e_rule_context_new:
 560  *
 561  * Create a new ERuleContext object.
 562  *
 563  * Return value: A new #ERuleContext object.
 564  **/
 565 ERuleContext *
 566 e_rule_context_new (void)
 567 {
 568 	return g_object_new (E_TYPE_RULE_CONTEXT, NULL);
 569 }
 570 
 571 void
 572 e_rule_context_add_part_set (ERuleContext *context,
 573                              const gchar *setname,
 574                              GType part_type,
 575                              ERuleContextPartFunc append,
 576                              ERuleContextNextPartFunc next)
 577 {
 578 	struct _part_set_map *map;
 579 
 580 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 581 	g_return_if_fail (setname != NULL);
 582 	g_return_if_fail (append != NULL);
 583 	g_return_if_fail (next != NULL);
 584 
 585 	map = g_hash_table_lookup (context->part_set_map, setname);
 586 	if (map != NULL) {
 587 		g_hash_table_remove (context->part_set_map, setname);
 588 		context->part_set_list = g_list_remove (context->part_set_list, map);
 589 		free_part_set (map);
 590 		map = NULL;
 591 	}
 592 
 593 	map = g_malloc0 (sizeof (*map));
 594 	map->type = part_type;
 595 	map->append = append;
 596 	map->next = next;
 597 	map->name = g_strdup (setname);
 598 	g_hash_table_insert (context->part_set_map, map->name, map);
 599 	context->part_set_list = g_list_append (context->part_set_list, map);
 600 }
 601 
 602 void
 603 e_rule_context_add_rule_set (ERuleContext *context,
 604                              const gchar *setname,
 605                              GType rule_type,
 606                              ERuleContextRuleFunc append,
 607                              ERuleContextNextRuleFunc next)
 608 {
 609 	struct _rule_set_map *map;
 610 
 611 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 612 	g_return_if_fail (setname != NULL);
 613 	g_return_if_fail (append != NULL);
 614 	g_return_if_fail (next != NULL);
 615 
 616 	map = g_hash_table_lookup (context->rule_set_map, setname);
 617 	if (map != NULL) {
 618 		g_hash_table_remove (context->rule_set_map, setname);
 619 		context->rule_set_list = g_list_remove (context->rule_set_list, map);
 620 		free_rule_set (map);
 621 		map = NULL;
 622 	}
 623 
 624 	map = g_malloc0 (sizeof (*map));
 625 	map->type = rule_type;
 626 	map->append = append;
 627 	map->next = next;
 628 	map->name = g_strdup (setname);
 629 	g_hash_table_insert (context->rule_set_map, map->name, map);
 630 	context->rule_set_list = g_list_append (context->rule_set_list, map);
 631 }
 632 
 633 /**
 634  * e_rule_context_load:
 635  * @f:
 636  * @system:
 637  * @user:
 638  *
 639  * Load a rule context from a system and user description file.
 640  *
 641  * Return value:
 642  **/
 643 gint
 644 e_rule_context_load (ERuleContext *context,
 645                      const gchar *system,
 646                      const gchar *user)
 647 {
 648 	ERuleContextClass *class;
 649 	gint result;
 650 
 651 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1);
 652 	g_return_val_if_fail (system != NULL, -1);
 653 	g_return_val_if_fail (user != NULL, -1);
 654 
 655 	class = E_RULE_CONTEXT_GET_CLASS (context);
 656 	g_return_val_if_fail (class->load != NULL, -1);
 657 
 658 	context->priv->frozen++;
 659 	result = class->load (context, system, user);
 660 	context->priv->frozen--;
 661 
 662 	return result;
 663 }
 664 
 665 /**
 666  * e_rule_context_save:
 667  * @f:
 668  * @user:
 669  *
 670  * Save a rule context to disk.
 671  *
 672  * Return value:
 673  **/
 674 gint
 675 e_rule_context_save (ERuleContext *context,
 676                      const gchar *user)
 677 {
 678 	ERuleContextClass *class;
 679 
 680 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1);
 681 	g_return_val_if_fail (user != NULL, -1);
 682 
 683 	class = E_RULE_CONTEXT_GET_CLASS (context);
 684 	g_return_val_if_fail (class->save != NULL, -1);
 685 
 686 	return class->save (context, user);
 687 }
 688 
 689 /**
 690  * e_rule_context_revert:
 691  * @f:
 692  * @user:
 693  *
 694  * Reverts a rule context from a user description file.  Assumes the
 695  * system description file is unchanged from when it was loaded.
 696  *
 697  * Return value:
 698  **/
 699 gint
 700 e_rule_context_revert (ERuleContext *context,
 701                        const gchar *user)
 702 {
 703 	ERuleContextClass *class;
 704 
 705 	g_return_val_if_fail (E_RULE_CONTEXT (context), 0);
 706 	g_return_val_if_fail (user != NULL, 0);
 707 
 708 	class = E_RULE_CONTEXT_GET_CLASS (context);
 709 	g_return_val_if_fail (class->revert != NULL, 0);
 710 
 711 	return class->revert (context, user);
 712 }
 713 
 714 EFilterPart *
 715 e_rule_context_find_part (ERuleContext *context,
 716                           const gchar *name)
 717 {
 718 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 719 	g_return_val_if_fail (name != NULL, NULL);
 720 
 721 	return e_filter_part_find_list (context->parts, name);
 722 }
 723 
 724 EFilterPart *
 725 e_rule_context_create_part (ERuleContext *context,
 726                             const gchar *name)
 727 {
 728 	EFilterPart *part;
 729 
 730 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 731 	g_return_val_if_fail (name != NULL, NULL);
 732 
 733 	part = e_rule_context_find_part (context, name);
 734 
 735 	if (part == NULL)
 736 		return NULL;
 737 
 738 	return e_filter_part_clone (part);
 739 }
 740 
 741 EFilterPart *
 742 e_rule_context_next_part (ERuleContext *context,
 743                           EFilterPart *last)
 744 {
 745 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 746 
 747 	return e_filter_part_next_list (context->parts, last);
 748 }
 749 
 750 EFilterRule *
 751 e_rule_context_next_rule (ERuleContext *context,
 752                           EFilterRule *last,
 753                           const gchar *source)
 754 {
 755 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 756 
 757 	return e_filter_rule_next_list (context->rules, last, source);
 758 }
 759 
 760 EFilterRule *
 761 e_rule_context_find_rule (ERuleContext *context,
 762                           const gchar *name,
 763                           const gchar *source)
 764 {
 765 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 766 	g_return_val_if_fail (name != NULL, NULL);
 767 
 768 	return e_filter_rule_find_list (context->rules, name, source);
 769 }
 770 
 771 void
 772 e_rule_context_add_part (ERuleContext *context,
 773                          EFilterPart *part)
 774 {
 775 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 776 	g_return_if_fail (E_IS_FILTER_PART (part));
 777 
 778 	context->parts = g_list_append (context->parts, part);
 779 }
 780 
 781 void
 782 e_rule_context_add_rule (ERuleContext *context,
 783                          EFilterRule *rule)
 784 {
 785 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 786 	g_return_if_fail (E_IS_FILTER_RULE (rule));
 787 
 788 	context->rules = g_list_append (context->rules, rule);
 789 
 790 	if (context->priv->frozen == 0) {
 791 		g_signal_emit (context, signals[RULE_ADDED], 0, rule);
 792 		g_signal_emit (context, signals[CHANGED], 0);
 793 	}
 794 }
 795 
 796 /* Add a rule, with a gui, asking for confirmation first,
 797  * and optionally save to path. */
 798 void
 799 e_rule_context_add_rule_gui (ERuleContext *context,
 800                              EFilterRule *rule,
 801                              const gchar *title,
 802                              const gchar *path)
 803 {
 804 	GtkDialog *dialog;
 805 	GtkWidget *widget;
 806 	GtkWidget *content_area;
 807 
 808 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 809 	g_return_if_fail (E_IS_FILTER_RULE (rule));
 810 
 811 	widget = e_filter_rule_get_widget (rule, context);
 812 	gtk_widget_show (widget);
 813 
 814 	dialog =(GtkDialog *) gtk_dialog_new ();
 815 	gtk_dialog_add_buttons (
 816 		dialog,
 817 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 818 		GTK_STOCK_OK, GTK_RESPONSE_OK,
 819 		NULL);
 820 
 821 	gtk_window_set_title ((GtkWindow *) dialog, title);
 822 	gtk_window_set_default_size ((GtkWindow *) dialog, 600, 400);
 823 	gtk_window_set_resizable ((GtkWindow *) dialog, TRUE);
 824 
 825 	content_area = gtk_dialog_get_content_area (dialog);
 826 	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
 827 
 828 	g_object_set_data_full ((GObject *) dialog, "rule", rule, g_object_unref);
 829 	if (path)
 830 		g_object_set_data_full ((GObject *) dialog, "path", g_strdup (path), g_free);
 831 
 832 	g_signal_connect (
 833 		dialog, "response",
 834 		G_CALLBACK (new_rule_response), context);
 835 
 836 	g_object_ref (context);
 837 
 838 	g_object_set_data_full ((GObject *) dialog, "context", context, g_object_unref);
 839 
 840 	gtk_widget_show ((GtkWidget *) dialog);
 841 }
 842 
 843 void
 844 e_rule_context_remove_rule (ERuleContext *context,
 845                             EFilterRule *rule)
 846 {
 847 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 848 	g_return_if_fail (E_IS_FILTER_RULE (rule));
 849 
 850 	context->rules = g_list_remove (context->rules, rule);
 851 
 852 	if (context->priv->frozen == 0) {
 853 		g_signal_emit (context, signals[RULE_REMOVED], 0, rule);
 854 		g_signal_emit (context, signals[CHANGED], 0);
 855 	}
 856 }
 857 
 858 void
 859 e_rule_context_rank_rule (ERuleContext *context,
 860                           EFilterRule *rule,
 861                           const gchar *source,
 862                           gint rank)
 863 {
 864 	GList *node;
 865 	gint i = 0, index = 0;
 866 
 867 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 868 	g_return_if_fail (E_IS_FILTER_RULE (rule));
 869 
 870 	if (e_rule_context_get_rank_rule (context, rule, source) == rank)
 871 		return;
 872 
 873 	context->rules = g_list_remove (context->rules, rule);
 874 	node = context->rules;
 875 	while (node) {
 876 		EFilterRule *r = node->data;
 877 
 878 		if (i == rank) {
 879 			context->rules = g_list_insert (context->rules, rule, index);
 880 			if (context->priv->frozen == 0)
 881 				g_signal_emit (context, signals[CHANGED], 0);
 882 
 883 			return;
 884 		}
 885 
 886 		index++;
 887 		if (source == NULL || (r->source && strcmp (r->source, source) == 0))
 888 			i++;
 889 
 890 		node = node->next;
 891 	}
 892 
 893 	context->rules = g_list_append (context->rules, rule);
 894 	if (context->priv->frozen == 0)
 895 		g_signal_emit (context, signals[CHANGED], 0);
 896 }
 897 
 898 gint
 899 e_rule_context_get_rank_rule (ERuleContext *context,
 900                               EFilterRule *rule,
 901                               const gchar *source)
 902 {
 903 	GList *node;
 904 	gint i = 0;
 905 
 906 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1);
 907 	g_return_val_if_fail (E_IS_FILTER_RULE (rule), -1);
 908 
 909 	node = context->rules;
 910 	while (node) {
 911 		EFilterRule *r = node->data;
 912 
 913 		if (r == rule)
 914 			return i;
 915 
 916 		if (source == NULL || (r->source && strcmp (r->source, source) == 0))
 917 			i++;
 918 
 919 		node = node->next;
 920 	}
 921 
 922 	return -1;
 923 }
 924 
 925 EFilterRule *
 926 e_rule_context_find_rank_rule (ERuleContext *context,
 927                                gint rank,
 928                                const gchar *source)
 929 {
 930 	GList *node;
 931 	gint i = 0;
 932 
 933 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 934 
 935 	node = context->rules;
 936 	while (node) {
 937 		EFilterRule *r = node->data;
 938 
 939 		if (source == NULL || (r->source && strcmp (r->source, source) == 0)) {
 940 			if (rank == i)
 941 				return r;
 942 			i++;
 943 		}
 944 
 945 		node = node->next;
 946 	}
 947 
 948 	return NULL;
 949 }
 950 
 951 GList *
 952 e_rule_context_rename_uri (ERuleContext *context,
 953                            const gchar *old_uri,
 954                            const gchar *new_uri,
 955                            GCompareFunc compare)
 956 {
 957 	ERuleContextClass *class;
 958 
 959 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 960 	g_return_val_if_fail (old_uri != NULL, NULL);
 961 	g_return_val_if_fail (new_uri != NULL, NULL);
 962 	g_return_val_if_fail (compare != NULL, NULL);
 963 
 964 	class = E_RULE_CONTEXT_GET_CLASS (context);
 965 
 966 	/* This method is optional. */
 967 	if (class->rename_uri == NULL)
 968 		return NULL;
 969 
 970 	return class->rename_uri (context, old_uri, new_uri, compare);
 971 }
 972 
 973 GList *
 974 e_rule_context_delete_uri (ERuleContext *context,
 975                            const gchar *uri,
 976                            GCompareFunc compare)
 977 {
 978 	ERuleContextClass *class;
 979 
 980 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
 981 	g_return_val_if_fail (uri != NULL, NULL);
 982 	g_return_val_if_fail (compare != NULL, NULL);
 983 
 984 	class = E_RULE_CONTEXT_GET_CLASS (context);
 985 
 986 	/* This method is optional. */
 987 	if (class->delete_uri == NULL)
 988 		return NULL;
 989 
 990 	return class->delete_uri (context, uri, compare);
 991 }
 992 
 993 void
 994 e_rule_context_free_uri_list (ERuleContext *context,
 995                               GList *uris)
 996 {
 997 	g_return_if_fail (E_IS_RULE_CONTEXT (context));
 998 
 999 	/* TODO: should be virtual */
1000 
1001 	g_list_foreach (uris, (GFunc) g_free, NULL);
1002 	g_list_free (uris);
1003 }
1004 
1005 /**
1006  * e_rule_context_new_element:
1007  * @context:
1008  * @name:
1009  *
1010  * create a new filter element based on name.
1011  *
1012  * Return value:
1013  **/
1014 EFilterElement *
1015 e_rule_context_new_element (ERuleContext *context,
1016                             const gchar *name)
1017 {
1018 	ERuleContextClass *class;
1019 
1020 	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
1021 	g_return_val_if_fail (name != NULL, NULL);
1022 
1023 	class = E_RULE_CONTEXT_GET_CLASS (context);
1024 	g_return_val_if_fail (class->new_element != NULL, NULL);
1025 
1026 	return class->new_element (context, name);
1027 }