aboutsummaryrefslogblamecommitdiffstats
path: root/filter/filter-driver.c
blob: e706558252d0ad70d6e8e1e8571e973f07e7454d (plain) (tree)





















                                                                       










                             
                   
                          
 
                        
 

                               
                                                      
                                    
 

                                                            
                                                                           


                                                                                  

                                                            
                           


                       

  












































































































                                                                                                              



                                    




                                                                               


                                              





                                           
                                                                                     
 


                                       
 

                                                                       
 
                             
 
                                                                         
 
                                        
                                    

                                             


                                                                 


                                                                   
 


                                                                  

                   

 
 














                                                                                                 









                   


                                          
          
                                                                              



                               
                                                 



                       

                                         









                                                      
                                                        
                                                                                      
                           






                                                                             
                                         








                                                                                                   


                                                       


                                                    


                                                                          
                                                         












                                                  

                                                                                                   


                       
                                                     
 











                                                                                
                                 
                                                                                          
                         
                                                       
                 
 

                                        
 









                                                                                               
                 
                                             
         
 



                                                              

 
                    
                                                                                  
 
                                                     


                                 
                       
                   

                                                 
                                                                                                                         
 





                            
                                                                                   
 
                                                     


                                                             
                       
                   
                                                 





                            
                                                                                


                 
                                                     
 
                               







                                                                                 



                                                                            
                         
 
                                       


                                                     
                                                                                  
 
                                                                                         
                                                                               

                                                                  
                                                                 

                                            






                    
                                                                                

                 
                                                     

                                                          
                       
                   

                                                                                 




                            

                                                    
 







                                                                  
                                             
                          
                                                                                   

                                                 

                           

 

                                                
 



                                                     
                                                     
 
                    
                                           
                             














                                                               

 
   













                                                     
                                                                           
 
                                                     
                       
                       
                       


                       
 
                                        
                           
 



                                                                  
 
                                    
                                   
 
                             

                                                         
 

                                     
                                                         

                                                            
                                                                                           
 
                                                                       
                               


                                           
                                                                  
 
                                                                                       

                                                                                                
                         
 


                                                                                            


                              
 
                                                              


                                                                   
                                      
 

                                       
 
                                        
                


                                               

                                                                                       


                                                      
                                     
 


                                                                 

                                                                                     
                                                                                               

                                                                              
                                                                                                                                              
                         
                        
                                                                           
                 
         
                                               


                                            
                         
                                         
                                 
 
                 
 
/*
 *  Copyright (C) 2000 Helix Code Inc.
 *
 *  Authors: Michael Zucchi <notzed@helixcode.com>
 *
 *  This program is free software; you can redistribute it and/or 
 *  modify it under the terms of the GNU General Public License as 
 *  published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *  USA
 */

#include "filter-driver.h"

#include <glib.h>
#include <gtk/gtk.h>
#include <gnome.h>
#include <gtkhtml/gtkhtml.h>

#include <gnome-xml/tree.h>
#include <gnome-xml/parser.h>

#include "filter-arg-types.h"
#include "filter-xml.h"
#include "e-sexp.h"
#include "filter-format.h"

#include <camel/camel.h>

struct _FilterDriverPrivate {
    GList *rules, *options;
    GHashTable *globals;    /* global variables */
    FilterFolderFetcher fetcher;

    /* run-time data */
    GHashTable *folders;    /* currently open folders */
    GList *matches;     /* all messages which match current rule */
    GHashTable *terminated; /* messages for which processing is terminated */
    GHashTable *processed;  /* all messages that were processed in some way */

    CamelFolder *source;    /* temporary input folder */

    CamelException *ex;

    /* evaluator */
    ESExp *eval;
};

#define _PRIVATE(o) (((FilterDriver *)(o))->priv)

static void filter_driver_class_init (FilterDriverClass *klass);
static void filter_driver_init       (FilterDriver *obj);
static void filter_driver_finalise   (GtkObject *obj);

static CamelFolder *open_folder(FilterDriver *d, const char *folder_url);
static int close_folders(FilterDriver *d);

static ESExpResult *do_delete(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *);
static ESExpResult *do_forward(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *);
static ESExpResult *do_copy(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *);
static ESExpResult *do_stop(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *);

static struct {
    char *name;
    ESExpFunc *func;
    int type;       /* set to 1 if a function can perform shortcut evaluation, or
                   doesn't execute everything, 0 otherwise */
} symbols[] = {
    { "delete", (ESExpFunc *)do_delete, 0 },
    { "forward-to", (ESExpFunc *)do_forward, 0 },
    { "copy-to", (ESExpFunc *)do_copy, 0 },
    { "stop", (ESExpFunc *)do_stop, 0 },
};

