/* $Cambridge: hermes/src/prayer/session/ml.c,v 1.3 2008/09/16 09:59:58 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

/* Wrapper functions for various c-client routines:
 *
 * Original idea was to provide transparent reopen on [CLOSED] events
 * probably no longer need this: main event loop checkpoints open streams
 * However this is still a useful way to hide all the ghastly c-client
 * callback routines from the main program. These routines also trap
 * mm_exists and mm_expunged events and call msgmap_invalidate if
 * things change unexpectly beneath our feet. Will be particularly
 * important with concurrent read-write sessions on IMAP server.
 *
 */

#include "prayer_session.h"

static struct memblock *errmsg = NIL;

/* Must call mm_init before any other fn. mm_init() calls ml_init() */
void ml_init()
{
    errmsg = memblock_create(NIL, 64);

    mail_parameters(NIL, SET_RSHTIMEOUT,   (void *) 0L);
    mail_parameters(NIL, SET_OPENTIMEOUT,  (void *) ML_OPENTIMEOUT);
    mail_parameters(NIL, SET_READTIMEOUT,  (void *) ML_READTIMEOUT);
    mail_parameters(NIL, SET_WRITETIMEOUT, (void *) ML_WRITETIMEOUT);
}

/* ====================================================================== */

static BOOL have_error = NIL;   /* Have error */

BOOL ml_have_error()
{
    return (have_error);
}

void ml_clear_error()
{
    have_error = NIL;
    mputs(errmsg, "");
}

void ml_set_error()
{
    have_error = T;
}

/* Local equivalents: don't rely on automatic inlining */
#define ml_have_error()   (have_error)
#define ml_clear_error() {have_error = NIL; }
#define ml_set_error()   {have_error = T;}

/* ====================================================================== */

char *ml_errmsg()
{
    return (memblock_string(errmsg));
}


BOOL ml_have_errmsg()
{
    char *s = memblock_string(errmsg);

    return ((s && *s) ? T : NIL);
}

void ml_clear_errmsg()
{
    mputs(errmsg, "");
}

void ml_set_errmsg(char *error)
{
    mputs(errmsg, error);
}

void ml_errmsg_printf(char *fmt, ...)
{
    struct memblock *m = errmsg;
    unsigned long size;
    va_list ap;

    va_start(ap, fmt);
    size = memblock_vprintf_size(m, fmt, ap);
    va_end(ap);

    memblock_resize(m, size + 1);

    va_start(ap, fmt);
    memblock_vprintf(m, fmt, ap);
    va_end(ap);
}


/* ====================================================================== */

static BOOL have_try_create = NIL;      /* Have try_create */

BOOL ml_have_try_create()
{
    return (have_try_create);
}

void ml_clear_try_create()
{
    have_try_create = NIL;
}

void ml_set_try_create()
{
    have_try_create = T;
}

/* Local equivalents: don't rely on automatic inlining */
#define ml_have_try_create()  (have_try_create)
#define ml_clear_try_create() {have_try_create = NIL;}
#define ml_set_try_create()   {have_try_create = T;}

/* ====================================================================== */

static BOOL have_close = NIL;   /* Stream closed */

BOOL ml_have_close()
{
    return (have_close);
}

void ml_clear_have_close()
{
    have_close = NIL;
}

void ml_set_have_close()
{
    have_close = T;
}

/* Local equivalents: don't rely on automatic inlining */
#define ml_have_close()       (have_close)
#define ml_clear_have_close() {have_close = NIL;}
#define ml_set_have_close()   {have_close = T;}

/* ====================================================================== */

static BOOL mailbox_update = NIL;       /* mm_exists or mm_expunged call */

BOOL ml_mailbox_update()
{
    return (mailbox_update);
}

void ml_clear_mailbox_update()
{
    mailbox_update = NIL;
}

void ml_set_mailbox_update()
{
    mailbox_update = T;
}

/* Local equivalents: don't rely on automatic inlining */
#define ml_mailbox_update()       (mailbox_update)
#define ml_clear_mailbox_update() {mailbox_update = NIL;}
#define ml_set_mailbox_update()   {mailbox_update = T;}

/* ====================================================================== */

