/* 
   elmo - ELectronic Mail Operator

   Copyright (C) 2003, 2004 rzyjontko

   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; version 2.

   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.  

   ----------------------------------------------------------------------

   maildir operations
   
*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

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

#ifdef HAVE_SYS_DIR_H
# include <sys/dir.h>
#endif

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include "xmalloc.h"
#include "mail.h"
#include "mlex.h"
#include "file.h"
#include "error.h"
#include "maildir.h"
#include "gettext.h"
#include "hash.h"
#include "misc.h"
#include "bitarray.h"
#include "clock.h"

/****************************************************************************
 *    IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
 ****************************************************************************/

#define MAILDIR_CACHE_FILE ".elmomdir.cache"

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

static char *directory     = NULL;
static char *directory_cur = NULL;
static char *directory_new = NULL;

/**
 * this indicates what was the time of last delivery - it's very important
 * to ensure that each file name is unique
 */
static unsigned last_time = 0;

/**
 * this is used when reading maildir
 */
static mail_array_t *new_marray = NULL;

static htable_t *file_table = NULL;

/**
 * this is used in maildir_refresh to mark the common messages
 */
static bitarray_t *common_messages = NULL;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/

static void free_resources (void);
static int  dir_is_valid (char *dir);
static void free_list (struct dirent **list, int count);
static int  select_fun (const struct dirent *entry);

/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/


static unsigned char
char_to_flag (char x)
{
        switch (x){
                case 'R':
                        return FLAG_ANSWERED;
                        break;

                case 'T':
                        return FLAG_TRASHED;
                        break;

                case 'D':
                        return FLAG_DRAFT;
                        break;

                case 'F':
                        return FLAG_FLAGGED;
                        break;

                case 'S':
                        return FLAG_READ;
                        break;

                case 'P':
                        return FLAG_PASSED;
                        break;

                case 'L':
                        return FLAG_LEGITIMATE;
                        break;
        }
        return 0;
}



static void
free_resources (void)
{
        if (directory){
                xfree (directory);
                directory = NULL;
        }
        if (directory_cur){
                xfree (directory_cur);
                directory_cur = NULL;
        }
        if (directory_new){
                xfree (directory_new);
                directory_new = NULL;
        }
}



static int
dir_is_valid (char *dir)
{
        DIR *dp;

        dp = opendir (dir);
        if (!dp)
                return 0;
        closedir (dp);
        return 1;
}



static void
make_dir_names_from_directory (void)
{
        size_t len = strlen (directory);

        directory_cur = xmalloc (len + 5);
        directory_new = xmalloc (len + 5);

        memcpy (directory_cur, directory, len);
        memcpy (directory_new, directory, len);
        memcpy (directory_cur + len, "/cur", 5);
        memcpy (directory_new + len, "/new", 5);
}



static void
make_dir_names (const char *dir)
{
        free_resources ();
  
        directory = xstrdup (dir);
        make_dir_names_from_directory ();
}



static void
free_list (struct dirent **list, int count)
{
        int i;

        for (i = 0; i < count; i++)
                free (list[i]);

        if (count > 0 && list)
                free (list);
}



static int
select_fun (const struct dirent *entry)
{
        return *entry->d_name != '.';
}



static int
select_old_fun (const struct dirent *entry)
{
        char *seek;
  
        if (*entry->d_name == '.')
                return 0;
        seek = strrchr (entry->d_name, ',');
        if (seek == NULL)
                return 1;
        seek = strchr (seek, 'S');
        if (seek == NULL)
                return 1;
        return 0;
}


static unsigned char
mail_flags (const char *fname, const char *dir, size_t dlen)
{
        unsigned char flags = 0;
        char *seek;
  
        if (dir[dlen - 1] == 'r')
                flags |= FLAG_OLD;

        seek = strstr (fname, ":");
        while (seek && *seek){
                flags |= char_to_flag (*seek);
                seek++;
        }

        return flags;
}



