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 3 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, see <http://www.gnu.org/licenses/>.
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"
42 #define PREFSBUFSIZE 1024
44 GSList * pre_global_processing = NULL;
45 GSList * post_global_processing = NULL;
46 GSList * filtering_rules = NULL;
48 gboolean debug_filtering_session = FALSE;
50 static gboolean filtering_is_final_action(FilteringAction *filtering_action);
52 #define STRLEN_WITH_CHECK(expr) \
53 strlen_with_check(#expr, __LINE__, expr)
55 static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
60 debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
65 FilteringAction * filteringaction_new(int type, int account_id,
67 gint labelcolor, gint score, gchar * header)
69 FilteringAction * action;
71 action = g_new0(FilteringAction, 1);
74 action->account_id = account_id;
76 action->destination = g_strdup(destination);
78 action->destination = NULL;
81 action->header = g_strdup(header);
83 action->header = NULL;
85 action->labelcolor = labelcolor;
86 action->score = score;
90 void filteringaction_free(FilteringAction * action)
92 g_return_if_fail(action);
93 g_free(action->header);
94 g_free(action->destination);
98 FilteringProp * filteringprop_new(gboolean enabled,
101 MatcherList * matchers,
102 GSList * action_list)
104 FilteringProp * filtering;
106 filtering = g_new0(FilteringProp, 1);
107 filtering->enabled = enabled;
108 filtering->name = name ? g_strdup(name): NULL;
109 filtering->account_id = account_id;
110 filtering->matchers = matchers;
111 filtering->action_list = action_list;
116 static FilteringAction * filteringaction_copy(FilteringAction * src)
118 FilteringAction * new;
120 new = g_new0(FilteringAction, 1);
122 new->type = src->type;
123 new->account_id = src->account_id;
124 if (src->destination)
125 new->destination = g_strdup(src->destination);
127 new->destination = NULL;
128 new->labelcolor = src->labelcolor;
129 new->score = src->score;
134 FilteringProp * filteringprop_copy(FilteringProp *src)
139 new = g_new0(FilteringProp, 1);
140 new->matchers = g_new0(MatcherList, 1);
142 for (tmp = src->matchers->matchers; tmp != NULL && tmp->data != NULL;) {
143 MatcherProp *matcher = (MatcherProp *)tmp->data;
145 new->matchers->matchers = g_slist_append(new->matchers->matchers,
146 matcherprop_copy(matcher));
150 new->matchers->bool_and = src->matchers->bool_and;
152 new->action_list = NULL;
154 for (tmp = src->action_list ; tmp != NULL ; tmp = tmp->next) {
155 FilteringAction *filtering_action;
157 filtering_action = tmp->data;
159 new->action_list = g_slist_append(new->action_list,
160 filteringaction_copy(filtering_action));
163 new->enabled = src->enabled;
164 new->name = g_strdup(src->name);
169 void filteringprop_free(FilteringProp * prop)
173 g_return_if_fail(prop);
174 matcherlist_free(prop->matchers);
176 for (tmp = prop->action_list ; tmp != NULL ; tmp = tmp->next) {
177 filteringaction_free(tmp->data);
183 void filtering_move_and_copy_msg(MsgInfo *msginfo)
185 GSList *list = g_slist_append(NULL, msginfo);
186 filtering_move_and_copy_msgs(list);
190 /* move and copy messages by batches to be faster on IMAP */
191 void filtering_move_and_copy_msgs(GSList *msgs)
193 GSList *messages = g_slist_copy(msgs);
194 FolderItem *last_item = NULL;
195 gboolean is_copy = FALSE, is_move = FALSE;
196 debug_print("checking %d messages\n", g_slist_length(msgs));
198 GSList *batch = NULL, *cur;
200 for (cur = messages; cur; cur = cur->next) {
201 MsgInfo *info = (MsgInfo *)cur->data;
202 if (last_item == NULL) {
203 last_item = info->to_filter_folder;
205 if (last_item == NULL)
207 if (!is_copy && !is_move) {
210 else if (info->is_move)
214 if (info->to_filter_folder == last_item
215 && info->is_copy == is_copy
216 && info->is_move == is_move) {
217 batch = g_slist_prepend(batch, info);
221 debug_print("no more messages to move/copy\n");
224 debug_print("%d messages to %s in %s\n", found,
225 is_copy ? "copy":"move", last_item->name ? last_item->name:"(noname)");
227 for (cur = batch; cur; cur = cur->next) {
228 MsgInfo *info = (MsgInfo *)cur->data;
229 messages = g_slist_remove(messages, info);
231 batch = g_slist_reverse(batch);
232 if (g_slist_length(batch)) {
233 MsgInfo *info = (MsgInfo *)batch->data;
234 if (is_copy && last_item != info->folder) {
235 folder_item_copy_msgs(last_item, batch);
236 } else if (is_move && last_item != info->folder) {
237 if (folder_item_move_msgs(last_item, batch) < 0)
238 folder_item_move_msgs(
239 folder_get_default_inbox(),
242 /* we don't reference the msginfos, because caller will do */
243 if (prefs_common.real_time_sync)
244 folder_item_synchronise(last_item);
253 /* we don't reference the msginfos, because caller will do */
254 g_slist_free(messages);
258 fitleringaction_apply
259 runs the action on one MsgInfo
260 return value : return TRUE if the action could be applied
263 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
265 FolderItem * dest_folder;
268 PrefsAccount * account;
271 switch(action->type) {
272 case MATCHACTION_MOVE:
274 folder_find_item_from_identifier(action->destination);
276 debug_print("*** folder not found '%s'\n",
277 action->destination ?action->destination :"");
281 /* check if mail is set to copy already,
282 * in which case we have to do it */
283 if (info->is_copy && info->to_filter_folder) {
284 debug_print("should cp and mv !\n");
285 folder_item_copy_msg(info->to_filter_folder, info);
286 info->is_copy = FALSE;
288 /* mark message to be moved */
289 info->is_move = TRUE;
290 info->to_filter_folder = dest_folder;
293 case MATCHACTION_COPY:
295 folder_find_item_from_identifier(action->destination);
298 debug_print("*** folder not found '%s'\n",
299 action->destination ?action->destination :"");
303 /* check if mail is set to copy already,
304 * in which case we have to do it */
305 if (info->is_copy && info->to_filter_folder) {
306 debug_print("should cp and mv !\n");
307 folder_item_copy_msg(info->to_filter_folder, info);
308 info->is_copy = FALSE;
310 /* mark message to be copied */
311 info->is_copy = TRUE;
312 info->to_filter_folder = dest_folder;
315 case MATCHACTION_SET_TAG:
316 case MATCHACTION_UNSET_TAG:
317 val = tags_get_id_for_str(action->destination);
319 debug_print("*** tag '%s' not found\n",
320 action->destination ?action->destination :"");
324 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
327 case MATCHACTION_CLEAR_TAGS:
328 procmsg_msginfo_clear_tags(info);
331 case MATCHACTION_DELETE:
332 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
336 case MATCHACTION_MARK:
337 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
340 case MATCHACTION_UNMARK:
341 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
344 case MATCHACTION_LOCK:
345 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
348 case MATCHACTION_UNLOCK:
349 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
352 case MATCHACTION_MARK_AS_READ:
353 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
356 case MATCHACTION_MARK_AS_UNREAD:
357 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
360 case MATCHACTION_MARK_AS_SPAM:
361 procmsg_spam_learner_learn(info, NULL, TRUE);
362 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
363 if (procmsg_spam_get_folder(info)) {
364 info->is_move = TRUE;
365 info->to_filter_folder = procmsg_spam_get_folder(info);
369 case MATCHACTION_MARK_AS_HAM:
370 procmsg_spam_learner_learn(info, NULL, FALSE);
371 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
374 case MATCHACTION_COLOR:
375 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
376 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
379 case MATCHACTION_FORWARD:
380 case MATCHACTION_FORWARD_AS_ATTACHMENT:
381 account = account_find_from_id(action->account_id);
382 compose = compose_forward(account, info,
383 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
385 compose_entry_append(compose, action->destination,
386 compose->account->protocol == A_NNTP
390 val = compose_send(compose);
392 return val == 0 ? TRUE : FALSE;
394 case MATCHACTION_REDIRECT:
395 account = account_find_from_id(action->account_id);
396 compose = compose_redirect(account, info, TRUE);
397 if (compose->account->protocol == A_NNTP)
400 compose_entry_append(compose, action->destination,
403 val = compose_send(compose);
405 return val == 0 ? TRUE : FALSE;
407 case MATCHACTION_EXECUTE:
408 cmd = matching_build_command(action->destination, info);
417 case MATCHACTION_SET_SCORE:
418 info->score = action->score;
421 case MATCHACTION_CHANGE_SCORE:
422 info->score += action->score;
425 case MATCHACTION_STOP:
428 case MATCHACTION_HIDE:
432 case MATCHACTION_IGNORE:
433 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
436 case MATCHACTION_WATCH:
437 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
440 case MATCHACTION_ADD_TO_ADDRESSBOOK:
442 AddressDataSource *book = NULL;
443 AddressBookFile *abf = NULL;
444 ItemFolder *folder = NULL;
449 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
450 g_warning("addressbook folder not found '%s'\n", action->destination);
454 g_warning("addressbook_peek_folder_exists returned NULL book\n");
458 abf = book->rawDataSource;
461 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
462 header = procheader_parse_header(buf);
464 /* add all addresses that are not already in */
465 if (header && *header->body && (*header->body != '\0')) {
466 GSList *address_list = NULL;
470 if (action->destination == NULL ||
471 strcasecmp(action->destination, _("Any")) == 0 ||
472 *(action->destination) == '\0')
475 path = action->destination;
476 start_address_completion(path);
478 address_list = address_list_append(address_list, header->body);
479 for (walk = address_list; walk != NULL; walk = walk->next) {
480 gchar *stripped_addr = g_strdup(walk->data);
481 extract_address(stripped_addr);
483 if (complete_matches_found(walk->data) == 0) {
484 debug_print("adding address '%s' to addressbook '%s'\n",
485 stripped_addr, action->destination);
486 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
487 g_warning("contact could not been added\n");
491 debug_print("address '%s' already found in addressbook '%s', skipping\n",
492 stripped_addr, action->destination);
494 g_free(stripped_addr);
497 g_slist_free(address_list);
498 end_address_completion();
500 g_warning("header '%s' not set or empty\n", action->header);
502 return (errors == 0);
511 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
514 g_return_val_if_fail(action_list, FALSE);
515 g_return_val_if_fail(info, FALSE);
516 for (p = action_list; p && p->data; p = g_slist_next(p)) {
517 FilteringAction *a = (FilteringAction *) p->data;
518 if (filteringaction_apply(a, info)) {
519 if (filtering_is_final_action(a))
528 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
529 PrefsAccount *ac_prefs)
531 /* this function returns true if a filtering rule applies regarding to its account
532 data and if it does, if the conditions list match.
534 per-account data of a filtering rule is either matched against current account
535 when filtering is done manually, or against the account currently used for
536 retrieving messages when it's an manual or automatic fetching of messages.
537 per-account data match doesn't apply to pre-/post-/folder-processing rules.
539 when filtering messages manually:
540 - either the filtering rule is not account-based and it will be processed
541 - or it's per-account and we check if we HAVE TO match it against the current
542 account (according to user-land settings, per-account rules might have to
543 be skipped, or only the rules that match the current account have to be
544 applied, or all rules will have to be applied regardless to if they are
545 account-based or not)
547 notes about debugging output in that function:
548 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
549 no debug output is done when filtering_debug_level is low
552 gboolean matches = FALSE;
554 if (ac_prefs != NULL) {
555 matches = ((filtering->account_id == 0)
556 || (filtering->account_id == ac_prefs->account_id));
559 if (debug_filtering_session) {
560 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
561 if (filtering->account_id == 0) {
562 log_status_ok(LOG_DEBUG_FILTERING,
563 _("rule is not account-based\n"));
565 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
566 log_status_ok(LOG_DEBUG_FILTERING,
567 _("rule is account-based [id=%d, name='%s'], "
568 "matching the account currently used to retrieve messages\n"),
569 ac_prefs->account_id, ac_prefs->account_name);
575 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
576 log_status_skip(LOG_DEBUG_FILTERING,
577 _("rule is account-based, "
578 "not matching the account currently used to retrieve messages\n"));
580 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
581 PrefsAccount *account = account_find_from_id(filtering->account_id);
583 log_status_skip(LOG_DEBUG_FILTERING,
584 _("rule is account-based [id=%d, name='%s'], "
585 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
586 filtering->account_id, account->account_name,
587 ac_prefs->account_id, ac_prefs->account_name);
593 switch (prefs_common.apply_per_account_filtering_rules) {
594 case FILTERING_ACCOUNT_RULES_FORCE:
595 /* apply filtering rules regardless to the account info */
599 if (debug_filtering_session) {
600 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
601 if (filtering->account_id == 0) {
602 log_status_ok(LOG_DEBUG_FILTERING,
603 _("rule is not account-based, "
604 "all rules are applied on user request anyway\n"));
606 PrefsAccount *account = account_find_from_id(filtering->account_id);
608 log_status_ok(LOG_DEBUG_FILTERING,
609 _("rule is account-based [id=%d, name='%s'], "
610 "but all rules are applied on user request\n"),
611 filtering->account_id, account->account_name);
616 case FILTERING_ACCOUNT_RULES_SKIP:
617 /* don't apply filtering rules that belong to an account */
618 matches = (filtering->account_id == 0);
621 if (debug_filtering_session) {
623 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
624 PrefsAccount *account = account_find_from_id(filtering->account_id);
626 log_status_skip(LOG_DEBUG_FILTERING,
627 _("rule is account-based [id=%d, name='%s'], "
628 "skipped on user request\n"),
629 filtering->account_id, account->account_name);
631 log_status_skip(LOG_DEBUG_FILTERING,
632 _("rule is account-based, "
633 "skipped on user request\n"));
636 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
637 log_status_ok(LOG_DEBUG_FILTERING,
638 _("rule is not account-based\n"));
643 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
644 matches = ((filtering->account_id == 0)
645 || (filtering->account_id == cur_account->account_id));
648 if (debug_filtering_session) {
650 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
651 PrefsAccount *account = account_find_from_id(filtering->account_id);
653 log_status_skip(LOG_DEBUG_FILTERING,
654 _("rule is account-based [id=%d, name='%s'], "
655 "not matching current account [id=%d, name='%s']\n"),
656 filtering->account_id, account->account_name,
657 cur_account->account_id, cur_account->account_name);
659 log_status_skip(LOG_DEBUG_FILTERING,
660 _("rule is account-based, "
661 "not matching current account\n"));
664 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
665 if (filtering->account_id == 0) {
666 log_status_ok(LOG_DEBUG_FILTERING,
667 _("rule is not account-based\n"));
669 PrefsAccount *account = account_find_from_id(filtering->account_id);
671 log_status_ok(LOG_DEBUG_FILTERING,
672 _("rule is account-based [id=%d, name='%s'], "
673 "current account [id=%d, name='%s']\n"),
674 account->account_id, account->account_name,
675 cur_account->account_id, cur_account->account_name);
684 return matches && matcherlist_match(filtering->matchers, info);
688 *\brief Apply a rule on message.
690 *\param filtering List of filtering rules.
691 *\param info Message to apply rules on.
692 *\param final Variable returning TRUE or FALSE if one of the
693 * encountered actions was final.
694 * See also \ref filtering_is_final_action.
696 *\return gboolean TRUE to continue applying rules.
698 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
701 gboolean result = TRUE;
706 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
707 FilteringAction * action;
710 filteringaction_to_string(buf, sizeof buf, action);
711 if (debug_filtering_session)
712 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
714 if (FALSE == (result = filteringaction_apply(action, info))) {
715 if (debug_filtering_session) {
716 if (action->type != MATCHACTION_STOP)
717 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
718 log_print(LOG_DEBUG_FILTERING,
719 _("no further processing after action [ %s ]\n"), buf);
721 g_warning("No further processing after rule %s\n", buf);
724 if (filtering_is_final_action(action)) {
733 *\brief Check if an action is "final", i.e. should break further
736 *\param filtering_action Action to check.
738 *\return gboolean TRUE if \a filtering_action is final.
740 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
742 switch(filtering_action->type) {
743 case MATCHACTION_MOVE:
744 case MATCHACTION_DELETE:
745 case MATCHACTION_STOP:
746 case MATCHACTION_MARK_AS_SPAM:
747 return TRUE; /* MsgInfo invalid for message */
753 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
759 g_return_val_if_fail(info != NULL, TRUE);
761 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
762 FilteringProp * filtering = (FilteringProp *) l->data;
764 if (filtering->enabled) {
765 if (debug_filtering_session) {
766 gchar *buf = filteringprop_to_string(filtering);
767 if (filtering->name && *filtering->name != '\0') {
768 log_print(LOG_DEBUG_FILTERING,
769 _("processing rule '%s' [ %s ]\n"),
770 filtering->name, buf);
772 log_print(LOG_DEBUG_FILTERING,
773 _("processing rule <unnamed> [ %s ]\n"),
778 if (filtering_match_condition(filtering, info, ac_prefs)) {
779 apply_next = filtering_apply_rule(filtering, info, &final);
785 if (debug_filtering_session) {
786 gchar *buf = filteringprop_to_string(filtering);
787 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
788 if (filtering->name && *filtering->name != '\0') {
789 log_status_skip(LOG_DEBUG_FILTERING,
790 _("disabled rule '%s' [ %s ]\n"),
791 filtering->name, buf);
793 log_status_skip(LOG_DEBUG_FILTERING,
794 _("disabled rule <unnamed> [ %s ]\n"),
803 /* put in inbox if a final rule could not be applied, or
804 * the last rule was not a final one. */
805 if ((final && !apply_next) || !final) {
813 *\brief Filter a message against a list of rules.
815 *\param flist List of filter rules.
816 *\param info Message.
818 *\return gboolean TRUE if filter rules handled the message.
820 *\note Returning FALSE means the message was not handled,
821 * and that the calling code should do the default
822 * processing. E.g. \ref inc.c::inc_start moves the
823 * message to the inbox.
825 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
826 FilteringInvocationType context, gchar *extra_info)
828 if (prefs_common.enable_filtering_debug) {
829 gchar *tmp = _("undetermined");
832 case FILTERING_INCORPORATION:
833 tmp = _("incorporation");
834 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
836 case FILTERING_MANUALLY:
838 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
840 case FILTERING_FOLDER_PROCESSING:
841 tmp = _("folder processing");
842 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
844 case FILTERING_PRE_PROCESSING:
845 tmp = _("pre-processing");
846 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
848 case FILTERING_POST_PROCESSING:
849 tmp = _("post-processing");
850 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
853 debug_filtering_session = FALSE;
856 if (debug_filtering_session) {
857 gchar *file = procmsg_get_message_file_path(info);
858 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
860 /* show context info and essential info about the message */
861 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
862 log_print(LOG_DEBUG_FILTERING,
863 _("filtering message (%s%s%s)\n"
864 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
865 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
866 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
867 spc, prefs_common_translated_header_name("From:"), info->from,
868 spc, prefs_common_translated_header_name("To:"), info->to,
869 spc, prefs_common_translated_header_name("Subject:"), info->subject);
871 log_print(LOG_DEBUG_FILTERING,
872 _("filtering message (%s%s%s)\n"
873 "%smessage file: %s\n"),
874 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
881 debug_filtering_session = FALSE;
882 return filter_msginfo(flist, info, ac_prefs);
885 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
887 const gchar *command_str;
889 gchar * quoted_header;
891 command_str = get_matchparser_tab_str(action->type);
893 if (command_str == NULL)
896 switch(action->type) {
897 case MATCHACTION_MOVE:
898 case MATCHACTION_COPY:
899 case MATCHACTION_EXECUTE:
900 case MATCHACTION_SET_TAG:
901 case MATCHACTION_UNSET_TAG:
902 quoted_dest = matcher_quote_str(action->destination);
903 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
907 case MATCHACTION_DELETE:
908 case MATCHACTION_MARK:
909 case MATCHACTION_UNMARK:
910 case MATCHACTION_LOCK:
911 case MATCHACTION_UNLOCK:
912 case MATCHACTION_MARK_AS_READ:
913 case MATCHACTION_MARK_AS_UNREAD:
914 case MATCHACTION_MARK_AS_SPAM:
915 case MATCHACTION_MARK_AS_HAM:
916 case MATCHACTION_STOP:
917 case MATCHACTION_HIDE:
918 case MATCHACTION_IGNORE:
919 case MATCHACTION_WATCH:
920 case MATCHACTION_CLEAR_TAGS:
921 g_snprintf(dest, destlen, "%s", command_str);
924 case MATCHACTION_REDIRECT:
925 case MATCHACTION_FORWARD:
926 case MATCHACTION_FORWARD_AS_ATTACHMENT:
927 quoted_dest = matcher_quote_str(action->destination);
928 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
932 case MATCHACTION_COLOR:
933 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
936 case MATCHACTION_CHANGE_SCORE:
937 case MATCHACTION_SET_SCORE:
938 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
941 case MATCHACTION_ADD_TO_ADDRESSBOOK:
942 quoted_header = matcher_quote_str(action->header);
943 quoted_dest = matcher_quote_str(action->destination);
944 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
946 g_free(quoted_header);
954 gchar * filteringaction_list_to_string(GSList * action_list)
956 gchar *action_list_str;
961 action_list_str = NULL;
962 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
964 FilteringAction * action;
968 action_str = filteringaction_to_string(buf,
971 if (action_list_str != NULL) {
972 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
973 g_free(action_list_str);
976 list_str = g_strdup(action_str);
978 action_list_str = list_str;
981 return action_list_str;
984 gchar * filteringprop_to_string(FilteringProp * prop)
987 gchar *action_list_str;
988 gchar *filtering_str;
990 action_list_str = filteringaction_list_to_string(prop->action_list);
992 if (action_list_str == NULL)
995 list_str = matcherlist_to_string(prop->matchers);
997 if (list_str == NULL) {
998 g_free(action_list_str);
1002 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1003 g_free(action_list_str);
1006 return filtering_str;
1009 void prefs_filtering_free(GSList * prefs_filtering)
1011 while (prefs_filtering != NULL) {
1012 FilteringProp * filtering = (FilteringProp *)
1013 prefs_filtering->data;
1014 filteringprop_free(filtering);
1015 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1019 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1021 FolderItem *item = node->data;
1023 g_return_val_if_fail(item, FALSE);
1024 g_return_val_if_fail(item->prefs, FALSE);
1026 prefs_filtering_free(item->prefs->processing);
1027 item->prefs->processing = NULL;
1032 void prefs_filtering_clear(void)
1036 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1039 folder = (Folder *) cur->data;
1040 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1041 prefs_filtering_free_func, NULL);
1044 prefs_filtering_free(filtering_rules);
1045 filtering_rules = NULL;
1046 prefs_filtering_free(pre_global_processing);
1047 pre_global_processing = NULL;
1048 prefs_filtering_free(post_global_processing);
1049 post_global_processing = NULL;
1052 void prefs_filtering_clear_folder(Folder *folder)
1054 g_return_if_fail(folder);
1055 g_return_if_fail(folder->node);
1057 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1058 prefs_filtering_free_func, NULL);
1059 /* FIXME: Note folder settings were changed, where the updates? */
1062 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1063 /* return TRUE if there's at least one per-account filtering rule */
1067 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1068 FilteringProp * filtering = (FilteringProp *) l->data;
1070 if (filtering->enabled && (filtering->account_id != 0)) {