2008-06-09 [colin] 3.4.0cvs83
[claws.git] / src / filtering.c
index 5dc72a7a4dc5744cee16147f011ca3408ad87e98..b3eb8b54f30b63f50593c48b4c08771fc03e5ba3 100644 (file)
@@ -4,7 +4,7 @@
  *
  * 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
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
@@ -13,8 +13,8 @@
  * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * 
  */
 
 #include "defs.h"
@@ -36,6 +36,7 @@
 #include "prefs_common.h"
 #include "addrbook.h"
 #include "addr_compl.h"
+#include "tags.h"
 #include "log.h"
 
 #define PREFSBUFSIZE           1024
@@ -56,7 +57,7 @@ static inline gint strlen_with_check(const gchar *expr, gint fline, const gchar
         if (str) 
                return strlen(str);
        else {
-               debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr);
+               debug_print("%s(%d) - invalid string %s\n", __FILE__, fline, expr?expr:"(null)");
                return 0;
        }
 }
@@ -179,19 +180,13 @@ void filteringprop_free(FilteringProp * prop)
        g_free(prop);
 }
 
-void filtering_move_and_copy_msg(MsgInfo *msginfo)
-{
-       GSList *list = g_slist_append(NULL, msginfo);
-       filtering_move_and_copy_msgs(list);
-       g_slist_free(list);
-}
-
 /* move and copy messages by batches to be faster on IMAP */
 void filtering_move_and_copy_msgs(GSList *msgs)
 {
        GSList *messages = g_slist_copy(msgs);
        FolderItem *last_item = NULL;
-       gboolean is_copy = FALSE, is_move = FALSE;
+       FiltOp cur_op = IS_NOTHING;
+
        debug_print("checking %d messages\n", g_slist_length(msgs));
        while (messages) {
                GSList *batch = NULL, *cur;
@@ -199,54 +194,71 @@ void filtering_move_and_copy_msgs(GSList *msgs)
                for (cur = messages; cur; cur = cur->next) {
                        MsgInfo *info = (MsgInfo *)cur->data;
                        if (last_item == NULL) {
-                               last_item = info->to_filter_folder;
+                               if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE)
+                                       last_item = info->to_filter_folder;
+                               else if (info->filter_op == IS_DELE)
+                                       last_item = info->folder;
                        }
                        if (last_item == NULL)
                                continue;
-                       if (!is_copy && !is_move) {
-                               if (info->is_copy)
-                                       is_copy = TRUE;
-                               else if (info->is_move)
-                                       is_move = TRUE;
+                       if (cur_op == IS_NOTHING) {
+                               if (info->filter_op == IS_COPY)
+                                       cur_op = IS_COPY;
+                               else if (info->filter_op == IS_MOVE)
+                                       cur_op = IS_MOVE;
+                               else if (info->filter_op == IS_DELE)
+                                       cur_op = IS_DELE;
                        }
-                       found++;
-                       if (info->to_filter_folder == last_item 
-                       &&  info->is_copy == is_copy
-                       &&  info->is_move == is_move) {
-                               batch = g_slist_prepend(batch, info);
+                       if (info->filter_op == IS_COPY || info->filter_op == IS_MOVE) {
+                               if (info->to_filter_folder == last_item 
+                               &&  cur_op == info->filter_op) {
+                                       found++;
+                                       batch = g_slist_prepend(batch, info);
+                               }
+                       } else if (info->filter_op == IS_DELE) {
+                               if (info->folder == last_item 
+                               &&  cur_op == info->filter_op) {
+                                       found++;
+                                       batch = g_slist_prepend(batch, info);
+                               }
                        }
                }
                if (found == 0) {
-                       debug_print("no more messages to move/copy\n");
+                       debug_print("no more messages to move/copy/del\n");
                        break;
                } else {
                        debug_print("%d messages to %s in %s\n", found,
-                               is_copy ? "copy":"move", last_item->name ? last_item->name:"(noname)");
+                               cur_op==IS_COPY ? "copy":(cur_op==IS_DELE ?"delete":"move"), 
+                               last_item?(last_item->name ? last_item->name:"(noname)"):"nowhere");
                }
                for (cur = batch; cur; cur = cur->next) {
                        MsgInfo *info = (MsgInfo *)cur->data;
                        messages = g_slist_remove(messages, info);
+                       info->to_filter_folder = NULL;
+                       info->filter_op = IS_NOTHING;
                }
                batch = g_slist_reverse(batch);
                if (g_slist_length(batch)) {
                        MsgInfo *info = (MsgInfo *)batch->data;
-                       if (is_copy && last_item != info->folder) {
+                       if (cur_op == IS_COPY && last_item != info->folder) {
                                folder_item_copy_msgs(last_item, batch);
-                       } else if (is_move && last_item != info->folder) {
+                       } else if (cur_op == IS_MOVE && last_item != info->folder) {
                                if (folder_item_move_msgs(last_item, batch) < 0)
                                        folder_item_move_msgs(
                                                folder_get_default_inbox(), 
                                                batch);
+                       } else if (cur_op == IS_DELE && last_item == info->folder) {
+                               folder_item_remove_msgs(last_item, batch);
                        }
                        /* we don't reference the msginfos, because caller will do */
                        if (prefs_common.real_time_sync)
                                folder_item_synchronise(last_item);
                        g_slist_free(batch);
                        batch = NULL;
+                       GTK_EVENTS_FLUSH();
                }
                last_item = NULL;
-               is_copy = FALSE;
-               is_move = FALSE;
+               cur_op = IS_NOTHING;
        }
        /* we don't reference the msginfos, because caller will do */
        g_slist_free(messages);
@@ -258,6 +270,14 @@ void filtering_move_and_copy_msgs(GSList *msgs)
   return value : return TRUE if the action could be applied
 */
 
+#define FLUSH_COPY_IF_NEEDED(info) {                                   \
+       if (info->filter_op == IS_COPY && info->to_filter_folder) {     \
+               debug_print("must debatch pending copy\n");             \
+               folder_item_copy_msg(info->to_filter_folder, info);     \
+               info->filter_op = IS_NOTHING;                           \
+       }                                                               \
+}
+
 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
 {
        FolderItem * dest_folder;
@@ -272,19 +292,13 @@ static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
                        folder_find_item_from_identifier(action->destination);
                if (!dest_folder) {
                        debug_print("*** folder not found '%s'\n",
-                               action->destination ?action->destination :"");
+                               action->destination ?action->destination :"(null)");
                        return FALSE;
                }
                
-               /* check if mail is set to copy already, 
-                * in which case we have to do it */
-               if (info->is_copy && info->to_filter_folder) {
-                       debug_print("should cp and mv !\n");
-                       folder_item_copy_msg(info->to_filter_folder, info);
-                       info->is_copy = FALSE;
-               }
+               FLUSH_COPY_IF_NEEDED(info);
                /* mark message to be moved */          
-               info->is_move = TRUE;
+               info->filter_op = IS_MOVE;
                info->to_filter_folder = dest_folder;
                return TRUE;
 
@@ -294,66 +308,86 @@ static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
 
                if (!dest_folder) {
                        debug_print("*** folder not found '%s'\n",
-                               action->destination ?action->destination :"");
+                               action->destination ?action->destination :"(null)");
                        return FALSE;
                }
 
