/* 
   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.  

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

   This module is a wrapper for multiple maildrops formats.

   There are 2 kinds of functions in this module.  Some of them may
   check the box type in wrapbox_marray.  The others need to behave
   well, no matter what kind of box do they get (or even no box at
   all).

   If you want to register a new format or extend an exisiting one,
   then follow these steps:

   0. suppose your format name is foo
   1. include foo.h
   2. register BOX_FOO in mail.h
   3. add functions
   4. there is no strict prototype of every function, please feed yours
   whith any data you find relevant
   5. add your name to copyright notice above and to the list below

   Please look at the functions below.  There is an explanation, of how
   to change them.


   People responsible for box types:
   maildir - rzyjontko
   mbox    - rzyjontko
*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

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

#include "mail.h"
#include "wrapbox.h"
#include "maildir.h"
#include "mbox.h"
#include "mime.h"
#include "rmime.h"
#include "error.h"
#include "mybox.h"
#include "xmalloc.h"
#include "folder.h"
#include "file.h"
#include "pop.h"
#include "hash.h"
#include "ask.h"

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

#define PREAMBLE do { if (wrapbox_marray == NULL) return; } while (0)

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/

struct size_pair {
        int total;
        int unread;
};

/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

/* This is used to cache information about box sizes.  This table holds
   size_pair structures mapped to box names. */
static htable_t *box_cache = NULL;

/* This tells us if user whishes to count messages in mbox files. */
static int count_mbox_mails = 0;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/

/**
 * this is where mails' info is stored
 */
mail_array_t *wrapbox_marray = NULL;

/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/


static int
size_cache_lookup (const char *box, int *unread)
{
        entry_t          *entry = htable_lookup (box_cache, box);
        struct size_pair *data;

        if (entry == NULL)
                return -1;

        data = (struct size_pair *) entry->content;

        if (data->total == -1)
                return -1;

        if (unread)
                *unread = data->unread;

        return data->total;
}



static void
size_cache_insert (const char *box, int total, int unread)
{
        entry_t          *entry;
        struct size_pair *data = xmalloc (sizeof (struct size_pair));

        data->total  = total;
        data->unread = unread;

        entry = htable_insert (box_cache, box, data);

        if (entry->content != data){
                xfree (entry->content);
                entry->content = data;
        }
}


static void
size_cache_remove (const char *box)
{
        const char       *seek;
        entry_t          *entry;
        struct size_pair *data;

        seek = strrchr (box, '/');
        if (seek == NULL)
                seek = box;
        else
                seek++;
        
        entry = htable_lookup (box_cache, seek);
        if (entry == NULL)
                return;

        data = (struct size_pair *) entry->content;

        data->total  = -1;
        data->unread = -1;
}



static void
size_cache_remove_file (const char *file)
{
        char *seek;
        char *tmp;
        
        seek = strrchr (file, '/');
        if (seek == NULL){
                size_cache_remove (file);
                return;
        }

        *seek = '\0';
        tmp   = strrchr (file, '/');

        if (tmp == NULL){
                size_cache_remove (file);
                *seek = '/';
                return;
        }

        *tmp  = '\0';
        size_cache_remove (file);
        *seek = '/';
        *tmp  = '/';
}



static void
free_temp_resources (void)
{
        /* maildir */
        maildir_free_resources ();

        /* mbox */
        mbox_free_resources ();

        if (wrapbox_marray){
                size_cache_remove (wrapbox_marray->path);
                mail_array_destroy (wrapbox_marray);
                wrapbox_marray = NULL;
        }
}


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


void
wrapbox_init (void)
{
        char *answer = ask_for_default ("count_mbox", 0);
        box_cache    = htable_create (4);

        if (answer && strcasecmp (answer, "yes") == 0)
                count_mbox_mails = 1;
}


/**
 * foo_free_resources should only free it's local resources (if any)
 * do not flush any data - just free unnecessary memory
 */
void
wrapbox_free_resources (void)
{
        free_temp_resources ();
        
        if (box_cache)
                htable_destroy (box_cache, xfree);
        box_cache = NULL;
}



/**
 * this function must be able to recognize box format depending only on its
 * name, so a sequence is very important
 */
mail_array_t *
wrapbox_open_box (const char *name)
{
        mail_array_t *new_marray = NULL;

        /* maildir */
        new_marray = maildir_read_dir (name);
        if (new_marray)
                return new_marray;
        
        /* mbox */
        if (mbox_may_be_valid (name)){
                new_marray = mbox_read_file (name);
        }

        return new_marray;
}