static void
add_mail (const char *fname, const char *dir)
{
        int     ret;
        size_t  dname_len = strlen (dir);
        char   *full_name;

        full_name = file_with_dir (dir, fname);
        yyin      = fopen (full_name, "r");

        if (!yyin){
                xfree (full_name);
                return;
        }

        ret = mlex_scan_file (0);
        if (ret == NEXT_MAIL){
                newmail->flags           |= mail_flags (fname, dir, dname_len);
                newmail->type             = BOX_MAILDIR;
                newmail->place.file_name  = full_name;
                newmail->mime->file       = full_name;
                mail_array_insert (new_marray, newmail);
        }
        else {
                error_ (0, _("%s has bad format"), fname);
                xfree (full_name);
        }

        fclose (yyin);
}



static void
process_list (struct dirent **list, int count, const char *dir, int p)
{
        int i;

        for (i = 0; i < count; i++){
                add_mail (list[i]->d_name, dir);
                progress_advance (p, 1);
        }
}



static void
process_list_new (struct dirent **list, int count, const char *dir, int base)
{
        int i;

        for (i = 0; i < count; i++){
                if (! bitarray_is_set (common_messages, i + base))
                        add_mail (list[i]->d_name, dir);
        }
}



static void
remove_files (struct dirent **list, int count, const char *dir)
{
        int   i;
        char *fname;

        for (i = 0; i < count; i++){
                fname = file_with_dir (dir, list[i]->d_name);
                unlink (fname);
                xfree (fname);
        }
}


static void
reset_array (struct dirent **list, int count, int base)
{
        int i;

        for (i = 0; i < count; i++){
                htable_insert (file_table, list[i]->d_name,
                               (void *) (i + base));
        }
}


static int
is_in_array (mail_t *mail)
{
        int      index;
        char    *seek;
        entry_t *entry;

        seek = strrchr (mail->place.file_name, '/');
        if (seek)
                seek++;
        else
                seek = mail->place.file_name;

        entry = htable_lookup (file_table, seek);
        if (entry == NULL)
                return 0;

        index = (int) entry->content;
        bitarray_set (common_messages, index);
        return 1;
}


static void
move_to_cur (mail_t *mail)
{
        char   *fname   = mail->place.file_name;
        size_t  len     = strlen (fname);
        char   *newname = xmalloc (len + 4);
        char   *seek;

        memcpy (newname, fname, len + 1);
        seek = strstr (newname, "new");
        if (seek == NULL){
                xfree (newname);
                return;
        }
        memcpy (seek, "cur", 3);

        memcpy (newname + len, ":2,", 4);
        if (file_rename (fname, newname)){
                error_ (errno, _("couldn't move message, possibly because "
                                 "another mail client is accessing this box "
                                 "in parallel"));
                xfree (newname);
                return;
        }
        xfree (fname);
        mail->place.file_name = newname;
}



static void
apply_flags (mail_t *mail)
{
        int          index     = 3;
        static char  flags[12] = ":2,";
        int          len;
        char        *seek;
        char        *fname;

        move_to_cur (mail);

        if (mail->flags & FLAG_DRAFT){
                flags[index] = 'D';
                index++;
        }
        if (mail->flags & FLAG_FLAGGED){
                flags[index] = 'F';
                index++;
        }
        if (mail->flags & FLAG_LEGITIMATE){
                flags[index] = 'L';
                index++;
        }
        if (mail->flags & FLAG_PASSED){
                flags[index] = 'P';
                index++;
        }
        if (mail->flags & FLAG_ANSWERED){
                flags[index] = 'R';
                index++;
        }
        if (mail->flags & FLAG_READ){
                flags[index] = 'S';
                index++;
        }
        if (mail->flags & FLAG_TRASHED){
                flags[index] = 'T';
                index++;
        }
        flags[index] = '\0';

        seek = strrchr (mail->place.file_name, ':');
  
        if (seek && strcmp (seek, flags) == 0)
                return;

        len   = (seek) ? (seek - mail->place.file_name)
                : strlen (mail->place.file_name);
        fname = xmalloc (len + index + 1);
        memcpy (fname, mail->place.file_name, len);
        memcpy (fname + len, flags, index + 1);

        file_rename (mail->place.file_name, fname);
        xfree (mail->place.file_name);
        mail->place.file_name = fname;
}



