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