int
wrapbox_read_box (const char *name)
{
        mail_array_t *new_marray;

        new_marray = wrapbox_open_box (name);

        if (new_marray){
                free_temp_resources ();
                wrapbox_marray = new_marray;
                return 0;
        }

        return 1;
}



/**
 * foo_mail_header should put header into *place
 */
int
wrapbox_mail_header (mail_t *mail, char **place)
{
        int ret = 1;

        if (wrapbox_marray == NULL || mail == NULL)
                return 1;
  
        switch (wrapbox_marray->type){

                case BOX_MBOX:
                        ret = mbox_mail_header (mail, place,
                                                wrapbox_marray->path);
                        break;

                case BOX_MAILDIR:
                        ret = maildir_mail_header (mail, place);
                        break;

                default:
                        return 1;
        }

        return ret;
}



/**
 * use the chosen mime
 * foo_mail_body should put body in *place, but not decode it
 * return 1 if something went wrong
 */
str_t *
wrapbox_mail_body (mail_t *mail, mime_t *mime, int no_decrypt)
{
        int   ret = 1;
        char *place;
        char *file;
  
        if (wrapbox_marray == NULL || mail == NULL)
                return NULL;
  
        errno = 0;
        if (mime == NULL){
                if (! no_decrypt){
                        mime_decrypt (mail->mime);
                }
                mime = mime_first_text (mail->mime);
                if (mime == NULL){
                        return NULL;
                }
        }

        switch (wrapbox_marray->type){

                case BOX_MBOX:
                        file = (mail->mime->d_file) ? mail->mime->d_file
                                : wrapbox_marray->path;
                        ret = mbox_mail_body (mail, & place, mime, file);
                        break;

                case BOX_MAILDIR:
                        file = (mail->mime->d_file) ? mail->mime->d_file
                                : mail->place.file_name;
                        ret  = maildir_mail_body (mail, & place, mime, file);
                        break;

                default:
                        return NULL;
        }

        return mime_decode (mime, place, 1);
}



/**
 * foo_fetch_single must save this mail into a file and return its name
 */
char *
wrapbox_fetch_single (mail_t *mail)
{
        switch (mail->type){

                case BOX_MAILDIR:
                        return maildir_fetch_single (mail);

                case BOX_MBOX:
                        if (wrapbox_marray == NULL)
                                return NULL;
                        return mbox_fetch_single (wrapbox_marray->path, mail);

                default:
                        return NULL;
        }

        return NULL;
}




/**
 * foo_fetch_where must return a valid file name, where elmo can
 * fetch new message, it will be then used in wrapbox_deliver_to
 */
char *
wrapbox_fetch_where (int new)
{
        if (wrapbox_marray == NULL)
                return NULL;
  
        switch (wrapbox_marray->type){

                case BOX_MAILDIR:
                        return maildir_fetch_where (wrapbox_marray->path,
                                                    new);

                case BOX_MBOX:
                        return NULL;

                default:
                        return NULL;
        }

        return NULL;
}



/**
 * foo_deliver_to must deliver a message stored in file to a box
 * return codes:
 *   0 - delivered and destroyed file
 *   1 - couldn't deliver (maybe bad box type)
 *   2 - delivered and left the file unchanged
 */
int
wrapbox_deliver_to (const char *file, const char *box)
{
        int ret;
  
        size_cache_remove (box);
        size_cache_remove_file (file);
        
        /* maildir */
        ret = maildir_deliver_to (file, box);
        if (ret == 0 || ret == 2)
                return ret;
  
        return 1;
}




/**
 * foo_remove must permanently remove a message
 */
void
wrapbox_remove (mail_t *mail)
{
        PREAMBLE;

        if (mail == NULL)
                return;
  
        switch (mail->type){

                case BOX_INVALID:
                        return;

                case BOX_MAILDIR:
                        size_cache_remove_file (mail->place.file_name);
                        maildir_remove (mail);
                        return;

                default:
                        return;
        }
}



/**
 * this function must not depend on box type, do not change it
 */
int
wrapbox_move_mail_to (mail_t *mail, const char *box)
{
        int   ret;
        char *file;

        if (mail == NULL || box == NULL)
                return 1;
  
        mail->flags &= ~ FLAG_FLAGGED;
        wrapbox_apply_flag (mail);
  
        file = wrapbox_fetch_single (mail);
        if (file == NULL)
                return 1;

        if (box){
                ret = wrapbox_deliver_to (file, box);
                if (ret == 0 || ret == 1){
                        xfree (file);
                        return ret;
                }
        }

        wrapbox_remove (mail);
        xfree (file);
        return 0;
}



