Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-filter-driver.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright (C) 2000 Ximian Inc.
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *           Jeffrey Stedfast <fejj@ximian.com>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU Lesser General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <time.h>
33
34 #ifndef G_OS_WIN32
35 #include <sys/wait.h>
36 #endif
37
38 #include <glib.h>
39 #include <glib/gstdio.h>
40 #include <glib/gi18n-lib.h>
41
42 #include <libedataserver/e-sexp.h>
43 #include <libedataserver/e-memory.h>
44 #include <libedataserver/e-msgport.h>
45
46 #include "camel-debug.h"
47 #include "camel-file-utils.h"
48 #include "camel-filter-driver.h"
49 #include "camel-filter-search.h"
50 #include "camel-mime-message.h"
51 #include "camel-private.h"
52 #include "camel-service.h"
53 #include "camel-stream-fs.h"
54 #include "camel-stream-mem.h"
55
56 #define d(x)
57
58 /* an invalid pointer */
59 #define FOLDER_INVALID ((void *)~0)
60
61 /* type of status for a log report */
62 enum filter_log_t {
63         FILTER_LOG_NONE,
64         FILTER_LOG_START,       /* start of new log entry */
65         FILTER_LOG_ACTION,      /* an action performed */
66         FILTER_LOG_END,         /* end of log */
67 };
68
69 /* list of rule nodes */
70 struct _filter_rule {
71         struct _filter_rule *next;
72         struct _filter_rule *prev;
73
74         char *match;
75         char *action;
76         char *name;
77 };
78
79 struct _CamelFilterDriverPrivate {
80         GHashTable *globals;       /* global variables */
81
82         CamelSession *session;
83
84         CamelFolder *defaultfolder;        /* defualt folder */
85         
86         CamelFilterStatusFunc *statusfunc; /* status callback */
87         void *statusdata;                  /* status callback data */
88         
89         CamelFilterShellFunc *shellfunc;    /* execute shell command callback */
90         void *shelldata;                    /* execute shell command callback data */
91         
92         CamelFilterPlaySoundFunc *playfunc; /* play-sound command callback */
93         void *playdata;                     /* play-sound command callback data */
94         
95         CamelFilterSystemBeepFunc *beep;    /* system beep callback */
96         void *beepdata;                     /* system beep callback data */
97         
98         /* for callback */
99         CamelFilterGetFolderFunc get_folder;
100         void *data;
101         
102         /* run-time data */
103         GHashTable *folders;       /* folders that message has been copied to */
104         int closed;                /* close count */
105         GHashTable *forwards;      /* addresses that have been forwarded the message */
106         GHashTable *only_once;     /* actions to run only-once */
107         
108         gboolean terminated;       /* message processing was terminated */
109         gboolean deleted;          /* message was marked for deletion */
110         gboolean copied;           /* message was copied to some folder or another */
111         gboolean moved;            /* message was moved to some folder or another */
112         
113         CamelMimeMessage *message; /* input message */
114         CamelMessageInfo *info;    /* message summary info */
115         const char *uid;           /* message uid */
116         CamelFolder *source;       /* message source folder */
117         gboolean modified;         /* has the input message been modified? */
118         
119         FILE *logfile;             /* log file */
120         
121         EDList rules;              /* list of _filter_rule structs */
122         
123         CamelException *ex;
124         
125         /* evaluator */
126         ESExp *eval;
127 };
128
129 #define _PRIVATE(o) (((CamelFilterDriver *)(o))->priv)
130
131 static void camel_filter_driver_class_init (CamelFilterDriverClass *klass);
132 static void camel_filter_driver_init       (CamelFilterDriver *obj);
133 static void camel_filter_driver_finalise   (CamelObject *obj);
134
135 static void camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const char *desc, ...);
136
137 static CamelFolder *open_folder (CamelFilterDriver *d, const char *folder_url);
138 static int close_folders (CamelFilterDriver *d);
139
140 static ESExpResult *do_delete (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
141 static ESExpResult *mark_forward (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
142 static ESExpResult *do_copy (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
143 static ESExpResult *do_move (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
144 static ESExpResult *do_stop (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
145 static ESExpResult *do_colour (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
146 static ESExpResult *do_score (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
147 static ESExpResult *do_adjust_score(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
148 static ESExpResult *set_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
149 static ESExpResult *unset_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
150 static ESExpResult *do_shell (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
151 static ESExpResult *do_beep (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
152 static ESExpResult *play_sound (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
153 static ESExpResult *do_only_once (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
154 static ESExpResult *pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
155
156 /* these are our filter actions - each must have a callback */
157 static struct {
158         char *name;
159         ESExpFunc *func;
160         int type;               /* set to 1 if a function can perform shortcut evaluation, or
161                                    doesn't execute everything, 0 otherwise */
162 } symbols[] = {
163         { "delete",            (ESExpFunc *) do_delete,    0 },
164         { "forward-to",        (ESExpFunc *) mark_forward, 0 },
165         { "copy-to",           (ESExpFunc *) do_copy,      0 },
166         { "move-to",           (ESExpFunc *) do_move,      0 },
167         { "stop",              (ESExpFunc *) do_stop,      0 },
168         { "set-colour",        (ESExpFunc *) do_colour,    0 },
169         { "set-score",         (ESExpFunc *) do_score,     0 },
170         { "adjust-score",      (ESExpFunc *) do_adjust_score, 0 },
171         { "set-system-flag",   (ESExpFunc *) set_flag,     0 },
172         { "unset-system-flag", (ESExpFunc *) unset_flag,   0 },
173         { "pipe-message",      (ESExpFunc *) pipe_message, 0 },
174         { "shell",             (ESExpFunc *) do_shell,     0 },
175         { "beep",              (ESExpFunc *) do_beep,      0 },
176         { "play-sound",        (ESExpFunc *) play_sound,   0 },
177         { "only-once",         (ESExpFunc *) do_only_once, 0 }
178 };
179
180 static CamelObjectClass *camel_filter_driver_parent;
181
182 CamelType
183 camel_filter_driver_get_type (void)
184 {
185         static CamelType type = CAMEL_INVALID_TYPE;
186
187         if (type == CAMEL_INVALID_TYPE) {
188                 type = camel_type_register (CAMEL_OBJECT_TYPE,
189                                             "CamelFilterDriver",
190                                             sizeof (CamelFilterDriver),
191                                             sizeof (CamelFilterDriverClass),
192                                             (CamelObjectClassInitFunc) camel_filter_driver_class_init,
193                                             NULL,
194                                             (CamelObjectInitFunc) camel_filter_driver_init,
195                                             (CamelObjectFinalizeFunc) camel_filter_driver_finalise);
196         }
197         
198         return type;
199 }
200
201 static void
202 camel_filter_driver_class_init (CamelFilterDriverClass *klass)
203 {
204         /*CamelObjectClass *object_class = (CamelObjectClass *) klass;*/
205
206         camel_filter_driver_parent = camel_type_get_global_classfuncs(camel_object_get_type());
207 }
208
209 static void
210 camel_filter_driver_init (CamelFilterDriver *obj)
211 {
212         struct _CamelFilterDriverPrivate *p;
213         int i;
214         
215         p = _PRIVATE (obj) = g_malloc0 (sizeof (*p));
216
217         e_dlist_init(&p->rules);
218
219         p->eval = e_sexp_new ();
220         /* Load in builtin symbols */
221         for (i = 0; i < sizeof (symbols) / sizeof (symbols[0]); i++) {
222                 if (symbols[i].type == 1) {
223                         e_sexp_add_ifunction (p->eval, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, obj);
224                 } else {
225                         e_sexp_add_function (p->eval, 0, symbols[i].name, symbols[i].func, obj);
226                 }
227         }
228         
229         p->globals = g_hash_table_new (g_str_hash, g_str_equal);
230         
231         p->folders = g_hash_table_new (g_str_hash, g_str_equal);
232         
233         p->only_once = g_hash_table_new (g_str_hash, g_str_equal);
234 }
235
236 static void
237 free_hash_strings (void *key, void *value, void *data)
238 {
239         g_free (key);
240         g_free (value);
241 }
242
243 static void
244 camel_filter_driver_finalise (CamelObject *obj)
245 {
246         CamelFilterDriver *driver = (CamelFilterDriver *) obj;
247         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);        
248         struct _filter_rule *node;
249
250         /* close all folders that were opened for appending */
251         close_folders (driver);
252         g_hash_table_destroy (p->folders);
253         
254         g_hash_table_foreach (p->globals, free_hash_strings, driver);
255         g_hash_table_destroy (p->globals);
256         
257         g_hash_table_foreach (p->only_once, free_hash_strings, driver);
258         g_hash_table_destroy (p->only_once);
259         
260         e_sexp_unref(p->eval);
261         
262         if (p->defaultfolder) {
263                 camel_folder_thaw (p->defaultfolder);
264                 camel_object_unref (p->defaultfolder);
265         }
266
267         while ((node = (struct _filter_rule *)e_dlist_remhead(&p->rules))) {
268                 g_free(node->match);
269                 g_free(node->action);
270                 g_free(node->name);
271                 g_free(node);
272         }
273
274         camel_object_unref(p->session);
275
276         g_free (p);
277 }
278
279 /**
280  * camel_filter_driver_new:
281  *
282  * Return value: A new CamelFilterDriver object
283  **/
284 CamelFilterDriver *
285 camel_filter_driver_new (CamelSession *session)
286 {
287         CamelFilterDriver *d = (CamelFilterDriver *)camel_object_new(camel_filter_driver_get_type());
288
289         d->priv->session = session;
290         camel_object_ref((CamelObject *)session);
291
292         return d;
293 }
294
295 void
296 camel_filter_driver_set_folder_func (CamelFilterDriver *d, CamelFilterGetFolderFunc get_folder, void *data)
297 {
298         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
299
300         p->get_folder = get_folder;
301         p->data = data;
302 }
303
304 void
305 camel_filter_driver_set_logfile (CamelFilterDriver *d, FILE *logfile)
306 {
307         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
308         
309         p->logfile = logfile;
310 }
311
312 void
313 camel_filter_driver_set_status_func (CamelFilterDriver *d, CamelFilterStatusFunc *func, void *data)
314 {
315         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
316         
317         p->statusfunc = func;
318         p->statusdata = data;
319 }
320
321 void
322 camel_filter_driver_set_shell_func (CamelFilterDriver *d, CamelFilterShellFunc *func, void *data)
323 {
324         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
325         
326         p->shellfunc = func;
327         p->shelldata = data;
328 }
329
330 void
331 camel_filter_driver_set_play_sound_func (CamelFilterDriver *d, CamelFilterPlaySoundFunc *func, void *data)
332 {
333         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
334         
335         p->playfunc = func;
336         p->playdata = data;
337 }
338
339 void
340 camel_filter_driver_set_system_beep_func (CamelFilterDriver *d, CamelFilterSystemBeepFunc *func, void *data)
341 {
342         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
343         
344         p->beep = func;
345         p->beepdata = data;
346 }
347
348 void
349 camel_filter_driver_set_default_folder (CamelFilterDriver *d, CamelFolder *def)
350 {
351         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
352         
353         if (p->defaultfolder) {
354                 camel_folder_thaw (p->defaultfolder);
355                 camel_object_unref (p->defaultfolder);
356         }
357         
358         p->defaultfolder = def;
359         
360         if (p->defaultfolder) {
361                 camel_folder_freeze (p->defaultfolder);
362                 camel_object_ref (p->defaultfolder);
363         }
364 }
365
366 void
367 camel_filter_driver_add_rule(CamelFilterDriver *d, const char *name, const char *match, const char *action)
368 {
369         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
370         struct _filter_rule *node;
371
372         node = g_malloc(sizeof(*node));
373         node->match = g_strdup(match);
374         node->action = g_strdup(action);
375         node->name = g_strdup(name);
376         e_dlist_addtail(&p->rules, (EDListNode *)node);
377 }
378
379 int
380 camel_filter_driver_remove_rule_by_name (CamelFilterDriver *d, const char *name)
381 {
382         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
383         struct _filter_rule *node;
384         
385         node = (struct _filter_rule *) p->rules.head;
386         while (node->next) {
387                 if (!strcmp (node->name, name)) {
388                         e_dlist_remove ((EDListNode *) node);
389                         g_free (node->match);
390                         g_free (node->action);
391                         g_free (node->name);
392                         g_free (node);
393                         
394                         return 0;
395                 }
396                 
397                 node = node->next;
398         }
399         
400         return -1;
401 }
402
403 static void
404 report_status (CamelFilterDriver *driver, enum camel_filter_status_t status, int pc, const char *desc, ...)
405 {
406         /* call user-defined status report function */
407         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
408         va_list ap;
409         char *str;
410         
411         if (p->statusfunc) {
412                 va_start (ap, desc);
413                 str = g_strdup_vprintf (desc, ap);
414                 p->statusfunc (driver, status, pc, str, p->statusdata);
415                 g_free (str);
416         }
417 }
418
419
420 #if 0
421 void
422 camel_filter_driver_set_global (CamelFilterDriver *d, const char *name, const char *value)
423 {
424         struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
425         char *oldkey, *oldvalue;
426         
427         if (g_hash_table_lookup_extended (p->globals, name, (void *)&oldkey, (void *)&oldvalue)) {
428                 g_free (oldvalue);
429                 g_hash_table_insert (p->globals, oldkey, g_strdup (value));
430         } else {
431                 g_hash_table_insert (p->globals, g_strdup (name), g_strdup (value));
432         }
433 }
434 #endif
435
436 static ESExpResult *
437 do_delete (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
438 {
439         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
440         
441         d(fprintf (stderr, "doing delete\n"));
442         p->deleted = TRUE;
443         camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Delete");
444         
445         return NULL;
446 }
447
448 static ESExpResult *
449 mark_forward (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
450 {
451         /*struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);*/
452         
453         d(fprintf (stderr, "marking message for forwarding\n"));
454         /* FIXME: do stuff here */
455         camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Forward");
456         
457         return NULL;
458 }
459
460 static ESExpResult *
461 do_copy (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
462 {
463         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
464         int i;
465         
466         d(fprintf (stderr, "copying message...\n"));
467         
468         for (i = 0; i < argc; i++) {
469                 if (argv[i]->type == ESEXP_RES_STRING) {
470                         /* open folders we intent to copy to */
471                         char *folder = argv[i]->value.string;
472                         CamelFolder *outbox;
473                         
474                         outbox = open_folder (driver, folder);
475                         if (!outbox)
476                                 break;
477                         
478                         if (outbox == p->source)
479                                 break;
480                         
481                         if (!p->modified && p->uid && p->source && camel_folder_has_summary_capability (p->source)) {
482                                 GPtrArray *uids;
483                                 
484                                 uids = g_ptr_array_new ();
485                                 g_ptr_array_add (uids, (char *) p->uid);
486                                 camel_folder_transfer_messages_to (p->source, uids, outbox, NULL, FALSE, p->ex);
487                                 g_ptr_array_free (uids, TRUE);
488                         } else {
489                                 if (p->message == NULL)
490                                         p->message = camel_folder_get_message (p->source, p->uid, p->ex);
491                                 
492                                 if (!p->message)
493                                         continue;
494                                 
495                                 camel_folder_append_message (outbox, p->message, p->info, NULL, p->ex);
496                         }
497                         
498                         if (!camel_exception_is_set (p->ex))
499                                 p->copied = TRUE;
500                         
501                         camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Copy to folder %s",
502                                                  folder);
503                 }
504         }
505         
506         return NULL;
507 }
508
509 static ESExpResult *
510 do_move (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
511 {
512         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
513         int i;
514         
515         d(fprintf (stderr, "moving message...\n"));
516         
517         for (i = 0; i < argc; i++) {
518                 if (argv[i]->type == ESEXP_RES_STRING) {
519                         /* open folders we intent to move to */
520                         char *folder = argv[i]->value.string;
521                         CamelFolder *outbox;
522                         int last;
523                         
524                         outbox = open_folder (driver, folder);
525                         if (!outbox)
526                                 break;
527                         
528                         if (outbox == p->source)
529                                 break;
530
531                         /* only delete on last folder (only 1 can ever be supplied by ui currently) */
532                         last = (i == argc-1);
533
534                         if (!p->modified && p->uid && p->source && camel_folder_has_summary_capability (p->source)) {
535                                 GPtrArray *uids;
536
537                                 uids = g_ptr_array_new ();
538                                 g_ptr_array_add (uids, (char *) p->uid);
539                                 camel_folder_transfer_messages_to (p->source, uids, outbox, NULL, last, p->ex);
540                                 g_ptr_array_free (uids, TRUE);
541                         } else {
542                                 if (p->message == NULL)
543                                         p->message = camel_folder_get_message (p->source, p->uid, p->ex);
544                                 
545                                 if (!p->message)
546                                         continue;
547                                 
548                                 camel_folder_append_message (outbox, p->message, p->info, NULL, p->ex);
549
550                                 if (!camel_exception_is_set(p->ex) && last) {
551                                         if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
552                                                 camel_folder_set_message_flags(p->source, p->uid, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN, ~0);
553                                         else
554                                                 camel_message_info_set_flags(p->info, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
555                                 }
556                         }
557                         
558                         if (!camel_exception_is_set (p->ex)) {
559                                 p->moved = TRUE;
560                                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Move to folder %s", folder);
561                         }
562                 }
563         }
564
565         /* implicit 'stop' with 'move' */
566         camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Stopped processing");
567         p->terminated = TRUE;
568
569         return NULL;
570 }
571
572 static ESExpResult *
573 do_stop (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
574 {
575         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
576         
577         camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Stopped processing");
578         d(fprintf (stderr, "terminating message processing\n"));
579         p->terminated = TRUE;
580         
581         return NULL;
582 }
583
584 static ESExpResult *
585 do_colour (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
586 {
587         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
588         
589         d(fprintf (stderr, "setting colour tag\n"));
590         if (argc > 0 && argv[0]->type == ESEXP_RES_STRING) {
591                 if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
592                         camel_folder_set_message_user_tag (p->source, p->uid, "colour", argv[0]->value.string);
593                 else
594                         camel_message_info_set_user_tag(p->info, "colour", argv[0]->value.string);
595                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Set colour to %s", argv[0]->value.string);
596         }
597         
598         return NULL;
599 }
600
601 static ESExpResult *
602 do_score (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
603 {
604         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
605         
606         d(fprintf (stderr, "setting score tag\n"));
607         if (argc > 0 && argv[0]->type == ESEXP_RES_INT) {
608                 char *value;
609                 
610                 value = g_strdup_printf ("%d", argv[0]->value.number);
611                 camel_message_info_set_user_tag(p->info, "score", value);
612                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Set score to %d", argv[0]->value.number);
613                 g_free (value);
614         }
615         
616         return NULL;
617 }
618
619 static ESExpResult *
620 do_adjust_score(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
621 {
622         struct _CamelFilterDriverPrivate *p = _PRIVATE(driver);
623         
624         d(fprintf (stderr, "adjusting score tag\n"));
625         if (argc > 0 && argv[0]->type == ESEXP_RES_INT) {
626                 char *value;
627                 int old;
628
629                 value = (char *)camel_message_info_user_tag(p->info, "score");
630                 old = value?atoi(value):0;
631                 value = g_strdup_printf ("%d", old+argv[0]->value.number);
632                 camel_message_info_set_user_tag(p->info, "score", value);
633                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Adjust score (%d) to %s", argv[0]->value.number, value);
634                 g_free (value);
635         }
636         
637         return NULL;
638 }
639
640 static ESExpResult *
641 set_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
642 {
643         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
644         guint32 flags;
645         
646         d(fprintf (stderr, "setting flag\n"));
647         if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
648                 flags = camel_system_flag (argv[0]->value.string);
649                 if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
650                         camel_folder_set_message_flags (p->source, p->uid, flags, ~0);
651                 else
652                         camel_message_info_set_flags(p->info, flags | CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
653                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Set %s flag", argv[0]->value.string);
654         }
655         
656         return NULL;
657 }
658
659 static ESExpResult *
660 unset_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
661 {
662         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
663         guint32 flags;
664         
665         d(fprintf (stderr, "unsetting flag\n"));
666         if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
667                 flags = camel_system_flag (argv[0]->value.string);
668                 if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
669                         camel_folder_set_message_flags (p->source, p->uid, flags, 0);
670                 else
671                         camel_message_info_set_flags(p->info, flags | CAMEL_MESSAGE_FOLDER_FLAGGED, 0);
672                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Unset %s flag", argv[0]->value.string);
673         }
674         
675         return NULL;
676 }
677
678 #ifndef G_OS_WIN32
679 static void
680 child_setup_func (gpointer user_data)
681 {
682         setsid ();
683 }
684 #else
685 #define child_setup_func NULL
686 #endif
687
688 typedef struct {
689         gint child_status;
690         GMainLoop *loop;
691 } child_watch_data_t;
692
693 static void
694 child_watch (GPid     pid,
695              gint     status,
696              gpointer data)
697 {
698         child_watch_data_t *child_watch_data = data;
699
700         g_spawn_close_pid (pid);
701
702         child_watch_data->child_status = status;
703
704         g_main_loop_quit (child_watch_data->loop);
705 }
706
707 static int
708 pipe_to_system (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
709 {
710         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
711         int i, pipe_to_child, pipe_from_child;
712         CamelMimeMessage *message = NULL;
713         CamelMimeParser *parser;
714         CamelStream *stream, *mem;
715         GPid child_pid;
716         GError *error = NULL;
717         GPtrArray *args;
718         child_watch_data_t child_watch_data;
719         GSource *source;
720         GMainContext *context;
721         
722         if (argc < 1 || argv[0]->value.string[0] == '\0')
723                 return 0;
724         
725         /* make sure we have the message... */
726         if (p->message == NULL) {
727                 if (!(p->message = camel_folder_get_message (p->source, p->uid, p->ex)))
728                         return -1;
729         }
730         
731         args = g_ptr_array_new ();
732         for (i = 0; i < argc; i++)
733                 g_ptr_array_add (args, argv[i]->value.string);
734         g_ptr_array_add (args, NULL);
735
736         if (!g_spawn_async_with_pipes (NULL,
737                                        (gchar **) args->pdata,
738                                        NULL,
739                                        G_SPAWN_DO_NOT_REAP_CHILD |
740                                        G_SPAWN_SEARCH_PATH |
741                                        G_SPAWN_STDERR_TO_DEV_NULL,
742                                        child_setup_func,
743                                        NULL,
744                                        &child_pid,
745                                        &pipe_to_child,
746                                        &pipe_from_child,
747                                        NULL,
748                                        &error)) {
749                 g_ptr_array_free (args, TRUE);
750
751                 camel_exception_setv (p->ex, CAMEL_EXCEPTION_SYSTEM,
752                                       _("Failed to create child process '%s': %s"),
753                                       argv[0]->value.string, error->message);
754                 g_error_free (error);
755                 return -1;
756         }
757         
758         g_ptr_array_free (args, TRUE);
759         
760         stream = camel_stream_fs_new_with_fd (pipe_to_child);
761         if (camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (p->message), stream) == -1) {
762                 camel_object_unref (stream);
763                 close (pipe_from_child);
764                 goto wait;
765         }
766         
767         if (camel_stream_flush (stream) == -1) {
768                 camel_object_unref (stream);
769                 close (pipe_from_child);
770                 goto wait;
771         }
772         
773         camel_object_unref (stream);
774         
775         stream = camel_stream_fs_new_with_fd (pipe_from_child);
776         mem = camel_stream_mem_new ();
777         if (camel_stream_write_to_stream (stream, mem) == -1) {
778                 camel_object_unref (stream);
779                 camel_object_unref (mem);
780                 goto wait;
781         }
782         
783         camel_object_unref (stream);
784         camel_stream_reset (mem);
785         
786         parser = camel_mime_parser_new ();
787         camel_mime_parser_init_with_stream (parser, mem);
788         camel_mime_parser_scan_from (parser, FALSE);
789         camel_object_unref (mem);
790         
791         message = camel_mime_message_new ();
792         if (camel_mime_part_construct_from_parser ((CamelMimePart *) message, parser) == -1) {
793                 camel_exception_setv (p->ex, CAMEL_EXCEPTION_SYSTEM,
794                                      _("Invalid message stream received from %s: %s"),
795                                       argv[0]->value.string,
796                                       g_strerror (camel_mime_parser_errno (parser)));
797                 camel_object_unref (message);
798                 message = NULL;
799         } else {
800                 camel_object_unref (p->message);
801                 p->message = message;
802                 p->modified = TRUE;
803         }
804         
805         camel_object_unref (parser);
806         
807  wait:
808         context = g_main_context_new ();
809         child_watch_data.loop = g_main_loop_new (context, FALSE);
810         g_main_context_unref (context);
811
812         source = g_child_watch_source_new (child_pid);
813         g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
814         g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
815         g_source_unref (source);
816
817         g_main_loop_run (child_watch_data.loop);
818         g_main_loop_unref (child_watch_data.loop);
819
820 #ifndef G_OS_WIN32
821         if (message && WIFEXITED (child_watch_data.child_status))
822                 return WEXITSTATUS (child_watch_data.child_status);
823         else
824                 return -1;
825 #else
826         return child_watch_data.child_status;
827 #endif
828 }
829
830 static ESExpResult *
831 pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
832 {
833         int i;
834         
835         /* make sure all args are strings */
836         for (i = 0; i < argc; i++) {
837                 if (argv[i]->type != ESEXP_RES_STRING)
838                         return NULL;
839         }
840         
841         camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Piping message to %s", argv[0]->value.string);
842         pipe_to_system (f, argc, argv, driver);
843         
844         return NULL;
845 }
846
847 static ESExpResult *
848 do_shell (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
849 {
850         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
851         GString *command;
852         GPtrArray *args;
853         int i;
854         
855         d(fprintf (stderr, "executing shell command\n"));
856         
857         command = g_string_new ("");
858         
859         args = g_ptr_array_new ();
860         
861         /* make sure all args are strings */
862         for (i = 0; i < argc; i++) {
863                 if (argv[i]->type != ESEXP_RES_STRING)
864                         goto done;
865                 
866                 g_ptr_array_add (args, argv[i]->value.string);
867                 
868                 g_string_append (command, argv[i]->value.string);
869                 g_string_append_c (command, ' ');
870         }
871         
872         g_string_truncate (command, command->len - 1);
873         
874         if (p->shellfunc && argc >= 1) {
875                 p->shellfunc (driver, argc, (char **) args->pdata, p->shelldata);
876                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Executing shell command: [%s]",
877                                          command->str);
878         }
879         
880  done:
881         
882         g_ptr_array_free (args, TRUE);
883         g_string_free (command, TRUE);
884         
885         return NULL;
886 }
887
888 static ESExpResult *
889 do_beep (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
890 {
891         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
892         
893         d(fprintf (stderr, "beep\n"));
894         
895         if (p->beep) {
896                 p->beep (driver, p->beepdata);
897                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Beep");
898         }
899         
900         return NULL;
901 }
902
903 static ESExpResult *
904 play_sound (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
905 {
906         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
907         
908         d(fprintf (stderr, "play sound\n"));
909         
910         if (p->playfunc && argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
911                 p->playfunc (driver, argv[0]->value.string, p->playdata);
912                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Play sound");
913         }
914         
915         return NULL;
916 }
917
918 static ESExpResult *
919 do_only_once (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
920 {
921         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
922         
923         d(fprintf (stderr, "only once\n"));
924         
925         if (argc == 2 && !g_hash_table_lookup (p->only_once, argv[0]->value.string))
926                 g_hash_table_insert (p->only_once, g_strdup (argv[0]->value.string),
927                                      g_strdup (argv[1]->value.string));
928         
929         return NULL;
930 }
931
932 static CamelFolder *
933 open_folder (CamelFilterDriver *driver, const char *folder_url)
934 {
935         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
936         CamelFolder *camelfolder;
937         
938         /* we have a lookup table of currently open folders */
939         camelfolder = g_hash_table_lookup (p->folders, folder_url);
940         if (camelfolder)
941                 return camelfolder == FOLDER_INVALID?NULL:camelfolder;
942
943         /* if we have a default folder, ignore exceptions.  This is so
944            a bad filter rule on pop or local delivery doesn't result
945            in duplicate mails, just mail going to inbox.  Otherwise,
946            we want to know about exceptions and abort processing */
947         if (p->defaultfolder) {
948                 CamelException ex;
949
950                 camel_exception_init (&ex);
951                 camelfolder = p->get_folder (driver, folder_url, p->data, &ex);
952                 camel_exception_clear (&ex);
953         } else {
954                 camelfolder = p->get_folder (driver, folder_url, p->data, p->ex);
955         }
956         
957         if (camelfolder) {
958                 g_hash_table_insert (p->folders, g_strdup (folder_url), camelfolder);
959                 camel_folder_freeze (camelfolder);
960         } else {
961                 g_hash_table_insert (p->folders, g_strdup (folder_url), FOLDER_INVALID);
962         }
963         
964         return camelfolder;
965 }
966
967 static void
968 close_folder (void *key, void *value, void *data)
969 {       
970         CamelFolder *folder = value;
971         CamelFilterDriver *driver = data;
972         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
973
974         p->closed++;
975         g_free (key);
976
977         if (folder != FOLDER_INVALID) {
978                 camel_folder_sync (folder, FALSE, camel_exception_is_set(p->ex)?NULL : p->ex);
979                 camel_folder_thaw (folder);
980                 camel_object_unref (folder);
981         }
982
983         report_status(driver, CAMEL_FILTER_STATUS_PROGRESS, g_hash_table_size(p->folders)* 100 / p->closed, _("Syncing folders"));
984 }
985
986 /* flush/close all folders */
987 static int
988 close_folders (CamelFilterDriver *driver)
989 {
990         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
991
992         report_status(driver, CAMEL_FILTER_STATUS_PROGRESS, 0, _("Syncing folders"));
993
994         p->closed = 0;
995         g_hash_table_foreach (p->folders, close_folder, driver);
996         g_hash_table_destroy (p->folders);
997         p->folders = g_hash_table_new (g_str_hash, g_str_equal);
998         
999         /* FIXME: status from driver */
1000         return 0;
1001 }
1002
1003 #if 0
1004 static void
1005 free_key (gpointer key, gpointer value, gpointer user_data)
1006 {
1007         g_free (key);
1008 }
1009 #endif
1010
1011
1012 static void
1013 camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const char *desc, ...)
1014 {
1015         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
1016         
1017         if (p->logfile) {
1018                 char *str = NULL;
1019                 
1020                 if (desc) {
1021                         va_list ap;
1022                         
1023                         va_start (ap, desc);
1024                         str = g_strdup_vprintf (desc, ap);
1025                 }
1026                 
1027                 switch (status) {
1028                 case FILTER_LOG_START: {
1029                         /* write log header */
1030                         const char *subject = NULL;
1031                         const char *from = NULL;
1032                         char date[50];
1033                         time_t t;
1034                         
1035                         /* FIXME: does this need locking?  Probably */
1036                         
1037                         from = camel_message_info_from (p->info);
1038                         subject = camel_message_info_subject (p->info);
1039                         
1040                         time (&t);
1041                         strftime (date, 49, "%a, %d %b %Y %H:%M:%S", localtime (&t));
1042                         fprintf (p->logfile, "Applied filter \"%s\" to message from %s - \"%s\" at %s\n",
1043                                  str, from ? from : "unknown", subject ? subject : "", date);
1044                         
1045                         break;
1046                 }
1047                 case FILTER_LOG_ACTION:
1048                         fprintf (p->logfile, "Action: %s\n", str);
1049                         break;
1050                 case FILTER_LOG_END:
1051                         fprintf (p->logfile, "\n");
1052                         break;
1053                 default:
1054                         /* nothing else is loggable */
1055                         break;
1056                 }
1057                 
1058                 g_free (str);
1059         }
1060 }
1061
1062
1063 struct _run_only_once {
1064         CamelFilterDriver *driver;
1065         CamelException *ex;
1066 };
1067
1068 static gboolean
1069 run_only_once (gpointer key, char *action, struct _run_only_once *data)
1070 {
1071         struct _CamelFilterDriverPrivate *p = _PRIVATE (data->driver);
1072         CamelException *ex = data->ex;
1073         ESExpResult *r;
1074         
1075         d(printf ("evaluating: %s\n\n", action));
1076         
1077         e_sexp_input_text (p->eval, action, strlen (action));
1078         if (e_sexp_parse (p->eval) == -1) {
1079                 if (!camel_exception_is_set (ex))
1080                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1081                                               _("Error parsing filter: %s: %s"),
1082                                               e_sexp_error (p->eval), action);
1083                 goto done;
1084         }
1085         
1086         r = e_sexp_eval (p->eval);
1087         if (r == NULL) {
1088                 if (!camel_exception_is_set (ex))
1089                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1090                                               _("Error executing filter: %s: %s"),
1091                                               e_sexp_error (p->eval), action);
1092                 goto done;
1093         }
1094         
1095         e_sexp_result_free (p->eval, r);
1096         
1097  done:
1098         
1099         g_free (key);
1100         g_free (action);
1101         
1102         return TRUE;
1103 }
1104
1105
1106 /**
1107  * camel_filter_driver_flush:
1108  * @driver:
1109  * @ex:
1110  *
1111  * Flush all of the only-once filter actions.
1112  **/
1113 void
1114 camel_filter_driver_flush (CamelFilterDriver *driver, CamelException *ex)
1115 {
1116         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
1117         struct _run_only_once data;
1118         
1119         if (!p->only_once)
1120                 return;
1121         
1122         data.driver = driver;
1123         data.ex = ex;
1124         
1125         g_hash_table_foreach_remove (p->only_once, (GHRFunc) run_only_once, &data);
1126 }
1127
1128 /**
1129  * camel_filter_driver_filter_mbox:
1130  * @driver: CamelFilterDriver
1131  * @mbox: mbox filename to be filtered
1132  * @original_source_url:
1133  * @ex: exception
1134  *
1135  * Filters an mbox file based on rules defined in the FilterDriver
1136  * object. Is more efficient as it doesn't need to open the folder
1137  * through Camel directly.
1138  *
1139  * Returns -1 if errors were encountered during filtering,
1140  * otherwise returns 0.
1141  *
1142  **/
1143 int
1144 camel_filter_driver_filter_mbox (CamelFilterDriver *driver, const char *mbox, const char *original_source_url, CamelException *ex)
1145 {
1146         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
1147         CamelMimeParser *mp = NULL;
1148         char *source_url = NULL;
1149         int fd = -1;
1150         int i = 0;
1151         struct stat st;
1152         int status;
1153         off_t last = 0;
1154         int ret = -1;
1155         
1156         fd = g_open (mbox, O_RDONLY|O_BINARY, 0);
1157         if (fd == -1) {
1158                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Unable to open spool folder"));
1159                 goto fail;
1160         }
1161         /* to get the filesize */
1162         fstat (fd, &st);
1163         
1164         mp = camel_mime_parser_new ();
1165         camel_mime_parser_scan_from (mp, TRUE);
1166         if (camel_mime_parser_init_with_fd (mp, fd) == -1) {
1167                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Unable to process spool folder"));
1168                 goto fail;
1169         }
1170         fd = -1;
1171         
1172         source_url = g_filename_to_uri (mbox, NULL, NULL);
1173         
1174         while (camel_mime_parser_step (mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) {
1175                 CamelMessageInfo *info;
1176                 CamelMimeMessage *msg;
1177                 int pc = 0;
1178                 
1179                 if (st.st_size > 0)
1180                         pc = (int)(100.0 * ((double)camel_mime_parser_tell (mp) / (double)st.st_size));
1181                 
1182                 report_status (driver, CAMEL_FILTER_STATUS_START, pc, _("Getting message %d (%d%%)"), i, pc);
1183                 
1184                 msg = camel_mime_message_new ();
1185                 if (camel_mime_part_construct_from_parser (CAMEL_MIME_PART (msg), mp) == -1) {
1186                         camel_exception_set (ex, (errno==EINTR)?CAMEL_EXCEPTION_USER_CANCEL:CAMEL_EXCEPTION_SYSTEM, _("Cannot open message"));
1187                         report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Failed on message %d"), i);
1188                         camel_object_unref (msg);
1189                         goto fail;
1190                 }
1191                 
1192                 info = camel_message_info_new_from_header(NULL, ((CamelMimePart *)msg)->headers);
1193                 ((CamelMessageInfoBase *)info)->size = camel_mime_parser_tell(mp) - last;
1194                 last = camel_mime_parser_tell(mp);
1195                 status = camel_filter_driver_filter_message (driver, msg, info, NULL, NULL, source_url, 
1196                                                              original_source_url ? original_source_url : source_url, ex);
1197                 camel_object_unref (msg);
1198                 if (camel_exception_is_set (ex) || status == -1) {
1199                         report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Failed on message %d"), i);
1200                         camel_message_info_free (info);
1201                         goto fail;
1202                 }
1203                 
1204                 i++;
1205                 
1206                 /* skip over the FROM_END state */
1207                 camel_mime_parser_step (mp, 0, 0);
1208
1209                 camel_message_info_free (info);
1210         }
1211         
1212         if (p->defaultfolder) {
1213                 report_status(driver, CAMEL_FILTER_STATUS_PROGRESS, 100, _("Syncing folder"));
1214                 camel_folder_sync(p->defaultfolder, FALSE, camel_exception_is_set (ex) ? NULL : ex);
1215         }
1216         
1217         report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete"));
1218         
1219         ret = 0;
1220 fail:
1221         g_free (source_url);
1222         if (fd != -1)
1223                 close (fd);
1224         if (mp)
1225                 camel_object_unref (mp);
1226         
1227         return ret;
1228 }
1229
1230
1231 /**
1232  * camel_filter_driver_filter_folder:
1233  * @driver: CamelFilterDriver
1234  * @folder: CamelFolder to be filtered
1235  * @cache: UID cache (needed for POP folders)
1236  * @uids: message uids to be filtered or NULL (as a shortcut to filter all messages)
1237  * @remove: TRUE to mark filtered messages as deleted
1238  * @ex: exception
1239  *
1240  * Filters a folder based on rules defined in the FilterDriver
1241  * object.
1242  *
1243  * Returns -1 if errors were encountered during filtering,
1244  * otherwise returns 0.
1245  *
1246  **/
1247 int
1248 camel_filter_driver_filter_folder (CamelFilterDriver *driver, CamelFolder *folder, CamelUIDCache *cache,
1249                                    GPtrArray *uids, gboolean remove, CamelException *ex)
1250 {
1251         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
1252         gboolean freeuids = FALSE;
1253         CamelMessageInfo *info;
1254         char *source_url, *service_url;
1255         int status = 0;
1256         CamelURL *url;
1257         int i;
1258         
1259         service_url = camel_service_get_url (CAMEL_SERVICE (camel_folder_get_parent_store (folder)));
1260         url = camel_url_new (service_url, NULL);
1261         g_free (service_url);
1262         
1263         source_url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
1264         camel_url_free (url);
1265         
1266         if (uids == NULL) {
1267                 uids = camel_folder_get_uids (folder);
1268                 freeuids = TRUE;
1269         }
1270         
1271         for (i = 0; i < uids->len; i++) {
1272                 int pc = (100 * i)/uids->len;
1273                 
1274                 report_status (driver, CAMEL_FILTER_STATUS_START, pc, _("Getting message %d of %d"), i+1,
1275                                uids->len);
1276                 
1277                 if (camel_folder_has_summary_capability (folder))
1278                         info = camel_folder_get_message_info (folder, uids->pdata[i]);
1279                 else
1280                         info = NULL;
1281                 
1282                 status = camel_filter_driver_filter_message (driver, NULL, info, uids->pdata[i],
1283                                                              folder, source_url, source_url, ex);
1284                 
1285                 if (camel_folder_has_summary_capability (folder))
1286                         camel_folder_free_message_info (folder, info);
1287                 
1288                 if (camel_exception_is_set (ex) || status == -1) {
1289                         report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Failed at message %d of %d"),
1290                                        i+1, uids->len);
1291                         status = -1;
1292                         break;
1293                 }
1294                 
1295                 if (remove)
1296                         camel_folder_set_message_flags (folder, uids->pdata[i],
1297                                                         CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, ~0);
1298                 
1299                 if (cache)
1300                         camel_uid_cache_save_uid (cache, uids->pdata[i]);
1301         }
1302         
1303         if (p->defaultfolder) {
1304                 report_status (driver, CAMEL_FILTER_STATUS_PROGRESS, 100, _("Syncing folder"));
1305                 camel_folder_sync (p->defaultfolder, FALSE, camel_exception_is_set (ex) ? NULL : ex);
1306         }
1307         
1308         if (i == uids->len)
1309                 report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete"));
1310         
1311         if (freeuids)
1312                 camel_folder_free_uids (folder, uids);
1313         
1314         g_free (source_url);
1315         
1316         return status;
1317 }
1318
1319
1320 struct _get_message {
1321         struct _CamelFilterDriverPrivate *p;
1322         const char *source_url;
1323 };
1324
1325
1326 static CamelMimeMessage *
1327 get_message_cb (void *data, CamelException *ex)
1328 {
1329         struct _get_message *msgdata = data;
1330         struct _CamelFilterDriverPrivate *p = msgdata->p;
1331         const char *source_url = msgdata->source_url;
1332         CamelMimeMessage *message;
1333         
1334         if (p->message) {
1335                 message = p->message;
1336                 camel_object_ref (message);
1337         } else {
1338                 const char *uid;
1339                 
1340                 if (p->uid)
1341                         uid = p->uid;
1342                 else
1343                         uid = camel_message_info_uid (p->info);
1344                 
1345                 message = camel_folder_get_message (p->source, uid, ex);
1346         }
1347         
1348         if (source_url && message && camel_mime_message_get_source (message) == NULL)
1349                 camel_mime_message_set_source (message, source_url);
1350         
1351         return message;
1352 }
1353
1354 /**
1355  * camel_filter_driver_filter_message:
1356  * @driver: CamelFilterDriver
1357  * @message: message to filter or NULL
1358  * @info: message info or NULL
1359  * @uid: message uid or NULL
1360  * @source: source folder or NULL
1361  * @source_url: url of source folder or NULL
1362  * @original_source_url: url of original source folder (pre-movemail) or NULL
1363  * @ex: exception
1364  *
1365  * Filters a message based on rules defined in the FilterDriver
1366  * object. If the source folder (@source) and the uid (@uid) are
1367  * provided, the filter will operate on the CamelFolder (which in
1368  * certain cases is more efficient than using the default
1369  * camel_folder_append_message() function).
1370  *
1371  * Returns -1 if errors were encountered during filtering,
1372  * otherwise returns 0.
1373  *
1374  **/
1375 int
1376 camel_filter_driver_filter_message (CamelFilterDriver *driver, CamelMimeMessage *message,
1377                                     CamelMessageInfo *info, const char *uid,
1378                                     CamelFolder *source, const char *source_url,
1379                                     const char *original_source_url,
1380                                     CamelException *ex)
1381 {
1382         struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
1383         struct _filter_rule *node;
1384         gboolean freeinfo = FALSE;
1385         gboolean filtered = FALSE;
1386         ESExpResult *r;
1387         int result;
1388         
1389         /* FIXME: make me into a g_return_if_fail/g_assert or whatever... */
1390         if (message == NULL && (source == NULL || uid == NULL)) {
1391                 g_warning ("there is no way to fetch the message using the information provided...");
1392                 return -1;
1393         }
1394         
1395         if (info == NULL) {
1396                 struct _camel_header_raw *h;
1397                 
1398                 if (message) {
1399                         camel_object_ref (message);
1400                 } else {
1401                         message = camel_folder_get_message (source, uid, ex);
1402                         if (!message)
1403                                 return -1;
1404                 }
1405                 
1406                 h = CAMEL_MIME_PART (message)->headers;
1407                 info = camel_message_info_new_from_header (NULL, h);
1408                 freeinfo = TRUE;
1409         } else {
1410                 if (camel_message_info_flags(info) & CAMEL_MESSAGE_DELETED)
1411                         return 0;
1412                 
1413                 uid = camel_message_info_uid (info);
1414                 
1415                 if (message)
1416                         camel_object_ref (message);
1417         }
1418         
1419         p->ex = ex;
1420         p->terminated = FALSE;
1421         p->deleted = FALSE;
1422         p->copied = FALSE;
1423         p->moved = FALSE;
1424         p->message = message;
1425         p->info = info;
1426         p->uid = uid;
1427         p->source = source;
1428         
1429         if (message && original_source_url && camel_mime_message_get_source (message) == NULL)
1430                 camel_mime_message_set_source (message, original_source_url);
1431         
1432         node = (struct _filter_rule *) p->rules.head;
1433         result = CAMEL_SEARCH_NOMATCH;
1434         while (node->next && !p->terminated) {
1435                 struct _get_message data;
1436                 
1437                 d(printf("applying rule %s\naction %s\n", node->match, node->action));
1438                 
1439                 data.p = p;
1440                 data.source_url = original_source_url;
1441                 
1442                 result = camel_filter_search_match (p->session, get_message_cb, &data, p->info, 
1443                                                     original_source_url ? original_source_url : source_url,
1444                                                     node->match, p->ex);
1445                 
1446                 switch (result) {
1447                 case CAMEL_SEARCH_ERROR:
1448                         goto error;
1449                 case CAMEL_SEARCH_MATCHED:
1450                         filtered = TRUE;
1451                         camel_filter_driver_log (driver, FILTER_LOG_START, node->name);
1452
1453                         if (camel_debug(":filter"))
1454                                 printf("filtering '%s' applying rule %s\n",
1455                                        camel_message_info_subject(info)?camel_message_info_subject(info):"?no subject?", node->name);
1456
1457                         /* perform necessary filtering actions */
1458                         e_sexp_input_text (p->eval, node->action, strlen (node->action));
1459                         if (e_sexp_parse (p->eval) == -1) {
1460                                 camel_exception_setv (ex, 1, _("Error parsing filter: %s: %s"),
1461                                                       e_sexp_error (p->eval), node->action);
1462                                 goto error;
1463                         }
1464                         r = e_sexp_eval (p->eval);
1465                         if (camel_exception_is_set(p->ex))
1466                                 goto error;
1467
1468                         if (r == NULL) {
1469                                 camel_exception_setv (ex, 1, _("Error executing filter: %s: %s"),
1470                                                       e_sexp_error (p->eval), node->action);
1471                                 goto error;
1472                         }
1473                         e_sexp_result_free (p->eval, r);
1474                 default:
1475                         break;
1476                 }
1477                 
1478                 node = node->next;
1479         }
1480         
1481         /* *Now* we can set the DELETED flag... */
1482         if (p->deleted) {
1483                 if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
1484                         camel_folder_set_message_flags(p->source, p->uid, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN, ~0);
1485                 else
1486                         camel_message_info_set_flags(info, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
1487         }
1488         
1489         /* Logic: if !Moved and there exists a default folder... */
1490         if (!(p->copied && p->deleted) && !p->moved && p->defaultfolder) {
1491                 /* copy it to the default inbox */
1492                 filtered = TRUE;
1493                 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Copy to default folder");
1494
1495                 if (camel_debug(":filter"))
1496                         printf("filtering '%s' copy %s to default folder\n",
1497                                camel_message_info_subject(info)?camel_message_info_subject(info):"?no subject?",
1498                                p->modified?"modified message":"");
1499
1500                 if (!p->modified && p->uid && p->source && camel_folder_has_summary_capability (p->source)) {
1501                         GPtrArray *uids;
1502                         
1503                         uids = g_ptr_array_new ();
1504                         g_ptr_array_add (uids, (char *) p->uid);
1505                         camel_folder_transfer_messages_to (p->source, uids, p->defaultfolder, NULL, FALSE, p->ex);
1506                         g_ptr_array_free (uids, TRUE);
1507                 } else {
1508                         if (p->message == NULL) {
1509                                 p->message = camel_folder_get_message (source, uid, ex);
1510                                 if (!p->message)
1511                                         goto error;
1512                         }
1513
1514                         camel_folder_append_message (p->defaultfolder, p->message, p->info, NULL, p->ex);
1515                 }
1516         }
1517         
1518         if (p->message)
1519                 camel_object_unref (p->message);
1520         
1521         if (freeinfo)
1522                 camel_message_info_free (info);
1523         
1524         return 0;
1525         
1526  error:
1527         if (filtered)
1528                 camel_filter_driver_log (driver, FILTER_LOG_END, NULL);
1529         
1530         if (p->message)
1531                 camel_object_unref (p->message);
1532         
1533         if (freeinfo)
1534                 camel_message_info_free (info);
1535         
1536         return -1;
1537 }