fix folder update stats (I hope)
[claws.git] / src / filtering.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto & The Sylpheed Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 /* (alfons) - Just a quick note of how this filtering module works on 
21  * new (arriving) messages.
22  * 
23  * 1) as an initialization step, code in inc.c and mbox.c set up the 
24  *    drop folder to the inbox (see inc.c and mbox.c).
25  *
26  * 2) the message is actually being copied to the drop folder using
27  *    folder_item_add_msg(dropfolder, file, TRUE). this function
28  *    eventually calls mh->add_msg(). however, the important thing
29  *    about this function is, is that the folder is not yet updated
30  *    to reflect the copy. i don't know about the validity of this
31  *    assumption, however, the filtering code assumes this and
32  *    updates the marks itself.
33  *
34  * 3) technically there's nothing wrong with the matcher (the 
35  *    piece of code which matches search strings). there's
36  *    one gotcha in procmsg.c:procmsg_get_message_file(): it
37  *    only reads a message file based on a MsgInfo. for design
38  *    reasons the filtering system should read directly from
39  *    a file (based on the file's name).
40  *
41  * 4) after the matcher sorts out any matches, it looks at the
42  *    action. this part again pushes the folder system design
43  *    to its limits. based on the assumption in 2), the matcher
44  *    knows the message has not been added to the folder system yet.
45  *    it can happily update mark files, and in fact it does.
46  * 
47  */ 
48
49 #include <ctype.h>
50 #include <string.h>
51 #include <stdlib.h>
52 #include <errno.h>
53 #include <gtk/gtk.h>
54 #include <stdio.h>
55 #include "intl.h"
56 #include "utils.h"
57 #include "defs.h"
58 #include "procheader.h"
59 #include "matcher.h"
60 #include "filtering.h"
61 #include "prefs.h"
62 #include "compose.h"
63
64 #define PREFSBUFSIZE            1024
65
66 GSList * prefs_filtering = NULL;
67
68 FilteringAction * filteringaction_new(int type, int account_id,
69                                       gchar * destination)
70 {
71         FilteringAction * action;
72
73         action = g_new0(FilteringAction, 1);
74
75         action->type = type;
76         action->account_id = account_id;
77         if (destination)
78                 action->destination = g_strdup(destination);
79
80         return action;
81 }
82
83 void filteringaction_free(FilteringAction * action)
84 {
85         if (action->destination)
86                 g_free(action->destination);
87         g_free(action);
88 }
89
90 FilteringAction * filteringaction_parse(gchar ** str)
91 {
92         FilteringAction * action;
93         gchar * tmp;
94         gchar * destination = NULL;
95         gint account_id = 0;
96         gint key;
97
98         tmp = * str;
99
100         key = matcher_parse_keyword(&tmp);
101
102         switch (key) {
103         case MATCHING_ACTION_MOVE:
104         case MATCHING_ACTION_COPY:
105         case MATCHING_EXECUTE:
106                 destination = matcher_parse_str(&tmp);
107                 if (tmp == NULL) {
108                         * str = NULL;
109                         return NULL;
110                 }
111                 break;
112         case MATCHING_ACTION_DELETE:
113                 break;
114         case MATCHING_ACTION_MARK:
115                 break;
116         case MATCHING_ACTION_MARK_AS_READ:
117                 break;
118         case MATCHING_ACTION_UNMARK:
119                 break;
120         case MATCHING_ACTION_MARK_AS_UNREAD:
121                 break;
122         case MATCHING_ACTION_FORWARD:
123         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
124                 account_id = matcher_parse_number(&tmp);
125                 if (tmp == NULL) {
126                         * str = NULL;
127                         return NULL;
128                 }
129
130                 destination = matcher_parse_str(&tmp);
131                 if (tmp == NULL) {
132                         * str = NULL;
133                         return NULL;
134                 }
135
136                 break;
137         default:
138                 * str = NULL;
139                 return NULL;
140         }
141
142         * str = tmp;
143         action = filteringaction_new(key, account_id, destination);
144
145         return action;
146 }
147
148 FilteringProp * filteringprop_parse(gchar ** str)
149 {
150         gchar * tmp;
151         gint key;
152         FilteringProp * filtering;
153         MatcherList * matchers;
154         FilteringAction * action;
155         
156         tmp = * str;
157
158         matchers = matcherlist_parse(&tmp);
159         if (tmp == NULL) {
160                 * str = NULL;
161                 return NULL;
162         }
163
164         if (tmp == NULL) {
165                 matcherlist_free(matchers);
166                 * str = NULL;
167                 return NULL;
168         }
169
170         action = filteringaction_parse(&tmp);
171         if (tmp == NULL) {
172                 matcherlist_free(matchers);
173                 * str = NULL;
174                 return NULL;
175         }
176
177         filtering = filteringprop_new(matchers, action);
178
179         * str = tmp;
180         return filtering;
181 }
182
183
184 FilteringProp * filteringprop_new(MatcherList * matchers,
185                                   FilteringAction * action)
186 {
187         FilteringProp * filtering;
188
189         filtering = g_new0(FilteringProp, 1);
190         filtering->matchers = matchers;
191         filtering->action = action;
192
193         return filtering;
194 }
195
196 void filteringprop_free(FilteringProp * prop)
197 {
198         matcherlist_free(prop->matchers);
199         filteringaction_free(prop->action);
200         g_free(prop);
201 }
202
203 /* filteringaction_update_mark() - updates a mark for a message. note that
204  * the message should not have been moved or copied. remember that the
205  * procmsg_open_mark_file(PATH, TRUE) actually _appends_ a new record.
206  */
207 static gboolean filteringaction_update_mark(MsgInfo * info)
208 {
209         gchar * dest_path;
210         FILE * fp;
211
212         if (info->folder->folder->type == F_MH) {
213                 dest_path = folder_item_get_path(info->folder);
214                 if (!is_dir_exist(dest_path))
215                         make_dir_hier(dest_path);
216                 
217                 if (dest_path == NULL) {
218                         g_warning(_("Can't open mark file.\n"));
219                         return FALSE;
220                 }
221                 
222                 if ((fp = procmsg_open_mark_file(dest_path, TRUE))
223                     == NULL) {
224                         g_warning(_("Can't open mark file.\n"));
225                         return FALSE;
226                 }
227                 
228                 procmsg_write_flags(info, fp);
229                 fclose(fp);
230                 return TRUE;
231         }
232         return FALSE;
233 }
234
235 static gchar * filteringaction_execute_command(gchar * cmd, MsgInfo * info)
236 {
237         gchar * s = cmd;
238         gchar * filename = NULL;
239         gchar * processed_cmd;
240         gchar * p;
241         gint size;
242
243         size = strlen(cmd) + 1;
244         while (*s != '\0') {
245                 if (*s == '%') {
246                         s++;
247                         switch (*s) {
248                         case '%':
249                                 size -= 1;
250                                 break;
251                         case 's': /* subject */
252                                 size += strlen(info->subject) - 2;
253                                 break;
254                         case 'f': /* from */
255                                 size += strlen(info->from) - 2;
256                                 break;
257                         case 't': /* to */
258                                 size += strlen(info->to) - 2;
259                                 break;
260                         case 'c': /* cc */
261                                 size += strlen(info->cc) - 2;
262                                 break;
263                         case 'd': /* date */
264                                 size += strlen(info->date) - 2;
265                                 break;
266                         case 'i': /* message-id */
267                                 size += strlen(info->msgid) - 2;
268                                 break;
269                         case 'n': /* newsgroups */
270                                 size += strlen(info->newsgroups) - 2;
271                                 break;
272                         case 'r': /* references */
273                                 size += strlen(info->references) - 2;
274                                 break;
275                         case 'F': /* file */
276                                 filename = folder_item_fetch_msg(info->folder,
277                                                                  info->msgnum);
278                                 
279                                 if (filename == NULL) {
280                                         g_warning(_("filename is not set"));
281                                         return NULL;
282                                 }
283                                 else
284                                         size += strlen(filename) - 2;
285                                 break;
286                         }
287                         s++;
288                 }
289                 else s++;
290         }
291
292
293         processed_cmd = g_new0(gchar, size);
294         s = cmd;
295         p = processed_cmd;
296
297         while (*s != '\0') {
298                 if (*s == '%') {
299                         s++;
300                         switch (*s) {
301                         case '%':
302                                 *p = '%';
303                                 p++;
304                                 break;
305                         case 's': /* subject */
306                                 if (info->subject != NULL)
307                                         strcpy(p, info->subject);
308                                 else
309                                         strcpy(p, "(none)");
310                                 p += strlen(p);
311                                 break;
312                         case 'f': /* from */
313                                 if (info->from != NULL)
314                                         strcpy(p, info->from);
315                                 else
316                                         strcpy(p, "(none)");
317                                 p += strlen(p);
318                                 break;
319                         case 't': /* to */
320                                 if (info->to != NULL)
321                                         strcpy(p, info->to);
322                                 else
323                                         strcpy(p, "(none)");
324                                 p += strlen(p);
325                                 break;
326                         case 'c': /* cc */
327                                 if (info->cc != NULL)
328                                         strcpy(p, info->cc);
329                                 else
330                                         strcpy(p, "(none)");
331                                 p += strlen(p);
332                                 break;
333                         case 'd': /* date */
334                                 if (info->date != NULL)
335                                         strcpy(p, info->date);
336                                 else
337                                         strcpy(p, "(none)");
338                                 p += strlen(p);
339                                 break;
340                         case 'i': /* message-id */
341                                 if (info->msgid != NULL)
342                                         strcpy(p, info->msgid);
343                                 else
344                                         strcpy(p, "(none)");
345                                 p += strlen(p);
346                                 break;
347                         case 'n': /* newsgroups */
348                                 if (info->newsgroups != NULL)
349                                         strcpy(p, info->newsgroups);
350                                 else
351                                         strcpy(p, "(none)");
352                                 p += strlen(p);
353                                 break;
354                         case 'r': /* references */
355                                 if (info->references != NULL)
356                                         strcpy(p, info->references);
357                                 else
358                                         strcpy(p, "(none)");
359                                 p += strlen(p);
360                                 break;
361                         case 'F': /* file */
362                                 strcpy(p, filename);
363                                 p += strlen(p);
364                                 break;
365                         default:
366                                 *p = '%';
367                                 p++;
368                                 *p = *s;
369                                 p++;
370                                 break;
371                         }
372                         s++;
373                 }
374                 else {
375                         *p = *s;
376                         p++;
377                         s++;
378                 }
379         }
380         return processed_cmd;
381 }
382
383 /*
384   fitleringaction_apply
385   runs the action on one MsgInfo
386   return value : return TRUE if the action could be applied
387 */
388
389 #define CHANGE_FLAGS(msginfo) \
390 { \
391 if (msginfo->folder->folder->change_flags != NULL) \
392 msginfo->folder->folder->change_flags(msginfo->folder->folder, \
393                                       msginfo->folder, \
394                                       msginfo); \
395 }
396
397 static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info,
398                                       GHashTable *folder_table)
399 {
400         FolderItem * dest_folder;
401         gint val;
402         Compose * compose;
403         PrefsAccount * account;
404         gchar * cmd;
405
406         switch(action->type) {
407         case MATCHING_ACTION_MOVE:
408                 dest_folder =
409                         folder_find_item_from_identifier(action->destination);
410                 if (!dest_folder)
411                         return FALSE;
412
413                 if (folder_item_move_msg(dest_folder, info) == -1)
414                         return FALSE;
415
416                 /* WRONG: can not update the mark, because the message has 
417                  * been moved. info pertains to original location. 
418                  * folder_item_move_msg() already updated the mark for the
419                  * destination folder.
420                 info->flags = 0;
421                 filteringaction_update_mark(info);
422                  */
423                 
424                 if (folder_table) {
425                         val = GPOINTER_TO_INT(g_hash_table_lookup
426                                               (folder_table, dest_folder));
427                         if (val == 0) {
428                                 folder_item_scan(dest_folder);
429                                 g_hash_table_insert(folder_table, dest_folder,
430                                                     GINT_TO_POINTER(1));
431                         }
432                         val = GPOINTER_TO_INT(g_hash_table_lookup
433                                               (folder_table, info->folder));
434                         if (val == 0) {
435                                 folder_item_scan(info->folder);
436                                 g_hash_table_insert(folder_table, info->folder,
437                                                     GINT_TO_POINTER(1));
438                         }
439                 }
440
441                 return TRUE;
442
443         case MATCHING_ACTION_COPY:
444                 dest_folder =
445                         folder_find_item_from_identifier(action->destination);
446
447                 if (!dest_folder)
448                         return FALSE;
449
450                 /* NOTE: the following call *will* update the mark file for
451                  * the destination folder. but the original message will
452                  * still be there in the inbox. */
453
454                 if (folder_item_copy_msg(dest_folder, info) == -1)
455                         return FALSE;
456
457                 if (folder_table) {
458                         val = GPOINTER_TO_INT(g_hash_table_lookup
459                                               (folder_table, dest_folder));
460                         if (val == 0) {
461                                 folder_item_scan(dest_folder);
462                                 g_hash_table_insert(folder_table, dest_folder,
463                                                     GINT_TO_POINTER(1));
464                         }
465                 }
466                         
467                 return TRUE;
468
469         case MATCHING_ACTION_DELETE:
470                 if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
471                         return FALSE;
472
473                 /* WRONG: can not update the mark. this would actually add
474                  * a bogus record to the mark file for the message's original 
475                  * folder. 
476                 info->flags = 0;
477                 filteringaction_update_mark(info);
478                  */
479
480                 return TRUE;
481
482         case MATCHING_ACTION_MARK:
483                 MSG_SET_FLAGS(info->flags, MSG_MARKED);
484                 filteringaction_update_mark(info);
485
486                 CHANGE_FLAGS(info);
487
488                 return TRUE;
489
490         case MATCHING_ACTION_UNMARK:
491                 MSG_UNSET_FLAGS(info->flags, MSG_MARKED);
492                 filteringaction_update_mark(info);
493
494                 CHANGE_FLAGS(info);
495
496                 return TRUE;
497                 
498         case MATCHING_ACTION_MARK_AS_READ:
499                 MSG_UNSET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
500                 filteringaction_update_mark(info);
501
502                 CHANGE_FLAGS(info);
503
504                 return TRUE;
505
506         case MATCHING_ACTION_MARK_AS_UNREAD:
507                 MSG_SET_FLAGS(info->flags, MSG_UNREAD | MSG_NEW);
508                 filteringaction_update_mark(info);
509
510                 CHANGE_FLAGS(info);
511                 
512                 return TRUE;
513
514         case MATCHING_ACTION_FORWARD:
515
516                 account = account_find_from_id(action->account_id);
517                 compose = compose_forward(account, info, FALSE);
518                 if (compose->account->protocol == A_NNTP)
519                         compose_entry_append(compose, action->destination,
520                                              COMPOSE_NEWSGROUPS);
521                 else
522                         compose_entry_append(compose, action->destination,
523                                              COMPOSE_TO);
524
525                 val = compose_send(compose);
526                 if (val == 0) {
527                         gtk_widget_destroy(compose->window);
528                         return TRUE;
529                 }
530
531                 gtk_widget_destroy(compose->window);
532                 return FALSE;
533
534         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
535
536                 account = account_find_from_id(action->account_id);
537                 compose = compose_forward(account, info, TRUE);
538                 if (compose->account->protocol == A_NNTP)
539                         compose_entry_append(compose, action->destination,
540                                              COMPOSE_NEWSGROUPS);
541                 else
542                         compose_entry_append(compose, action->destination,
543                                              COMPOSE_TO);
544
545                 val = compose_send(compose);
546                 if (val == 0) {
547                         gtk_widget_destroy(compose->window);
548                         return TRUE;
549                 }
550
551                 gtk_widget_destroy(compose->window);
552                 return FALSE;
553
554         case MATCHING_EXECUTE:
555
556                 cmd = matching_build_command(action->destination, info);
557                 if (cmd == NULL)
558                         return TRUE;
559                 else {
560                         system(cmd);
561                         g_free(cmd);
562                 }
563
564                 return TRUE;
565
566         default:
567                 return FALSE;
568         }
569 }
570
571 /* filteringprop_apply() - runs the action on one MsgInfo if it matches the 
572  * criterium. certain actions can be followed by other actions. in this
573  * case the function returns FALSE. if an action can not be followed
574  * by others, the function returns TRUE.
575  *
576  * remember that this is because of the fact that msg flags are always
577  * _appended_ to mark files. currently sylpheed does not insert messages 
578  * at a certain index. 
579  * now, after having performed a certain action, the MsgInfo is still
580  * valid for the message. in *this* case the function returns FALSE.
581  */
582 static gboolean filteringprop_apply(FilteringProp * filtering, MsgInfo * info,
583                                     GHashTable *folder_table)
584 {
585         if (matcherlist_match(filtering->matchers, info)) {
586                 gint result;
587                 gchar * action_str;
588
589                 result = TRUE;
590
591                 result = filteringaction_apply(filtering->action, info,
592                                                folder_table);
593                 action_str =
594                         filteringaction_to_string(filtering->action);
595                 if (!result) {
596                         g_warning(_("action %s could not be applied"),
597                                   action_str);
598                 }
599                 else {
600                         debug_print(_("message %i %s..."),
601                                       info->msgnum, action_str);
602                 }
603
604                 g_free(action_str);
605
606                 switch(filtering->action->type) {
607                 case MATCHING_ACTION_MOVE:
608                 case MATCHING_ACTION_DELETE:
609                         return TRUE; /* MsgInfo invalid for message */
610                 case MATCHING_EXECUTE:
611                 case MATCHING_ACTION_COPY:
612                 case MATCHING_ACTION_MARK:
613                 case MATCHING_ACTION_MARK_AS_READ:
614                 case MATCHING_ACTION_UNMARK:
615                 case MATCHING_ACTION_MARK_AS_UNREAD:
616                 case MATCHING_ACTION_FORWARD:
617                 case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
618                         return FALSE; /* MsgInfo still valid for message */
619                 default:
620                         return FALSE;
621                 }
622         }
623         else
624                 return FALSE;
625 }
626
627 void filter_msginfo(GSList * filtering_list, MsgInfo * info,
628                     GHashTable *folder_table)
629 {
630         GSList * l;
631
632         if (info == NULL) {
633                 g_warning(_("msginfo is not set"));
634                 return;
635         }
636         
637         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
638                 FilteringProp * filtering = (FilteringProp *) l->data;
639                 
640                 if (filteringprop_apply(filtering, info, folder_table))
641                         break;
642         }
643 }
644
645 void filter_msginfo_move_or_delete(GSList * filtering_list, MsgInfo * info,
646                                    GHashTable *folder_table)
647 {
648         GSList * l;
649
650         if (info == NULL) {
651                 g_warning(_("msginfo is not set"));
652                 return;
653         }
654         
655         for(l = filtering_list ; l != NULL ; l = g_slist_next(l)) {
656                 FilteringProp * filtering = (FilteringProp *) l->data;
657
658                 switch (filtering->action->type) {
659                 case MATCHING_ACTION_MOVE:
660                 case MATCHING_ACTION_DELETE:
661                         if (filteringprop_apply(filtering, info, folder_table))
662                                 return;
663                 }
664         }
665 }
666
667 void filter_message(GSList * filtering_list, FolderItem * item,
668                     gint msgnum, GHashTable *folder_table)
669 {
670         MsgInfo * msginfo;
671         gchar * filename;
672
673         if (item == NULL) {
674                 g_warning(_("folderitem not set"));
675                 return;
676         }
677
678         filename = folder_item_fetch_msg(item, msgnum);
679
680         if (filename == NULL) {
681                 g_warning(_("filename is not set"));
682                 return;
683         }
684
685         msginfo = procheader_parse(filename, 0, TRUE);
686
687         g_free(filename);
688
689         if (msginfo == NULL) {
690                 g_warning(_("could not get info for %s"), filename);
691                 return;
692         }
693
694         msginfo->folder = item;
695         msginfo->msgnum = msgnum;
696
697         filter_msginfo(filtering_list, msginfo, folder_table);
698 }
699
700 void prefs_filtering_read_config(void)
701 {
702         gchar *rcpath;
703         FILE *fp;
704         gchar buf[PREFSBUFSIZE];
705
706         debug_print(_("Reading filtering configuration...\n"));
707
708         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
709                              FILTERING_RC, NULL);
710         if ((fp = fopen(rcpath, "r")) == NULL) {
711                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
712                 g_free(rcpath);
713                 prefs_filtering = NULL;
714                 return;
715         }
716         g_free(rcpath);
717
718         /* remove all filtering */
719         while (prefs_filtering != NULL) {
720                 FilteringProp * filtering =
721                         (FilteringProp *) prefs_filtering->data;
722                 filteringprop_free(filtering);
723                 prefs_filtering = g_slist_remove(prefs_filtering, filtering);
724         }
725
726         while (fgets(buf, sizeof(buf), fp) != NULL) {
727                 FilteringProp * filtering;
728                 gchar * tmp;
729
730                 g_strchomp(buf);
731
732                 if ((*buf != '#') && (*buf != '\0')) {
733                         tmp = buf;
734                         filtering = filteringprop_parse(&tmp);
735                         if (tmp != NULL) {
736                                 prefs_filtering =
737                                         g_slist_append(prefs_filtering,
738                                                        filtering);
739                         }
740                         else {
741                                 /* debug */
742                                 g_warning(_("syntax error : %s\n"), buf);
743                         }
744                 }
745         }
746
747         fclose(fp);
748 }
749
750 gchar * filteringaction_to_string(FilteringAction * action)
751 {
752         gchar * command_str;
753         gint i;
754         gchar * account_id_str;
755
756         command_str = NULL;
757         command_str = get_matchparser_tab_str(action->type);
758
759         if (command_str == NULL)
760                 return NULL;
761
762         switch(action->type) {
763         case MATCHING_ACTION_MOVE:
764         case MATCHING_ACTION_COPY:
765         case MATCHING_EXECUTE:
766                 return g_strconcat(command_str, " \"", action->destination,
767                                    "\"", NULL);
768
769         case MATCHING_ACTION_DELETE:
770         case MATCHING_ACTION_MARK:
771         case MATCHING_ACTION_UNMARK:
772         case MATCHING_ACTION_MARK_AS_READ:
773         case MATCHING_ACTION_MARK_AS_UNREAD:
774                 return g_strdup(command_str);
775                 break;
776
777         case MATCHING_ACTION_FORWARD:
778         case MATCHING_ACTION_FORWARD_AS_ATTACHMENT:
779                 account_id_str = itos(action->account_id);
780                 return g_strconcat(command_str, " ", account_id_str,
781                                    " \"", action->destination, "\"", NULL);
782
783         default:
784                 return NULL;
785         }
786 }
787
788 gchar * filteringprop_to_string(FilteringProp * prop)
789 {
790         gchar * list_str;
791         gchar * action_str;
792         gchar * filtering_str;
793
794         action_str = filteringaction_to_string(prop->action);
795
796         if (action_str == NULL)
797                 return NULL;
798
799         list_str = matcherlist_to_string(prop->matchers);
800
801         if (list_str == NULL) {
802                 g_free(action_str);
803                 return NULL;
804         }
805
806         filtering_str = g_strconcat(list_str, " ", action_str, NULL);
807         g_free(list_str);
808         g_free(action_str);
809
810         return filtering_str;
811 }
812
813 void prefs_filtering_write_config(void)
814 {
815         gchar *rcpath;
816         PrefFile *pfile;
817         GSList *cur;
818         FilteringProp * prop;
819
820         debug_print(_("Writing filtering configuration...\n"));
821
822         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTERING_RC, NULL);
823
824         if ((pfile = prefs_write_open(rcpath)) == NULL) {
825                 g_warning(_("failed to write configuration to file\n"));
826                 g_free(rcpath);
827                 return;
828         }
829
830         for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
831                 gchar *filtering_str;
832
833                 prop = (FilteringProp *) cur->data;
834                 filtering_str = filteringprop_to_string(prop);
835                 if (fputs(filtering_str, pfile->fp) == EOF ||
836                     fputc('\n', pfile->fp) == EOF) {
837                         FILE_OP_ERROR(rcpath, "fputs || fputc");
838                         prefs_write_close_revert(pfile);
839                         g_free(rcpath);
840                         g_free(filtering_str);
841                         return;
842                 }
843                 g_free(filtering_str);
844         }
845
846         g_free(rcpath);
847
848         if (prefs_write_close(pfile) < 0) {
849                 g_warning(_("failed to write configuration to file\n"));
850                 return;
851         }
852 }