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?expr:"(null)");
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 /* move and copy messages by batches to be faster on IMAP */
184 void filtering_move_and_copy_msgs(GSList *msgs)
186 GSList *messages = g_slist_copy(msgs);
187 FolderItem *last_item = NULL;
188 gboolean is_copy = FALSE, is_move = FALSE;
189 debug_print("checking %d messages\n", g_slist_length(msgs));
191 GSList *batch = NULL, *cur;
193 for (cur = messages; cur; cur = cur->next) {
194 MsgInfo *info = (MsgInfo *)cur->data;
195 if (last_item == NULL) {
196 last_item = info->to_filter_folder;
198 if (last_item == NULL)
200 if (!is_copy && !is_move) {
203 else if (info->is_move)
207 if (info->to_filter_folder == last_item
208 && info->is_copy == is_copy
209 && info->is_move == is_move) {
210 batch = g_slist_prepend(batch, info);
214 debug_print("no more messages to move/copy\n");
217 debug_print("%d messages to %s in %s\n", found,
218 is_copy ? "copy":"move", last_item->name ? last_item->name:"(noname)");
220 for (cur = batch; cur; cur = cur->next) {
221 MsgInfo *info = (MsgInfo *)cur->data;
222 messages = g_slist_remove(messages, info);
224 batch = g_slist_reverse(batch);
225 if (g_slist_length(batch)) {
226 MsgInfo *info = (MsgInfo *)batch->data;
227 if (is_copy && last_item != info->folder) {
228 folder_item_copy_msgs(last_item, batch);
229 } else if (is_move && last_item != info->folder) {
230 if (folder_item_move_msgs(last_item, batch) < 0)
231 folder_item_move_msgs(
232 folder_get_default_inbox(),
235 /* we don't reference the msginfos, because caller will do */
236 if (prefs_common.real_time_sync)
237 folder_item_synchronise(last_item);
246 /* we don't reference the msginfos, because caller will do */
247 g_slist_free(messages);
251 fitleringaction_apply
252 runs the action on one MsgInfo
253 return value : return TRUE if the action could be applied
256 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
258 FolderItem * dest_folder;
261 PrefsAccount * account;
264 switch(action->type) {
265 case MATCHACTION_MOVE:
267 folder_find_item_from_identifier(action->destination);
269 debug_print("*** folder not found '%s'\n",
270 action->destination ?action->destination :"(null)");
274 /* check if mail is set to copy already,
275 * in which case we have to do it */
276 if (info->is_copy && info->to_filter_folder) {
277 debug_print("should cp and mv !\n");
278 folder_item_copy_msg(info->to_filter_folder, info);
279 info->is_copy = FALSE;
281 /* mark message to be moved */
282 info->is_move = TRUE;
283 info->to_filter_folder = dest_folder;
286 case MATCHACTION_COPY:
288 folder_find_item_from_identifier(action->destination);
291 debug_print("*** folder not found '%s'\n",
292 action->destination ?action->destination :"(null)");
296 /* check if mail is set to copy already,
297 * in which case we have to do it */
298 if (info->is_copy && info->to_filter_folder) {
299 debug_print("should cp and mv !\n");
300 folder_item_copy_msg(info->to_filter_folder, info);
301 info->is_copy = FALSE;
303 /* mark message to be copied */
304 info->is_copy = TRUE;
305 info->to_filter_folder = dest_folder;
308 case MATCHACTION_SET_TAG:
309 case MATCHACTION_UNSET_TAG:
310 val = tags_get_id_for_str(action->destination);
312 debug_print("*** tag '%s' not found\n",
313 action->destination ?action->destination :"(null)");
317 procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
320 case MATCHACTION_CLEAR_TAGS:
321 procmsg_msginfo_clear_tags(info);
324 case MATCHACTION_DELETE:
325 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
329 case MATCHACTION_MARK:
330 procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
333 case MATCHACTION_UNMARK:
334 procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
337 case MATCHACTION_LOCK:
338 procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
341 case MATCHACTION_UNLOCK:
342 procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);
345 case MATCHACTION_MARK_AS_READ:
346 procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
349 case MATCHACTION_MARK_AS_UNREAD:
350 procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
353 case MATCHACTION_MARK_AS_SPAM:
354 procmsg_spam_learner_learn(info, NULL, TRUE);
355 procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
356 if (procmsg_spam_get_folder(info)) {
357 info->is_move = TRUE;
358 info->to_filter_folder = procmsg_spam_get_folder(info);
362 case MATCHACTION_MARK_AS_HAM:
363 procmsg_spam_learner_learn(info, NULL, FALSE);
364 procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
367 case MATCHACTION_COLOR:
368 procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0);
369 procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
372 case MATCHACTION_FORWARD:
373 case MATCHACTION_FORWARD_AS_ATTACHMENT:
374 account = account_find_from_id(action->account_id);
375 compose = compose_forward(account, info,
376 action->type == MATCHACTION_FORWARD ? FALSE : TRUE,
378 compose_entry_append(compose, action->destination,
379 compose->account->protocol == A_NNTP
383 val = compose_send(compose);
385 return val == 0 ? TRUE : FALSE;
387 case MATCHACTION_REDIRECT:
388 account = account_find_from_id(action->account_id);
389 compose = compose_redirect(account, info, TRUE);
390 if (compose->account->protocol == A_NNTP)
393 compose_entry_append(compose, action->destination,
396 val = compose_send(compose);
398 return val == 0 ? TRUE : FALSE;
400 case MATCHACTION_EXECUTE:
401 cmd = matching_build_command(action->destination, info);
410 case MATCHACTION_SET_SCORE:
411 info->score = action->score;
414 case MATCHACTION_CHANGE_SCORE:
415 info->score += action->score;
418 case MATCHACTION_STOP:
421 case MATCHACTION_HIDE:
425 case MATCHACTION_IGNORE:
426 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
429 case MATCHACTION_WATCH:
430 procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
433 case MATCHACTION_ADD_TO_ADDRESSBOOK:
435 AddressDataSource *book = NULL;
436 AddressBookFile *abf = NULL;
437 ItemFolder *folder = NULL;
442 if (!addressbook_peek_folder_exists(action->destination, &book, &folder)) {
443 g_warning("addressbook folder not found '%s'\n", action->destination);
447 g_warning("addressbook_peek_folder_exists returned NULL book\n");
451 abf = book->rawDataSource;
454 procheader_get_header_from_msginfo(info, buf, sizeof(buf), action->header);
455 header = procheader_parse_header(buf);
457 /* add all addresses that are not already in */
458 if (header && *header->body && (*header->body != '\0')) {
459 GSList *address_list = NULL;
463 if (action->destination == NULL ||
464 strcasecmp(action->destination, _("Any")) == 0 ||
465 *(action->destination) == '\0')
468 path = action->destination;
469 start_address_completion(path);
471 address_list = address_list_append(address_list, header->body);
472 for (walk = address_list; walk != NULL; walk = walk->next) {
473 gchar *stripped_addr = g_strdup(walk->data);
474 extract_address(stripped_addr);
476 if (complete_matches_found(walk->data) == 0) {
477 debug_print("adding address '%s' to addressbook '%s'\n",
478 stripped_addr, action->destination);
479 if (!addrbook_add_contact(abf, folder, stripped_addr, stripped_addr, NULL)) {
480 g_warning("contact could not been added\n");
484 debug_print("address '%s' already found in addressbook '%s', skipping\n",
485 stripped_addr, action->destination);
487 g_free(stripped_addr);
490 g_slist_free(address_list);
491 end_address_completion();
493 g_warning("header '%s' not set or empty\n", action->header);
495 return (errors == 0);
504 gboolean filteringaction_apply_action_list(GSList *action_list, MsgInfo *info)
507 g_return_val_if_fail(action_list, FALSE);
508 g_return_val_if_fail(info, FALSE);
509 for (p = action_list; p && p->data; p = g_slist_next(p)) {
510 FilteringAction *a = (FilteringAction *) p->data;
511 if (filteringaction_apply(a, info)) {
512 if (filtering_is_final_action(a))
521 static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info,
522 PrefsAccount *ac_prefs)
524 /* this function returns true if a filtering rule applies regarding to its account
525 data and if it does, if the conditions list match.
527 per-account data of a filtering rule is either matched against current account
528 when filtering is done manually, or against the account currently used for
529 retrieving messages when it's an manual or automatic fetching of messages.
530 per-account data match doesn't apply to pre-/post-/folder-processing rules.
532 when filtering messages manually:
533 - either the filtering rule is not account-based and it will be processed
534 - or it's per-account and we check if we HAVE TO match it against the current
535 account (according to user-land settings, per-account rules might have to
536 be skipped, or only the rules that match the current account have to be
537 applied, or all rules will have to be applied regardless to if they are
538 account-based or not)
540 notes about debugging output in that function:
541 when not matching, log_status_skip() is used, otherwise log_status_ok() is used
542 no debug output is done when filtering_debug_level is low
545 gboolean matches = FALSE;
547 if (ac_prefs != NULL) {
548 matches = ((filtering->account_id == 0)
549 || (filtering->account_id == ac_prefs->account_id));
552 if (debug_filtering_session) {
553 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
554 if (filtering->account_id == 0) {
555 log_status_ok(LOG_DEBUG_FILTERING,
556 _("rule is not account-based\n"));
558 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
559 log_status_ok(LOG_DEBUG_FILTERING,
560 _("rule is account-based [id=%d, name='%s'], "
561 "matching the account currently used to retrieve messages\n"),
562 ac_prefs->account_id, ac_prefs->account_name);
568 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
569 log_status_skip(LOG_DEBUG_FILTERING,
570 _("rule is account-based, "
571 "not matching the account currently used to retrieve messages\n"));
573 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
574 PrefsAccount *account = account_find_from_id(filtering->account_id);
576 log_status_skip(LOG_DEBUG_FILTERING,
577 _("rule is account-based [id=%d, name='%s'], "
578 "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
579 filtering->account_id, account->account_name,
580 ac_prefs->account_id, ac_prefs->account_name);
586 switch (prefs_common.apply_per_account_filtering_rules) {
587 case FILTERING_ACCOUNT_RULES_FORCE:
588 /* apply filtering rules regardless to the account info */
592 if (debug_filtering_session) {
593 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
594 if (filtering->account_id == 0) {
595 log_status_ok(LOG_DEBUG_FILTERING,
596 _("rule is not account-based, "
597 "all rules are applied on user request anyway\n"));
599 PrefsAccount *account = account_find_from_id(filtering->account_id);
601 log_status_ok(LOG_DEBUG_FILTERING,
602 _("rule is account-based [id=%d, name='%s'], "
603 "but all rules are applied on user request\n"),
604 filtering->account_id, account->account_name);
609 case FILTERING_ACCOUNT_RULES_SKIP:
610 /* don't apply filtering rules that belong to an account */
611 matches = (filtering->account_id == 0);
614 if (debug_filtering_session) {
616 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
617 PrefsAccount *account = account_find_from_id(filtering->account_id);
619 log_status_skip(LOG_DEBUG_FILTERING,
620 _("rule is account-based [id=%d, name='%s'], "
621 "skipped on user request\n"),
622 filtering->account_id, account->account_name);
624 log_status_skip(LOG_DEBUG_FILTERING,
625 _("rule is account-based, "
626 "skipped on user request\n"));
629 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
630 log_status_ok(LOG_DEBUG_FILTERING,
631 _("rule is not account-based\n"));
636 case FILTERING_ACCOUNT_RULES_USE_CURRENT:
637 matches = ((filtering->account_id == 0)
638 || (filtering->account_id == cur_account->account_id));
641 if (debug_filtering_session) {
643 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
644 PrefsAccount *account = account_find_from_id(filtering->account_id);
646 log_status_skip(LOG_DEBUG_FILTERING,
647 _("rule is account-based [id=%d, name='%s'], "
648 "not matching current account [id=%d, name='%s']\n"),
649 filtering->account_id, account->account_name,
650 cur_account->account_id, cur_account->account_name);
652 log_status_skip(LOG_DEBUG_FILTERING,
653 _("rule is account-based, "
654 "not matching current account\n"));
657 if (matches && prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
658 if (filtering->account_id == 0) {
659 log_status_ok(LOG_DEBUG_FILTERING,
660 _("rule is not account-based\n"));
662 PrefsAccount *account = account_find_from_id(filtering->account_id);
664 log_status_ok(LOG_DEBUG_FILTERING,
665 _("rule is account-based [id=%d, name='%s'], "
666 "current account [id=%d, name='%s']\n"),
667 account->account_id, account->account_name,
668 cur_account->account_id, cur_account->account_name);
677 return matches && matcherlist_match(filtering->matchers, info);
681 *\brief Apply a rule on message.
683 *\param filtering List of filtering rules.
684 *\param info Message to apply rules on.
685 *\param final Variable returning TRUE or FALSE if one of the
686 * encountered actions was final.
687 * See also \ref filtering_is_final_action.
689 *\return gboolean TRUE to continue applying rules.
691 static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
694 gboolean result = TRUE;
699 for (tmp = filtering->action_list ; tmp != NULL ; tmp = tmp->next) {
700 FilteringAction * action;
703 filteringaction_to_string(buf, sizeof buf, action);
704 if (debug_filtering_session)
705 log_print(LOG_DEBUG_FILTERING, _("applying action [ %s ]\n"), buf);
707 if (FALSE == (result = filteringaction_apply(action, info))) {
708 if (debug_filtering_session) {
709 if (action->type != MATCHACTION_STOP)
710 log_warning(LOG_DEBUG_FILTERING, _("action could not apply\n"));
711 log_print(LOG_DEBUG_FILTERING,
712 _("no further processing after action [ %s ]\n"), buf);
714 g_warning("No further processing after rule %s\n", buf);
717 if (filtering_is_final_action(action)) {
726 *\brief Check if an action is "final", i.e. should break further
729 *\param filtering_action Action to check.
731 *\return gboolean TRUE if \a filtering_action is final.
733 static gboolean filtering_is_final_action(FilteringAction *filtering_action)
735 switch(filtering_action->type) {
736 case MATCHACTION_MOVE:
737 case MATCHACTION_DELETE:
738 case MATCHACTION_STOP:
739 case MATCHACTION_MARK_AS_SPAM:
740 return TRUE; /* MsgInfo invalid for message */
746 static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAccount* ac_prefs)
752 g_return_val_if_fail(info != NULL, TRUE);
754 for (l = filtering_list, final = FALSE, apply_next = FALSE; l != NULL; l = g_slist_next(l)) {
755 FilteringProp * filtering = (FilteringProp *) l->data;
757 if (filtering->enabled) {
758 if (debug_filtering_session) {
759 gchar *buf = filteringprop_to_string(filtering);
760 if (filtering->name && *filtering->name != '\0') {
761 log_print(LOG_DEBUG_FILTERING,
762 _("processing rule '%s' [ %s ]\n"),
763 filtering->name, buf);
765 log_print(LOG_DEBUG_FILTERING,
766 _("processing rule <unnamed> [ %s ]\n"),
771 if (filtering_match_condition(filtering, info, ac_prefs)) {
772 apply_next = filtering_apply_rule(filtering, info, &final);
778 if (debug_filtering_session) {
779 gchar *buf = filteringprop_to_string(filtering);
780 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
781 if (filtering->name && *filtering->name != '\0') {
782 log_status_skip(LOG_DEBUG_FILTERING,
783 _("disabled rule '%s' [ %s ]\n"),
784 filtering->name, buf);
786 log_status_skip(LOG_DEBUG_FILTERING,
787 _("disabled rule <unnamed> [ %s ]\n"),
796 /* put in inbox if a final rule could not be applied, or
797 * the last rule was not a final one. */
798 if ((final && !apply_next) || !final) {
806 *\brief Filter a message against a list of rules.
808 *\param flist List of filter rules.
809 *\param info Message.
811 *\return gboolean TRUE if filter rules handled the message.
813 *\note Returning FALSE means the message was not handled,
814 * and that the calling code should do the default
815 * processing. E.g. \ref inc.c::inc_start moves the
816 * message to the inbox.
818 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
819 FilteringInvocationType context, gchar *extra_info)
823 if (prefs_common.enable_filtering_debug) {
824 gchar *tmp = _("undetermined");
827 case FILTERING_INCORPORATION:
828 tmp = _("incorporation");
829 debug_filtering_session = prefs_common.enable_filtering_debug_inc;
831 case FILTERING_MANUALLY:
833 debug_filtering_session = prefs_common.enable_filtering_debug_manual;
835 case FILTERING_FOLDER_PROCESSING:
836 tmp = _("folder processing");
837 debug_filtering_session = prefs_common.enable_filtering_debug_folder_proc;
839 case FILTERING_PRE_PROCESSING:
840 tmp = _("pre-processing");
841 debug_filtering_session = prefs_common.enable_filtering_debug_pre_proc;
843 case FILTERING_POST_PROCESSING:
844 tmp = _("post-processing");
845 debug_filtering_session = prefs_common.enable_filtering_debug_post_proc;
848 debug_filtering_session = FALSE;
851 if (debug_filtering_session) {
852 gchar *file = procmsg_get_message_file_path(info);
853 gchar *spc = g_strnfill(LOG_TIME_LEN + 1, ' ');
855 /* show context info and essential info about the message */
856 if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
857 log_print(LOG_DEBUG_FILTERING,
858 _("filtering message (%s%s%s)\n"
859 "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
860 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
861 spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
862 spc, prefs_common_translated_header_name("From:"), info->from,
863 spc, prefs_common_translated_header_name("To:"), info->to,
864 spc, prefs_common_translated_header_name("Subject:"), info->subject);
866 log_print(LOG_DEBUG_FILTERING,
867 _("filtering message (%s%s%s)\n"
868 "%smessage file: %s\n"),
869 tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
876 debug_filtering_session = FALSE;
878 ret = filter_msginfo(flist, info, ac_prefs);
879 debug_filtering_session = FALSE;
883 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
885 const gchar *command_str;
887 gchar * quoted_header;
889 command_str = get_matchparser_tab_str(action->type);
891 if (command_str == NULL)
894 switch(action->type) {
895 case MATCHACTION_MOVE:
896 case MATCHACTION_COPY:
897 case MATCHACTION_EXECUTE:
898 case MATCHACTION_SET_TAG:
899 case MATCHACTION_UNSET_TAG:
900 quoted_dest = matcher_quote_str(action->destination);
901 g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
905 case MATCHACTION_DELETE:
906 case MATCHACTION_MARK:
907 case MATCHACTION_UNMARK:
908 case MATCHACTION_LOCK:
909 case MATCHACTION_UNLOCK:
910 case MATCHACTION_MARK_AS_READ:
911 case MATCHACTION_MARK_AS_UNREAD:
912 case MATCHACTION_MARK_AS_SPAM:
913 case MATCHACTION_MARK_AS_HAM:
914 case MATCHACTION_STOP:
915 case MATCHACTION_HIDE:
916 case MATCHACTION_IGNORE:
917 case MATCHACTION_WATCH:
918 case MATCHACTION_CLEAR_TAGS:
919 g_snprintf(dest, destlen, "%s", command_str);
922 case MATCHACTION_REDIRECT:
923 case MATCHACTION_FORWARD:
924 case MATCHACTION_FORWARD_AS_ATTACHMENT:
925 quoted_dest = matcher_quote_str(action->destination);
926 g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, quoted_dest);
930 case MATCHACTION_COLOR:
931 g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
934 case MATCHACTION_CHANGE_SCORE:
935 case MATCHACTION_SET_SCORE:
936 g_snprintf(dest, destlen, "%s %d", command_str, action->score);
939 case MATCHACTION_ADD_TO_ADDRESSBOOK:
940 quoted_header = matcher_quote_str(action->header);
941 quoted_dest = matcher_quote_str(action->destination);
942 g_snprintf(dest, destlen, "%s \"%s\" \"%s\"", command_str, quoted_header, quoted_dest);
944 g_free(quoted_header);
952 gchar * filteringaction_list_to_string(GSList * action_list)
954 gchar *action_list_str;
959 action_list_str = NULL;
960 for (tmp = action_list ; tmp != NULL ; tmp = tmp->next) {
962 FilteringAction * action;
966 action_str = filteringaction_to_string(buf,
969 if (action_list_str != NULL) {
970 list_str = g_strconcat(action_list_str, " ", action_str, NULL);
971 g_free(action_list_str);
974 list_str = g_strdup(action_str);
976 action_list_str = list_str;
979 return action_list_str;
982 gchar * filteringprop_to_string(FilteringProp * prop)
985 gchar *action_list_str;
986 gchar *filtering_str;
988 action_list_str = filteringaction_list_to_string(prop->action_list);
990 if (action_list_str == NULL)
993 list_str = matcherlist_to_string(prop->matchers);
995 if (list_str == NULL) {
996 g_free(action_list_str);
1000 filtering_str = g_strconcat(list_str, " ", action_list_str, NULL);
1001 g_free(action_list_str);
1004 return filtering_str;
1007 static void prefs_filtering_free(GSList * prefs_filtering)
1009 while (prefs_filtering != NULL) {
1010 FilteringProp * filtering = (FilteringProp *)
1011 prefs_filtering->data;
1012 filteringprop_free(filtering);
1013 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
1017 static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
1019 FolderItem *item = node->data;
1021 g_return_val_if_fail(item, FALSE);
1022 g_return_val_if_fail(item->prefs, FALSE);
1024 prefs_filtering_free(item->prefs->processing);
1025 item->prefs->processing = NULL;
1030 void prefs_filtering_clear(void)
1034 for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
1037 folder = (Folder *) cur->data;
1038 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1039 prefs_filtering_free_func, NULL);
1042 prefs_filtering_free(filtering_rules);
1043 filtering_rules = NULL;
1044 prefs_filtering_free(pre_global_processing);
1045 pre_global_processing = NULL;
1046 prefs_filtering_free(post_global_processing);
1047 post_global_processing = NULL;
1050 void prefs_filtering_clear_folder(Folder *folder)
1052 g_return_if_fail(folder);
1053 g_return_if_fail(folder->node);
1055 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1056 prefs_filtering_free_func, NULL);
1057 /* FIXME: Note folder settings were changed, where the updates? */
1060 gboolean filtering_peek_per_account_rules(GSList *filtering_list)
1061 /* return TRUE if there's at least one per-account filtering rule */
1065 for (l = filtering_list; l != NULL; l = g_slist_next(l)) {
1066 FilteringProp * filtering = (FilteringProp *) l->data;
1068 if (filtering->enabled && (filtering->account_id != 0)) {