-               /* check if mail is set to copy already, 
-                * in which case we have to do it */
-               if (info->is_copy && info->to_filter_folder) {
-                       debug_print("should cp and mv !\n");
-                       folder_item_copy_msg(info->to_filter_folder, info);
-                       info->is_copy = FALSE;
-               }
+               FLUSH_COPY_IF_NEEDED(info);
                /* mark message to be copied */         
-               info->is_copy = TRUE;
+               info->filter_op = IS_COPY;
                info->to_filter_folder = dest_folder;
                return TRUE;
 
-       case MATCHACTION_DELETE:
-               if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
+       case MATCHACTION_SET_TAG:
+       case MATCHACTION_UNSET_TAG:
+               val = tags_get_id_for_str(action->destination);
+               if (val == -1) {
+                       debug_print("*** tag '%s' not found\n",
+                               action->destination ?action->destination :"(null)");
                        return FALSE;
+               }
+               FLUSH_COPY_IF_NEEDED(info);
+               procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
+               return TRUE;
+
+       case MATCHACTION_CLEAR_TAGS:
+               FLUSH_COPY_IF_NEEDED(info);
+               procmsg_msginfo_clear_tags(info);
+               return TRUE;
+
+       case MATCHACTION_DELETE:
+               FLUSH_COPY_IF_NEEDED(info);
+               info->filter_op = IS_DELE;
                return TRUE;
 
        case MATCHACTION_MARK:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_msginfo_set_flags(info, MSG_MARKED, 0);
                return TRUE;
 
        case MATCHACTION_UNMARK:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_msginfo_unset_flags(info, MSG_MARKED, 0);
                return TRUE;
 
        case MATCHACTION_LOCK:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_msginfo_set_flags(info, MSG_LOCKED, 0);
                return TRUE;
 
        case MATCHACTION_UNLOCK:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_msginfo_unset_flags(info, MSG_LOCKED, 0);       
                return TRUE;
                
        case MATCHACTION_MARK_AS_READ:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_msginfo_unset_flags(info, MSG_UNREAD | MSG_NEW, 0);
                return TRUE;
 
        case MATCHACTION_MARK_AS_UNREAD:
