aboutsummaryrefslogblamecommitdiffstats
path: root/camel/providers/local/camel-spool-store.c
blob: b1e7c7e0915d0d11be5dd29366371cc44191f30f (plain) (tree)
1
2
3
4
5
6
7
8
9



                                                                        
                                                  

                                                                 
                                                                    
                                                        















                                                                      
                      
                     
                  



                   
                   




                               
                          











                                                                                                                                 


                                                                                                                










                                                                                                   
                                                                      






                                                             
                                                               










                                                                     
                                                                                                             













                                                                                                                     

                       

                                                                                                  




                                                                                            





                                                                                                     
                                                  
                                                                         

                                                                          
                       
         
 










                                                                                        




                                                                                           



                                   
 
                                                                       
 
                                                     



































                                                                                                       

         
                      




                                                






                                                                                  





                                                
                                                    
            











































































                                                                                                                                                                            




























                                                                                                              



                                                                         
                                                            













































                                                                                                                   


                                                                                             
















































































                                                                                                                               


                        
                                                                                           
 

                                                      
                            
 
                                                       
                                            


                                                                                                            



                                                                            
                                                                                                 




                                                      


                  

 

                                                                                      
 



                                                                       
 
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Authors: Michael Zucchi <notzed@ximian.com>
 *
 * Copyright (C) 2001 Ximian Inc (www.ximian.com/)
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of version 2 of the GNU General Public 
 * License as published by the Free Software Foundation.
 *
 * 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
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <dirent.h>

#include "camel-spool-store.h"
#include "camel-spool-folder.h"
#include "camel-exception.h"
#include "camel-url.h"
#include "camel-private.h"

#define d(x)

/* Returns the class for a CamelSpoolStore */
#define CSPOOLS_CLASS(so) CAMEL_SPOOL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))

static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
static char *get_name(CamelService *service, gboolean brief);
static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
static void rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
static void free_folder_info (CamelStore *store, CamelFolderInfo *fi);

static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);

static CamelStoreClass *parent_class = NULL;

static void
camel_spool_store_class_init (CamelSpoolStoreClass *camel_spool_store_class)
{
    CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_spool_store_class);
    CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_spool_store_class);
    
    parent_class = CAMEL_STORE_CLASS(camel_mbox_store_get_type());

    /* virtual method overload */
    camel_service_class->construct = construct;
    camel_service_class->get_name = get_name;
    camel_store_class->get_folder = get_folder;
    camel_store_class->get_inbox = get_inbox;
    camel_store_class->get_folder_info = get_folder_info;
    camel_store_class->free_folder_info = free_folder_info;

    camel_store_class->delete_folder = delete_folder;
    camel_store_class->rename_folder = rename_folder;
}

CamelType
camel_spool_store_get_type (void)
{
    static CamelType camel_spool_store_type = CAMEL_INVALID_TYPE;
    
    if (camel_spool_store_type == CAMEL_INVALID_TYPE)   {
        camel_spool_store_type = camel_type_register (camel_mbox_store_get_type(), "CamelSpoolStore",
                                 sizeof (CamelSpoolStore),
                                 sizeof (CamelSpoolStoreClass),
                                 (CamelObjectClassInitFunc) camel_spool_store_class_init,
                                 NULL,
                                 NULL,
                                 NULL);
    }
    
    return camel_spool_store_type;
}

static void
construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
{
    struct stat st;

    d(printf("constructing store of type %s '%s:%s'\n",
         camel_type_to_name(((CamelObject *)service)->s.type), url->protocol, url->path));

    CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
    if (camel_exception_is_set (ex))
        return;

    if (service->url->path[0] != '/') {
        camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                     _("Store root %s is not an absolute path"), service->url->path);
        return;
    }

    if (stat(service->url->path, &st) == -1) {
        camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                     _("Spool `%s' cannot be opened: %s"),
                     service->url->path, strerror(errno));
        return;
    }

    if (S_ISREG(st.st_mode))
        ((CamelSpoolStore *)service)->type = CAMEL_SPOOL_STORE_MBOX;
    else if (S_ISDIR(st.st_mode))
        /* we could check here for slight variations */
        ((CamelSpoolStore *)service)->type = CAMEL_SPOOL_STORE_ELM;
    else {
        camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                     _("Spool `%s' is not a regular file or directory"),
                     service->url->path);
        return;
    }
}

