2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto & The Claws Mail Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include <glib/gi18n.h>
31 #include "procheader.h"
33 #include "filtering.h"
34 #include "prefs_gtk.h"
36 #include "prefs_common.h"
38 #include "addr_compl.h"
41 #define PREFSBUFSIZE 1024
43 GSList * pre_global_processing = NULL;
44 GSList * post_global_processing = NULL;
45 GSList * filtering_rules = NULL;
47 gboolean debug_filtering_session = FALSE;
49 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
51 #define STRLEN_WITH_CHECK(expr) \
52 strlen_with_check(#expr, __LINE__, expr)
54 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
59 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
64 FilteringAction * filteringaction_new(int type, int account_id,
66 gint labelcolor, gint score, gchar * header)
68 FilteringAction * action;
70 action = g_new0(FilteringAction, 1);
73 action->account_id = account_id;
75 action->destination = g_strdup(destination);
77 action->destination = NULL;
80 action->header = g_strdup(header);
82 action->header = NULL;
84 action->labelcolor = labelcolor;
85 action->score = score;
89 void filteringaction_free(FilteringAction * action)
91 g_return_if_fail(action);
92 g_free(action->header);
93 g_free(action->destination);
97 FilteringProp * filteringprop_new(gboolean enabled,
100 MatcherList * matchers,
101 GSList * action_list)
103 FilteringProp * filtering;
105 filtering = g_new0(FilteringProp, 1);
106 filtering->enabled = enabled;
107 filtering->name = name ? g_strdup(name): NULL;
108 filtering->account_id = account_id;
109 filtering->matchers = matchers;
110 filtering->action_list = action_list;
115 static FilteringAction * filteringaction_copy(FilteringAction * src)
117 FilteringAction * new;
119 new = g_new0(FilteringAction, 1);
121 new->type = src->type;
122 new->account_id = src->account_id;
123 if (src->destination)
124 new->destination = g_strdup(src->destination);
126 new->destination = NULL;
127 new->labelcolor = src->labelcolor;
128 new->score = src->score;
133 FilteringProp * filteringprop_copy(FilteringProp *src)
138 new = g_new0(FilteringProp, 1);
139 new->matchers = g_new0(MatcherList, 1);
141 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
142 MatcherProp *matcher = (MatcherProp *)tmp->data;
144 new->matchers->matchers = g_slist_append(new->matchers->matchers,
145 matcherprop_copy(matcher));
149 new->matchers->bool_and = src->matchers->bool_and;
151 new->action_list = NULL;
153 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
154 FilteringAction *filtering_action;
156 filtering_action = tmp->data;
158 new->action_list = g_slist_append(new->action_list,
159 filteringaction_copy(filtering_action));
162 new->enabled = src->enabled;
163 new->name = g_strdup(src->name);
168 void filteringprop_free(FilteringProp * prop)
172 g_return_if_fail(prop);
173 matcherlist_free(prop->matchers);
175 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
176 filteringaction_free(tmp->data);
182 void filtering_move_and_copy_msg(MsgInfo *msginfo)
184 GSList *list = g_slist_append(NULL, msginfo);
185 filtering_move_and_copy_msgs(list);
189 /* move and copy messages by batches to be faster on IMAP */
190 void filtering_move_and_copy_msgs(GSList *msgs)
192 GSList *messages = g_slist_copy(msgs);
193 FolderItem *last_item = NULL;
194 gboolean is_copy = FALSE, is_move = FALSE;
195 debug_print("checking %d messages\n", g_slist_length(msgs));
197 GSList *batch = NULL, *cur;
199 for (cur = messages; cur; cur = cur->next) {
200 MsgInfo *info = (MsgInfo *)cur->data;
201 if (last_item == NULL) {
202 last_item = info->to_filter_folder;
204 if (last_item == NULL)
206 if (!is_copy && !is_move) {
209 else if (info->is_move)
213 if (info->to_filter_folder == last_item
214 && info->is_copy == is_copy
215 && info->is_move == is_move) {
216 batch = g_slist_prepend(batch, info);
220 debug_print("no more messages to move/copy\n");
223 debug_print("%d messages to %s in %s\n", found,
224 is_copy ? "copy":"move", last_item->name ? last_item->name:"(noname)");
226 for (cur = batch; cur; cur = cur->next) {
227 MsgInfo *info = (MsgInfo *)cur->data;
228 messages = g_slist_remove(messages, info);
230 batch = g_slist_reverse(batch);
231 if (g_slist_length(batch)) {
232 MsgInfo *info = (MsgInfo *)batch->data;
233 if (is_copy && last_item != info->folder) {
234 folder_item_copy_msgs(last_item, batch);
235 } else if (is_move && last_item != info->folder) {
236 if (folder_item_move_msgs(last_item, batch) < 0)
237 folder_item_move_msgs(
238 folder_get_default_inbox(),
241 /* we don't reference the msginfos, because caller will do */
242 if (prefs_common.real_time_sync)
243 folder_item_synchronise(last_item);
251 /* we don't reference the msginfos, because caller will do */
252 g_slist_free(messages);
256 fitleringaction_apply
257 runs the action on one MsgInfo
258 return value : return TRUE if the action could be applied
261 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
263 FolderItem * dest_folder;
266 PrefsAccount * account;
269 switch(action->type) {
270 case MATCHACTION_MOVE:
272 folder_find_item_from_identifier(action->destination);
274 debug_print("*** folder not found '%s'\n",
275 action->destination ?action->destination :"");
279 /* check if mail is set to copy already,
280 * in which case we have to do it */
281 if (info->is_copy && info->to_filter_folder) {
282 debug_print("should cp and mv !\n");
283 folder_item_copy_msg(info->to_filter_folder, info);
284 info->is_copy = FALSE;
286 /* mark message to be moved */
287 info->is_move = TRUE;
288 info->to_filter_folder = dest_folder;
291 case MATCHACTION_COPY:
293 folder_find_item_from_identifier(action->destination);
296 debug_print("*** folder not found '%s'\n",
297 action->destination ?action->destination :"");
301 /* check if mail is set to copy already,
302 * in which case we have to do it */
303 if (info->is_copy && info->to_filter_folder) {
304 debug_print("should cp and mv !\n");
305 folder_item_copy_msg(info->to_filter_folder, info);
306 info->is_copy = FALSE;
308 /* mark message to be copied */
309 info->is_copy = TRUE;
310 info->to_filter_folder = dest_folder;
313 case MATCHACTION_DELETE:
314 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
318 case MATCHACTION_MARK:
319 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
322 case MATCHACTION_UNMARK:
323 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
326 case MATCHACTION_LOCK:
327 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
330 case MATCHACTION_UNLOCK:
331 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
334 case MATCHACTION_MARK_AS_READ:
335 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
338 case MATCHACTION_MARK_AS_UNREAD:
339 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
342 case MATCHACTION_MARK_AS_SPAM:
343 procmsg_spam_learner_learn(info, NULL, TRUE);
344 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
345 if (procmsg_spam_get_folder(info)) {
346 info->is_move = TRUE;
347 info->to_filter_folder = procmsg_spam_get_folder(info);
351 case MATCHACTION_MARK_AS_HAM:
352 procmsg_spam_learner_learn(info, NULL, FALSE);
353 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
356 case MATCHACTION_COLOR:
357 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
358 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
361 case MATCHACTION_FORWARD:
362 case MATCHACTION_FORWARD_AS_ATTACHMENT:
363 account = account_find_from_id(action->account_id);
364 compose = compose_forward(account, info,
365 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
367 compose_entry_append(compose, action->destination,
368 compose->account->protocol == A_NNTP
372 val = compose_send(compose);
374 return val == 0 ? TRUE : FALSE;
376 case MATCHACTION_REDIRECT:
377 account = account_find_from_id(action->account_id);
378 compose = compose_redirect(account, info, TRUE);
379 if (compose->account->protocol == A_NNTP)
382 compose_entry_append(compose, action->destination,
385 val = compose_send(compose);
387 return val == 0 ? TRUE : FALSE;
389 case MATCHACTION_EXECUTE:
390 cmd = matching_build_command(action->destination, info);
399 case MATCHACTION_SET_SCORE:
400 info->score = action->score;
403 case MATCHACTION_CHANGE_SCORE:
404 info->score += action->score;
407 case MATCHACTION_STOP:
410 case MATCHACTION_HIDE:
414 case MATCHACTION_IGNORE:
415 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
418 case MATCHACTION_ADD_TO_ADDRESSBOOK:
420 AddressDataSource *book = NULL;
421 AddressBookFile *abf = NULL;
422 ItemFolder *folder = NULL;
427 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
428 g_warning("addressbook folder not found '%s'\n", action->destination);
432 g_warning("addressbook_peek_folder_exists returned NULL book\n");
436 abf = book->rawDataSource;
439 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
440 header = procheader_parse_header(buf);
442 /* add all addresses that are not already in */
443 if (header && *header->body && (*header->body != '\0')) {
444 GSList *address_list = NULL;
448 if (action->destination == NULL ||
449 strcasecmp(action->destination, _("Any")) == 0 ||
450 *(action->destination) == '\0')
453 path = action->destination;
454 start_address_completion(path);
456 address_list = address_list_append(address_list, header->body);
457 for (walk = address_list; walk != NULL; walk = walk->next) {
458 gchar *stripped_addr = g_strdup(walk->data);
459 extract_address(stripped_addr);
461 if (complete_matches_found(walk->data) == 0) {
462 debug_print("adding address '%s' to addressbook '%s'\n",
463 stripped_addr, action->destination);
464 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
465 g_warning("contact could not been added\n");
469 debug_print("address '%s' already found in addressbook '%s', skipping\n",
470 stripped_addr, action->destination);
472 g_free(stripped_addr);
475 g_slist_free(address_list);
476 end_address_completion();
478 g_warning("header '%s' not set or empty\n", action->header);
480 return (errors == 0);
489 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
492 g_return_val_if_fail(action_list, FALSE);
493 g_return_val_if_fail(info, FALSE);
494 for (p = action_list; p && p->data; p = g_slist_next(p)) {
495 FilteringAction *a = (FilteringAction *) p->data;
496 if (filteringaction_apply(a, info)) {
497 if (filtering_is_final_action(a))
506 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
507 PrefsAccount *ac_prefs)
509 /* this function returns true if a filtering rule applies regarding to its account
510 data and if it does, if the conditions list match.
512 per-account data of a filtering rule is either matched against current account
513 when filtering is done manually, or against the account currently used for
514 retrieving messages when it's an manual or automatic fetching of messages.
515 per-account data match doesn't apply to pre-/post-/folder-processing rules.
517 when filtering messages manually:
518 - either the filtering rule is not account-based and it will be processed
519 - or it's per-account and we check if we HAVE TO match it against the current
520 account (according to user-land settings, per-account rules might have to
521 be skipped, or only the rules that match the current account have to be
522 applied, or all rules will have to be applied regardless to if they are
523 account-based or not)
525 notes about debugging output in that function:
526 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
527 no debug output is done when filtering_debug_level is low
530 gboolean matches = FALSE;
532 if (ac_prefs != NULL) {
533 matches = ((filtering->account_id == 0)
534 || (filtering->account_id == ac_prefs->account_id));
537 if (debug_filtering_session) {
538 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
539 if (filtering->account_id == 0) {
540 log_status_ok(LOG_DEBUG_FILTERING,
541 _("rule is not account-based\n"));
543 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
544 log_status_ok(LOG_DEBUG_FILTERING,
545 _("rule is account-based [id=%d, name='%s'], "
546 "matching the account currently used to retrieve messages\n"),
547 ac_prefs->account_id, ac_prefs->account_name);
553 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
554 log_status_skip(LOG_DEBUG_FILTERING,
555 _("rule is account-based, "
556 "not matching the account currently used to retrieve messages\n"));
558 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
559 PrefsAccount *account = account_find_from_id(filtering->account_id);
561 log_status_skip(LOG_DEBUG_FILTERING,
562 _("rule is account-based [id=%d, name='%s'], "
563 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
564 filtering->account_id, account->account_name,
565 ac_prefs->account_id, ac_prefs->account_name);
571 switch (prefs_common.apply_per_account_filtering_rules) {
572 case FILTERING_ACCOUNT_RULES_FORCE:
573 /* apply filtering rules regardless to the account info */
577 if (debug_filtering_session) {
578 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
579 if (filtering->account_id == 0) {
580 log_status_ok(LOG_DEBUG_FILTERING,
581 _("rule is not account-based, "
582 "all rules are applied on user request anyway\n"));
584 PrefsAccount *account = account_find_from_id(filtering->account_id);
586 log_status_ok(LOG_DEBUG_FILTERING,
587 _("rule is account-based [id=%d, name='%s'], "
588 "but all rules are applied on user request\n"),
589 filtering->account_id, account->account_name);
594 case FILTERING_ACCOUNT_RULES_SKIP:
595 /* don't apply filtering rules that belong to an account */
596 matches = (filtering->account_id == 0);
599 if (debug_filtering_session) {
601 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
602 PrefsAccount *account = account_find_from_id(filtering->account_id);
604 log_status_skip(LOG_DEBUG_FILTERING,
605 _("rule is account-based [id=%d, name='%s'], "
606 "skipped on user request\n"),
607 filtering->account_id, account->account_name);
609 log_status_skip(LOG_DEBUG_FILTERING,
610 _("rule is account-based, "
611 "skipped on user request\n"));
614 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
615 log_status_ok(LOG_DEBUG_FILTERING,
616 _("rule is not account-based\n"));
621 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
622 matches = ((filtering->account_id == 0)
623 || (filtering->account_id == cur_account->account_id));
626 if (debug_filtering_session) {
628 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
629 PrefsAccount *account = account_find_from_id(filtering->account_id);
631 log_status_skip(LOG_DEBUG_FILTERING,
632 _("rule is account-based [id=%d, name='%s'], "
633 "not matching current account [id=%d, name='%s']\n"),
634 filtering->account_id, account->account_name,
635 cur_account->account_id, cur_account->account_name);
637 log_status_skip(LOG_DEBUG_FILTERING,
638 _("rule is account-based, "
639 "not matching current account\n"));
642 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
643 if (filtering->account_id == 0) {
644 log_status_ok(LOG_DEBUG_FILTERING,
645 _("rule is not account-based\n"));
647 PrefsAccount *account = account_find_from_id(filtering->account_id);
649 log_status_ok(LOG_DEBUG_FILTERING,
650 _("rule is account-based [id=%d, name='%s'], "
651 "current account [id=%d, name='%s']\n"),
652 account->account_id, account->account_name,
653 cur_account->account_id, cur_account->account_name);
662 return matches && matcherlist_match(filtering->matchers, info);
666 *\brief Apply a rule on message.
668 *\param filtering List of filtering rules.
669 *\param info Message to apply rules on.
670 *\param final Variable returning TRUE or FALSE if one of the
671 * encountered actions was final.
672 * See also \ref filtering_is_final_action.
674 *\return gboolean TRUE to continue applying rules.
676 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
679 gboolean result = TRUE;
684 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
685 FilteringAction * action;
688 filteringaction_to_string(buf, sizeof buf, action);
689 if (debug_filtering_session)
690 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
692 if (FALSE == (result = filteringaction_apply(action, info))) {
693 if (debug_filtering_session) {
694 if (action->type != MATCHACTION_STOP)
695 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
696 log_print(LOG_DEBUG_FILTERING,
697 _("no further processing after action [ %s ]\n"), buf);
699 g_warning("No further processing after rule %s\n", buf);
702 if (filtering_is_final_action(action)) {
711 *\brief Check if an action is "final", i.e. should break further
714 *\param filtering_action Action to check.
716 *\return gboolean TRUE if \a filtering_action is final.
718 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
720 switch(filtering_action->type) {
721 case MATCHACTION_MOVE:
722 case MATCHACTION_DELETE:
723 case MATCHACTION_STOP:
724 case MATCHACTION_MARK_AS_SPAM:
725 return TRUE; /* MsgInfo invalid for message */
731 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
737 g_return_val_if_fail(info != NULL, TRUE);
739 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
740 FilteringProp * filtering = (FilteringProp *) l->data;
742 if (filtering->enabled) {
743 if (debug_filtering_session) {
744 gchar *buf = filteringprop_to_string(filtering);
745 if (filtering->name && *filtering->name != '\0') {
746 log_print(LOG_DEBUG_FILTERING,
747 _("processing rule '%s' [ %s ]\n"),
748 filtering->name, buf);
750 log_print(LOG_DEBUG_FILTERING,
751 _("processing rule <unnamed> [ %s ]\n"),
756 if (filtering_match_condition(filtering, info, ac_prefs)) {
757 apply_next = filtering_apply_rule(filtering, info, &final);
763 if (debug_filtering_session) {
764 gchar *buf = filteringprop_to_string(filtering);
765 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
766 if (filtering->name && *filtering->name != '\0') {
767 log_status_skip(LOG_DEBUG_FILTERING,
768 _("disabled rule '%s' [ %s ]\n"),
769 filtering->name, buf);
771 log_status_skip(LOG_DEBUG_FILTERING,
772 _("disabled rule <unnamed> [ %s ]\n"),
781 /* put in inbox if a final rule could not be applied, or
782 * the last rule was not a final one. */
783 if ((final && !apply_next) || !final) {
791 *\brief Filter a message against a list of rules.
793 *\param flist List of filter rules.
794 *\param info Message.
796 *\return gboolean TRUE if filter rules handled the message.
798 *\note Returning FALSE means the message was not handled,
799 * and that the calling code should do the default
800 * processing. E.g. \ref inc.c::inc_start moves the
801 * message to the inbox.
803 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
804 FilteringInvocationType context, gchar *extra_info)
806 if (prefs_common.enable_filtering_debug) {
807 gchar *tmp = _("undetermined");
810 case FILTERING_INCORPORATION:
811 tmp = _("incorporation");
812 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
814 case FILTERING_MANUALLY:
816 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
818 case FILTERING_FOLDER_PROCESSING:
819 tmp = _("folder processing");
820 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
822 case FILTERING_PRE_PROCESSING:
823 tmp = _("pre-processing");
824 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
826 case FILTERING_POST_PROCESSING:
827 tmp = _("post-processing");
828 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
831 debug_filtering_session = FALSE;
834 if (debug_filtering_session) {
835 gchar *file = procmsg_get_message_file_path(info);
836 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
838 /* show context info and essential info about the message */
839 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
840 log_print(LOG_DEBUG_FILTERING,
841 _("filtering message (%s%s%s)\n"
842 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
843 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
844 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
845 spc, prefs_common_translated_header_name("From:"), info->from,
846 spc, prefs_common_translated_header_name("To:"), info->to,
847 spc, prefs_common_translated_header_name("Subject:"), info->subject);
849 log_print(LOG_DEBUG_FILTERING,
850 _("filtering message (%s%s%s)\n"
851 "%smessage file: %s\n"),
852 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
859 debug_filtering_session = FALSE;
860 return filter_msginfo(flist, info, ac_prefs);
863 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
865 const gchar *command_str;
867 gchar * quoted_header;
869 command_str = get_matchparser_tab_str(action->type);
871 if (command_str == NULL)
874 switch(action->type) {
875 case MATCHACTION_MOVE:
876 case MATCHACTION_COPY:
877 case MATCHACTION_EXECUTE:
878 quoted_dest = matcher_quote_str(action->destination);
879 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
883 case MATCHACTION_DELETE:
884 case MATCHACTION_MARK:
885 case MATCHACTION_UNMARK:
886 case MATCHACTION_LOCK:
887 case MATCHACTION_UNLOCK:
888 case MATCHACTION_MARK_AS_READ:
889 case MATCHACTION_MARK_AS_UNREAD:
890 case MATCHACTION_MARK_AS_SPAM:
891 case MATCHACTION_MARK_AS_HAM:
892 case MATCHACTION_STOP:
893 case MATCHACTION_HIDE:
894 case MATCHACTION_IGNORE:
895 g_snprintf(dest, destlen, "%s", command_str);
898 case MATCHACTION_REDIRECT:
899 case MATCHACTION_FORWARD:
900 case MATCHACTION_FORWARD_AS_ATTACHMENT:
901 quoted_dest = matcher_quote_str(action->destination);
902 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
906 case MATCHACTION_COLOR:
907 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
910 case MATCHACTION_CHANGE_SCORE:
911 case MATCHACTION_SET_SCORE:
912 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
915 case MATCHACTION_ADD_TO_ADDRESSBOOK:
916 quoted_header = matcher_quote_str(action->header);
917 quoted_dest = matcher_quote_str(action->destination);
918 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
920 g_free(quoted_header);
928 gchar * filteringaction_list_to_string(GSList * action_list)
930 gchar *action_list_str;
935 action_list_str = NULL;
936 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
938 FilteringAction * action;
942 action_str = filteringaction_to_string(buf,
945 if (action_list_str != NULL) {
946 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
947 g_free(action_list_str);
950 list_str = g_strdup(action_str);
952 action_list_str = list_str;
955 return action_list_str;
958 gchar * filteringprop_to_string(FilteringProp * prop)
961 gchar *action_list_str;
962 gchar *filtering_str;
964 action_list_str = filteringaction_list_to_string(prop->action_list);
966 if (action_list_str == NULL)
969 list_str = matcherlist_to_string(prop->matchers);
971 if (list_str == NULL) {
972 g_free(action_list_str);
976 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
977 g_free(action_list_str);
980 return filtering_str;
983 void prefs_filtering_free(GSList * prefs_filtering)
985 while (prefs_filtering != NULL) {
986 FilteringProp * filtering = (FilteringProp *)
987 prefs_filtering->data;
988 filteringprop_free(filtering);
989 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
993 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
995 FolderItem *item = node->data;
997 g_return_val_if_fail(item, FALSE);
998 g_return_val_if_fail(item->prefs, FALSE);
1000 prefs_filtering_free(item->prefs->processing);
1001 item->prefs->processing = NULL;
1006 void prefs_filtering_clear(void)
1010 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1013 folder = (Folder *) cur->data;
1014 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1015 prefs_filtering_free_func, NULL);
1018 prefs_filtering_free(filtering_rules);
1019 filtering_rules = NULL;
1020 prefs_filtering_free(pre_global_processing);
1021 pre_global_processing = NULL;
1022 prefs_filtering_free(post_global_processing);
1023 post_global_processing = NULL;
1026 void prefs_filtering_clear_folder(Folder *folder)
1028 g_return_if_fail(folder);
1029 g_return_if_fail(folder->node);
1031 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1032 prefs_filtering_free_func, NULL);
1033 /* FIXME: Note folder settings were changed, where the updates? */
1036 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1037 /* return TRUE if there's at least one per-account filtering rule */
1041 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1042 FilteringProp * filtering = (FilteringProp *) l->data;
1044 if (filtering->enabled && (filtering->account_id != 0)) {