/**
 * return mail size in bytes
 */
int
wrapbox_mail_size (mail_t *mail)
{
        switch (mail->type){

                case BOX_MBOX:
                        return mbox_mail_size (mail);

                case BOX_MAILDIR:
                        return maildir_mail_size (mail);

                case BOX_POP3:
                        return pop_mail_size (mail->place.num);

                default:
                        return 0;
        }
}



/**
 * foo_mail_count should check if unread != NULL and return -1 if it is
 * not a box of type foo,
 */
int
wrapbox_box_mail_count (const char *box, int *unread)
{
        int         ret;
        char       *tmp = NULL;
        const char *the_box;
  
        ret = size_cache_lookup (box, unread);
        if (ret != -1)
                return ret;
        
        if (*box != '/'){
                tmp     = file_with_dir (mybox_dir, box);
                the_box = tmp;
        }
        else {
                the_box = box;
        }
        
        /* maildir */
        ret = maildir_box_mail_count (the_box, unread);
        if (ret != -1){
                if (tmp)
                        xfree (tmp);
                size_cache_insert (box, ret, *unread);
                return ret;
        }

        /* mbox */
        if (count_mbox_mails)
                ret = mbox_box_mail_count (the_box, unread);
        if (ret != -1){
                if (tmp)
                        xfree (tmp);
                size_cache_insert (box, ret, *unread);
                return ret;
        }
  
        /* fallback */
        if (unread)
                *unread = 0;

        if (tmp)
                xfree (tmp);

        return 0;
}


/**
 * foo_refresh could read box once more but try to avoid it if it was
 * unnecessary,  wrapbox_marray must be left unchanged on any failure
 */
void
wrapbox_refresh (void)
{
        mail_array_t *new_marray = NULL;
  
        PREAMBLE;

        switch (wrapbox_marray->type){

                case BOX_INVALID:
                        break;

                case BOX_MAILDIR:
                        new_marray = maildir_refresh (wrapbox_marray);
                        if (new_marray)
                                mail_array_substitute (wrapbox_marray,
                                                       new_marray);
                        new_marray = NULL;
                        break;

                default:
                        break;
        }

}



void
wrapbox_apply_flag (mail_t *mail)
{
        PREAMBLE;
  
        switch (wrapbox_marray->type){

                case BOX_MAILDIR:
                        maildir_apply_flag (mail);
                        break;

                default:
                        break;
        }
}



/**
 * foo_apply_flags must compare flags of every message in a box with
 * those available on filesystem and update them
 */
void
wrapbox_apply_flags (void)
{
        PREAMBLE;
  
        switch (wrapbox_marray->type){

                case BOX_MAILDIR:
                        maildir_apply_flags (wrapbox_marray);
                        break;

                default:
                        break;
        }
}


/**
 * this returns number of messages in wrapbox_marray or -1 if none
 */
int
wrapbox_mail_count (void)
{
        if (wrapbox_marray)
                return wrapbox_marray->count;

        return -1;
}



/**
 * foo_dump_place should dump 'place' member of 'mail' structure to
 * the given memchunk using its functions
 *
 * foo_dump_file_name should return an allocated string with name of
 * the cache file
 */
void
wrapbox_dump_box (void)
{
        int   fd;
        FILE *fp;
        char *fname;

        PREAMBLE;
  
        if (mybox_no_cache (wrapbox_marray->path))
                return;
        
        switch (wrapbox_marray->type){

                case BOX_MAILDIR:
                        fname = maildir_dump_file_name (wrapbox_marray);
                        break;

                default:
                        return;
        }
  
        fd = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
        fp = fdopen (fd, "w");
        if (fp == NULL){
                error_ (errno, "%s", fname);
                return;
        }

        xfree (fname);

        switch (wrapbox_marray->type){

                case BOX_MAILDIR:
                        mail_array_dump (wrapbox_marray, fp,
                                         maildir_dump_place);
                        break;

                default:
                        break;
        }

        fclose (fp);
}



void
wrapbox_empty_trash (void)
{
        char *trash = mybox_trash ();

        PREAMBLE;
  
        switch (wrapbox_marray->type){

                case BOX_MAILDIR:
                        maildir_empty_box (trash);
                        folder_update();
                        break;

                default:
                        break;
        }

        xfree (trash);
}



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


syntax highlighted by Code2HTML, v. 0.9.1