MESSAGECACHE *ml_elt(struct session *session, MAILSTREAM * stream,
                     unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (msgno < 1 || msgno > stream->nmsgs) {
        session_message(session,
                        "Bad msgno %lu in mail_elt, nmsgs = %lu",
                        msgno, stream->nmsgs);

        session_log(session,
                    "[ml_elt] Bad msgno %lu in mail_elt, nmsgs = %lu",
                    msgno, stream->nmsgs);

        session_paniclog(session,
                         "Bad msgno %lu in mail_elt, nmsgs = %lu",
                         msgno, stream->nmsgs);

#if 0 /* Unfriendly now that concurrent access mostly works
         (though testing for things changing under your feet is hard work) */
        abort();
#endif
        return (NIL);
    }

    ml_clear_have_close();

    if (!(elt = mail_elt(stream, msgno)))
        return (NIL);

    /* No ml_check_update here... */
    return (elt);
}

unsigned long
ml_msgno(struct session *session, MAILSTREAM * stream, unsigned long uid)
{
    unsigned long result;

    ml_clear_have_close();

    if (!((result = mail_msgno(stream, uid)) > 0))
        return (0L);

    return (result);
}

unsigned long
ml_uid(struct session *session, MAILSTREAM * stream, unsigned long msgno)
{
    if ((msgno == 0) || (msgno > stream->nmsgs))
        return (0);

    ml_clear_have_close();

    return (mail_uid(stream, msgno));
}

BOOL
ml_fetch_fast(struct session * session, MAILSTREAM * stream,
              char *seq, unsigned long flags)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_fetch_fast(stream, seq, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (T);
}


BOOL
ml_fetch_overview(struct session * session,
                  MAILSTREAM * stream, char *sequence, overview_t ofn)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_fetch_overview(stream, sequence, ofn);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (T);
}

