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_ADD_TO_ADDRESSBOOK:
438 AddressDataSource *book = NULL;
439 AddressBookFile *abf = NULL;
440 ItemFolder *folder = NULL;
445 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
446 g_warning("addressbook folder not found '%s'\n", action->destination);
450 g_warning("addressbook_peek_folder_exists returned NULL book\n");
454 abf = book->rawDataSource;
457 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
458 header = procheader_parse_header(buf);
460 /* add all addresses that are not already in */
461 if (header && *header->body && (*header->body != '\0')) {
462 GSList *address_list = NULL;
466 if (action->destination == NULL ||
467 strcasecmp(action->destination, _("Any")) == 0 ||
468 *(action->destination) == '\0')
471 path = action->destination;
472 start_address_completion(path);
474 address_list = address_list_append(address_list, header->body);
475 for (walk = address_list; walk != NULL; walk = walk->next) {
476 gchar *stripped_addr = g_strdup(walk->data);
477 extract_address(stripped_addr);
479 if (complete_matches_found(walk->data) == 0) {
480 debug_print("adding address '%s' to addressbook '%s'\n",
481 stripped_addr, action->destination);
482 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
483 g_warning("contact could not been added\n");
487 debug_print("address '%s' already found in addressbook '%s', skipping\n",
488 stripped_addr, action->destination);
490 g_free(stripped_addr);
493 g_slist_free(address_list);
494 end_address_completion();
496 g_warning("header '%s' not set or empty\n", action->header);
498 return (errors == 0);
507 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
510 g_return_val_if_fail(action_list, FALSE);
511 g_return_val_if_fail(info, FALSE);
512 for (p = action_list; p && p->data; p = g_slist_next(p)) {
513 FilteringAction *a = (FilteringAction *) p->data;
514 if (filteringaction_apply(a, info)) {
515 if (filtering_is_final_action(a))
524 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
525 PrefsAccount *ac_prefs)
527 /* this function returns true if a filtering rule applies regarding to its account
528 data and if it does, if the conditions list match.
530 per-account data of a filtering rule is either matched against current account
531 when filtering is done manually, or against the account currently used for
532 retrieving messages when it's an manual or automatic fetching of messages.
533 per-account data match doesn't apply to pre-/post-/folder-processing rules.
535 when filtering messages manually:
536 - either the filtering rule is not account-based and it will be processed
537 - or it's per-account and we check if we HAVE TO match it against the current
538 account (according to user-land settings, per-account rules might have to
539 be skipped, or only the rules that match the current account have to be
540 applied, or all rules will have to be applied regardless to if they are
541 account-based or not)
543 notes about debugging output in that function:
544 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
545 no debug output is done when filtering_debug_level is low
548 gboolean matches = FALSE;
550 if (ac_prefs != NULL) {
551 matches = ((filtering->account_id == 0)
552 || (filtering->account_id == ac_prefs->account_id));
555 if (debug_filtering_session) {
556 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
557 if (filtering->account_id == 0) {
558 log_status_ok(LOG_DEBUG_FILTERING,
559 _("rule is not account-based\n"));
561 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
562 log_status_ok(LOG_DEBUG_FILTERING,
563 _("rule is account-based [id=%d, name='%s'], "
564 "matching the account currently used to retrieve messages\n"),
565 ac_prefs->account_id, ac_prefs->account_name);
571 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
572 log_status_skip(LOG_DEBUG_FILTERING,
573 _("rule is account-based, "
574 "not matching the account currently used to retrieve messages\n"));
576 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
577 PrefsAccount *account = account_find_from_id(filtering->account_id);
579 log_status_skip(LOG_DEBUG_FILTERING,
580 _("rule is account-based [id=%d, name='%s'], "
581 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
582 filtering->account_id, account->account_name,
583 ac_prefs->account_id, ac_prefs->account_name);
589 switch (prefs_common.apply_per_account_filtering_rules) {
590 case FILTERING_ACCOUNT_RULES_FORCE:
591 /* apply filtering rules regardless to the account info */
595 if (debug_filtering_session) {
596 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
597 if (filtering->account_id == 0) {
598 log_status_ok(LOG_DEBUG_FILTERING,
599 _("rule is not account-based, "
600 "all rules are applied on user request anyway\n"));
602 PrefsAccount *account = account_find_from_id(filtering->account_id);
604 log_status_ok(LOG_DEBUG_FILTERING,
605 _("rule is account-based [id=%d, name='%s'], "
606 "but all rules are applied on user request\n"),
607 filtering->account_id, account->account_name);
612 case FILTERING_ACCOUNT_RULES_SKIP:
613 /* don't apply filtering rules that belong to an account */
614 matches = (filtering->account_id == 0);
617 if (debug_filtering_session) {
619 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
620 PrefsAccount *account = account_find_from_id(filtering->account_id);
622 log_status_skip(LOG_DEBUG_FILTERING,
623 _("rule is account-based [id=%d, name='%s'], "
624 "skipped on user request\n"),
625 filtering->account_id, account->account_name);
627 log_status_skip(LOG_DEBUG_FILTERING,
628 _("rule is account-based, "
629 "skipped on user request\n"));
632 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
633 log_status_ok(LOG_DEBUG_FILTERING,
634 _("rule is not account-based\n"));
639 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
640 matches = ((filtering->account_id == 0)
641 || (filtering->account_id == cur_account->account_id));
644 if (debug_filtering_session) {
646 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
647 PrefsAccount *account = account_find_from_id(filtering->account_id);
649 log_status_skip(LOG_DEBUG_FILTERING,
650 _("rule is account-based [id=%d, name='%s'], "
651 "not matching current account [id=%d, name='%s']\n"),
652 filtering->account_id, account->account_name,
653 cur_account->account_id, cur_account->account_name);
655 log_status_skip(LOG_DEBUG_FILTERING,
656 _("rule is account-based, "
657 "not matching current account\n"));
660 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
661 if (filtering->account_id == 0) {
662 log_status_ok(LOG_DEBUG_FILTERING,
663 _("rule is not account-based\n"));
665 PrefsAccount *account = account_find_from_id(filtering->account_id);
667 log_status_ok(LOG_DEBUG_FILTERING,
668 _("rule is account-based [id=%d, name='%s'], "
669 "current account [id=%d, name='%s']\n"),
670 account->account_id, account->account_name,
671 cur_account->account_id, cur_account->account_name);
680 return matches && matcherlist_match(filtering->matchers, info);
684 *\brief Apply a rule on message.
686 *\param filtering List of filtering rules.
687 *\param info Message to apply rules on.
688 *\param final Variable returning TRUE or FALSE if one of the
689 * encountered actions was final.
690 * See also \ref filtering_is_final_action.
692 *\return gboolean TRUE to continue applying rules.
694 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
697 gboolean result = TRUE;
702 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
703 FilteringAction * action;
706 filteringaction_to_string(buf, sizeof buf, action);
707 if (debug_filtering_session)
708 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
710 if (FALSE == (result = filteringaction_apply(action, info))) {
711 if (debug_filtering_session) {
712 if (action->type != MATCHACTION_STOP)
713 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
714 log_print(LOG_DEBUG_FILTERING,
715 _("no further processing after action [ %s ]\n"), buf);
717 g_warning("No further processing after rule %s\n", buf);
720 if (filtering_is_final_action(action)) {
729 *\brief Check if an action is "final", i.e. should break further
732 *\param filtering_action Action to check.
734 *\return gboolean TRUE if \a filtering_action is final.
736 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
738 switch(filtering_action->type) {
739 case MATCHACTION_MOVE:
740 case MATCHACTION_DELETE:
741 case MATCHACTION_STOP:
742 case MATCHACTION_MARK_AS_SPAM:
743 return TRUE; /* MsgInfo invalid for message */
749 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
755 g_return_val_if_fail(info != NULL, TRUE);
757 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
758 FilteringProp * filtering = (FilteringProp *) l->data;
760 if (filtering->enabled) {
761 if (debug_filtering_session) {
762 gchar *buf = filteringprop_to_string(filtering);
763 if (filtering->name && *filtering->name != '\0') {
764 log_print(LOG_DEBUG_FILTERING,
765 _("processing rule '%s' [ %s ]\n"),
766 filtering->name, buf);
768 log_print(LOG_DEBUG_FILTERING,
769 _("processing rule <unnamed> [ %s ]\n"),
774 if (filtering_match_condition(filtering, info, ac_prefs)) {
775 apply_next = filtering_apply_rule(filtering, info, &final);
781 if (debug_filtering_session) {
782 gchar *buf = filteringprop_to_string(filtering);
783 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
784 if (filtering->name && *filtering->name != '\0') {
785 log_status_skip(LOG_DEBUG_FILTERING,
786 _("disabled rule '%s' [ %s ]\n"),
787 filtering->name, buf);
789 log_status_skip(LOG_DEBUG_FILTERING,
790 _("disabled rule <unnamed> [ %s ]\n"),
799 /* put in inbox if a final rule could not be applied, or
800 * the last rule was not a final one. */
801 if ((final && !apply_next) || !final) {
809 *\brief Filter a message against a list of rules.
811 *\param flist List of filter rules.
812 *\param info Message.
814 *\return gboolean TRUE if filter rules handled the message.
816 *\note Returning FALSE means the message was not handled,
817 * and that the calling code should do the default
818 * processing. E.g. \ref inc.c::inc_start moves the
819 * message to the inbox.
821 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
822 FilteringInvocationType context, gchar *extra_info)
824 if (prefs_common.enable_filtering_debug) {
825 gchar *tmp = _("undetermined");
828 case FILTERING_INCORPORATION:
829 tmp = _("incorporation");
830 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
832 case FILTERING_MANUALLY:
834 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
836 case FILTERING_FOLDER_PROCESSING:
837 tmp = _("folder processing");
838 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
840 case FILTERING_PRE_PROCESSING:
841 tmp = _("pre-processing");
842 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
844 case FILTERING_POST_PROCESSING:
845 tmp = _("post-processing");
846 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
849 debug_filtering_session = FALSE;
852 if (debug_filtering_session) {
853 gchar *file = procmsg_get_message_file_path(info);
854 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
856 /* show context info and essential info about the message */
857 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
858 log_print(LOG_DEBUG_FILTERING,
859 _("filtering message (%s%s%s)\n"
860 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
861 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
862 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
863 spc, prefs_common_translated_header_name("From:"), info->from,
864 spc, prefs_common_translated_header_name("To:"), info->to,
865 spc, prefs_common_translated_header_name("Subject:"), info->subject);
867 log_print(LOG_DEBUG_FILTERING,
868 _("filtering message (%s%s%s)\n"
869 "%smessage file: %s\n"),
870 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
877 debug_filtering_session = FALSE;
878 return filter_msginfo(flist, info, ac_prefs);
881 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
883 const gchar *command_str;
885 gchar * quoted_header;
887 command_str = get_matchparser_tab_str(action->type);
889 if (command_str == NULL)
892 switch(action->type) {
893 case MATCHACTION_MOVE:
894 case MATCHACTION_COPY:
895 case MATCHACTION_EXECUTE:
896 case MATCHACTION_SET_TAG:
897 case MATCHACTION_UNSET_TAG:
898 quoted_dest = matcher_quote_str(action->destination);
899 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
903 case MATCHACTION_DELETE:
904 case MATCHACTION_MARK:
905 case MATCHACTION_UNMARK:
906 case MATCHACTION_LOCK:
907 case MATCHACTION_UNLOCK:
908 case MATCHACTION_MARK_AS_READ:
909 case MATCHACTION_MARK_AS_UNREAD:
910 case MATCHACTION_MARK_AS_SPAM:
911 case MATCHACTION_MARK_AS_HAM:
912 case MATCHACTION_STOP:
913 case MATCHACTION_HIDE:
914 case MATCHACTION_IGNORE:
915 case MATCHACTION_CLEAR_TAGS:
916 g_snprintf(dest, destlen, "%s", command_str);
919 case MATCHACTION_REDIRECT:
920 case MATCHACTION_FORWARD:
921 case MATCHACTION_FORWARD_AS_ATTACHMENT:
922 quoted_dest = matcher_quote_str(action->destination);
923 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
927 case MATCHACTION_COLOR:
928 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
931 case MATCHACTION_CHANGE_SCORE:
932 case MATCHACTION_SET_SCORE:
933 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
936 case MATCHACTION_ADD_TO_ADDRESSBOOK:
937 quoted_header = matcher_quote_str(action->header);
938 quoted_dest = matcher_quote_str(action->destination);
939 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
941 g_free(quoted_header);
949 gchar * filteringaction_list_to_string(GSList * action_list)
951 gchar *action_list_str;
956 action_list_str = NULL;
957 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
959 FilteringAction * action;
963 action_str = filteringaction_to_string(buf,
966 if (action_list_str != NULL) {
967 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
968 g_free(action_list_str);
971 list_str = g_strdup(action_str);
973 action_list_str = list_str;
976 return action_list_str;
979 gchar * filteringprop_to_string(FilteringProp * prop)
982 gchar *action_list_str;
983 gchar *filtering_str;
985 action_list_str = filteringaction_list_to_string(prop->action_list);
987 if (action_list_str == NULL)
990 list_str = matcherlist_to_string(prop->matchers);
992 if (list_str == NULL) {
993 g_free(action_list_str);
997 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
998 g_free(action_list_str);
1001 return filtering_str;
1004 void prefs_filtering_free(GSList * prefs_filtering)
1006 while (prefs_filtering != NULL) {
1007 FilteringProp * filtering = (FilteringProp *)
1008 prefs_filtering->data;
1009 filteringprop_free(filtering);
1010 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1014 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1016 FolderItem *item = node->data;
1018 g_return_val_if_fail(item, FALSE);
1019 g_return_val_if_fail(item->prefs, FALSE);
1021 prefs_filtering_free(item->prefs->processing);
1022 item->prefs->processing = NULL;
1027 void prefs_filtering_clear(void)
1031 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1034 folder = (Folder *) cur->data;
1035 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1036 prefs_filtering_free_func, NULL);
1039 prefs_filtering_free(filtering_rules);
1040 filtering_rules = NULL;
1041 prefs_filtering_free(pre_global_processing);
1042 pre_global_processing = NULL;
1043 prefs_filtering_free(post_global_processing);
1044 post_global_processing = NULL;
1047 void prefs_filtering_clear_folder(Folder *folder)
1049 g_return_if_fail(folder);
1050 g_return_if_fail(folder->node);
1052 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1053 prefs_filtering_free_func, NULL);
1054 /* FIXME: Note folder settings were changed, where the updates? */
1057 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1058 /* return TRUE if there's at least one per-account filtering rule */
1062 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1063 FilteringProp * filtering = (FilteringProp *) l->data;
1065 if (filtering->enabled && (filtering->account_id != 0)) {