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 */
249 /* we don't reference the msginfos, because caller will do */
250 g_slist_free(messages);
254 fitleringaction_apply
255 runs the action on one MsgInfo
256 return value : return TRUE if the action could be applied
259 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
261 FolderItem * dest_folder;
264 PrefsAccount * account;
267 switch(action->type) {
268 case MATCHACTION_MOVE:
270 folder_find_item_from_identifier(action->destination);
272 debug_print("*** folder not found '%s'\n",
273 action->destination ?action->destination :"");
277 /* check if mail is set to copy already,
278 * in which case we have to do it */
279 if (info->is_copy && info->to_filter_folder) {
280 debug_print("should cp and mv !\n");
281 folder_item_copy_msg(info->to_filter_folder, info);
282 info->is_copy = FALSE;
284 /* mark message to be moved */
285 info->is_move = TRUE;
286 info->to_filter_folder = dest_folder;
289 case MATCHACTION_COPY:
291 folder_find_item_from_identifier(action->destination);
294 debug_print("*** folder not found '%s'\n",
295 action->destination ?action->destination :"");
299 /* check if mail is set to copy already,
300 * in which case we have to do it */
301 if (info->is_copy && info->to_filter_folder) {
302 debug_print("should cp and mv !\n");
303 folder_item_copy_msg(info->to_filter_folder, info);
304 info->is_copy = FALSE;
306 /* mark message to be copied */
307 info->is_copy = TRUE;
308 info->to_filter_folder = dest_folder;
311 case MATCHACTION_DELETE:
312 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
316 case MATCHACTION_MARK:
317 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
320 case MATCHACTION_UNMARK:
321 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
324 case MATCHACTION_LOCK:
325 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
328 case MATCHACTION_UNLOCK:
329 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
332 case MATCHACTION_MARK_AS_READ:
333 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
336 case MATCHACTION_MARK_AS_UNREAD:
337 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
340 case MATCHACTION_MARK_AS_SPAM:
341 procmsg_spam_learner_learn(info, NULL, TRUE);
342 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
343 if (procmsg_spam_get_folder(info)) {
344 info->is_move = TRUE;
345 info->to_filter_folder = procmsg_spam_get_folder(info);
349 case MATCHACTION_MARK_AS_HAM:
350 procmsg_spam_learner_learn(info, NULL, FALSE);
351 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
354 case MATCHACTION_COLOR:
355 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
356 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
359 case MATCHACTION_FORWARD:
360 case MATCHACTION_FORWARD_AS_ATTACHMENT:
361 account = account_find_from_id(action->account_id);
362 compose = compose_forward(account, info,
363 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
365 compose_entry_append(compose, action->destination,
366 compose->account->protocol == A_NNTP
370 val = compose_send(compose);
372 return val == 0 ? TRUE : FALSE;
374 case MATCHACTION_REDIRECT:
375 account = account_find_from_id(action->account_id);
376 compose = compose_redirect(account, info, TRUE);
377 if (compose->account->protocol == A_NNTP)
380 compose_entry_append(compose, action->destination,
383 val = compose_send(compose);
385 return val == 0 ? TRUE : FALSE;
387 case MATCHACTION_EXECUTE:
388 cmd = matching_build_command(action->destination, info);
397 case MATCHACTION_SET_SCORE:
398 info->score = action->score;
401 case MATCHACTION_CHANGE_SCORE:
402 info->score += action->score;
405 case MATCHACTION_STOP:
408 case MATCHACTION_HIDE:
412 case MATCHACTION_IGNORE:
413 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
416 case MATCHACTION_ADD_TO_ADDRESSBOOK:
418 AddressDataSource *book = NULL;
419 AddressBookFile *abf = NULL;
420 ItemFolder *folder = NULL;
425 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
426 g_warning("addressbook folder not found '%s'\n", action->destination);
430 g_warning("addressbook_peek_folder_exists returned NULL book\n");
434 abf = book->rawDataSource;
437 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
438 header = procheader_parse_header(buf);
440 /* add all addresses that are not already in */
441 if (header && *header->body && (*header->body != '\0')) {
442 GSList *address_list = NULL;
446 if (action->destination == NULL ||
447 strcasecmp(action->destination, _("Any")) == 0 ||
448 *(action->destination) == '\0')
451 path = action->destination;
452 start_address_completion(path);
454 address_list = address_list_append(address_list, header->body);
455 for (walk = address_list; walk != NULL; walk = walk->next) {
456 gchar *stripped_addr = g_strdup(walk->data);
457 extract_address(stripped_addr);
459 if (complete_matches_found(walk->data) == 0) {
460 debug_print("adding address '%s' to addressbook '%s'\n",
461 stripped_addr, action->destination);
462 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
463 g_warning("contact could not been added\n");
467 debug_print("address '%s' already found in addressbook '%s', skipping\n",
468 stripped_addr, action->destination);
470 g_free(stripped_addr);
473 g_slist_free(address_list);
474 end_address_completion();
476 g_warning("header '%s' not set or empty\n", action->header);
478 return (errors == 0);
487 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
490 g_return_val_if_fail(action_list, FALSE);
491 g_return_val_if_fail(info, FALSE);
492 for (p = action_list; p && p->data; p = g_slist_next(p)) {
493 FilteringAction *a = (FilteringAction *) p->data;
494 if (filteringaction_apply(a, info)) {
495 if (filtering_is_final_action(a))
504 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
505 PrefsAccount *ac_prefs)
507 /* this function returns true if a filtering rule applies regarding to its account
508 data and if it does, if the conditions list match.
510 per-account data of a filtering rule is either matched against current account
511 when filtering is done manually, or against the account currently used for
512 retrieving messages when it's an manual or automatic fetching of messages.
513 per-account data match doesn't apply to pre-/post-/folder-processing rules.
515 when filtering messages manually:
516 - either the filtering rule is not account-based and it will be processed
517 - or it's per-account and we check if we HAVE TO match it against the current
518 account (according to user-land settings, per-account rules might have to
519 be skipped, or only the rules that match the current account have to be
520 applied, or all rules will have to be applied regardless to if they are
521 account-based or not)
523 notes about debugging output in that function:
524 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
525 no debug output is done when filtering_debug_level is low
528 gboolean matches = FALSE;
530 if (ac_prefs != NULL) {
531 matches = ((filtering->account_id == 0)
532 || (filtering->account_id == ac_prefs->account_id));
535 if (debug_filtering_session) {
536 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
537 if (filtering->account_id == 0) {
538 log_status_ok(LOG_DEBUG_FILTERING,
539 _("rule is not account-based\n"));
541 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
542 log_status_ok(LOG_DEBUG_FILTERING,
543 _("rule is account-based [id=%d, name='%s'], "
544 "matching the account currently used to retrieve messages\n"),
545 ac_prefs->account_id, ac_prefs->account_name);
551 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
552 log_status_skip(LOG_DEBUG_FILTERING,
553 _("rule is account-based, "
554 "not matching the account currently used to retrieve messages\n"));
556 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
557 PrefsAccount *account = account_find_from_id(filtering->account_id);
559 log_status_skip(LOG_DEBUG_FILTERING,
560 _("rule is account-based [id=%d, name='%s'], "
561 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
562 filtering->account_id, account->account_name,
563 ac_prefs->account_id, ac_prefs->account_name);
569 switch (prefs_common.apply_per_account_filtering_rules) {
570 case FILTERING_ACCOUNT_RULES_FORCE:
571 /* apply filtering rules regardless to the account info */
575 if (debug_filtering_session) {
576 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
577 if (filtering->account_id == 0) {
578 log_status_ok(LOG_DEBUG_FILTERING,
579 _("rule is not account-based, "
580 "all rules are applied on user request anyway\n"));
582 PrefsAccount *account = account_find_from_id(filtering->account_id);
584 log_status_ok(LOG_DEBUG_FILTERING,
585 _("rule is account-based [id=%d, name='%s'], "
586 "but all rules are applied on user request\n"),
587 filtering->account_id, account->account_name);
592 case FILTERING_ACCOUNT_RULES_SKIP:
593 /* don't apply filtering rules that belong to an account */
594 matches = (filtering->account_id == 0);
597 if (debug_filtering_session) {
599 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
600 PrefsAccount *account = account_find_from_id(filtering->account_id);
602 log_status_skip(LOG_DEBUG_FILTERING,
603 _("rule is account-based [id=%d, name='%s'], "
604 "skipped on user request\n"),
605 filtering->account_id, account->account_name);
607 log_status_skip(LOG_DEBUG_FILTERING,
608 _("rule is account-based, "
609 "skipped on user request\n"));
612 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
613 log_status_ok(LOG_DEBUG_FILTERING,
614 _("rule is not account-based\n"));
619 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
620 matches = ((filtering->account_id == 0)
621 || (filtering->account_id == cur_account->account_id));
624 if (debug_filtering_session) {
626 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
627 PrefsAccount *account = account_find_from_id(filtering->account_id);
629 log_status_skip(LOG_DEBUG_FILTERING,
630 _("rule is account-based [id=%d, name='%s'], "
631 "not matching current account [id=%d, name='%s']\n"),
632 filtering->account_id, account->account_name,
633 cur_account->account_id, cur_account->account_name);
635 log_status_skip(LOG_DEBUG_FILTERING,
636 _("rule is account-based, "
637 "not matching current account\n"));
640 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
641 if (filtering->account_id == 0) {
642 log_status_ok(LOG_DEBUG_FILTERING,
643 _("rule is not account-based\n"));
645 PrefsAccount *account = account_find_from_id(filtering->account_id);
647 log_status_ok(LOG_DEBUG_FILTERING,
648 _("rule is account-based [id=%d, name='%s'], "
649 "current account [id=%d, name='%s']\n"),
650 account->account_id, account->account_name,
651 cur_account->account_id, cur_account->account_name);
660 return matches && matcherlist_match(filtering->matchers, info);
664 *\brief Apply a rule on message.
666 *\param filtering List of filtering rules.
667 *\param info Message to apply rules on.
668 *\param final Variable returning TRUE or FALSE if one of the
669 * encountered actions was final.
670 * See also \ref filtering_is_final_action.
672 *\return gboolean TRUE to continue applying rules.
674 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
677 gboolean result = TRUE;
682 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
683 FilteringAction * action;
686 filteringaction_to_string(buf, sizeof buf, action);
687 if (debug_filtering_session)
688 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
690 if (FALSE == (result = filteringaction_apply(action, info))) {
691 if (debug_filtering_session) {
692 if (action->type != MATCHACTION_STOP)
693 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
694 log_print(LOG_DEBUG_FILTERING,
695 _("no further processing after action [ %s ]\n"), buf);
697 g_warning("No further processing after rule %s\n", buf);
700 if (filtering_is_final_action(action)) {
709 *\brief Check if an action is "final", i.e. should break further
712 *\param filtering_action Action to check.
714 *\return gboolean TRUE if \a filtering_action is final.
716 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
718 switch(filtering_action->type) {
719 case MATCHACTION_MOVE:
720 case MATCHACTION_DELETE:
721 case MATCHACTION_STOP:
722 case MATCHACTION_MARK_AS_SPAM:
723 return TRUE; /* MsgInfo invalid for message */
729 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
735 g_return_val_if_fail(info != NULL, TRUE);
737 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
738 FilteringProp * filtering = (FilteringProp *) l->data;
740 if (filtering->enabled) {
741 if (debug_filtering_session) {
742 gchar *buf = filteringprop_to_string(filtering);
743 if (filtering->name && *filtering->name != '\0') {
744 log_print(LOG_DEBUG_FILTERING,
745 _("processing rule '%s' [ %s ]\n"),
746 filtering->name, buf);
748 log_print(LOG_DEBUG_FILTERING,
749 _("processing rule <unnamed> [ %s ]\n"),
754 if (filtering_match_condition(filtering, info, ac_prefs)) {
755 apply_next = filtering_apply_rule(filtering, info, &final);
761 if (debug_filtering_session) {
762 gchar *buf = filteringprop_to_string(filtering);
763 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
764 if (filtering->name && *filtering->name != '\0') {
765 log_status_skip(LOG_DEBUG_FILTERING,
766 _("disabled rule '%s' [ %s ]\n"),
767 filtering->name, buf);
769 log_status_skip(LOG_DEBUG_FILTERING,
770 _("disabled rule <unnamed> [ %s ]\n"),
779 /* put in inbox if a final rule could not be applied, or
780 * the last rule was not a final one. */
781 if ((final && !apply_next) || !final) {
789 *\brief Filter a message against a list of rules.
791 *\param flist List of filter rules.
792 *\param info Message.
794 *\return gboolean TRUE if filter rules handled the message.
796 *\note Returning FALSE means the message was not handled,
797 * and that the calling code should do the default
798 * processing. E.g. \ref inc.c::inc_start moves the
799 * message to the inbox.
801 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
802 FilteringInvocationType context, gchar *extra_info)
804 if (prefs_common.enable_filtering_debug) {
805 gchar *tmp = _("undetermined");
808 case FILTERING_INCORPORATION:
809 tmp = _("incorporation");
810 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
812 case FILTERING_MANUALLY:
814 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
816 case FILTERING_FOLDER_PROCESSING:
817 tmp = _("folder processing");
818 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
820 case FILTERING_PRE_PROCESSING:
821 tmp = _("pre-processing");
822 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
824 case FILTERING_POST_PROCESSING:
825 tmp = _("post-processing");
826 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
829 debug_filtering_session = FALSE;
832 if (debug_filtering_session) {
833 gchar *file = procmsg_get_message_file_path(info);
834 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
836 /* show context info and essential info about the message */
837 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
838 log_print(LOG_DEBUG_FILTERING,
839 _("filtering message (%s%s%s)\n"
840 "%smessage file: %s\n%sDate: %s\n%sFrom: %s\n%sTo: %s\n%sSubject: %s\n"),
841 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
842 spc, file, spc, info->date, spc, info->from,
843 spc, info->to, spc, info->subject);
845 log_print(LOG_DEBUG_FILTERING,
846 _("filtering message (%s%s%s)\n"
847 "%smessage file: %s\n"),
848 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
855 debug_filtering_session = FALSE;
856 return filter_msginfo(flist, info, ac_prefs);
859 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
861 const gchar *command_str;
863 gchar * quoted_header;
865 command_str = get_matchparser_tab_str(action->type);
867 if (command_str == NULL)
870 switch(action->type) {
871 case MATCHACTION_MOVE:
872 case MATCHACTION_COPY:
873 case MATCHACTION_EXECUTE:
874 quoted_dest = matcher_quote_str(action->destination);
875 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
879 case MATCHACTION_DELETE:
880 case MATCHACTION_MARK:
881 case MATCHACTION_UNMARK:
882 case MATCHACTION_LOCK:
883 case MATCHACTION_UNLOCK:
884 case MATCHACTION_MARK_AS_READ:
885 case MATCHACTION_MARK_AS_UNREAD:
886 case MATCHACTION_MARK_AS_SPAM:
887 case MATCHACTION_MARK_AS_HAM:
888 case MATCHACTION_STOP:
889 case MATCHACTION_HIDE:
890 case MATCHACTION_IGNORE:
891 g_snprintf(dest, destlen, "%s", command_str);
894 case MATCHACTION_REDIRECT:
895 case MATCHACTION_FORWARD:
896 case MATCHACTION_FORWARD_AS_ATTACHMENT:
897 quoted_dest = matcher_quote_str(action->destination);
898 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
902 case MATCHACTION_COLOR:
903 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
906 case MATCHACTION_CHANGE_SCORE:
907 case MATCHACTION_SET_SCORE:
908 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
911 case MATCHACTION_ADD_TO_ADDRESSBOOK:
912 quoted_header = matcher_quote_str(action->header);
913 quoted_dest = matcher_quote_str(action->destination);
914 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
916 g_free(quoted_header);
924 gchar * filteringaction_list_to_string(GSList * action_list)
926 gchar *action_list_str;
931 action_list_str = NULL;
932 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
934 FilteringAction * action;
938 action_str = filteringaction_to_string(buf,
941 if (action_list_str != NULL) {
942 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
943 g_free(action_list_str);
946 list_str = g_strdup(action_str);
948 action_list_str = list_str;
951 return action_list_str;
954 gchar * filteringprop_to_string(FilteringProp * prop)
957 gchar *action_list_str;
958 gchar *filtering_str;
960 action_list_str = filteringaction_list_to_string(prop->action_list);
962 if (action_list_str == NULL)
965 list_str = matcherlist_to_string(prop->matchers);
967 if (list_str == NULL) {
968 g_free(action_list_str);
972 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
973 g_free(action_list_str);
976 return filtering_str;
979 void prefs_filtering_free(GSList * prefs_filtering)
981 while (prefs_filtering != NULL) {
982 FilteringProp * filtering = (FilteringProp *)
983 prefs_filtering->data;
984 filteringprop_free(filtering);
985 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
989 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
991 FolderItem *item = node->data;
993 g_return_val_if_fail(item, FALSE);
994 g_return_val_if_fail(item->prefs, FALSE);
996 prefs_filtering_free(item->prefs->processing);
997 item->prefs->processing = NULL;
1002 void prefs_filtering_clear(void)
1006 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1009 folder = (Folder *) cur->data;
1010 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1011 prefs_filtering_free_func, NULL);
1014 prefs_filtering_free(filtering_rules);
1015 filtering_rules = NULL;
1016 prefs_filtering_free(pre_global_processing);
1017 pre_global_processing = NULL;
1018 prefs_filtering_free(post_global_processing);
1019 post_global_processing = NULL;
1022 void prefs_filtering_clear_folder(Folder *folder)
1024 g_return_if_fail(folder);
1025 g_return_if_fail(folder->node);
1027 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1028 prefs_filtering_free_func, NULL);
1029 /* FIXME: Note folder settings were changed, where the updates? */
1032 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1033 /* return TRUE if there's at least one per-account filtering rule */
1037 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1038 FilteringProp * filtering = (FilteringProp *) l->data;
1040 if (filtering->enabled && (filtering->account_id != 0)) {