claws-mail/src/filtering.c
Christoph Hohmann c70a9c4ee5 * src/filtering.[ch]
* src/inc.c
        do filtering stuff after all messages have been received
          1. drop all messages to .processing
          2. scan processing folder for messages
          3. filter messages or move to inbox
        this will also find old messages left in processing after
        a crash or something like that and we get a real msginfo
        from folder->get_msg_list and do not have to create our
        own in filter_message
* src/matcher_parser_parse.y
        remove MIX/MAX warning (reordered includes)
2002-02-25 16:56:22 +00:00

528 lines
13 KiB
C

/*
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
* Copyright (C) 1999-2002 Hiroyuki Yamamoto & The Sylpheed Claws Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "defs.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <stdio.h>
#include "intl.h"
#include "utils.h"
#include "procheader.h"
#include "matcher.h"
#include "filtering.h"
#include "prefs.h"
#include "compose.h"
#define PREFSBUFSIZE 1024
GSList * global_processing = NULL;
#define STRLEN_WITH_CHECK(expr) \
strlen_with_check(#expr, __LINE__, expr)
static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar *str)
{
if (str)
return strlen(str);
else {
debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
return 0;
}
}
FilteringAction * filteringaction_new(int type, int account_id,
gchar * destination,
gint labelcolor)
{
FilteringAction * action;
action = g_new0(FilteringAction, 1);
action->type = type;
action->account_id = account_id;
if (destination) {
action->destination = g_strdup(destination);
action->unesc_destination = matcher_unescape_str(g_strdup(destination));
} else {
action->destination = NULL;
action->unesc_destination = NULL;
}
action->labelcolor = labelcolor;
return action;
}
void filteringaction_free(FilteringAction * action)
{
g_return_if_fail(action);
if (action->destination)
g_free(action->destination);
if (action->unesc_destination)
g_free(action->unesc_destination);
g_free(action);
}
FilteringProp * filteringprop_new(MatcherList * matchers,
FilteringAction * action)
{
FilteringProp * filtering;
filtering = g_new0(FilteringProp, 1);
filtering->matchers = matchers;
filtering->action = action;
return filtering;
}
void filteringprop_free(FilteringProp * prop)
{
matcherlist_free(prop->matchers);
filteringaction_free(prop->action);
g_free(prop);
}
/*
fitleringaction_apply
runs the action on one MsgInfo
return value : return TRUE if the action could be applied
*/
#define CHANGE_FLAGS(msginfo) \
{ \
if (msginfo->folder->folder->change_flags != NULL) \
msginfo->folder->folder->change_flags(msginfo->folder->folder, \
msginfo->folder, \
msginfo); \
}
static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info,
GHashTable *folder_table)
{
FolderItem * dest_folder;
gint val;
Compose * compose;
PrefsAccount * account;
gchar * cmd;
switch(action->type) {
case MATCHACTION_MOVE:
dest_folder =
folder_find_item_from_identifier(action->destination);
if (!dest_folder)
return FALSE;
if (folder_item_move_msg(dest_folder, info) == -1) {
debug_print("*** could not move message\n");
return FALSE;
}
if (folder_table) {
val = GPOINTER_TO_INT(g_hash_table_lookup
(folder_table, dest_folder));
if (val == 0) {
folder_item_scan(dest_folder);
g_hash_table_insert(folder_table, dest_folder,
GINT_TO_POINTER(1));
}
}
return TRUE;
case MATCHACTION_COPY:
dest_folder =
folder_find_item_from_identifier(action->destination);
if (!dest_folder)
return FALSE;
if (folder_item_copy_msg(dest_folder, info) == -1)
return FALSE;
if (folder_table) {
val = GPOINTER_TO_INT(g_hash_table_lookup
(folder_table, dest_folder));
if (val == 0) {
folder_item_scan(dest_folder);
g_hash_table_insert(folder_table, dest_folder,
GINT_TO_POINTER(1));
}
}
return TRUE;
case MATCHACTION_DELETE:
if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
return FALSE;
return TRUE;
case MATCHACTION_MARK:
MSG_SET_PERM_FLAGS(info->flags, MSG_MARKED);
return TRUE;
case MATCHACTION_UNMARK:
MSG_UNSET_PERM_FLAGS(info->flags, MSG_MARKED);
return TRUE;
case MATCHACTION_MARK_AS_READ:
MSG_UNSET_PERM_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
return TRUE;
case MATCHACTION_MARK_AS_UNREAD:
debug_print("*** setting unread flags\n");
MSG_SET_PERM_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
return TRUE;
case MATCHACTION_COLOR:
MSG_UNSET_PERM_FLAGS(info->flags, MSG_CLABEL_FLAG_MASK);
MSG_SET_COLORLABEL_VALUE(info->flags, action->labelcolor);
return TRUE;
case MATCHACTION_FORWARD:
account = account_find_from_id(action->account_id);
compose = compose_forward(account, info, FALSE, NULL);
if (compose->account->protocol == A_NNTP)
compose_entry_append(compose, action->destination,
COMPOSE_NEWSGROUPS);
else
compose_entry_append(compose, action->destination,
COMPOSE_TO);
val = compose_send(compose);
if (val == 0) {
gtk_widget_destroy(compose->window);
return TRUE;
}
gtk_widget_destroy(compose->window);
return FALSE;
case MATCHACTION_FORWARD_AS_ATTACHMENT:
account = account_find_from_id(action->account_id);
compose = compose_forward(account, info, TRUE, NULL);
if (compose->account->protocol == A_NNTP)
compose_entry_append(compose, action->destination,
COMPOSE_NEWSGROUPS);
else
compose_entry_append(compose, action->destination,
COMPOSE_TO);
val = compose_send(compose);
if (val == 0) {
gtk_widget_destroy(compose->window);
return TRUE;
}
gtk_widget_destroy(compose->window);
return FALSE;
case MATCHACTION_BOUNCE:
account = account_find_from_id(action->account_id);
compose = compose_bounce(account, info);
if (compose->account->protocol == A_NNTP)
break;
else
compose_entry_append(compose, action->destination,
COMPOSE_TO);
val = compose_send(compose);
if (val == 0) {
gtk_widget_destroy(compose->window);
return TRUE;
}
gtk_widget_destroy(compose->window);
return FALSE;
case MATCHACTION_EXECUTE:
cmd = matching_build_command(action->unesc_destination, info);
if (cmd == NULL)
return FALSE;
else {
system(cmd);
g_free(cmd);
}
return TRUE;
default:
return FALSE;
}
}
static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *info)
{
return matcherlist_match(filtering->matchers, info);
}
static gboolean filtering_apply_rule(FilteringProp *filtering, MsgInfo *info,
GHashTable *foldertable)
{
gboolean result;
gchar actionstr;
gchar buf[50];
if (FALSE == (result = filteringaction_apply(filtering->action, info, foldertable))) {
g_warning(_("action %s could not be applied"),
filteringaction_to_string(buf, sizeof buf, filtering->action));
}
return result;
}
static gboolean filtering_is_final_action(FilteringProp *filtering)
{
switch(filtering->action->type) {
case MATCHACTION_MOVE:
case MATCHACTION_DELETE:
return TRUE; /* MsgInfo invalid for message */
case MATCHACTION_EXECUTE:
case MATCHACTION_COPY:
case MATCHACTION_MARK:
case MATCHACTION_MARK_AS_READ:
case MATCHACTION_UNMARK:
case MATCHACTION_MARK_AS_UNREAD:
case MATCHACTION_FORWARD:
case MATCHACTION_FORWARD_AS_ATTACHMENT:
case MATCHACTION_BOUNCE:
return FALSE; /* MsgInfo still valid for message */
default:
return FALSE;
}
}
static void filter_msginfo(GSList * filtering_list, FolderItem *inbox,
MsgInfo * info, GHashTable *folder_table)
{
GSList *l;
gboolean final;
gboolean applied;
gint val;
if (info == NULL) {
g_warning(_("msginfo is not set"));
return;
}
for (l = filtering_list, final = FALSE, applied = FALSE; l != NULL; l = g_slist_next(l)) {
FilteringProp * filtering = (FilteringProp *) l->data;
if (filtering_match_condition(filtering, info)) {
applied = filtering_apply_rule(filtering, info, folder_table);
if (TRUE == (final = filtering_is_final_action(filtering)))
break;
}
}
/* put in inbox if a final rule could not be applied, or
* the last rule was not a final one. */
if ((final && !applied) || !final) {
if (inbox != info->folder) {
if (folder_item_move_msg(inbox, info) == -1) {
debug_print(_("*** Could not drop message in inbox; check .processing\n"));
return;
}
if (folder_table) {
val = GPOINTER_TO_INT(g_hash_table_lookup
(folder_table, inbox));
if (val == 0) {
folder_item_scan(inbox);
g_hash_table_insert(folder_table, inbox,
GINT_TO_POINTER(1));
}
}
}
}
}
/*!
*\brief filters a message based on its message info data
*
*\param flist filter and actions list
*\param info message
*\param ftable table with changed folders after call
*/
void filter_message_by_msginfo_with_inbox(GSList *flist, MsgInfo *info, GHashTable *ftable, FolderItem *def_inbox)
{
FolderItem *inbox;
if ((def_inbox == NULL)) {
debug_print("using default inbox as final destination!\n");
inbox = folder_get_default_inbox();
} else
inbox = def_inbox;
/*
* message is already in a folder. the filtering code will
* handle duplicate moves and copies.
*/
filter_msginfo(flist, inbox, info, ftable);
}
void filter_message_by_msginfo(GSList *flist, MsgInfo *info, GHashTable *ftable)
{
filter_message_by_msginfo_with_inbox(flist, info, ftable, info->folder);
}
/*!
*\brief filters a message waiting to be processed in the
* .processing folder.
*
*\param filtering_list list of filters and actions
*\param inbox default inbox when no filter could be applied
*\param msgnum message number in processing folder
*\param folder_table table with folders that have been
* changed after the call to this function
*/
void filter_message(GSList *filtering_list, FolderItem *inbox,
gint msgnum, GHashTable *folder_table)
{
MsgInfo *msginfo;
gchar *filename;
MsgFlags msgflags = { 0, 0 };
FolderItem *item = folder_get_default_processing();
if (item == NULL) {
g_warning(_("folderitem not set"));
return;
}
filename = folder_item_fetch_msg(item, msgnum);
if (filename == NULL) {
g_warning(_("filename is not set"));
return;
}
msginfo = procheader_parse(filename, msgflags, TRUE, FALSE);
g_free(filename);
if (msginfo == NULL) {
g_warning(_("could not get info for %s"), filename);
return;
}
msginfo->folder = item;
msginfo->msgnum = msgnum;
filter_msginfo(filtering_list, inbox, msginfo, folder_table);
procmsg_msginfo_free(msginfo);
}
gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
{
gchar *command_str;
command_str = get_matchparser_tab_str(action->type);
if (command_str == NULL)
return NULL;
switch(action->type) {
case MATCHACTION_MOVE:
case MATCHACTION_COPY:
case MATCHACTION_EXECUTE:
g_snprintf(dest, destlen, "%s \"%s\"", command_str, action->destination);
return dest;
case MATCHACTION_DELETE:
case MATCHACTION_MARK:
case MATCHACTION_UNMARK:
case MATCHACTION_MARK_AS_READ:
case MATCHACTION_MARK_AS_UNREAD:
g_snprintf(dest, destlen, "%s", command_str);
return dest;
case MATCHACTION_BOUNCE:
case MATCHACTION_FORWARD:
case MATCHACTION_FORWARD_AS_ATTACHMENT:
g_snprintf(dest, destlen, "%s %d \"%s\"", command_str, action->account_id, action->destination);
return dest;
case MATCHACTION_COLOR:
g_snprintf(dest, destlen, "%s %d", command_str, action->labelcolor);
return dest;
case MATCHACTION_DELETE_ON_SERVER:
g_snprintf(dest, destlen, "%s", command_str);
return dest;
default:
return NULL;
}
}
gchar * filteringprop_to_string(FilteringProp * prop)
{
gchar *list_str;
gchar *action_str;
gchar *filtering_str;
gchar buf[256];
action_str = filteringaction_to_string(buf, sizeof buf, prop->action);
if (action_str == NULL)
return NULL;
list_str = matcherlist_to_string(prop->matchers);
if (list_str == NULL)
return NULL;
filtering_str = g_strconcat(list_str, " ", action_str, NULL);
g_free(list_str);
return filtering_str;
}
void prefs_filtering_free(GSList * prefs_filtering)
{
while (prefs_filtering != NULL) {
FilteringProp * filtering = (FilteringProp *)
prefs_filtering->data;
filteringprop_free(filtering);
prefs_filtering = g_slist_remove(prefs_filtering, filtering);
}
}
static gboolean prefs_filtering_free_func(GNode *node, gpointer data)
{
FolderItem *item = node->data;
if(!item->prefs)
return FALSE;
prefs_filtering_free(item->prefs->processing);
item->prefs->processing = NULL;
return FALSE;
}
void prefs_filtering_clear()
{
GList * cur;
for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
Folder *folder;
folder = (Folder *) cur->data;
g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
prefs_filtering_free_func, NULL);
}
prefs_filtering_free(global_processing);
global_processing = NULL;
}