static void
destroy_not_flagged (mail_array_t *marray)
{
        int     i;
        mail_t *mail;

        for (i = 0; i < marray->count; i++){
                mail = mail_array_get (marray, i);
                if (mail->flags & FLAG_FLAGGED)
                        continue;
                mail_destroy (mail, BOX_MAILDIR);
        }
}



static void
read_place (memchunk_t *memchunk, mail_t *mail)
{
        mail->place.file_name = memchunk_strget (memchunk);
        mail->mime->file      = mail->place.file_name;
}


static mail_array_t *
read_directory (const char *dir)
{
        int             p;
        int             cur_count;
        int             new_count;
        struct dirent **cur_list;
        struct dirent **new_list;
        mail_array_t   *result;
  
        make_dir_names (dir);
        if (!dir_is_valid (directory_cur) || !dir_is_valid (directory_new)){
                free_resources ();
                return NULL;
        }

        cur_count = scandir (directory_cur, &cur_list, select_fun, alphasort);
        new_count = scandir (directory_new, &new_list, select_fun, alphasort);

        if (cur_count == -1 || new_count == -1){
                free_list (cur_list, cur_count);
                free_list (new_list, new_count);
                free_resources ();
                return NULL;
        }

        new_marray = mail_array_create_size (cur_count + new_count,
                                             BOX_MAILDIR, dir);

        p = progress_setup (cur_count + new_count, _("reading box"));
        process_list (cur_list, cur_count, directory_cur, p);
        process_list (new_list, new_count, directory_new, p);
        progress_close (p);

        free_list (cur_list, cur_count);
        free_list (new_list, new_count);

        result     = new_marray;
        new_marray = NULL;

        return result;
}



static mail_array_t *
read_cache (const char *dir)
{
        int           i;
        char         *fname;
        FILE         *fp;
        mail_array_t *marray;
        mail_array_t *marray_r;

        fname = file_with_dir (dir, MAILDIR_CACHE_FILE);
        fp    = fopen (fname, "r");

        if (file_size(fname) == 0 || fp == NULL){
                xfree (fname);
                return NULL;
        }

        marray = mail_array_read_from_file (fp, BOX_MAILDIR, dir, read_place);

        if (marray == NULL){
                xfree (fname);
                return NULL;
        }

        marray_r = maildir_refresh (marray);
        if (marray_r)
                mail_array_substitute (marray, marray_r);

        for (i = 0; i < marray->count; i++)
                marray->array[i].type = BOX_MAILDIR;
  
        xfree (fname);
        return marray;
}


/****************************************************************************
 *    INTERFACE FUNCTIONS
 ****************************************************************************/


void
maildir_free_resources (void)
{
        free_resources ();
}



mail_array_t *
maildir_read_dir (const char *dir)
{
        mail_array_t *result;

        result = read_cache (dir);
        if (result)
                return result;

        return read_directory (dir);
}



