global names for folder / read permission for folders / filtering to mbox
[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                 val = GPOINTER_TO_INT(g_hash_table_lookup
237                                       (folder_table, dest_folder));
238                 if (val == 0) {
239                         folder_item_scan(dest_folder);
240                         g_hash_table_insert(folder_table, dest_folder,
241                                             GINT_TO_POINTER(1));
242                 }
243                 val = GPOINTER_TO_INT(g_hash_table_lookup
244                                       (folder_table, info->folder));
245                 if (val == 0) {
246                         folder_item_scan(info->folder);
247                         g_hash_table_insert(folder_table, info->folder,
248                                             GINT_TO_POINTER(1));
249                 }
250
251                 return TRUE;
252
253         case MATCHING_ACTION_COPY:
254                 dest_folder =
255                         folder_find_item_from_identifier(action->destination);
256                 if (!dest_folder)
257                         return FALSE;
258
259                 if (folder_item_copy_msg(dest_folder, info) == -1)
260                         return FALSE;
261
262                 val = GPOINTER_TO_INT(g_hash_table_lookup
263                                       (folder_table, dest_folder));
264                 if (val == 0) {
265                         folder_item_scan(dest_folder);
266                         g_hash_table_insert(folder_table, dest_folder,
267                                             GINT_TO_POINTER(1));
268                 }
269
270                 return TRUE;
271
272         case MATCHING_ACTION_DELETE:
273                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
274                         return FALSE;
275
276                 info->flags = 0;
277                 filteringaction_update_mark(info);
278
279                 return TRUE;
280
281         case MATCHING_ACTION_MARK:
282                 MSG_SET_FLAGS(info->flags, MSG_MARKED);
283                 filteringaction_update_mark(info);
284
285                 CHANGE_FLAGS(info);
286
287                 return TRUE;
288
289         case MATCHING_ACTION_UNMARK:
290                 MSG_UNSET_FLAGS(info->flags, MSG_MARKED);
291                 filteringaction_update_mark(info);
292
293                 CHANGE_FLAGS(info);
294
295                 return TRUE;
296                 
297         case MATCHING_ACTION_MARK_AS_READ:
298                 MSG_UNSET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
299                 filteringaction_update_mark(info);
300
301                 CHANGE_FLAGS(info);
302
303                 return TRUE;
304
305         case MATCHING_ACTION_MARK_AS_UNREAD:
306                 MSG_SET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
307                 filteringaction_update_mark(info);
308
309                 CHANGE_FLAGS(info);
310                 
311                 return TRUE;
312
313         case MATCHING_ACTION_FORWARD:
314
315                 account = account_find_from_id(action->account_id);
316                 compose = compose_forward(account, info, FALSE);
317                 if (compose->account->protocol == A_NNTP)
318                         compose_entry_append(compose, action->destination,
319                                              COMPOSE_NEWSGROUPS);
320                 else
321                         compose_entry_append(compose, action->destination,
322                                              COMPOSE_TO);
323
324                 val = compose_send(compose);
325                 if (val == 0) {
326                         gtk_widget_destroy(compose->window);
327                         return TRUE;
328                 }
329
330                 gtk_widget_destroy(compose->window);
331                 return FALSE;
332
333         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
334
335                 account = account_find_from_id(action->account_id);
336                 compose = compose_forward(account, info, TRUE);
337                 if (compose->account->protocol == A_NNTP)
338                         compose_entry_append(compose, action->destination,
339                                              COMPOSE_NEWSGROUPS);
340                 else
341                         compose_entry_append(compose, action->destination,
342                                              COMPOSE_TO);
343
344                 val = compose_send(compose);
345                 if (val == 0) {
346                         gtk_widget_destroy(compose->window);
347                         return TRUE;
348                 }
349
350                 gtk_widget_destroy(compose->window);
351                 return FALSE;
352
353         default:
354                 return FALSE;
355         }
356 }
357
358 /*
359   filteringprop_apply
360   runs the action on one MsgInfo if it matches the criterium
361   return value : return TRUE if the action doesn't allow more actions
362 */
363
364 static gboolean filteringprop_apply(FilteringProp * filtering, MsgInfo * info,
365                                     GHashTable *folder_table)
366 {
367         if (matcherlist_match(filtering->matchers, info)) {
368                 gint result;
369                 gchar * action_str;
370
371                 result = TRUE;
372
373                 result = filteringaction_apply(filtering->action, info,
374                                                folder_table);
375                 action_str =
376                         filteringaction_to_string(filtering->action);
377                 if (!result) {
378                         g_warning(_("action %s could not be applied"),
379                                   action_str);
380                 }
381                 else {
382                         debug_print(_("message %i %s..."),
383                                       info->msgnum, action_str);
384                 }
385
386                 g_free(action_str);
387
388                 switch(filtering->action->type) {
389                 case MATCHING_ACTION_MOVE:
390                 case MATCHING_ACTION_DELETE:
391                         return TRUE;
392                 case MATCHING_ACTION_COPY:
393                 case MATCHING_ACTION_MARK:
394                 case MATCHING_ACTION_MARK_AS_READ:
395                 case MATCHING_ACTION_UNMARK:
396                 case MATCHING_ACTION_MARK_AS_UNREAD:
397                 case MATCHING_ACTION_FORWARD:
398                 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
399                         return FALSE;
400                 default:
401                         return FALSE;
402                 }
403         }
404         else
405                 return FALSE;
406 }
407
408 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
409                     GHashTable *folder_table)
410 {
411         GSList * l;
412
413         if (info == NULL) {
414                 g_warning(_("msginfo is not set"));
415                 return;
416         }
417         
418         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
419                 FilteringProp * filtering = (FilteringProp *) l->data;
420                 
421                 if (filteringprop_apply(filtering, info, folder_table))
422                         break;
423         }
424 }
425
426 void filter_message(GSList * filtering_list, FolderItem * item,
427                     gint msgnum, GHashTable *folder_table)
428 {
429         MsgInfo * msginfo;
430         gchar * filename;
431
432         if (item == NULL) {
433                 g_warning(_("folderitem not set"));
434                 return;
435         }
436
437         filename = folder_item_fetch_msg(item, msgnum);
438
439         if (filename == NULL) {
440                 g_warning(_("filename is not set"));
441                 return;
442         }
443
444         msginfo = procheader_parse(filename, 0, TRUE);
445
446         g_free(filename);
447
448         if (msginfo == NULL) {
449                 g_warning(_("could not get info for %s"), filename);
450                 return;
451         }
452
453         msginfo->folder = item;
454         msginfo->msgnum = msgnum;
455
456         filter_msginfo(filtering_list, msginfo, folder_table);
457 }
458
459 void prefs_filtering_read_config(void)
460 {
461         gchar *rcpath;
462         FILE *fp;
463         gchar buf[PREFSBUFSIZE];
464
465         debug_print(_("Reading filtering configuration...\n"));
466
467         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
468                              FILTERING_RC, NULL);
469         if ((fp = fopen(rcpath, "r")) == NULL) {
470                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
471                 g_free(rcpath);
472                 prefs_filtering = NULL;
473                 return;
474         }
475         g_free(rcpath);
476
477         /* remove all filtering */
478         while (prefs_filtering != NULL) {
479                 FilteringProp * filtering =
480                         (FilteringProp *) prefs_filtering->data;
481                 filteringprop_free(filtering);
482                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
483         }
484
485         while (fgets(buf, sizeof(buf), fp) != NULL) {
486                 FilteringProp * filtering;
487                 gchar * tmp;
488
489                 g_strchomp(buf);
490
491                 if ((*buf != '#') && (*buf != '\0')) {
492                         tmp = buf;
493                         filtering = filteringprop_parse(&tmp);
494                         if (tmp != NULL) {
495                                 prefs_filtering =
496                                         g_slist_append(prefs_filtering,
497                                                        filtering);
498                         }
499                         else {
500                                 /* debug */
501                                 g_warning(_("syntax error : %s\n"), buf);
502                         }
503                 }
504         }
505
506         fclose(fp);
507 }
508
509 gchar * filteringaction_to_string(FilteringAction * action)
510 {
511         gchar * command_str;
512         gint i;
513         gchar * account_id_str;
514
515         command_str = NULL;
516         command_str = get_matchparser_tab_str(action->type);
517
518         if (command_str == NULL)
519                 return NULL;
520
521         switch(action->type) {
522         case MATCHING_ACTION_MOVE:
523         case MATCHING_ACTION_COPY:
524                 return g_strconcat(command_str, " \"", action->destination,
525                                    "\"", NULL);
526
527         case MATCHING_ACTION_DELETE:
528         case MATCHING_ACTION_MARK:
529         case MATCHING_ACTION_UNMARK:
530         case MATCHING_ACTION_MARK_AS_READ:
531         case MATCHING_ACTION_MARK_AS_UNREAD:
532                 return g_strdup(command_str);
533                 break;
534
535         case MATCHING_ACTION_FORWARD:
536         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
537                 account_id_str = itos(action->account_id);
538                 return g_strconcat(command_str, " ", account_id_str,
539                                    " \"", action->destination, "\"", NULL);
540
541         default:
542                 return NULL;
543         }
544 }
545
546 gchar * filteringprop_to_string(FilteringProp * prop)
547 {
548         gchar * list_str;
549         gchar * action_str;
550         gchar * filtering_str;
551
552         action_str = filteringaction_to_string(prop->action);
553
554         if (action_str == NULL)
555                 return NULL;
556
557         list_str = matcherlist_to_string(prop->matchers);
558
559         if (list_str == NULL) {
560                 g_free(action_str);
561                 return NULL;
562         }
563
564         filtering_str = g_strconcat(list_str, " ", action_str, NULL);
565         g_free(list_str);
566         g_free(action_str);
567
568         return filtering_str;
569 }
570
571 void prefs_filtering_write_config(void)
572 {
573         gchar *rcpath;
574         PrefFile *pfile;
575         GSList *cur;
576         FilteringProp * prop;
577
578         debug_print(_("Writing filtering configuration...\n"));
579
580         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
581
582         if ((pfile = prefs_write_open(rcpath)) == NULL) {
583                 g_warning(_("failed to write configuration to file\n"));
584                 g_free(rcpath);
585                 return;
586         }
587
588         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
589                 gchar *filtering_str;
590
591                 prop = (FilteringProp *) cur->data;
592                 filtering_str = filteringprop_to_string(prop);
593                 if (fputs(filtering_str, pfile->fp) == EOF ||
594                     fputc('\n', pfile->fp) == EOF) {
595                         FILE_OP_ERROR(rcpath, "fputs || fputc");
596                         prefs_write_close_revert(pfile);
597                         g_free(rcpath);
598                         g_free(filtering_str);
599                         return;
600                 }
601                 g_free(filtering_str);
602         }
603
604         g_free(rcpath);
605
606         if (prefs_write_close(pfile) < 0) {
607                 g_warning(_("failed to write configuration to file\n"));
608                 return;
609         }
610 }