static CamelFolder *
get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
{
    CamelFolder *folder = NULL;
    int fd;
    struct stat st;
    char *name;

    d(printf("opening folder %s on path %s\n", folder_name, path));

    /* we only support an 'INBOX' in mbox mode */
    if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX) {
        if (strcmp(folder_name, "INBOX") != 0) {
            camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                         _("Folder `%s/%s' does not exist."),
                         ((CamelService *)store)->url->path, folder_name);
        } else {
            folder = camel_spool_folder_new(store, folder_name, flags, ex);
        }
    } else {
        name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
        if (stat(name, &st) == -1) {
            if (errno != ENOENT) {
                camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                             _("Could not open folder `%s':\n%s"),
                             folder_name, strerror(errno));
            } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
                camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                             _("Folder `%s' does not exist."), folder_name);
            } else {
                fd = open(name, O_CREAT, 0600);
                if (fd == -1) {
                    camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                                 _("Could not create folder `%s':\n%s"),
                                 folder_name, strerror(errno));
                } else {
                    close(fd);
                    folder = camel_spool_folder_new(store, folder_name, flags, ex);
                }
            }
        } else if (!S_ISREG(st.st_mode)) {
            camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                         _("`%s' is not a mailbox file."), name);
        } else {
            folder = camel_spool_folder_new(store, folder_name, flags, ex);
        }
        g_free(name);
    }

    return folder;
}

static CamelFolder *
get_inbox(CamelStore *store, CamelException *ex)
{
    if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX)
        return get_folder (store, "INBOX", CAMEL_STORE_FOLDER_CREATE, ex);
    else {
        camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
                     _("Store does not support an INBOX"));
        return NULL;
    }
}

static char *
get_name (CamelService *service, gboolean brief)
{
    if (brief)
        return g_strdup(service->url->path);
    else
        return g_strdup_printf(((CamelSpoolStore *)service)->type == CAMEL_SPOOL_STORE_MBOX?
                       _("Spool mail file %s"):_("Spool folder tree %s"), service->url->path);
}

/* default implementation, rename all */
static void
rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
{
    camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                 _("Spool folders cannot be renamed"));
}

/* default implementation, only delete metadata */
static void
delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
{
    camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                 _("Spool folders cannot be deleted"));
}

static void free_folder_info (CamelStore *store, CamelFolderInfo *fi)
{
    if (fi) {
        g_free(fi->url);
        g_free(fi->name);
        g_free(fi->full_name);
        g_free(fi->path);
        g_free(fi);
    }
}

static CamelFolderInfo *
camel_folder_info_new(const char *url, const char *full, const char *name, int unread)
{
    CamelFolderInfo *fi;

    fi = g_malloc0(sizeof(*fi));
    fi->url = g_strdup(url);
    fi->full_name = g_strdup(full);
    fi->name = g_strdup(name);
    fi->unread_message_count = unread;
    camel_folder_info_build_path(fi, '/');

    d(printf("Adding spoold info: '%s' '%s' '%s' '%s'\n", fi->path, fi->name, fi->full_name, fi->url));

    return fi;
}

/* used to find out where we've visited already */
struct _inode {
    dev_t dnode;
    ino_t inode;
};