mail_array_t *
maildir_refresh (mail_array_t *marray)
{
        int             i;
        int             cur_count;
        int             new_count;
        struct dirent **cur_list;
        struct dirent **new_list;
        mail_array_t   *result;
        mail_t         *mail;
  
        make_dir_names (marray->path);

        /**
         * step 1: get the list of files in cur and new
         */
        cur_count = scandir (directory_cur, &cur_list, select_fun, alphasort);
        new_count = scandir (directory_new, &new_list, select_fun, alphasort);

        if (cur_count == -1 || new_count == -1){
                free_list (cur_list, cur_count);
                free_list (new_list, new_count);
                mail_array_destroy (marray);
                return NULL;
        }

        /**
         * step 2: create new mail array and a hashing table
         */
        file_table = htable_create (misc_logarithm (cur_count + new_count));
        new_marray = mail_array_create_size (cur_count + new_count, BOX_MAILDIR,
                                             marray->path);
  
        /**
         * step 3: reset marker and put file names into hashing table
         */
        common_messages = bitarray_create (cur_count + new_count);
        bitarray_zeros (common_messages);
        reset_array (cur_list, cur_count, 0);
        reset_array (new_list, new_count, cur_count);

        /**
         * step 4: check the old array if it contains these files
         */
        for (i = 0; i < marray->count; i++){
                mail = mail_array_get (marray, i);
                if (is_in_array (mail)){
                        mail->parent = -1;
                        mail_array_insert (new_marray, mail);
                        mail->flags |= FLAG_FLAGGED;
                }
        }
  
        /**
         * step 5: read the messages that weren't in the box
         */
        process_list_new (cur_list, cur_count, directory_cur, 0);
        process_list_new (new_list, new_count, directory_new, cur_count);

        /**
         * step 6: destroy the messages that weren't flagged
         */
        destroy_not_flagged (marray);

        /**
         * step 7: free resources
         */
        free_list (cur_list, cur_count);
        free_list (new_list, new_count);

        htable_destroy (file_table, NULL);
        if (common_messages)
                bitarray_destroy (common_messages);

        result          = new_marray;
        file_table      = NULL;
        new_marray      = NULL;
        common_messages = NULL;

        return result;
}



int
maildir_mail_header (mail_t *mail, char **place)
{
        mime_t *mime = mail->mime->mime;
        
        if (file_part (mail->place.file_name, 0, mime->off_start, place)){
                *place = NULL;
                return 1;
        }
        return 0;
}



int
maildir_mail_body (mail_t *mail, char **place, mime_t *mime,
                   const char *fname)
{
        if (file_part (fname, mime->off_start, mime->off_end, place)){
                *place = NULL;
                return 1;
        }
        return 0;
}



char *
maildir_fetch_single (mail_t *mail)
{
        return xstrdup (mail->place.file_name);
}



int
maildir_deliver_to (const char *file, const char *dir)
{
        int   ret;
        int   read = 0;
        int   flen;
        int   dlen = strlen (dir);
        const char *seek = strrchr (file, '/');
        char *new_fname  = NULL;
        char *new_location;

        /**
         * step 1:
         *   find the file name
         */
        if (seek == NULL)
                seek = file;
        else {
                seek++;
                if (strchr (seek, ':'))
                        read = 1;
        }

        /**
         * step 2:
         *   check if this message is from maildir
         */
        if (! isdigit (*seek)){
                new_fname = maildir_valid_file_name ();
                flen      = strlen (new_fname);
        }
        else {
                flen = strlen (seek);
        }

        /*                       dir   /  new  /   file  \0 */
        new_location = xmalloc (dlen + 1 + 3 + 1 + flen + 1);

        memcpy (new_location, dir, dlen);
        if (read)
                memcpy (new_location + dlen, "/cur/", 5);
        else
                memcpy (new_location + dlen, "/new/", 5);

        if (new_fname){
                memcpy (new_location + dlen + 5, new_fname, flen + 1);
                xfree (new_fname);
        }
        else {
                memcpy (new_location + dlen + 5, seek, flen + 1);
        }

        ret = file_rename (file, new_location);
        xfree (new_location);
        return ret;
}



void
maildir_remove (mail_t *mail)
{
        unlink (mail->place.file_name);
}



char *
maildir_valid_file_name (void)
{
        size_t    hlen      = 100;
        unsigned  timestamp = time (NULL);
        char     *hname     = xmalloc (hlen);
        char     *fname;

        while (gethostname (hname, hlen)){
                hlen  = (hlen + 1) * 2;
                hname = xrealloc (hname, hlen);
        }
        hlen = strlen (hname);

        if (timestamp <= last_time){
                timestamp = ++last_time;
        }
        else {
                last_time = timestamp;
        }

        /*              time  .   pid  . hostname \0 */
        fname = xmalloc (10 + 1 + 10 + 1 + hlen + 1);

        sprintf (fname, "%d.%d.%s", timestamp, getpid (), hname);
        xfree (hname);
        return fname;
}



