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