/* returns number of records found at or below this level */
static int scan_dir(CamelStore *store, GHashTable *visited, char *root, const char *path, guint32 flags, CamelFolderInfo *parent, CamelFolderInfo **fip, CamelException *ex)
{
    DIR *dir;
    struct dirent *d;
    char *name, *uri, *tmp, *fname;
    CamelFolderInfo *fi = NULL;
    struct stat st;
    CamelFolder *folder;
    int unread;
    char from[80];
    FILE *fp;

    d(printf("checking dir '%s' part '%s' for mbox content\n", root, path));

    /* look for folders matching the right structure, recursively */
    if (path) {
        name = alloca(strlen(root)+strlen(path)+2);
        sprintf(name, "%s/%s", root, path);
    } else
        name = root;

    if (stat(name, &st) == -1) {
        camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                     _("Could not scan folder `%s': %s"),
                     name, strerror(errno));
    } else if (S_ISREG(st.st_mode)) {
        /* incase we start scanning from a file.  messy duplication :-/ */
        if (path) {
            CAMEL_STORE_LOCK(store, cache_lock);
            folder = g_hash_table_lookup(store->folders, path);
            if (folder)
                unread = camel_folder_get_unread_message_count(folder);
            else
                unread = -1;
            CAMEL_STORE_UNLOCK(store, cache_lock);
            tmp = strrchr(path, '/');
            if (tmp)
                tmp++;
            else
                tmp = (char *)path;
            uri = g_strdup_printf("%s:%s#%s", ((CamelService *)store)->url->protocol, root, path);
            fi = camel_folder_info_new(uri, path, tmp, unread);
            fi->parent = parent;
            fi->sibling = *fip;
            *fip = fi;
            g_free(uri);
        }
        return 0;
    }

    dir = opendir(name);
    if (dir == NULL) {
        camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
                     _("Could not scan folder `%s': %s"),
                     name, strerror(errno));
        return -1;
    }

    if (path != NULL) {
        uri = g_strdup_printf("%s:%s;noselect=yes#%s", ((CamelService *)store)->url->protocol, root, path);
        tmp = strrchr(path, '/');
        if (tmp == NULL)
            tmp = (char *)path;
        else
            tmp++;
        fi = camel_folder_info_new(uri, path, tmp, -1);
        fi->parent = parent;
        fi->sibling = *fip;
        *fip = fi;
        g_free(uri);
    
        fip = &fi->child;
        parent = fi;
    }
    
    while ( (d = readdir(dir)) ) {
        if (strcmp(d->d_name, ".") == 0
            || strcmp(d->d_name, "..") == 0)
            continue;
        
        tmp = g_strdup_printf("%s/%s", name, d->d_name);
        if (stat(tmp, &st) == 0) {
            if (path)
                fname = g_strdup_printf("%s/%s", path, d->d_name);
            else
                fname = g_strdup(d->d_name);

            if (S_ISREG(st.st_mode)) {
                /* first, see if we already have it open */
                CAMEL_STORE_LOCK(store, cache_lock);
                folder = g_hash_table_lookup(store->folders, fname);
                if (folder)
                    unread = camel_folder_get_unread_message_count(folder);
                else
                    unread = -1;
                CAMEL_STORE_UNLOCK(store, cache_lock);

                /* no?  check its content to see if its a folder or not */
                if (folder == NULL) {
                    fp = fopen(tmp, "r");
                    if (fp != NULL) {
                        if (st.st_size == 0
                            || (fgets(from, sizeof(from), fp) != NULL
                            && strncmp(from, "From ", 5) == 0)) {
                            folder = (CamelFolder *)1;
                            /* TODO: if slow mode selected, we could look up unread counts here -
                               but its pretty expensive */
                        }
                        fclose(fp);
                    }
                }

                if (folder != NULL) {
                    uri = g_strdup_printf("%s:%s#%s", ((CamelService *)store)->url->protocol, root, fname);
                    fi = camel_folder_info_new(uri, fname, d->d_name, unread);
                    fi->parent = parent;
                    fi->sibling = *fip;
                    *fip = fi;
                    g_free(uri);
                }

            } else if (S_ISDIR(st.st_mode)) {
                struct _inode in = { st.st_dev, st.st_ino };
            
                /* see if we've visited already */
                if (g_hash_table_lookup(visited, &in) == NULL) {
                    struct _inode *inew = g_malloc(sizeof(*inew));
                
                    *inew = in;
                    g_hash_table_insert(visited, inew, inew);

                    if (scan_dir(store, visited, root, fname, flags, parent, fip, ex) == -1) {
                        g_free(tmp);
                        g_free(fname);
                        closedir(dir);
                        return -1;
                    }
                }
            }
            g_free(fname);

        }
        g_free(tmp);
    }
    closedir(dir);

    return 0;
}

static guint inode_hash(const void *d)
{
    const struct _inode *v = d;

    return v->inode ^ v->dnode;
}

static gboolean inode_equal(const void *a, const void *b)
{
    const struct _inode *v1 = a, *v2 = b;
    
    return v1->inode == v2->inode && v1->dnode == v2->dnode;
}

static void inode_free(void *k, void *v, void *d)
{
    g_free(k);
}

static CamelFolderInfo *
get_folder_info_elm(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
{
    CamelFolderInfo *fi = NULL;
    GHashTable *visited;

    visited = g_hash_table_new(inode_hash, inode_equal);

    if (scan_dir(store, visited, ((CamelService *)store)->url->path, top, flags, NULL, &fi, ex) == -1 && fi != NULL) {
        camel_store_free_folder_info_full(store, fi);
        fi = NULL;
    }

    g_hash_table_foreach(visited, inode_free, NULL);
    g_hash_table_destroy(visited);

    return fi;
}

static CamelFolderInfo *
get_folder_info_mbox(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
{
    CamelFolderInfo *fi = NULL;
    CamelService *service = (CamelService *)store;
    CamelFolder *folder;

    if (top == NULL || strcmp(top, "INBOX") == 0) {
        fi = g_malloc0(sizeof(*fi));
        fi->full_name = g_strdup("INBOX");
        fi->name = g_strdup("INBOX");
        fi->url = g_strdup_printf("%s:%s#%s", service->url->protocol, service->url->path, fi->name);

        CAMEL_STORE_LOCK(store, cache_lock);
        folder = g_hash_table_lookup(store->folders, fi->full_name);
        if (folder)
            fi->unread_message_count = camel_folder_get_unread_message_count(folder);
        else
            fi->unread_message_count = -1;
        CAMEL_STORE_UNLOCK(store, cache_lock);

        camel_folder_info_build_path(fi, '/');
    }

    return fi;
}

static CamelFolderInfo *
get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
{
    if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX)
        return get_folder_info_mbox(store, top, flags, ex);
    else
        return get_folder_info_elm(store, top, flags, ex);
}