-               procmsg_msginfo_set_flags(info, MSG_UNREAD | MSG_NEW, 0);
+               FLUSH_COPY_IF_NEEDED(info);
+               procmsg_msginfo_set_flags(info, MSG_UNREAD, 0);
                return TRUE;
        
        case MATCHACTION_MARK_AS_SPAM:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_spam_learner_learn(info, NULL, TRUE);
                procmsg_msginfo_change_flags(info, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
                if (procmsg_spam_get_folder(info)) {
-                       info->is_move = TRUE;
+                       info->filter_op = IS_MOVE;
                        info->to_filter_folder = procmsg_spam_get_folder(info);
                }
                return TRUE;
 
        case MATCHACTION_MARK_AS_HAM:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_spam_learner_learn(info, NULL, FALSE);
                procmsg_msginfo_unset_flags(info, MSG_SPAM, 0);
                return TRUE;
        
        case MATCHACTION_COLOR:
+               FLUSH_COPY_IF_NEEDED(info);
                procmsg_msginfo_unset_flags(info, MSG_CLABEL_FLAG_MASK, 0); 
                procmsg_msginfo_set_flags(info, MSG_COLORLABEL_TO_FLAGS(action->labelcolor), 0);
                return TRUE;
@@ -397,10 +431,12 @@ static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
                return TRUE;
 
        case MATCHACTION_SET_SCORE:
+               FLUSH_COPY_IF_NEEDED(info);
                info->score = action->score;
                return TRUE;
 
        case MATCHACTION_CHANGE_SCORE:
+               FLUSH_COPY_IF_NEEDED(info);
                info->score += action->score;
                return TRUE;
 
@@ -408,13 +444,20 @@ static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
                 return FALSE;
 
        case MATCHACTION_HIDE:
+               FLUSH_COPY_IF_NEEDED(info);
                 info->hidden = TRUE;
                 return TRUE;
 
        case MATCHACTION_IGNORE:
+               FLUSH_COPY_IF_NEEDED(info);
                 procmsg_msginfo_set_flags(info, MSG_IGNORE_THREAD, 0);
                 return TRUE;
 
+       case MATCHACTION_WATCH:
+               FLUSH_COPY_IF_NEEDED(info);
+                procmsg_msginfo_set_flags(info, MSG_WATCH_THREAD, 0);
+                return TRUE;
+
        case MATCHACTION_ADD_TO_ADDRESSBOOK:
                {
                        AddressDataSource *book = NULL;
@@ -446,7 +489,7 @@ static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
                                gchar *path = NULL;
 
                                if (action->destination == NULL ||
-                                               strcasecmp(action->destination, _("Any")) == 0 ||
+                                               strcasecmp(action->destination, "Any") == 0 ||
                                                *(action->destination) == '\0')
                                        path = NULL;
                                else
@@ -561,8 +604,8 @@ static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *inf
                                                log_status_skip(LOG_DEBUG_FILTERING,
                                                                _("rule is account-based [id=%d, name='%s'], "
                                                                "not matching the account currently used to retrieve messages [id=%d, name='%s']\n"),
-                                                               filtering->account_id, account->account_name,
-                                                               ac_prefs->account_id, ac_prefs->account_name);
+                                                               filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
+                                                               ac_prefs->account_id, ac_prefs?ac_prefs->account_name:_("NON_EXISTENT"));
                                        }
                                }
                        }
@@ -586,7 +629,7 @@ static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *inf
                                                log_status_ok(LOG_DEBUG_FILTERING,
                                                                _("rule is account-based [id=%d, name='%s'], "
                                                                "but all rules are applied on user request\n"),
-                                                               filtering->account_id, account->account_name);
+                                                               filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
                                        }
                                }
                        }