static GtkObjectClass *filter_driver_parent;

enum SIGNALS {
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

guint
filter_driver_get_type (void)
{
    static guint type = 0;
    
    if (!type) {
        GtkTypeInfo type_info = {
            "FilterDriver",
            sizeof (FilterDriver),
            sizeof (FilterDriverClass),
            (GtkClassInitFunc) filter_driver_class_init,
            (GtkObjectInitFunc) filter_driver_init,
            (GtkArgSetFunc) NULL,
            (GtkArgGetFunc) NULL
        };
        
        type = gtk_type_unique (gtk_object_get_type (), &type_info);
    }
    
    return type;
}

static void
filter_driver_class_init (FilterDriverClass *klass)
{
    GtkObjectClass *object_class = (GtkObjectClass *) klass;
    
    filter_driver_parent = gtk_type_class (gtk_object_get_type ());

    object_class->finalize = filter_driver_finalise;

    gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
}

static void
filter_driver_init (FilterDriver *obj)
{
    struct _FilterDriverPrivate *p;
    int i;

    p = _PRIVATE(obj) = g_malloc0(sizeof(*p));

    p->eval = e_sexp_new();
    /* Load in builtin symbols */
    for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
        if (symbols[i].type == 1) {
            e_sexp_add_ifunction(p->eval, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, obj);
        } else {
            e_sexp_add_function(p->eval, 0, symbols[i].name, symbols[i].func, obj);
        }
    }

    p->globals = g_hash_table_new(g_str_hash, g_str_equal);

    p->ex = camel_exception_new ();
}

static void
free_hash_strings(void *key, void *value, void *data)
{
    g_free(key);
    g_free(value);
}

static void
filter_driver_finalise (GtkObject *obj)
{
    FilterDriver *d = (FilterDriver *)obj;
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    g_hash_table_foreach(p->globals, free_hash_strings, d);
    g_hash_table_destroy(p->globals);

    gtk_object_unref((GtkObject *)p->eval);

    camel_exception_free(p->ex);
    
    g_free(p);

    ((GtkObjectClass *)(filter_driver_parent))->finalize((GtkObject *)obj);
}

/**
 * filter_driver_new:
 * @system: path to system rules
 * @user: path to user rules
 * @fetcher: function to call to fetch folders
 *
 * Create a new FilterDriver object.
 * 
 * Return value: A new FilterDriver widget.
 **/
FilterDriver *
filter_driver_new (const char *system, const char *user, FilterFolderFetcher fetcher)
{
    FilterDriver *new;
    struct _FilterDriverPrivate *p;
    xmlDocPtr desc, filt;

    new = FILTER_DRIVER (gtk_type_new (filter_driver_get_type ()));
    p = _PRIVATE(new);

    p->fetcher = fetcher;

    printf("Loading system '%s'\nLoading user '%s'\n", system, user);

#warning "fix leaks, free xml docs here"
    desc = xmlParseFile(system);
    p->rules = filter_load_ruleset(desc);

    filt = xmlParseFile(user);
    if (filt == NULL) {
        g_warning("Couldn't load filter file %s!", user);
        p->options = NULL;
    } else
        p->options = filter_load_optionset(filt, p->rules);

#warning "Zucchi: is this safe? Doesn't seem to cause problems..."
    filter_load_ruleset_free (p->rules);
    xmlFreeDoc (desc);

    return new;
}


void filter_driver_set_global(FilterDriver *d, const char *name, const char *value)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);
    char *oldkey, *oldvalue;

    if (g_hash_table_lookup_extended(p->globals, name, (void *)&oldkey, (void *)&oldvalue)) {
        g_free(oldvalue);
        g_hash_table_insert(p->globals, oldkey, g_strdup(value));
    } else {
        g_hash_table_insert(p->globals, g_strdup(name), g_strdup(value));
    }
}

extern int filter_find_arg(FilterArg *a, char *name);

/*

  foreach rule
    find matches

  foreach action
    get all matches

 */