ENVELOPE *ml_fetch_structure(struct session * session,
                             MAILSTREAM * stream, unsigned long msgno,
                             BODY ** body, long flags)
{
    ENVELOPE *env;

    ml_clear_have_close();
    ml_clear_error();

    if (body) {
        if (!((env = mail_fetch_structure(stream, msgno, body, flags))
              && *body))
            env = NIL;          /* Envelope and body must work */
    } else
        env = mail_fetch_structure(stream, msgno, body, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (env);
}

char *ml_fetch_message(struct session *session,
                       MAILSTREAM * stream, unsigned long msgno,
                       unsigned long *len, long flags)
{
    char *msg;

    ml_clear_have_close();
    ml_clear_error();

    msg = mail_fetch_message(stream, msgno, len, flags);

    if (ml_have_close() || ml_have_close())
        return (NIL);

    return (msg);
}

char *ml_fetch_header(struct session *session,
                      MAILSTREAM * stream, unsigned long msgno,
                      char *section, STRINGLIST * lines,
                      unsigned long *len, long flags)
{
    char *hdr;

    ml_clear_have_close();
    ml_clear_error();

    hdr = mail_fetch_header(stream, msgno, section, lines, len, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (hdr);
}

char *ml_fetch_text(struct session *session,
                    MAILSTREAM * stream, unsigned long msgno,
                    char *section, unsigned long *len, long flags)
{
    char *text;

    ml_clear_have_close();
    ml_clear_error();

    text = mail_fetch_text(stream, msgno, section, len, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (text);
}


char *ml_fetch_mime(struct session *session,
                    MAILSTREAM * stream, unsigned long msgno,
                    char *section, unsigned long *len, long flags)
{
    char *mime;

    ml_clear_have_close();
    ml_clear_error();

    mime = mail_fetch_mime(stream, msgno, section, len, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (mime);
}

char *ml_fetch_body(struct session *session,
                    MAILSTREAM * stream, unsigned long msgno,
                    char *section, unsigned long *len, long flags)
{
    char *body;

    ml_clear_have_close();
    ml_clear_error();

    body = mail_fetch_body(stream, msgno, section, len, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (body);
}

BODY *ml_body(struct session * session,
              MAILSTREAM * stream, unsigned long msgno, char *section)
{
    BODY *body;

    ml_clear_have_close();
    ml_clear_error();

    body = mail_body(stream, msgno, (unsigned char *)section);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (body);
}

/* ====================================================================== */

BOOL ml_ping(struct session * session, MAILSTREAM * stream)
{
    long result;

    ml_clear_have_close();
    ml_clear_error();

    result = mail_ping(stream);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return ((result != 0) ? T : NIL);
}

/* ml_append transparently creates target mail folder on TRYCREATE error */

BOOL
ml_append(struct session * session,
          MAILSTREAM * stream, char *mailbox, STRING * message)
{
    STRING spare;
    char *s;

    /* mail_append() blats STRING * arg */
    memcpy(&spare, message, sizeof(STRING));

    ml_clear_try_create();
    ml_clear_have_close();
    ml_clear_error();
    ml_clear_errmsg();

    if (mail_append(stream, mailbox, message)) {
        /* Additional paranoia for append: make all warnings error codes */
        /* other than: Cyrus is a bit verbose: APPENDUID is not an error */
        if (ml_have_errmsg() && (s = ml_errmsg()) &&
            (strncmp(s, "[APPENDUID ", strlen("[APPENDUID ")) != 0)) {
            ml_set_error();
            return (NIL);
        }
        return (T);
    }

    if (ml_have_close())
        return (NIL);

    if (!ml_have_try_create())
        return (NIL);

    if (!ml_create(session, stream, mailbox))
        return (NIL);

    ml_clear_try_create();
    ml_clear_have_close();
    ml_clear_error();
    ml_clear_errmsg();

    if (mail_append(stream, mailbox, &spare)) {
        /* Additional paranoia for append: make all warnings error codes */
        /* other than: Cyrus is a bit verbose: APPENDUID is not an error */
        if (ml_have_errmsg() && (s = ml_errmsg()) &&
            (strncmp(s, "[APPENDUID ", strlen("[APPENDUID ")) != 0)) {
            ml_set_error();
            return (NIL);
        }
        return (T);
    }
    return (NIL);
}

BOOL
ml_append_multiple(struct session * session,
                   MAILSTREAM * stream, char *mailbox, append_t fn,
                   void *ap)
{
    char *s;

    ml_clear_try_create();
    ml_clear_have_close();
    ml_clear_error();
    ml_clear_errmsg();

    if (mail_append_multiple(stream, mailbox, fn, ap)) {
        /* Additional paranoia for append: make all warnings error codes */
        /* other than: Cyrus is a bit verbose: APPENDUID is not an error */
        if (ml_have_errmsg() && (s = ml_errmsg()) &&
            (strncmp(s, "[APPENDUID ", strlen("[APPENDUID ")) != 0)) {
            ml_set_error();
            return (NIL);
        }
        return (T);
    }

    if (ml_have_close())
        return (NIL);

    if (!ml_have_try_create())
        return (NIL);

    if (!ml_create(session, stream, mailbox))
        return (NIL);

    ml_clear_try_create();
    ml_clear_have_close();
    ml_clear_error();
    ml_clear_errmsg();

    if (mail_append_multiple(stream, mailbox, fn, ap)) {
        /* Additional paranoia for append: make all warnings error codes */
        /* other than: Cyrus is a bit verbose: APPENDUID is not an error */
        if (ml_have_errmsg() && (s = ml_errmsg()) &&
            (strncmp(s, "[APPENDUID ", strlen("[APPENDUID ")) != 0)) {
            ml_set_error();
            return (NIL);
        }
        return (T);
    }
    return (NIL);
}

/* ====================================================================== */

BOOL
ml_copy(struct session * session,
        MAILSTREAM * stream, char *sequence, char *mailbox, long options)
{
    long result;
    char *s;

    /* We leave it up to the client routine to handle trycreate */
    ml_clear_have_close();
    ml_clear_error();
    ml_clear_errmsg();

    result = mail_copy_full(stream, sequence, mailbox, options);

    if (ml_have_close() || (result == 0))
        return (NIL);

    /* Additional paranoia for copy: make all warnings error codes */
    /* other than: Cyrus is a bit verbose: COPYUID is not an error */
    if (ml_have_errmsg() && (s = ml_errmsg()) &&
        (strncmp(s, "[COPYUID ", strlen("[COPYUID ")) != 0)) {
        ml_set_error();
        return (NIL);
    }
    return (T);
}

BOOL ml_check(struct session * session, MAILSTREAM * stream)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_check(stream);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (T);
}

void ml_close(struct session *session, MAILSTREAM * stream)
{
    mail_close(stream);
    /* No point in reopening, just to close */
}

BOOL ml_create(struct session *session, MAILSTREAM * stream, char *mailbox)
{
    long result;

    ml_clear_have_close();
    ml_clear_error();

    result = mail_create(stream, mailbox);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return ((result != 0) ? T : NIL);
}

BOOL
ml_delete(struct session * session, MAILSTREAM * stream, char *mailbox)
{
    long result;

    ml_clear_have_close();
    ml_clear_error();

    result = mail_delete(stream, mailbox);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return ((result != 0) ? T : NIL);
}

BOOL ml_expunge(struct session * session, MAILSTREAM * stream)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_expunge(stream);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (T);
}

BOOL
ml_flag(struct session * session,
        MAILSTREAM * stream, char *sequence, char *flag, long flags)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_flag(stream, sequence, flag, flags);

    if (ml_have_error() || ml_have_close())
        return (NIL);

    return (T);
}

BOOL
ml_list(struct session * session, MAILSTREAM * stream, char *ref,
        char *pat)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_list(stream, ref, pat);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (T);
}

static MAILSTATUS *ml_status_val = NIL;

void ml_status_callback(MAILSTATUS *status)
{
    if (ml_status_val)
        memcpy(ml_status_val, status, sizeof(MAILSTATUS));
}

BOOL ml_status(struct session * session, MAILSTREAM * stream, char *mbx,
               int flags, MAILSTATUS *status)
{
    BOOL result;
    ml_clear_have_close();
    ml_clear_error();

    memset(status, 0, sizeof(MAILSTATUS));

    ml_status_val = status;
    result = mail_status(stream, mbx, flags) ? T : NIL;
    ml_status_val = NIL;

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (result);
}

/* ====================================================================== */

static struct session *this_session = NIL;

static void
ml_open_login_callback(NETMBX * mb, char *user, char *pwd, long trial)
{
    struct session *session = this_session;

    if ((trial > 0) && session->newpassword) {
        session->password = session->newpassword;
        session->newpassword = NIL;
    }

    strcpy(user, this_session->username);
    strcpy(pwd, this_session->password);
}

static MAILSTREAM *ml_open_work(struct session *session,
                                MAILSTREAM * oldstream, char *name,
                                long options)
{
    MAILSTREAM *stream;

    /* Register login callback above, which provides username and password */
    this_session = session;
    mm_register_login_callback(ml_open_login_callback);

    if (session->newpassword)
        mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) 2L);
    else
        mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) 1L);

    ml_clear_have_close();
    ml_clear_error();
    /* No mailbox update check: open will always call mm_exists! */

    if ((stream = mail_open(oldstream, name, options)))
        return (stream);

    if (!oldstream)
        return (NIL);           /* No point in trying to reopen unless was open */

    if (ml_have_close())
        return (NIL);

    return (mail_open(NIL, name, options));
}

MAILSTREAM *ml_open(struct session * session,
                    MAILSTREAM * oldstream, char *name, long options)
{
    MAILSTREAM *result = ml_open_work(session, oldstream, name, options);
    char *error;

    if (result)
        return (result);

    error = ml_errmsg();

    /* Translate silly default message from imap_login() back into English */
    if (!error || !error[0] || !strcmp(error, "Too many login failures"))
        ml_set_errmsg("Incorrect username or password");

    return (NIL);
}

BOOL
ml_rename(struct session * session,
          MAILSTREAM * stream, char *oldname, char *newname)
{
    long result;

    ml_clear_have_close();
    ml_clear_error();

    result = mail_rename(stream, oldname, newname);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return ((result != 0) ? T : NIL);
}

BOOL
ml_search_full(struct session * session,
               MAILSTREAM * stream, char *charset, SEARCHPGM * pgm,
               long flags)
{
    ml_clear_have_close();
    ml_clear_error();

    mail_search_full(stream, charset, pgm, flags);

    if (ml_have_close() || ml_have_error())
        return (NIL);

    return (T);
}