@@ -604,7 +647,7 @@ static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *inf
                                                log_status_skip(LOG_DEBUG_FILTERING,
                                                                _("rule is account-based [id=%d, name='%s'], "
                                                                "skipped on user request\n"),
-                                                               filtering->account_id, account->account_name);
+                                                               filtering->account_id, account?account->account_name:_("NON_EXISTENT"));
                                        } else {
                                                log_status_skip(LOG_DEBUG_FILTERING,
                                                                _("rule is account-based, "
@@ -631,8 +674,8 @@ static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *inf
                                                log_status_skip(LOG_DEBUG_FILTERING,
                                                                _("rule is account-based [id=%d, name='%s'], "
                                                                "not matching current account [id=%d, name='%s']\n"),
-                                                               filtering->account_id, account->account_name,
-                                                               cur_account->account_id, cur_account->account_name);
+                                                               filtering->account_id, account?account->account_name:_("NON_EXISTENT"),
+                                                               cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
                                        } else {
                                                log_status_skip(LOG_DEBUG_FILTERING,
                                                                _("rule is account-based, "
@@ -649,8 +692,8 @@ static gboolean filtering_match_condition(FilteringProp *filtering, MsgInfo *inf
                                                        log_status_ok(LOG_DEBUG_FILTERING,
                                                                        _("rule is account-based [id=%d, name='%s'], "
                                                                        "current account [id=%d, name='%s']\n"),
-                                                                       account->account_id, account->account_name,
-                                                                       cur_account->account_id, cur_account->account_name);
+                                                                       account->account_id, account?account->account_name:_("NON_EXISTENT"),
+                                                                       cur_account->account_id, cur_account?cur_account->account_name:_("NON_EXISTENT"));
                                                }
                                        }
                                }
@@ -803,6 +846,8 @@ static gboolean filter_msginfo(GSList * filtering_list, MsgInfo * info, PrefsAcc
 gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* ac_prefs,
                                                                   FilteringInvocationType context, gchar *extra_info)
 {
+       gboolean ret;
+
        if (prefs_common.enable_filtering_debug) {
                gchar *tmp = _("undetermined");
 
@@ -839,10 +884,12 @@ gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* a
                        if (prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_MED) {
                                log_print(LOG_DEBUG_FILTERING,
                                                _("filtering message (%s%s%s)\n"
-                                               "%smessage file: %s\n%sDate: %s\n%sFrom: %s\n%sTo: %s\n%sSubject: %s\n"),
+                                               "%smessage file: %s\n%s%s %s\n%s%s %s\n%s%s %s\n%s%s %s\n"),
                                                tmp, extra_info ? _(": ") : "", extra_info ? extra_info : "",
-                                               spc, file, spc, info->date, spc, info->from,
-                                               spc, info->to, spc, info->subject);
+                                               spc, file, spc, prefs_common_translated_header_name("Date:"), info->date,
+                                               spc, prefs_common_translated_header_name("From:"), info->from,
+                                               spc, prefs_common_translated_header_name("To:"), info->to,
+                                               spc, prefs_common_translated_header_name("Subject:"), info->subject);
                        } else {
                                log_print(LOG_DEBUG_FILTERING,
                                                _("filtering message (%s%s%s)\n"
@@ -855,7 +902,10 @@ gboolean filter_message_by_msginfo(GSList *flist, MsgInfo *info, PrefsAccount* a
                }
        } else
                debug_filtering_session = FALSE;
-       return filter_msginfo(flist, info, ac_prefs);
+
+       ret = filter_msginfo(flist, info, ac_prefs);
+       debug_filtering_session = FALSE;
+       return ret;
 }
 
 gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *action)
@@ -873,6 +923,8 @@ gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *act
        case MATCHACTION_MOVE:
        case MATCHACTION_COPY:
        case MATCHACTION_EXECUTE:
+       case MATCHACTION_SET_TAG:
+       case MATCHACTION_UNSET_TAG:
                quoted_dest = matcher_quote_str(action->destination);
                g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
                g_free(quoted_dest);
@@ -890,6 +942,8 @@ gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *act
        case MATCHACTION_STOP:
        case MATCHACTION_HIDE:
        case MATCHACTION_IGNORE:
+       case MATCHACTION_WATCH:
+       case MATCHACTION_CLEAR_TAGS:
                g_snprintf(dest, destlen, "%s", command_str);
                return dest;
 
@@ -978,7 +1032,7 @@ gchar * filteringprop_to_string(FilteringProp * prop)
        return filtering_str;
 }
 
-void prefs_filtering_free(GSList * prefs_filtering)
+static void prefs_filtering_free(GSList * prefs_filtering)
 {
        while (prefs_filtering != NULL) {
                FilteringProp * filtering = (FilteringProp *)