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