/*
  splices ${cc} lines into a single string
*/
static int
expand_variables(GString *out, char *source, GList *args, GHashTable *globals)
{
    GList *argl;
    FilterArg *arg;
    char *name= alloca(32);
    char *start, *end, *newstart, *tmp, *val;
    int namelen=32;
    int len=0;
    int ok = 0;

    printf("expanding %s\n", source);

    start = source;
    while ( (newstart = strstr(start, "${"))
        && (end = strstr(newstart+2, "}")) ) {
        len = end-newstart-2;
        if (len+1>namelen) {
            namelen = (len+1)*2;
            name = alloca(namelen);
        }
        memcpy(name, newstart+2, len);
        name[len] = 0;
        printf("looking for name '%s'\n", name);
        argl = g_list_find_custom(args, name, (GCompareFunc) filter_find_arg);
        if (argl) {
            int i, count;

            tmp = g_strdup_printf("%.*s", newstart-start, start);
            printf("appending: %s\n", tmp);
            g_string_append(out, tmp);
            g_free(tmp);

            arg = argl->data;
            count = filter_arg_get_count(arg);
            for (i=0;i<count;i++) {
                printf("appending '%s'\n", filter_arg_get_value_as_string(arg, i));
                g_string_append(out, " \"");
                g_string_append(out, filter_arg_get_value_as_string(arg, i));
                g_string_append(out, "\"");
            }
        } else if ( (val = g_hash_table_lookup(globals, name)) ) {
            tmp = g_strdup_printf("%.*s", newstart-start, start);
            printf("appending: %s\n", tmp);
            g_string_append(out, tmp);
            g_free(tmp);
            g_string_append(out, " \"");
            g_string_append(out, val);
            g_string_append(out, "\"");
        } else {
            ok = 1;
            tmp = g_strdup_printf("%.*s", end-start+1, start);
            printf("appending: '%s'\n", tmp);
            g_string_append(out, tmp);
            g_free(tmp);
        }
        start = end+1;
    }
    g_string_append(out, start);

    return ok;
}

/*
  build an expression for the filter
*/
void
filter_driver_expand_option(FilterDriver *d, GString *s, GString *action, struct filter_option *op)
{
    GList *optionl;
    FilterArg *arg;
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    if (s) {
        g_string_append(s, "(and ");
        optionl = op->options;
        while (optionl) {
            struct filter_optionrule *or = optionl->data;
            if (or->rule->type == FILTER_XML_MATCH
                || or->rule->type == FILTER_XML_EXCEPT) {
                if (or->args) {
                    arg = or->args->data;
                    if (arg) {
                        printf("arg = %s\n", arg->name);
                    }
                }
                expand_variables(s, or->rule->code, or->args, p->globals);
            }
            optionl = g_list_next(optionl);
        }

        g_string_append(s, ")");
    }

    if (action) {
        g_string_append(action, "(begin ");
        optionl = op->options;
        while (optionl) {
            struct filter_optionrule *or = optionl->data;
            if (or->rule->type == FILTER_XML_ACTION) {
                expand_variables(action, or->rule->code, or->args, p->globals);
                g_string_append(action, " ");
            }
            optionl = g_list_next(optionl);
        }
        g_string_append(action, ")");
    }

    if (s)
        printf("combined rule '%s'\n", s->str);
    if (action)
        printf("combined action '%s'\n", action->str);
}

static ESExpResult *
do_delete(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *d)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);
    GList *m;

    printf("doing delete\n");
    m = p->matches;
    while (m) {
        printf(" %s\n", (char *)m->data);

        camel_folder_set_message_flags (p->source, m->data, CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_DELETED, p->ex);

        m = m->next;
    }
    return NULL;
}

static ESExpResult *
do_forward(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *d)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);
    GList *m;

    printf("doing forward on the following messages:\n");
    m = p->matches;
    while (m) {
        printf(" %s\n", (char *)m->data);
        m = m->next;
    }
    return NULL;
}

static ESExpResult *
do_copy(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *d)
{
    GList *m;
    int i;
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    printf("doing copy\n");
    for (i=0;i<argc;i++) {
        if (argv[i]->type == ESEXP_RES_STRING) {
            char *folder = argv[i]->value.string;
            CamelFolder *outbox;

            /* FIXME: this might have to find another store, based on
               the folder as a url??? */
            printf("opening outpbox %s\n", folder);
            outbox = open_folder(d, folder);
            if (outbox == NULL) {
                g_warning("Cannot open folder: %s", folder);
                continue;
            }

            m = p->matches;
            while (m) {
                CamelMimeMessage *mm;

                printf("appending message %s\n", (char *)m->data);

                mm = camel_folder_get_message(p->source, m->data, p->ex);
                camel_folder_append_message(outbox, mm, p->ex);
                gtk_object_unref((GtkObject *)mm);

                printf(" %s\n", (char *)m->data);
                m = m->next;
            }
        }
    }

    return NULL;
}

static ESExpResult *
do_stop(struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterDriver *d)
{
    GList *m;
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    printf("doing stop on the following messages:\n");
    m = p->matches;
    while (m) {
        printf(" %s\n", (char *)m->data);
        g_hash_table_insert(p->terminated, g_strdup(m->data), (void *)1);
        m = m->next;
    }
    return NULL;
}