char *
maildir_fetch_where (const char *dir, int new)
{
        char   *fname;
        char   *file  = maildir_valid_file_name ();
        size_t  dlen  = strlen (dir);
        size_t  flen  = strlen (file);

        /*               dir    /  tmp  /   file :2,S  \0 */
        fname = xmalloc (dlen + 1 + 3 + 1 + flen + 4 + 1);

        sprintf (fname, "%s/tmp", dir);
        if (!dir_is_valid (fname))
                if (mkdir (fname, 0700)){
                        error_ (errno, "%s", fname);
                        return NULL;
                }

        if (new)
                sprintf (fname, "%s/tmp/%s", dir, file);
        else
                sprintf (fname, "%s/tmp/%s:2,S", dir, file);
        xfree (file);
        return fname;
}



int
maildir_create_dir (const char *name)
{
        char *dir;

        mkdir (name, 0700);
  
        dir = file_with_dir (name, "cur");
        if (mkdir (dir, 0700)){
                error_ (errno, "%s", dir);
                xfree (dir);
                return 1;
        }
        xfree (dir);

        dir = file_with_dir (name, "new");
        if (mkdir (dir, 0700)){
                error_ (errno, "%s", dir);
                xfree (dir);
                return 1;
        }
        xfree (dir);

        dir = file_with_dir (name, "tmp");
        if (mkdir (dir, 0700)){
                error_ (errno, "%s", dir);
                xfree (dir);
                return 1;
        }
        xfree (dir);
        return 0;
}



int
maildir_mail_size (mail_t *mail)
{
        struct stat st;

        if (stat (mail->place.file_name, &st)){
                return -1;
        }

        return st.st_size;
}



int
maildir_box_mail_count (const char *box, int *unread)
{
        char           *cur = file_with_dir (box, "cur");
        char           *new = file_with_dir (box, "new");
        struct dirent **cur_list;
        struct dirent **new_list;
        int             cur_count;
        int             new_count;
        int             total_count;

        cur_count = scandir (cur, &cur_list, select_fun, alphasort);
        new_count = scandir (new, &new_list, select_fun, alphasort);

        if (cur_count == -1 || new_count == -1){
                free_list (cur_list, cur_count);
                free_list (new_list, new_count);
                xfree (cur);
                xfree (new);
                if (unread)
                        *unread = 0;
                return -1;
        }

        free_list (cur_list, cur_count);
        free_list (new_list, new_count);

        total_count = new_count + cur_count;

        cur_count = scandir (cur, &cur_list, select_old_fun, alphasort);
        free_list (cur_list, cur_count);
  
  
        if (unread)
                *unread = new_count + cur_count;

        xfree (cur);
        xfree (new);
  
        return total_count;
}



void
maildir_apply_flag (mail_t *mail)
{
        apply_flags (mail);
}




void
maildir_apply_flags (mail_array_t *marray)
{
        int     i;
        mail_t *mail;

        for (i = 0; i < marray->count; i++){
                mail = mail_array_get (marray, i);
                apply_flags (mail);
        }
}



void
maildir_dump_place (memchunk_t *memchunk, mail_t *mail)
{
        memchunk_strdump (memchunk, mail->place.file_name);
}



char *
maildir_dump_file_name (mail_array_t *marray)
{
        return file_with_dir (marray->path, MAILDIR_CACHE_FILE);
}



void
maildir_empty_box (const char *box)
{
        char           *cur = file_with_dir (box, "cur");
        char           *new = file_with_dir (box, "new");
        struct dirent **cur_list;
        struct dirent **new_list;
        int             cur_count;
        int             new_count;

        cur_count = scandir (cur, &cur_list, select_fun, alphasort);
        new_count = scandir (new, &new_list, select_fun, alphasort);

        if (cur_count == -1 || new_count == -1){
                free_list (cur_list, cur_count);
                free_list (new_list, new_count);
                xfree (cur);
                xfree (new);
                return;
        }

        remove_files (cur_list, cur_count, cur);
        remove_files (new_list, new_count, new);
  
        free_list (cur_list, cur_count);
        free_list (new_list, new_count);

        xfree (cur);
        xfree (new);
}


/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/
/****************************************************************************
 *
 *    END MODULE maildir.c
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1