kill score / folder scoring / buf fixed for local account prefs
[claws.git] / src / filtering.c
1 #include <ctype.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <errno.h>
5 #include <gtk/gtk.h>
6 #include <stdio.h>
7 #include "intl.h"
8 #include "utils.h"
9 #include "defs.h"
10 #include "procheader.h"
11 #include "matcher.h"
12 #include "filtering.h"
13 #include "prefs.h"
14 #include "compose.h"
15
16 #define PREFSBUFSIZE            1024
17
18 GSList * prefs_filtering = NULL;
19
20 FilteringAction * filteringaction_new(int type, int account_id,
21                                       gchar * destination)
22 {
23         FilteringAction * action;
24
25         action = g_new0(FilteringAction, 1);
26
27         action->type = type;
28         action->account_id = account_id;
29         if (destination)
30                 action->destination = g_strdup(destination);
31
32         return action;
33 }
34
35 void filteringaction_free(FilteringAction * action)
36 {
37         if (action->destination)
38                 g_free(action->destination);
39         g_free(action);
40 }
41
42 FilteringAction * filteringaction_parse(gchar ** str)
43 {
44         FilteringAction * action;
45         gchar * tmp;
46         gchar * destination = NULL;
47         gint account_id = 0;
48         gint key;
49
50         tmp = * str;
51
52         key = matcher_parse_keyword(&tmp);
53
54         switch (key) {
55         case MATCHING_ACTION_MOVE:
56                 destination = matcher_parse_str(&tmp);
57                 if (tmp == NULL) {
58                         * str = NULL;
59                         return NULL;
60                 }
61                 break;
62         case MATCHING_ACTION_COPY:
63                 destination = matcher_parse_str(&tmp);
64                 if (tmp == NULL) {
65                         * str = NULL;
66                         return NULL;
67                 }
68                 break;
69         case MATCHING_ACTION_DELETE:
70                 break;
71         case MATCHING_ACTION_MARK:
72                 break;
73         case MATCHING_ACTION_MARK_AS_READ:
74                 break;
75         case MATCHING_ACTION_UNMARK:
76                 break;
77         case MATCHING_ACTION_MARK_AS_UNREAD:
78                 break;
79         case MATCHING_ACTION_FORWARD:
80                 account_id = matcher_parse_number(&tmp);
81                 if (tmp == NULL) {
82                         * str = NULL;
83                         return NULL;
84                 }
85
86                 destination = matcher_parse_str(&tmp);
87                 if (tmp == NULL) {
88                         * str = NULL;
89                         return NULL;
90                 }
91
92                 break;
93         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
94                 account_id = matcher_parse_number(&tmp);
95                 if (tmp == NULL) {
96                         * str = NULL;
97                         return NULL;
98                 }
99
100                 destination = matcher_parse_str(&tmp);
101                 if (tmp == NULL) {
102                         * str = NULL;
103                         return NULL;
104                 }
105
106                 break;
107         default:
108                 * str = NULL;
109                 return NULL;
110         }
111
112         * str = tmp;
113         action = filteringaction_new(key, account_id, destination);
114
115         return action;
116 }
117
118 FilteringProp * filteringprop_parse(gchar ** str)
119 {
120         gchar * tmp;
121         gint key;
122         FilteringProp * filtering;
123         MatcherList * matchers;
124         FilteringAction * action;
125         
126         tmp = * str;
127
128         matchers = matcherlist_parse(&tmp);
129         if (tmp == NULL) {
130                 * str = NULL;
131                 return NULL;
132         }
133
134         if (tmp == NULL) {
135                 matcherlist_free(matchers);
136                 * str = NULL;
137                 return NULL;
138         }
139
140         action = filteringaction_parse(&tmp);
141         if (tmp == NULL) {
142                 matcherlist_free(matchers);
143                 * str = NULL;
144                 return NULL;
145         }
146
147         filtering = filteringprop_new(matchers, action);
148
149         * str = tmp;
150         return filtering;
151 }
152
153
154 FilteringProp * filteringprop_new(MatcherList * matchers,
155                                   FilteringAction * action)
156 {
157         FilteringProp * filtering;
158
159         filtering = g_new0(FilteringProp, 1);
160         filtering->matchers = matchers;
161         filtering->action = action;
162
163         return filtering;
164 }
165
166 void filteringprop_free(FilteringProp * prop)
167 {
168         matcherlist_free(prop->matchers);
169         filteringaction_free(prop->action);
170         g_free(prop);
171 }
172
173 static gboolean filteringaction_update_mark(MsgInfo * info)
174 {
175         gchar * dest_path;
176         FILE * fp;
177
178         if (info->folder->folder->type == F_MH) {
179                 dest_path = folder_item_get_path(info->folder);
180                 if (!is_dir_exist(dest_path))
181                         make_dir_hier(dest_path);
182                 
183                 if (dest_path == NULL) {
184                         g_warning(_("Can't open mark file.\n"));
185                         return FALSE;
186                 }
187                 
188                 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
189                     == NULL) {
190                         g_warning(_("Can't open mark file.\n"));
191                         return FALSE;
192                 }
193                 
194                 procmsg_write_flags(info, fp);
195                 fclose(fp);
196                 return TRUE;
197         }
198         return FALSE;
199 }
200
201 /*
202   fitleringaction_apply
203   runs the action on one MsgInfo
204   return value : return TRUE if the action could be applied
205 */
206
207 #define CHANGE_FLAGS(msginfo) \
208 { \
209 if (msginfo->folder->folder->change_flags != NULL) \
210 msginfo->folder->folder->change_flags(msginfo->folder->folder, \
211                                       msginfo->folder, \
212                                       msginfo); \
213 }
214
215 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info,
216                                       GHashTable *folder_table)
217 {
218         FolderItem * dest_folder;
219         gint val;
220         Compose * compose;
221         PrefsAccount * account;
222
223         switch(action->type) {
224         case MATCHING_ACTION_MOVE:
225                 dest_folder =
226                         folder_find_item_from_identifier(action->destination);
227                 if (!dest_folder)
228                         return FALSE;
229
230                 if (folder_item_move_msg(dest_folder, info) == -1)
231                         return FALSE;
232
233                 info->flags = 0;
234                 filteringaction_update_mark(info);
235                 
236                 if (folder_table) {
237                         val = GPOINTER_TO_INT(g_hash_table_lookup
238                                               (folder_table, dest_folder));
239                         if (val == 0) {
240                                 folder_item_scan(dest_folder);
241                                 g_hash_table_insert(folder_table, dest_folder,
242                                                     GINT_TO_POINTER(1));
243                         }
244                         val = GPOINTER_TO_INT(g_hash_table_lookup
245                                               (folder_table, info->folder));
246                         if (val == 0) {
247                                 folder_item_scan(info->folder);
248                                 g_hash_table_insert(folder_table, info->folder,
249                                                     GINT_TO_POINTER(1));
250                         }
251                 }
252
253                 return TRUE;
254
255         case MATCHING_ACTION_COPY:
256                 dest_folder =
257                         folder_find_item_from_identifier(action->destination);
258                 if (!dest_folder)
259                         return FALSE;
260
261                 if (folder_item_copy_msg(dest_folder, info) == -1)
262                         return FALSE;
263
264                 if (folder_table) {
265                         val = GPOINTER_TO_INT(g_hash_table_lookup
266                                               (folder_table, dest_folder));
267                         if (val == 0) {
268                                 folder_item_scan(dest_folder);
269                                 g_hash_table_insert(folder_table, dest_folder,
270                                                     GINT_TO_POINTER(1));
271                         }
272                 }
273                         
274                 return TRUE;
275
276         case MATCHING_ACTION_DELETE:
277                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
278                         return FALSE;
279
280                 info->flags = 0;
281                 filteringaction_update_mark(info);
282
283                 return TRUE;
284
285         case MATCHING_ACTION_MARK:
286                 MSG_SET_FLAGS(info->flags, MSG_MARKED);
287                 filteringaction_update_mark(info);
288
289                 CHANGE_FLAGS(info);
290
291                 return TRUE;
292
293         case MATCHING_ACTION_UNMARK:
294                 MSG_UNSET_FLAGS(info->flags, MSG_MARKED);
295                 filteringaction_update_mark(info);
296
297                 CHANGE_FLAGS(info);
298
299                 return TRUE;
300                 
301         case MATCHING_ACTION_MARK_AS_READ:
302                 MSG_UNSET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
303                 filteringaction_update_mark(info);
304
305                 CHANGE_FLAGS(info);
306
307                 return TRUE;
308
309         case MATCHING_ACTION_MARK_AS_UNREAD:
310                 MSG_SET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
311                 filteringaction_update_mark(info);
312
313                 CHANGE_FLAGS(info);
314                 
315                 return TRUE;
316
317         case MATCHING_ACTION_FORWARD:
318
319                 account = account_find_from_id(action->account_id);
320                 compose = compose_forward(account, info, FALSE);
321                 if (compose->account->protocol == A_NNTP)
322                         compose_entry_append(compose, action->destination,
323                                              COMPOSE_NEWSGROUPS);
324                 else
325                         compose_entry_append(compose, action->destination,
326                                              COMPOSE_TO);
327
328                 val = compose_send(compose);
329                 if (val == 0) {
330                         gtk_widget_destroy(compose->window);
331                         return TRUE;
332                 }
333
334                 gtk_widget_destroy(compose->window);
335                 return FALSE;
336
337         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
338
339                 account = account_find_from_id(action->account_id);
340                 compose = compose_forward(account, info, TRUE);
341                 if (compose->account->protocol == A_NNTP)
342                         compose_entry_append(compose, action->destination,
343                                              COMPOSE_NEWSGROUPS);
344                 else
345                         compose_entry_append(compose, action->destination,
346                                              COMPOSE_TO);
347
348                 val = compose_send(compose);
349                 if (val == 0) {
350                         gtk_widget_destroy(compose->window);
351                         return TRUE;
352                 }
353
354                 gtk_widget_destroy(compose->window);
355                 return FALSE;
356
357         default:
358                 return FALSE;
359         }
360 }
361
362 /*
363   filteringprop_apply
364   runs the action on one MsgInfo if it matches the criterium
365   return value : return TRUE if the action doesn't allow more actions
366 */
367
368 static gboolean filteringprop_apply(FilteringProp * filtering, MsgInfo * info,
369                                     GHashTable *folder_table)
370 {
371         if (matcherlist_match(filtering->matchers, info)) {
372                 gint result;
373                 gchar * action_str;
374
375                 result = TRUE;
376
377                 result = filteringaction_apply(filtering->action, info,
378                                                folder_table);
379                 action_str =
380                         filteringaction_to_string(filtering->action);
381                 if (!result) {
382                         g_warning(_("action %s could not be applied"),
383                                   action_str);
384                 }
385                 else {
386                         debug_print(_("message %i %s..."),
387                                       info->msgnum, action_str);
388                 }
389
390                 g_free(action_str);
391
392                 switch(filtering->action->type) {
393                 case MATCHING_ACTION_MOVE:
394                 case MATCHING_ACTION_DELETE:
395                         return TRUE;
396                 case MATCHING_ACTION_COPY:
397                 case MATCHING_ACTION_MARK:
398                 case MATCHING_ACTION_MARK_AS_READ:
399                 case MATCHING_ACTION_UNMARK:
400                 case MATCHING_ACTION_MARK_AS_UNREAD:
401                 case MATCHING_ACTION_FORWARD:
402                 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
403                         return FALSE;
404                 default:
405                         return FALSE;
406                 }
407         }
408         else
409                 return FALSE;
410 }
411
412 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
413                     GHashTable *folder_table)
414 {
415         GSList * l;
416
417         if (info == NULL) {
418                 g_warning(_("msginfo is not set"));
419                 return;
420         }
421         
422         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
423                 FilteringProp * filtering = (FilteringProp *) l->data;
424                 
425                 if (filteringprop_apply(filtering, info, folder_table))
426                         break;
427         }
428 }
429
430 void filter_msginfo_move_or_delete(GSList * filtering_list, MsgInfo * info,
431                                    GHashTable *folder_table)
432 {
433         GSList * l;
434
435         if (info == NULL) {
436                 g_warning(_("msginfo is not set"));
437                 return;
438         }
439         
440         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
441                 FilteringProp * filtering = (FilteringProp *) l->data;
442
443                 switch (filtering->action->type) {
444                 case MATCHING_ACTION_MOVE:
445                 case MATCHING_ACTION_DELETE:
446                         if (filteringprop_apply(filtering, info, folder_table))
447                                 return;
448                 }
449         }
450 }
451
452 void filter_message(GSList * filtering_list, FolderItem * item,
453                     gint msgnum, GHashTable *folder_table)
454 {
455         MsgInfo * msginfo;
456         gchar * filename;
457
458         if (item == NULL) {
459                 g_warning(_("folderitem not set"));
460                 return;
461         }
462
463         filename = folder_item_fetch_msg(item, msgnum);
464
465         if (filename == NULL) {
466                 g_warning(_("filename is not set"));
467                 return;
468         }
469
470         msginfo = procheader_parse(filename, 0, TRUE);
471
472         g_free(filename);
473
474         if (msginfo == NULL) {
475                 g_warning(_("could not get info for %s"), filename);
476                 return;
477         }
478
479         msginfo->folder = item;
480         msginfo->msgnum = msgnum;
481
482         filter_msginfo(filtering_list, msginfo, folder_table);
483 }
484
485 void prefs_filtering_read_config(void)
486 {
487         gchar *rcpath;
488         FILE *fp;
489         gchar buf[PREFSBUFSIZE];
490
491         debug_print(_("Reading filtering configuration...\n"));
492
493         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
494                              FILTERING_RC, NULL);
495         if ((fp = fopen(rcpath, "r")) == NULL) {
496                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
497                 g_free(rcpath);
498                 prefs_filtering = NULL;
499                 return;
500         }
501         g_free(rcpath);
502
503         /* remove all filtering */
504         while (prefs_filtering != NULL) {
505                 FilteringProp * filtering =
506                         (FilteringProp *) prefs_filtering->data;
507                 filteringprop_free(filtering);
508                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
509         }
510
511         while (fgets(buf, sizeof(buf), fp) != NULL) {
512                 FilteringProp * filtering;
513                 gchar * tmp;
514
515                 g_strchomp(buf);
516
517                 if ((*buf != '#') && (*buf != '\0')) {
518                         tmp = buf;
519                         filtering = filteringprop_parse(&tmp);
520                         if (tmp != NULL) {
521                                 prefs_filtering =
522                                         g_slist_append(prefs_filtering,
523                                                        filtering);
524                         }
525                         else {
526                                 /* debug */
527                                 g_warning(_("syntax error : %s\n"), buf);
528                         }
529                 }
530         }
531
532         fclose(fp);
533 }
534
535 gchar * filteringaction_to_string(FilteringAction * action)
536 {
537         gchar * command_str;
538         gint i;
539         gchar * account_id_str;
540
541         command_str = NULL;
542         command_str = get_matchparser_tab_str(action->type);
543
544         if (command_str == NULL)
545                 return NULL;
546
547         switch(action->type) {
548         case MATCHING_ACTION_MOVE:
549         case MATCHING_ACTION_COPY:
550                 return g_strconcat(command_str, " \"", action->destination,
551                                    "\"", NULL);
552
553         case MATCHING_ACTION_DELETE:
554         case MATCHING_ACTION_MARK:
555         case MATCHING_ACTION_UNMARK:
556         case MATCHING_ACTION_MARK_AS_READ:
557         case MATCHING_ACTION_MARK_AS_UNREAD:
558                 return g_strdup(command_str);
559                 break;
560
561         case MATCHING_ACTION_FORWARD:
562         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
563                 account_id_str = itos(action->account_id);
564                 return g_strconcat(command_str, " ", account_id_str,
565                                    " \"", action->destination, "\"", NULL);
566
567         default:
568                 return NULL;
569         }
570 }
571
572 gchar * filteringprop_to_string(FilteringProp * prop)
573 {
574         gchar * list_str;
575         gchar * action_str;
576         gchar * filtering_str;
577
578         action_str = filteringaction_to_string(prop->action);
579
580         if (action_str == NULL)
581                 return NULL;
582
583         list_str = matcherlist_to_string(prop->matchers);
584
585         if (list_str == NULL) {
586                 g_free(action_str);
587                 return NULL;
588         }
589
590         filtering_str = g_strconcat(list_str, " ", action_str, NULL);
591         g_free(list_str);
592         g_free(action_str);
593
594         return filtering_str;
595 }
596
597 void prefs_filtering_write_config(void)
598 {
599         gchar *rcpath;
600         PrefFile *pfile;
601         GSList *cur;
602         FilteringProp * prop;
603
604         debug_print(_("Writing filtering configuration...\n"));
605
606         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
607
608         if ((pfile = prefs_write_open(rcpath)) == NULL) {
609                 g_warning(_("failed to write configuration to file\n"));
610                 g_free(rcpath);
611                 return;
612         }
613
614         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
615                 gchar *filtering_str;
616
617                 prop = (FilteringProp *) cur->data;
618                 filtering_str = filteringprop_to_string(prop);
619                 if (fputs(filtering_str, pfile->fp) == EOF ||
620                     fputc('\n', pfile->fp) == EOF) {
621                         FILE_OP_ERROR(rcpath, "fputs || fputc");
622                         prefs_write_close_revert(pfile);
623                         g_free(rcpath);
624                         g_free(filtering_str);
625                         return;
626                 }
627                 g_free(filtering_str);
628         }
629
630         g_free(rcpath);
631
632         if (prefs_write_close(pfile) < 0) {
633                 g_warning(_("failed to write configuration to file\n"));
634                 return;
635         }
636 }