static CamelFolder *
open_folder(FilterDriver *d, const char *folder_url)
{
    CamelFolder *camelfolder;
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    /* we have a lookup table of currently open folders */
    camelfolder = g_hash_table_lookup(p->folders, folder_url);
    if (camelfolder)
        return camelfolder;

    camelfolder = p->fetcher(folder_url);
    if (camelfolder) {
        g_hash_table_insert(p->folders, g_strdup(folder_url), camelfolder);
        camel_folder_freeze(camelfolder);
    }

    return camelfolder;
}

static void
close_folder(void *key, void *value, void *data)
{
    CamelFolder *f = value;
    FilterDriver *d = data;
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    printf("closing folder: %s\n", (char *) key);

    g_free(key);
    camel_folder_sync(f, FALSE, p->ex);
    camel_folder_thaw(f);
    gtk_object_unref((GtkObject *)f);
}

/* flush/close all folders */
static int
close_folders(FilterDriver *d)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);

    g_hash_table_foreach(p->folders, close_folder, d);
    g_hash_table_destroy(p->folders);
    p->folders = g_hash_table_new(g_str_hash, g_str_equal);

    /* FIXME: status from d */
    return 0;
}

int
filter_driver_rule_count(FilterDriver *d)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);
    return g_list_length(p->options);
}

struct filter_option *
filter_driver_rule_get(FilterDriver *d, int n)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);
    return g_list_nth_data(p->options, n);
}

int
filter_driver_run(FilterDriver *d, CamelFolder *source, CamelFolder *inbox)
{
    struct _FilterDriverPrivate *p = _PRIVATE(d);
    ESExpResult *r;
    GList *options;
    GString *s, *a;
    GPtrArray *all;
    GList *m;
    int i;

#warning "This must be made mega-robust"
    p->source = source;

    /* setup runtime data */
    p->folders = g_hash_table_new(g_str_hash, g_str_equal);
    p->terminated = g_hash_table_new(g_str_hash, g_str_equal);
    p->processed = g_hash_table_new(g_str_hash, g_str_equal);

    camel_exception_init(p->ex);
    camel_folder_freeze(inbox);

    options = p->options;
    while (options) {
        struct filter_option *fo = options->data;

        s = g_string_new("");
        a = g_string_new("");
        filter_driver_expand_option(d, s, a, fo);

        printf("searching expression %s\n", s->str);
        p->matches = camel_folder_search_by_expression  (p->source, s->str, p->ex);

        /* remove uid's for which processing is complete ... */
        m = p->matches;
        while (m) {
            GList *n = m->next;

            printf("matched: %s\n", (char *) m->data);

            /* for all matching id's, so we can work out what to default */
            if (g_hash_table_lookup(p->processed, m->data) == NULL) {
                g_hash_table_insert(p->processed, g_strdup(m->data), (void *)1);
            }

            if (g_hash_table_lookup(p->terminated, m->data)) {
                printf("removing terminated message %s\n", (char *)m->data);
                p->matches = g_list_remove_link(p->matches, m);
            }
            m = n;
        }

        printf("applying actions ... '%s'\n", a->str);
        e_sexp_input_text(p->eval, a->str, strlen(a->str));
        e_sexp_parse(p->eval);
        r = e_sexp_eval(p->eval);
        e_sexp_result_free(r);

        g_string_free(s, TRUE);
        g_string_free(a, TRUE);

        g_list_free(p->matches);
        
        options = g_list_next(options);
    }

    /* apply the default of copying to an inbox, if we are given one, and make sure
       we delete everything as well */
    all = camel_folder_get_uids(p->source, p->ex);
    for (i = 0; i < all->len; i++) {
        char *uid = all->pdata[i], *procuid;
        CamelMimeMessage *mm;

        procuid = g_hash_table_lookup(p->processed, uid);
        if (procuid == NULL) {
            if (inbox) {
                printf("Applying default rule to message %s\n", uid);

                mm = camel_folder_get_message(p->source, all->pdata[i], p->ex);
                camel_folder_append_message(inbox, mm, p->ex);
                gtk_object_unref((GtkObject *)mm);
                camel_folder_set_message_flags(p->source, all->pdata[i], CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_DELETED, p->ex);
            }
        } else {
            camel_folder_delete_message(p->source, uid, p->ex);
        }
    }
    camel_folder_free_uids(p->source, all);

    g_hash_table_destroy(p->processed);
    g_hash_table_destroy(p->terminated);
    close_folders(d);
    g_hash_table_destroy(p->folders);
    camel_folder_thaw(inbox);

    return 0;
}