Logo Search packages:      
Sourcecode: balsa version File versions

mailbox_imap.c

/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
/* Balsa E-Mail Client
 * Copyright (C) 1997-2003 Stuart Parmenter and others,
 *                         See the file AUTHORS for a list.
 *
 * 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, 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.
 */

/* NOTES:

   CACHING: persistent cache is implemented using a directory. 

   CONNECTIONS: there is always one connection per opened mailbox to
   keep track of untagged responses. Understand idea of untagged
   responses particularly for shared mailboxes before you try messing
   with this.
*/
#include "config.h"

#define _XOPEN_SOURCE 500

#include <stdlib.h>
#include <dirent.h>
#include <string.h>

/* for open() */
#include <sys/stat.h>
#include <fcntl.h>

/* for uint32_t */
#include <stdint.h>

#ifdef BALSA_USE_THREADS
#include <pthread.h>
#endif

#include "libimap.h"
#include "filter-funcs.h"
#include "filter.h"
#include "mailbox-filter.h"
#include "message.h"
#include "libbalsa_private.h"
#include "misc.h"
#include "imap-handle.h"
#include "imap-commands.h"
#include "imap-server.h"
#include "libbalsa-conf.h"
#include "i18n.h"

struct _LibBalsaMailboxImap {
    LibBalsaMailboxRemote mailbox;
    ImapMboxHandle *handle;     /* stream that has this mailbox selected */
    guint handle_refs;        /* reference counter */
    gint search_stamp;        /* search result validator */

    gchar *path;        /* Imap local path (third part of URL) */
    ImapUID      uid_validity;

    GArray* messages_info;
    gboolean opened;

    GArray *sort_ranks;
    LibBalsaMailboxSortFields sort_field;
};

struct _LibBalsaMailboxImapClass {
    LibBalsaMailboxRemoteClass klass;
};

struct message_info {
    LibBalsaMessage *message;
    LibBalsaMessageFlag user_flags;
};

static LibBalsaMailboxClass *parent_class = NULL;

static off_t ImapCacheSize = 15*1024*1024; /* 15MB */

 /* issue message if downloaded part has more than this size */
static unsigned SizeMsgThreshold = 50*1024;
static void libbalsa_mailbox_imap_finalize(GObject * object);
static void libbalsa_mailbox_imap_class_init(LibBalsaMailboxImapClass *
                                   klass);
static void libbalsa_mailbox_imap_init(LibBalsaMailboxImap * mailbox);
static gboolean libbalsa_mailbox_imap_open(LibBalsaMailbox * mailbox,
                                 GError **err);
static void libbalsa_mailbox_imap_close(LibBalsaMailbox * mailbox,
                                        gboolean expunge);
static GMimeStream *libbalsa_mailbox_imap_get_message_stream(LibBalsaMailbox *
                                               mailbox,
                                               LibBalsaMessage *
                                               message);
static void libbalsa_mailbox_imap_check(LibBalsaMailbox * mailbox);

static void
libbalsa_mailbox_imap_search_iter_free(LibBalsaMailboxSearchIter * iter);
static gboolean libbalsa_mailbox_imap_message_match(LibBalsaMailbox* mailbox,
                                        guint msgno,
                                        LibBalsaMailboxSearchIter
                                        * search_iter);
static gboolean libbalsa_mailbox_imap_can_match(LibBalsaMailbox  *mbox,
                                    LibBalsaCondition *condition);
static void libbalsa_mailbox_imap_save_config(LibBalsaMailbox * mailbox,
                                    const gchar * prefix);
static void libbalsa_mailbox_imap_load_config(LibBalsaMailbox * mailbox,
                                    const gchar * prefix);

static gboolean libbalsa_mailbox_imap_sync(LibBalsaMailbox * mailbox,
                                           gboolean expunge);
static LibBalsaMessage* libbalsa_mailbox_imap_get_message(LibBalsaMailbox*
                                            mailbox,
                                            guint msgno);
static void libbalsa_mailbox_imap_prepare_threading(LibBalsaMailbox *mailbox, 
                                                    guint lo, guint hi);
static gboolean libbalsa_mailbox_imap_fetch_structure(LibBalsaMailbox *
                                                      mailbox,
                                                      LibBalsaMessage *
                                                      message,
                                                      LibBalsaFetchFlag
                                                      flags);
static void libbalsa_mailbox_imap_fetch_headers(LibBalsaMailbox *mailbox,
                                                LibBalsaMessage *message);
static gboolean libbalsa_mailbox_imap_get_msg_part(LibBalsaMessage *msg,
                                       LibBalsaMessageBody *);

static int libbalsa_mailbox_imap_add_message(LibBalsaMailbox * mailbox,
                                   LibBalsaMessage * message,
                                             GError **err);

static gboolean lbm_imap_messages_change_flags(LibBalsaMailbox * mailbox,
                                               GArray * seqno,
                                              LibBalsaMessageFlag set,
                                              LibBalsaMessageFlag clear);
static gboolean libbalsa_mailbox_imap_msgno_has_flags(LibBalsaMailbox *
                                                      mailbox, guint seqno,
                                                      LibBalsaMessageFlag
                                                      set,
                                                      LibBalsaMessageFlag
                                                      unset);
static gboolean libbalsa_mailbox_imap_can_do(LibBalsaMailbox* mbox,
                                             enum LibBalsaMailboxCapability c);

static void libbalsa_mailbox_imap_set_threading(LibBalsaMailbox *mailbox,
                                    LibBalsaMailboxThreadingType
                                    thread_type);
static void lbm_imap_update_view_filter(LibBalsaMailbox   *mailbox,
                                        LibBalsaCondition *view_filter);
static void libbalsa_mailbox_imap_sort(LibBalsaMailbox *mailbox,
                                       GArray *array);
static guint libbalsa_mailbox_imap_total_messages(LibBalsaMailbox *
                                      mailbox);
static gboolean libbalsa_mailbox_imap_messages_copy(LibBalsaMailbox *
                                        mailbox,
                                        GArray * msgnos,
                                        LibBalsaMailbox *
                                        dest,
                                                    GError **err);

static void server_host_settings_changed_cb(LibBalsaServer * server,
                                  gchar * host,
#ifdef USE_SSL
                                  gboolean use_ssl,
#endif
                                  LibBalsaMailbox * mailbox);

static struct message_info *message_info_from_msgno(
                                      LibBalsaMailboxImap * mimap,
                                      guint msgno)
{
    struct message_info *msg_info = 
      &g_array_index(mimap->messages_info, struct message_info, msgno - 1);
    return msg_info;
}

#define IMAP_MESSAGE_UID(msg) \
        (mi_get_imsg(LIBBALSA_MAILBOX_IMAP((msg)->mailbox),\
                 (msg)->msgno)->uid)

#define IMAP_MAILBOX_UID_VALIDITY(mailbox) (LIBBALSA_MAILBOX_IMAP(mailbox)->uid_validity)

GType
libbalsa_mailbox_imap_get_type(void)
{
    static GType mailbox_type = 0;

    if (!mailbox_type) {
      static const GTypeInfo mailbox_info = {
          sizeof(LibBalsaMailboxImapClass),
            NULL,               /* base_init */
            NULL,               /* base_finalize */
          (GClassInitFunc) libbalsa_mailbox_imap_class_init,
            NULL,               /* class_finalize */
            NULL,               /* class_data */
          sizeof(LibBalsaMailboxImap),
            0,                  /* n_preallocs */
          (GInstanceInitFunc) libbalsa_mailbox_imap_init
      };

      mailbox_type =
          g_type_register_static(LIBBALSA_TYPE_MAILBOX_REMOTE,
                                 "LibBalsaMailboxImap",
                             &mailbox_info, 0);
    }

    return mailbox_type;
}

static void
libbalsa_mailbox_imap_class_init(LibBalsaMailboxImapClass * klass)
{
    GObjectClass *object_class;
    LibBalsaMailboxClass *libbalsa_mailbox_class;

    object_class = G_OBJECT_CLASS(klass);
    libbalsa_mailbox_class = LIBBALSA_MAILBOX_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    object_class->finalize = libbalsa_mailbox_imap_finalize;

    libbalsa_mailbox_class->open_mailbox = libbalsa_mailbox_imap_open;
    libbalsa_mailbox_class->close_mailbox = libbalsa_mailbox_imap_close;

    libbalsa_mailbox_class->check = libbalsa_mailbox_imap_check;
    
    libbalsa_mailbox_class->search_iter_free =
      libbalsa_mailbox_imap_search_iter_free;
    libbalsa_mailbox_class->message_match =
      libbalsa_mailbox_imap_message_match;
    libbalsa_mailbox_class->can_match =
      libbalsa_mailbox_imap_can_match;

    libbalsa_mailbox_class->save_config =
      libbalsa_mailbox_imap_save_config;
    libbalsa_mailbox_class->load_config =
      libbalsa_mailbox_imap_load_config;
    libbalsa_mailbox_class->sync = libbalsa_mailbox_imap_sync;
    libbalsa_mailbox_class->get_message = libbalsa_mailbox_imap_get_message;
    libbalsa_mailbox_class->prepare_threading =
        libbalsa_mailbox_imap_prepare_threading;
    libbalsa_mailbox_class->fetch_message_structure = 
        libbalsa_mailbox_imap_fetch_structure;
    libbalsa_mailbox_class->fetch_headers = 
        libbalsa_mailbox_imap_fetch_headers;
    libbalsa_mailbox_class->get_message_part = 
        libbalsa_mailbox_imap_get_msg_part;
    libbalsa_mailbox_class->get_message_stream =
      libbalsa_mailbox_imap_get_message_stream;
    libbalsa_mailbox_class->add_message = libbalsa_mailbox_imap_add_message;
    libbalsa_mailbox_class->messages_change_flags =
      lbm_imap_messages_change_flags;
    libbalsa_mailbox_class->msgno_has_flags =
      libbalsa_mailbox_imap_msgno_has_flags;
    libbalsa_mailbox_class->can_do =
      libbalsa_mailbox_imap_can_do;
    libbalsa_mailbox_class->set_threading =
      libbalsa_mailbox_imap_set_threading;
    libbalsa_mailbox_class->update_view_filter =
        lbm_imap_update_view_filter;
    libbalsa_mailbox_class->sort = libbalsa_mailbox_imap_sort;
    libbalsa_mailbox_class->total_messages =
      libbalsa_mailbox_imap_total_messages;
    libbalsa_mailbox_class->messages_copy =
      libbalsa_mailbox_imap_messages_copy;
}

static void
libbalsa_mailbox_imap_init(LibBalsaMailboxImap * mailbox)
{
    mailbox->path = NULL;
    mailbox->handle = NULL;
    mailbox->handle_refs = 0;
    mailbox->sort_ranks = g_array_new(FALSE, FALSE, sizeof(guint));
    mailbox->sort_field = -1; /* Initially invalid. */
}

/* libbalsa_mailbox_imap_finalize:
   NOTE: we have to close mailbox ourselves without waiting for
   LibBalsaMailbox::finalize because we want to destroy server as well,
   and close requires server for proper operation.  
*/
static void
libbalsa_mailbox_imap_finalize(GObject * object)
{
    LibBalsaMailboxImap *mailbox;
    LibBalsaMailboxRemote *remote;

    g_return_if_fail(LIBBALSA_IS_MAILBOX_IMAP(object));

    mailbox = LIBBALSA_MAILBOX_IMAP(object);

    g_assert(LIBBALSA_MAILBOX(mailbox)->open_ref == 0);

    remote = LIBBALSA_MAILBOX_REMOTE(object);
    g_free(mailbox->path); mailbox->path = NULL;

    if(remote->server) {
        g_signal_handlers_disconnect_matched(remote->server,
                                             G_SIGNAL_MATCH_DATA, 0,
                                             (GQuark) 0, NULL, NULL,
                                             remote);
      g_object_unref(G_OBJECT(remote->server));
      remote->server = NULL;
    }

    g_array_free(mailbox->sort_ranks, TRUE); mailbox->sort_ranks = NULL;

    if (G_OBJECT_CLASS(parent_class)->finalize)
      G_OBJECT_CLASS(parent_class)->finalize(object);
}

LibBalsaMailboxImap*
libbalsa_mailbox_imap_new(void)
{
    LibBalsaMailboxImap *mailbox;
    mailbox = g_object_new(LIBBALSA_TYPE_MAILBOX_IMAP, NULL);

    return mailbox;
}

/* libbalsa_mailbox_imap_update_url:
   this is to be used only by mailboxImap functions, with exception
   for the folder scanner, which has to go around libmutt limitations.
*/
void
libbalsa_mailbox_imap_update_url(LibBalsaMailboxImap* mailbox)
{
    LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox);

    g_free(LIBBALSA_MAILBOX(mailbox)->url);
    LIBBALSA_MAILBOX(mailbox)->url = libbalsa_imap_url(s, mailbox->path);
}

void
libbalsa_mailbox_imap_set_path(LibBalsaMailboxImap* mailbox, const gchar* path)
{
    g_return_if_fail(mailbox);
    g_free(mailbox->path);
    mailbox->path = g_strdup(path);
    libbalsa_mailbox_imap_update_url(mailbox);
}

const gchar*
libbalsa_mailbox_imap_get_path(LibBalsaMailboxImap * mailbox)
{
    return mailbox->path;
}

static void
server_host_settings_changed_cb(LibBalsaServer * server, gchar * host,
#ifdef USE_SSL
                                  gboolean use_ssl,
#endif
                        LibBalsaMailbox * mailbox)
{
    libbalsa_mailbox_imap_update_url(LIBBALSA_MAILBOX_IMAP(mailbox));
}

static gchar*
get_cache_dir(LibBalsaMailboxImap* mailbox, const gchar* type,
               gboolean is_persistent)
{
    gchar *fname;
    if(mailbox && is_persistent) {
        LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox);
        gchar *start;
        const gchar *home = g_get_home_dir();
        fname = g_strconcat(home,
                            "/.balsa/", s->user, "@", s->host, "-",
                            (mailbox->path ? mailbox->path : "INBOX"),
                            "-", type, ".dir", NULL);
        for(start=fname+strlen(home)+8; *start; start++)
            if(*start == '/') *start = '-';
    } else
        fname = g_strconcat("/tmp/balsa-",  g_get_user_name(), NULL);

    return fname;
}

static gchar**
get_cache_name_pair(LibBalsaMailboxImap* mailbox, const gchar *type,
                    ImapUID uid)
{
    LibBalsaServer *s      = LIBBALSA_MAILBOX_REMOTE(mailbox)->server;
    LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s);
    gboolean is_persistent = libbalsa_imap_server_has_persistent_cache(is);
    gchar **res = g_malloc(3*sizeof(gchar*));
    ImapUID uid_validity = LIBBALSA_MAILBOX_IMAP(mailbox)->uid_validity;

    res[0] = get_cache_dir(mailbox, type, is_persistent);
    if(is_persistent) 
        res[1] = g_strdup_printf("%u-%u", uid_validity, uid);
    else {
        gchar *start;
        res[1] = g_strdup_printf("%s@%s-%s-%s-%u-%u",
                                 s->user, s->host,
                                 (mailbox->path ? mailbox->path : "INBOX"),
                                 type, uid_validity, uid);
        for(start=res[1]; *start; start++)
            if(*start == '/') *start = '-';
    }
    res[2] = NULL;
    return res;
}

/* clean_cache:
   removes unused entries from the cache file.
*/
struct file_info {
    char  *name;
    off_t  size;
    time_t time;
};
static gint
cmp_by_time (gconstpointer  a, gconstpointer  b)
{
    return ((const struct file_info*)b)->time
        -((const struct file_info*)a)->time;
}

static void
clean_dir(const char *dir_name, off_t cache_size)
{
    DIR* dir;
    struct dirent* key;
    GList *list, *lst;
    off_t sz;
    dir = opendir(dir_name);
    if(!dir)
        return;

    list = NULL;
    while ( (key=readdir(dir)) != NULL) {
        struct stat st;
        struct file_info *fi;
        gchar *fname = g_strconcat(dir_name, "/", key->d_name, NULL);
        if(stat(fname, &st) == -1 || !S_ISREG(st.st_mode)) {
          g_free(fname);
            continue;
      }
        fi = g_new(struct file_info,1);
        fi->name = fname;
        fi->size = st.st_size;
        fi->time = st.st_atime;
        list = g_list_prepend(list, fi);
    }
    closedir(dir);

    list = g_list_sort(list, cmp_by_time);
    sz = 0;
    for(lst = list; lst; lst = lst->next) {
        struct file_info *fi = (struct file_info*)(lst->data);
        sz += fi->size;
        if(sz>cache_size) {
            printf("removing %s\n", fi->name);
            unlink(fi->name);
        }
        g_free(fi->name);
        g_free(fi);
    }
    g_list_free(list);
}

static gboolean
clean_cache(LibBalsaMailbox* mailbox)
{
    LibBalsaServer *s= LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox);
    gboolean is_persistent =
        libbalsa_imap_server_has_persistent_cache(LIBBALSA_IMAP_SERVER(s));
    gchar* dir;

    dir = get_cache_dir(LIBBALSA_MAILBOX_IMAP(mailbox), "body",
                        is_persistent);
    clean_dir(dir, ImapCacheSize);
    g_free(dir);
    /* the body and part dirs are different only with persistent caching. */
    if(is_persistent) {
        dir = get_cache_dir(LIBBALSA_MAILBOX_IMAP(mailbox), "part",
                            is_persistent);
        clean_dir(dir, ImapCacheSize);
        g_free(dir);
    }
 
    return TRUE;
}
struct ImapCacheManager;
static void icm_destroy(struct ImapCacheManager *icm);
static struct ImapCacheManager *icm_store_cached_data(ImapMboxHandle *h);
static void icm_restore_from_cache(ImapMboxHandle *h,
                                   struct ImapCacheManager *icm);

static ImapResult
mi_reconnect(ImapMboxHandle *h)
{
    struct ImapCacheManager *icm = icm_store_cached_data(h);
    ImapResult r = imap_mbox_handle_reconnect(h, NULL);
    if(r==IMAP_SUCCESS) icm_restore_from_cache(h, icm);
    icm_destroy(icm);
    return r;
}
/* ImapIssue macro handles reconnecting. We might issue a
   LIBBALSA_INFORMATION_MESSAGE here but it would be overwritten by
   login information... */
#define II(rc,h,line) \
   {int trials=2;do{\
    if(imap_mbox_is_disconnected(h) &&mi_reconnect(h)!=IMAP_SUCCESS)\
        {rc=IMR_NO;break;};\
    rc=line; \
    if(rc==IMR_SEVERED) \
    libbalsa_information(LIBBALSA_INFORMATION_WARNING, \
    _("IMAP connection has been severed. Reconnecting...")); \
    else if(rc==IMR_BYE) {char *msg = imap_mbox_handle_get_last_msg(h); \
    libbalsa_information(LIBBALSA_INFORMATION_WARNING, \
    _("IMAP server has shut the connection: %s Reconnecting..."), msg); \
    g_free(msg);}\
    else break;}while(trials-->0);}

static ImapMboxHandle *
libbalsa_mailbox_imap_get_handle(LibBalsaMailboxImap *mimap, GError **err)
{

    g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mimap), NULL);

    if(!mimap->handle) {
        LibBalsaServer *server = LIBBALSA_MAILBOX_REMOTE_SERVER(mimap);
        LibBalsaImapServer *imap_server;
        if (!LIBBALSA_IS_IMAP_SERVER(server))
            return NULL;
        imap_server = LIBBALSA_IMAP_SERVER(server);
        mimap->handle = libbalsa_imap_server_get_handle(imap_server, err);
      mimap->handle_refs = 1;
    } else
      ++mimap->handle_refs;

    return mimap->handle;
}

#define RELEASE_HANDLE(mailbox,handle) \
    libbalsa_imap_server_release_handle( \
            LIBBALSA_IMAP_SERVER(LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox)),\
            handle)

static void
lbimap_update_flags(LibBalsaMessage *message, ImapMessage *imsg)
{
    message->flags = 0;
    if (!IMSG_FLAG_SEEN(imsg->flags))
        message->flags |= LIBBALSA_MESSAGE_FLAG_NEW;
    if (IMSG_FLAG_DELETED(imsg->flags))
        message->flags |= LIBBALSA_MESSAGE_FLAG_DELETED;
    if (IMSG_FLAG_FLAGGED(imsg->flags))
        message->flags |= LIBBALSA_MESSAGE_FLAG_FLAGGED;
    if (IMSG_FLAG_ANSWERED(imsg->flags))
        message->flags |= LIBBALSA_MESSAGE_FLAG_REPLIED;
    if (IMSG_FLAG_RECENT(imsg->flags))
      message->flags |= LIBBALSA_MESSAGE_FLAG_RECENT;
}

/* mi_get_imsg is a thin wrapper around imap_mbox_handle_get_msg().
   We wrap around imap_mbox_handle_get_msg() in case the libimap data
   was invalidated by eg. disconnect.
*/
struct collect_seq_data {
    unsigned *msgno_arr;
    unsigned cnt;
    unsigned needed_msgno;
    unsigned has_it;
};

static const unsigned MAX_CHUNK_LENGTH = 40; 
static gboolean
collect_seq_cb(GNode *node, gpointer data)
{
    /* We prefetch envelopes in chunks to save on RTTs.
     * Try to get the messages both before and after the message. */
    struct collect_seq_data *csd = (struct collect_seq_data*)data;
    unsigned msgno = GPOINTER_TO_UINT(node->data);
    if(msgno==0) /* root node */
        return FALSE;
    csd->msgno_arr[(csd->cnt++) % MAX_CHUNK_LENGTH] = msgno;
    if(csd->has_it>0) csd->has_it++;
    if(csd->needed_msgno == msgno)
        csd->has_it = 1;
    /* quit if we have enough messages and at least half of them are
     * after message in question. */
    return csd->cnt >= MAX_CHUNK_LENGTH && csd->has_it*2>MAX_CHUNK_LENGTH;
}

static int
cmp_msgno(const void* a, const void *b)
{
    return (*(unsigned*)a) - (*(unsigned*)b);
}

static ImapMessage*
mi_get_imsg(LibBalsaMailboxImap *mimap, unsigned msgno)
{
    ImapMessage* imsg;
    struct collect_seq_data csd;
    ImapResponse rc;

    /* This test too weak: I can imagine unsolicited ENVELOPE
     * responses sent from server that wil create the ImapMessage
     * structure but message size or UID etc will not be available. */
    if( (imsg = imap_mbox_handle_get_msg(mimap->handle, msgno)) 
        != NULL && imsg->envelope) return imsg;
    csd.needed_msgno = msgno;
    csd.msgno_arr    = g_malloc(MAX_CHUNK_LENGTH*sizeof(csd.msgno_arr[0]));
    csd.cnt          = 0;
    csd.has_it       = 0;
    if(LIBBALSA_MAILBOX(mimap)->msg_tree) {
        g_node_traverse(LIBBALSA_MAILBOX(mimap)->msg_tree,
                        G_PRE_ORDER, G_TRAVERSE_ALL, -1, collect_seq_cb,
                        &csd);
        if(csd.cnt>MAX_CHUNK_LENGTH) csd.cnt = MAX_CHUNK_LENGTH;
        qsort(csd.msgno_arr, csd.cnt, sizeof(csd.msgno_arr[0]), cmp_msgno);
    } else {
        /* It may happen that we want to perform an automatic
           operation on a mailbox without view (like filtering on
           reception). The searching will be done server side but
           current eg. _copy() instructions will require that
           LibBalsaMessage object are present, and these require that
           some basic information is fetched from the server.  */
        unsigned i, total_msgs = mimap->messages_info->len;
        csd.cnt = msgno+MAX_CHUNK_LENGTH>total_msgs
            ? total_msgs-msgno+1 : MAX_CHUNK_LENGTH;
        for(i=0; i<csd.cnt; i++) csd.msgno_arr[i] = msgno+i;
    }
    II(rc,mimap->handle,
       imap_mbox_handle_fetch_set(mimap->handle, csd.msgno_arr,
                                  csd.cnt,
                                  IMFETCH_FLAGS |
                                  IMFETCH_UID |
                                  IMFETCH_ENV |
                                  IMFETCH_RFC822SIZE |
                                  IMFETCH_CONTENT_TYPE));
    g_free(csd.msgno_arr);
    if (rc != IMR_OK)
        return FALSE;
    return imap_mbox_handle_get_msg(mimap->handle, msgno);
}


/** imap_flags_cb() is called by the imap backend when flags are
   fetched. Note that we may not have yet the preprocessed data in
   LibBalsaMessage.  We ignore the info in this case.
*/
static void
imap_flags_cb(unsigned cnt, const unsigned seqno[], LibBalsaMailboxImap *mimap)
{
    LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap);
    unsigned i;
    gboolean changed = FALSE;

    libbalsa_lock_mailbox(mailbox);
    for(i=0; i<cnt; i++) {
        struct message_info *msg_info = 
            message_info_from_msgno(mimap, seqno[i]);
      /* FIXME: We must update mailbox->unread_messages even when we
       * don't have msg_info->message. It's not easy, as we have no
       * record of what the flags were before this change.  */
        if(msg_info->message) {
            LibBalsaMessageFlag flags;
            gboolean was_unread_undeleted, is_unread_undeleted;
            /* since we are talking here about updating just received,
               usually unsolicited flags from the server, we do not
               need to go to great lengths to assure that the
               connection is up. */
            ImapMessage *imsg = 
                imap_mbox_handle_get_msg(mimap->handle, seqno[i]);
            if(!imsg) continue;

            flags = msg_info->message->flags;
            lbimap_update_flags(msg_info->message, imsg);
            if (flags == msg_info->message->flags)
                continue;

          libbalsa_mailbox_index_set_flags(mailbox, seqno[i],
                                   msg_info->message->flags);
            libbalsa_mailbox_msgno_changed(mailbox, seqno[i]);
          ++mimap->search_stamp;

            was_unread_undeleted = (flags & LIBBALSA_MESSAGE_FLAG_NEW)
                && !(flags & LIBBALSA_MESSAGE_FLAG_DELETED);
            flags = msg_info->message->flags;
            is_unread_undeleted = (flags & LIBBALSA_MESSAGE_FLAG_NEW)
                && !(flags & LIBBALSA_MESSAGE_FLAG_DELETED);
            mailbox->unread_messages +=
                is_unread_undeleted - was_unread_undeleted;
            if (is_unread_undeleted - was_unread_undeleted)
                changed = TRUE;
        }
    }
    libbalsa_unlock_mailbox(mailbox);
    if (changed)
      libbalsa_mailbox_set_unread_messages_flag(mailbox,
                                                  mailbox->unread_messages
                                      > 0);
}

/* Forward reference. */
static void lbm_imap_get_unseen(LibBalsaMailboxImap * mimap);

static gboolean
update_counters_and_filter(void *data)
{
    LibBalsaMailbox *mailbox= (LibBalsaMailbox*)data;

    gdk_threads_enter();
    libbalsa_lock_mailbox(mailbox);
    libbalsa_mailbox_run_filters_on_reception(mailbox);
    lbm_imap_get_unseen(LIBBALSA_MAILBOX_IMAP(mailbox));
    libbalsa_unlock_mailbox(mailbox);
    gdk_threads_leave();
    g_object_unref(G_OBJECT(mailbox));
    return FALSE;
}

static void
imap_exists_cb(ImapMboxHandle *handle, LibBalsaMailboxImap *mimap)
{
    unsigned cnt = imap_mbox_handle_get_exists(mimap->handle);
    LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap);

    mimap->sort_field = -1;   /* Invalidate. */
    if(cnt<mimap->messages_info->len) {
        /* remove messages; we probably missed some EXPUNGE responses
           - the only sensible scenario is that the connection was
           severed. Still, we need to recover from this somehow... -
           be aware, w are just guessing. In principle, we should
           invalidate all the cache now. */
        printf("%s: expunge ignored? Had %u messages and now only %u. "
               "Bug in the program or broken connection\n",
               __func__, mimap->messages_info->len, cnt);
        while(cnt<mimap->messages_info->len) {
            unsigned seqno = mimap->messages_info->len;
            struct message_info *msg_info =
                message_info_from_msgno(mimap, seqno);
                if(msg_info->message)
                g_object_unref(msg_info->message);
            g_array_remove_index(mimap->messages_info, seqno-1);
        }
        ++mimap->search_stamp;
    } else if (cnt > mimap->messages_info->len) { /* new messages arrived */
        unsigned i;
      struct message_info a = {0};

      /* EXISTS response may result from any IMAP action. */
      libbalsa_lock_mailbox(mailbox);

      i=mimap->messages_info->len+1;
        do {
            g_array_append_val(mimap->messages_info, a);
          /* dummy entry in mindex for now */
          g_ptr_array_add(mailbox->mindex, NULL);
            libbalsa_mailbox_msgno_inserted(mailbox, i);
        } while (++i <= cnt);
      ++mimap->search_stamp;

        /* we run filters and get unseen messages in a idle callback:
         * these things do not need to be done immediately and we do 
         * not want to issue too many new overlapping IMAP requests.
         */
        g_object_ref(G_OBJECT(mailbox));
        g_idle_add(update_counters_and_filter, mailbox);

      libbalsa_unlock_mailbox(mailbox);
    }
}

static void
imap_expunge_cb(ImapMboxHandle *handle, unsigned seqno,
                LibBalsaMailboxImap *mimap)
{
    ImapMessage *imsg;
    guint i;

    LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap);
    struct message_info *msg_info = message_info_from_msgno(mimap, seqno);

    libbalsa_lock_mailbox(mailbox);

    libbalsa_mailbox_msgno_removed(mailbox, seqno);
    ++mimap->search_stamp;
    mimap->sort_field = -1;   /* Invalidate. */

    /* Use imap_mbox_handle_get_msg(mimap->handle, seqno)->uid, not
     * IMAP_MESSAGE_UID(msg_info->message), as the latter may try to
     * fetch the message from the server. */
    if ((imsg = imap_mbox_handle_get_msg(mimap->handle, seqno))) {
      gchar **pair = get_cache_name_pair(mimap, "body", imsg->uid);
        gchar *fn = g_strconcat(pair[0], "/", pair[1], NULL);
        unlink(fn); /* ignore error; perhaps the message 
                     * was not in the cache.  */
        g_free(fn);
        g_strfreev(pair);
    }

    if(msg_info->message)
        g_object_unref(msg_info->message);
    g_array_remove_index(mimap->messages_info, seqno-1);

    for (i = seqno - 1; i < mimap->messages_info->len; i++) {
      struct message_info *msg_info =
          &g_array_index(mimap->messages_info, struct message_info, i);
      if (msg_info->message)
          msg_info->message->msgno = i + 1;
    }

    libbalsa_unlock_mailbox(mailbox);
}

static void
libbalsa_mailbox_imap_release_handle(LibBalsaMailboxImap * mimap)
{
    g_assert(mimap->handle != NULL);
    g_assert(mimap->handle_refs > 0);

    if (--mimap->handle_refs == 0) {
      /* Only selected handles have these signal handlers, but we'll
       * disconnect them anyway. */
      g_signal_handlers_disconnect_matched(mimap->handle,
                                   G_SIGNAL_MATCH_DATA,
                                   0, 0, NULL, NULL, mimap);
        imap_handle_set_flagscb(mimap->handle, NULL, NULL);
      RELEASE_HANDLE(mimap, mimap->handle);
      mimap->handle = NULL;
    }
}

static ImapMboxHandle *
libbalsa_mailbox_imap_get_selected_handle(LibBalsaMailboxImap *mimap,
                                GError **err)
{
    LibBalsaServer *server;
    LibBalsaImapServer *imap_server;
    ImapResponse rc;
    unsigned uidval;

    g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mimap), NULL);

    server = LIBBALSA_MAILBOX_REMOTE_SERVER(mimap);
    if (!LIBBALSA_IS_IMAP_SERVER(server))
      return NULL;
    imap_server = LIBBALSA_IMAP_SERVER(server);
    if(!mimap->handle) {
        mimap->handle = 
          libbalsa_imap_server_get_handle_with_user(imap_server,
                                          mimap, err);
        if (!mimap->handle)
            return NULL;
    }
    II(rc,mimap->handle,
       imap_mbox_select(mimap->handle, mimap->path,
                        &(LIBBALSA_MAILBOX(mimap)->readonly)));
    if (rc != IMR_OK) {
      gchar *msg = imap_mbox_handle_get_last_msg(mimap->handle);
      g_set_error(err, LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_OPEN_ERROR,
                "%s", msg);
      g_free(msg);
      RELEASE_HANDLE(mimap, mimap->handle);
        mimap->handle = NULL;
      return NULL;
    }
    /* test validity */
    uidval = imap_mbox_handle_get_validity(mimap->handle);
    if (mimap->uid_validity != uidval) {
      mimap->uid_validity = uidval;
      /* FIXME: update/remove msg uids */
    }

    imap_handle_set_flagscb(mimap->handle, (ImapFlagsCb)imap_flags_cb, mimap);
    g_signal_connect(G_OBJECT(mimap->handle),
                     "exists-notify", G_CALLBACK(imap_exists_cb),
                     mimap);
    g_signal_connect(G_OBJECT(mimap->handle),
                     "expunge-notify", G_CALLBACK(imap_expunge_cb),
                     mimap);
    mimap->handle_refs = 1;
    return mimap->handle;
}

/* Get the list of unseen messages from the server and set
 * the unread-messages count. */
static void
lbm_imap_get_unseen(LibBalsaMailboxImap * mimap)
{
    LibBalsaMailbox *mailbox;
    guint i, count, total;

    if (!mimap->handle)
      return;

    mailbox = LIBBALSA_MAILBOX(mimap);
    total = imap_mbox_handle_get_exists(mimap->handle);
    mailbox->first_unread = total;
    for(i=count=0; i<total; i++) {
        if(imap_mbox_handle_msgno_has_flags(mimap->handle,
                                            i+1,
                                            0, IMSGF_SEEN|IMSGF_DELETED)) {
            count++;
            if(mailbox->first_unread>i)
                mailbox->first_unread = i+1;
        }
    }
    mailbox->unread_messages = count;

    libbalsa_mailbox_set_unread_messages_flag(mailbox, count > 0);
}

/* libbalsa_mailbox_imap_open:
   opens IMAP mailbox. On failure leaves the object in sane state.
*/
static gboolean
libbalsa_mailbox_imap_open(LibBalsaMailbox * mailbox, GError **err)
{
    LibBalsaMailboxImap *mimap;
    LibBalsaServer *server;
    unsigned i;
    guint total_messages;
    struct ImapCacheManager *icm;

    g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), FALSE);

    mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    server = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox);

    mimap->handle = libbalsa_mailbox_imap_get_selected_handle(mimap, err);
    if (!mimap->handle) {
        mimap->opened         = FALSE;
      mailbox->disconnected = TRUE;
      return FALSE;
    }

    mimap->opened         = TRUE;
    mailbox->disconnected = FALSE;
    total_messages = imap_mbox_handle_get_exists(mimap->handle);
    mimap->messages_info = g_array_sized_new(FALSE, TRUE, 
                                   sizeof(struct message_info),
                                   total_messages);
    for(i=0; i < total_messages; i++) {
      struct message_info a = {0};
      g_array_append_val(mimap->messages_info, a);
        /* dummy entry in mindex for now */
        g_ptr_array_add(mailbox->mindex, NULL);
    }
    icm = g_object_get_data(G_OBJECT(mailbox), "cache-manager");
    if(icm)
        icm_restore_from_cache(mimap->handle, icm);

    mailbox->first_unread = imap_mbox_handle_first_unseen(mimap->handle);
    libbalsa_mailbox_run_filters_on_reception(mailbox);
    lbm_imap_get_unseen(mimap);
    if (mimap->search_stamp)
      ++mimap->search_stamp;
    else
      mimap->search_stamp = mailbox->stamp;

#ifdef DEBUG
    g_print(_("%s: Opening %s Refcount: %d\n"),
          "LibBalsaMailboxImap", mailbox->name, mailbox->open_ref);
#endif
    return TRUE;
}

static void
free_messages_info(LibBalsaMailboxImap * mbox)
{
    guint i;
    GArray *messages_info = mbox->messages_info;

    for (i = 0; i < messages_info->len; i++) {
      struct message_info *msg_info =
          &g_array_index(messages_info, struct message_info, i);
      if (msg_info->message) {
          msg_info->message->mailbox = NULL;
          g_object_unref(msg_info->message);
      }
    }
    g_array_free(mbox->messages_info, TRUE);
    mbox->messages_info = NULL;
}

static void
libbalsa_mailbox_imap_close(LibBalsaMailbox * mailbox, gboolean expunge)
{
    LibBalsaMailboxImap *mbox = LIBBALSA_MAILBOX_IMAP(mailbox);

    /* FIXME: save headers differently: save_to_cache(mailbox); */
    clean_cache(mailbox);

    mbox->opened = FALSE;
    g_object_set_data(G_OBJECT(mailbox), "cache-manager",
                      icm_store_cached_data(mbox->handle));

    /* we do not attempt to reconnect here */
    if (expunge)
      imap_mbox_close(mbox->handle);
    else
      imap_mbox_unselect(mbox->handle);
    free_messages_info(mbox);
    libbalsa_mailbox_imap_release_handle(mbox);
    mbox->sort_field = -1;    /* Invalidate. */
}

static FILE*
get_cache_stream(LibBalsaMailbox *mailbox, LibBalsaMessage *msg)
{
    unsigned uid = IMAP_MESSAGE_UID(msg);
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    FILE *stream;
    gchar **pair, *path;

    g_assert(mimap->handle);
    pair = get_cache_name_pair(mimap, "body", uid);
    path = g_strconcat(pair[0], "/", pair[1], NULL);
    stream = fopen(path, "rb");
    if(!stream) {
        FILE *cache;
      ImapResponse rc;

        libbalsa_assure_balsa_dir();
        mkdir(pair[0], S_IRUSR|S_IWUSR|S_IXUSR); /* ignore errors */
        if(msg->length>(signed)SizeMsgThreshold)
            libbalsa_information(LIBBALSA_INFORMATION_MESSAGE, 
                                 _("Downloading %ld kB"),
                                 msg->length/1024);
        cache = fopen(path, "wb");
        II(rc,mimap->handle,
           imap_mbox_handle_fetch_rfc822_uid(mimap->handle, uid, cache));
        fclose(cache);

      stream = fopen(path,"rb");
    }
    g_free(path); 
    g_strfreev(pair);
    return stream;
}

/* libbalsa_mailbox_imap_get_message_stream: 
   Fetch data from cache first, if available.
   When calling imap_fetch_message(), we make use of fact that
   imap_fetch_message doesn't set msg->path field.
*/
static GMimeStream *
libbalsa_mailbox_imap_get_message_stream(LibBalsaMailbox * mailbox,
                               LibBalsaMessage * message)
{
    FILE *stream;

    g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), NULL);
    g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), NULL);

    libbalsa_lock_mailbox(mailbox);
    /* this may get called when the mailbox is being opened ie,
       open_ref==0 */
    if(!LIBBALSA_MAILBOX_IMAP(mailbox)->handle) {
        libbalsa_unlock_mailbox(mailbox);
        return NULL;
    }

    stream = get_cache_stream(mailbox, message);
    libbalsa_unlock_mailbox(mailbox);

    return g_mime_stream_file_new(stream);
}

/* libbalsa_mailbox_imap_check:
   checks imap mailbox for new messages.
   Called with the mailbox locked.
*/
struct mark_info {
    const gchar *path;
    gboolean marked;
};

static void
lbm_imap_list_cb(ImapMboxHandle * handle, int delim, ImapMboxFlags * flags,
                 char *folder, gpointer data)
{
    struct mark_info *info = data;

    if (strcmp(folder, info->path) == 0
        && IMAP_MBOX_HAS_FLAG(*flags, IMLIST_MARKED))
        info->marked = TRUE;
}

static gboolean
lbm_imap_check(LibBalsaMailbox * mailbox)
{
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    ImapMboxHandle *handle;
    gulong id;
    struct mark_info info;

    handle = libbalsa_mailbox_imap_get_handle(mimap, NULL);
    if (!handle)
      return FALSE;

    info.path = mimap->path;
    info.marked = FALSE;

    id = g_signal_connect(G_OBJECT(handle), "list-response",
                          G_CALLBACK(lbm_imap_list_cb), &info);

    if (imap_mbox_list(handle, mimap->path) != IMR_OK)
        info.marked = FALSE;

    g_signal_handler_disconnect(G_OBJECT(handle), id);
    libbalsa_mailbox_imap_release_handle(mimap);

    return info.marked;
}

static void
libbalsa_mailbox_imap_check(LibBalsaMailbox * mailbox)
{
    g_assert(LIBBALSA_IS_MAILBOX_IMAP(mailbox));

    if (!MAILBOX_OPEN(mailbox)) {
        libbalsa_mailbox_set_unread_messages_flag(mailbox,
                                                  lbm_imap_check(mailbox));
      return;

    }

    if (LIBBALSA_MAILBOX_IMAP(mailbox)->handle)
      libbalsa_mailbox_imap_noop(LIBBALSA_MAILBOX_IMAP(mailbox));
    else
      g_warning("mailbox has open_ref>0 but no handle!\n");
}

/* Search iters */

static ImapSearchKey *lbmi_build_imap_query(const LibBalsaCondition * cond,
                                  ImapSearchKey * last);
static gboolean
libbalsa_mailbox_imap_message_match(LibBalsaMailbox* mailbox, guint msgno,
                            LibBalsaMailboxSearchIter * search_iter)
{
    LibBalsaMailboxImap *mimap;
    struct message_info *msg_info;
    GHashTable *matchings;

    mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    msg_info = message_info_from_msgno(mimap, msgno);

    if (msg_info->message)
        g_object_ref(msg_info->message);
    else if (imap_mbox_handle_get_msg(mimap->handle, msgno)
             && imap_mbox_handle_get_msg(mimap->handle, msgno)->envelope)
        /* The backend has already downloaded the data, we can just
        * convert it to LibBalsaMessage. */
        libbalsa_mailbox_imap_get_message(mailbox, msgno);
    if (msg_info->message) {
        if (libbalsa_condition_can_match(search_iter->condition,
                                         msg_info->message)) {
            gboolean retval =
                libbalsa_condition_matches(search_iter->condition,
                                           msg_info->message);
            g_object_unref(msg_info->message);
            return retval;
        }
        g_object_unref(msg_info->message);
    }

    if (search_iter->stamp != mimap->search_stamp && search_iter->mailbox
      && LIBBALSA_MAILBOX_GET_CLASS(search_iter->mailbox)->
      search_iter_free)
      LIBBALSA_MAILBOX_GET_CLASS(search_iter->mailbox)->
          search_iter_free(search_iter);

    matchings = search_iter->user_data;
    if (!matchings) {
      ImapSearchKey* query;
      ImapResult rc;

      matchings = g_hash_table_new(NULL, NULL);
      query = lbmi_build_imap_query(search_iter->condition, NULL);
      II(rc,mimap->handle,
           imap_mbox_filter_msgnos(mimap->handle, query, matchings));
      imap_search_key_free(query);
      if (rc != IMR_OK) {
          g_hash_table_destroy(matchings);
          return FALSE;
      }
      search_iter->user_data = matchings;
      search_iter->mailbox = mailbox;
      search_iter->stamp = mimap->search_stamp;
    }

    return g_hash_table_lookup(matchings, GUINT_TO_POINTER(msgno)) != NULL;
}

static void
libbalsa_mailbox_imap_search_iter_free(LibBalsaMailboxSearchIter * iter)
{
    GHashTable *matchings = iter->user_data;

    if (matchings) {
      g_hash_table_destroy(matchings);
      iter->user_data = NULL;
    }
    /* iter->condition and iter are freed in the LibBalsaMailbox method. */
}

/* add_or_query() adds a new term to an set of eqs. that can be or-ed.
   There are at least two ways to do it:
   a). transform a and b to NOT (NOT a NOT b)
   b). transform a and b to OR a b
   We keep it simple.
*/
static ImapSearchKey*
add_or_query(ImapSearchKey *or_query, gboolean neg, ImapSearchKey *new_term)
{
    if(!or_query) return new_term;
    if(neg) {
        imap_search_key_set_next(new_term, or_query);
        return new_term;
    } else return imap_search_key_new_or(FALSE, new_term, or_query);
}

static ImapSearchKey*
lbmi_build_imap_query(const LibBalsaCondition* cond,
                      ImapSearchKey *next)
{
    gboolean neg;
    ImapSearchKey *query = NULL;
    int cnt=0;

    if(!cond) return NULL;
    neg = cond->negate;
    switch (cond->type) {
    case CONDITION_STRING:
        if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_TO))
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_string
                 (neg, IMSE_S_TO, cond->match.string.string, NULL));
        if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_FROM))
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_string
                 (neg, IMSE_S_FROM, cond->match.string.string, NULL));
        if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_SUBJECT))
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_string
                (neg, IMSE_S_SUBJECT,cond->match.string.string, NULL));
        if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_CC))
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_string
                 (neg, IMSE_S_CC, cond->match.string.string, NULL));
        if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_BODY))
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_string
                 (neg, IMSE_S_BODY, cond->match.string.string, NULL));
        if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_US_HEAD))
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_string
                 (neg, IMSE_S_HEADER, cond->match.string.string,
                  cond->match.string.user_header));
        if(neg && cnt>1)
            query = imap_search_key_new_not(FALSE, query);
        imap_search_key_set_next(query, next);
        break;
    case CONDITION_DATE: {
        ImapSearchKey *slo = NULL, *shi = NULL;
        if (cond->match.date.date_low)
            query  = slo = imap_search_key_new_date
                (IMSE_D_SINCE, FALSE, cond->match.date.date_low);
        if (cond->match.date.date_high) {
            shi = imap_search_key_new_date
                (IMSE_D_BEFORE, FALSE, cond->match.date.date_high);
            imap_search_key_set_next(query, shi);
        }
        /* this might be redundant if only one limit was specified. */
        if(query)
            query = imap_search_key_new_not(neg, query);
        break;
    }
    case CONDITION_FLAG:
        if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_REPLIED)
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_flag
                 (neg, IMSGF_ANSWERED));
        if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_NEW)
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_flag
                 (!neg, IMSGF_SEEN));
        if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_DELETED)
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_flag
                 (neg, IMSGF_DELETED));
        if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_FLAGGED)
            cnt++,query = add_or_query
                (query, neg, imap_search_key_new_flag
                 (neg, IMSGF_FLAGGED));
      if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_RECENT)
          cnt++, query = add_or_query
            (query, neg, imap_search_key_new_flag
             (neg, IMSGF_RECENT));
        if(neg && cnt>1)
            query = imap_search_key_new_not(FALSE, query);
        imap_search_key_set_next(query, next);
        break;
    case CONDITION_AND:
        if(neg) {
            query = imap_search_key_new_not
                (TRUE, lbmi_build_imap_query
                 (cond->match.andor.left,
                  lbmi_build_imap_query
                  (cond->match.andor.right, NULL)));
            imap_search_key_set_next(query, next);
        } else
            query = lbmi_build_imap_query
                (cond->match.andor.left,
                 lbmi_build_imap_query
                 (cond->match.andor.right, next));
        break;
    case CONDITION_OR: 
        query = 
            imap_search_key_new_or
            (neg,
             lbmi_build_imap_query(cond->match.andor.left, NULL),
             lbmi_build_imap_query(cond->match.andor.right, NULL));
            imap_search_key_set_next(query, next);
        break;
    case CONDITION_NONE:
    case CONDITION_REGEX:
    default:
        break;
    }
    return query;
}

typedef struct {
    GHashTable * uids;
    GHashTable * res;
} ImapSearchData;

static void
imap_matched(unsigned uid, ImapSearchData* data)
{
    LibBalsaMessage* m = 
        g_hash_table_lookup(data->uids,GUINT_TO_POINTER(uid)); 
    if(m) 
        g_hash_table_insert(data->res, m, m);
    else
        printf("Could not find UID: %u in message list\n", uid);
}

/* Gets the messages matching the conditions via the IMAP search command
   error is put to TRUE if an error occured
*/

GHashTable * libbalsa_mailbox_imap_get_matchings(LibBalsaMailboxImap* mbox,
                                     LibBalsaCondition *ct,
                                     gboolean only_recent,
                                     gboolean * err)
{
    ImapSearchKey* query;
    ImapResult rc = IMR_NO;
    ImapSearchData * cbdata;

    *err = FALSE;
    
    cbdata = g_new( ImapSearchData, 1 );
    cbdata->uids = g_hash_table_new(NULL, NULL); 
    cbdata->res  = g_hash_table_new(NULL, NULL);
    query = lbmi_build_imap_query(ct /* FIXME: ONLY RECENT! */, NULL);
    if (query) {
#ifdef UID_SEARCH_IMPLEMENTED
      for(msgs= LIBBALSA_MAILBOX(mbox)->message_list; msgs;
          msgs = msgs->next){
          LibBalsaMessage *m = LIBBALSA_MESSAGE(msgs->data);
          ImapUID uid = IMAP_MESSAGE_UID(m);
          g_hash_table_insert(cbdata->uids, GUINT_TO_POINTER(uid), m);
      }
#else 
        g_warning("Search results ignored. Fixme!");
#endif
        II(rc,mbox->handle,
           imap_mbox_uid_search(mbox->handle, query,
                                (void(*)(unsigned,void*))imap_matched,
                                cbdata));
        imap_search_key_free(query);
    }
    g_hash_table_destroy(cbdata->uids);
    /* Clean up on error */
    if (rc != IMR_OK) {
      g_hash_table_destroy(cbdata->res);
      cbdata->res = NULL;
      *err = TRUE;
      libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
                       _("IMAP SEARCH command failed for mailbox %s\n"
                         "falling back to default searching method"),
                       LIBBALSA_MAILBOX(mbox)->url);
    };
    return cbdata->res;
}

/* Returns false if the conditions contain regex matches
   User must be informed that regex match on IMAP will
   be done by default filters functions hence leading to
   SLOW match
*/
gboolean libbalsa_mailbox_imap_can_match(LibBalsaMailbox  *mailbox,
                               LibBalsaCondition *condition)
{
#if 0
    GSList *cnds;
    for (cnds =  conditions->cond_list; cnds; cnds = g_slist_next(cnds)) {
      LibBalsaCondition * cnd = cnds->data;
      
      if (cnd->type==CONDITION_REGEX) return FALSE;
    }
#endif
    return TRUE;
}

static void
libbalsa_mailbox_imap_save_config(LibBalsaMailbox * mailbox,
                          const gchar * prefix)
{
    LibBalsaMailboxImap *mimap;

    g_return_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox));

    mimap = LIBBALSA_MAILBOX_IMAP(mailbox);

    libbalsa_conf_set_string("Path", mimap->path);

    libbalsa_server_save_config(LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox));

    if (LIBBALSA_MAILBOX_CLASS(parent_class)->save_config)
      LIBBALSA_MAILBOX_CLASS(parent_class)->save_config(mailbox, prefix);
}

static void
libbalsa_mailbox_imap_load_config(LibBalsaMailbox * mailbox,
                          const gchar * prefix)
{
    LibBalsaMailboxImap *mimap;
    LibBalsaMailboxRemote *remote;

    g_return_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox));

    mimap = LIBBALSA_MAILBOX_IMAP(mailbox);

    g_free(mimap->path);
    mimap->path = libbalsa_conf_get_string("Path");
    if (!mimap->path) {
      mimap->path = g_strdup("INBOX");
      libbalsa_information(LIBBALSA_INFORMATION_WARNING,
                             _("No path found for mailbox \"%s\", "
                         "using \"%s\""),
                       mailbox->name, mimap->path);
    }

    remote = LIBBALSA_MAILBOX_REMOTE(mailbox);
    remote->server = LIBBALSA_SERVER(libbalsa_imap_server_new_from_config());

    g_signal_connect(G_OBJECT(remote->server), "set-host",
                 G_CALLBACK(server_host_settings_changed_cb),
                 (gpointer) mailbox);

    if (LIBBALSA_MAILBOX_CLASS(parent_class)->load_config)
      LIBBALSA_MAILBOX_CLASS(parent_class)->load_config(mailbox, prefix);

    libbalsa_mailbox_imap_update_url(mimap);
}

/* libbalsa_mailbox_imap_subscribe:
   change subscribed status for a mailbox.
   Can be called for a closed mailbox.
 */
gboolean
libbalsa_mailbox_imap_subscribe(LibBalsaMailboxImap * mailbox, 
                             gboolean subscribe)
{
    ImapResult rc;
    ImapMboxHandle* handle;

    g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), FALSE);

    handle = libbalsa_mailbox_imap_get_handle(mailbox, NULL);
    if (!handle)
      return FALSE;

    II(rc, handle, imap_mbox_subscribe(handle, mailbox->path, subscribe));

    libbalsa_mailbox_imap_release_handle(mailbox);
    return rc == IMR_OK;
}

/* libbalsa_mailbox_imap_noop:
 * pings the connection with NOOP for an open IMAP mailbox.
 * this keeps the connections alive.
 * new messages are loaded in exists-notify signal handler.
 *
 * FIXME: shift responsibility for keeping the connection alive to
 * LibBalsaImapServer.
 */

void
libbalsa_mailbox_imap_noop(LibBalsaMailboxImap* mimap)
{
    g_return_if_fail(mimap != NULL);

    if (mimap->handle) /* we do not attempt to reconnect here */
      if (imap_mbox_handle_noop(mimap->handle) != IMR_OK)
          /* FIXME: report error... */
          ;
}

/* imap_close_all_connections:
   close all connections to leave the place cleanly.
*/
void
libbalsa_imap_close_all_connections(void)
{
    libbalsa_imap_server_close_all_connections();
}

/* libbalsa_imap_rename_subfolder:
   dir+parent determine current name. 
   folder - new name. Can be called for a closed mailbox.
 */
gboolean
libbalsa_imap_rename_subfolder(LibBalsaMailboxImap* imap,
                               const gchar *new_parent, const gchar *folder, 
                               gboolean subscribe,
                               GError **err)
{
    ImapResult rc;
    ImapMboxHandle* handle;
    gchar *new_path;

    handle = libbalsa_mailbox_imap_get_handle(imap, NULL);
    if (!handle) {
        g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                    LIBBALSA_MAILBOX_RENAME_ERROR,
                    _("Cannot get IMAP handle"));
      return FALSE;
    }

    II(rc,handle,
       imap_mbox_subscribe(handle, imap->path, FALSE));
    /* FIXME: should use imap server folder separator */ 
    new_path = g_strjoin("/", new_parent, folder, NULL);
    rc = imap_mbox_rename(handle, imap->path, new_path);
    if (subscribe && rc == IMR_OK)
      rc = imap_mbox_subscribe(handle, new_path, TRUE);
    g_free(new_path);
    if(rc != IMR_OK) {
        gchar *msg = imap_mbox_handle_get_last_msg(handle);
        g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                    LIBBALSA_MAILBOX_RENAME_ERROR,
                    "%s", msg);
        g_free(msg);
    }
    libbalsa_mailbox_imap_release_handle(imap);

    return rc == IMR_OK;
}

gboolean
libbalsa_imap_new_subfolder(const gchar *parent, const gchar *folder,
                      gboolean subscribe, LibBalsaServer *server,
                            GError **err)
{
    ImapResult rc;
    ImapMboxHandle* handle;
    gchar *new_path;

    if (!LIBBALSA_IS_IMAP_SERVER(server))
      return FALSE;
    handle = libbalsa_imap_server_get_handle(LIBBALSA_IMAP_SERVER(server),
                                   NULL);
    if (!handle) {
        g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                    LIBBALSA_MAILBOX_RENAME_ERROR,
                    _("Cannot get IMAP handle"));
      return FALSE;
    }
    if(parent && *parent) {
        char delim[2];
        delim[0] = imap_mbox_handle_get_delim(handle, parent);
        delim[1] = '\0';
        if(parent[strlen(parent)-1] == delim[0])
            new_path = g_strconcat(parent, folder, NULL);
        else
            new_path = g_strjoin(delim, parent, folder, NULL);
    } else
        new_path = g_strdup(folder);
    II(rc,handle,
       imap_mbox_create(handle, new_path));
    if (subscribe && rc == IMR_OK)
      rc = imap_mbox_subscribe(handle, new_path, TRUE);
    g_free(new_path);
    if(rc != IMR_OK) {
        gchar *msg = imap_mbox_handle_get_last_msg(handle);
        g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                    LIBBALSA_MAILBOX_RENAME_ERROR,
                    "%s", msg);
        g_free(msg);
    }

    libbalsa_imap_server_release_handle(LIBBALSA_IMAP_SERVER(server), handle);
    return rc == IMR_OK;
}

gboolean
libbalsa_imap_delete_folder(LibBalsaMailboxImap *mailbox, GError **err)
{
    ImapResponse rc;
    ImapMboxHandle* handle;

    handle = libbalsa_mailbox_imap_get_handle(mailbox, NULL);
    if (!handle)
      return FALSE;

    /* Some IMAP servers (UW2000) do not like removing subscribed mailboxes:
     * they do not remove the mailbox from the subscription list since 
     * the subscription list should be treated as a list of bookmarks,
     * not a list of physically existing mailboxes. */
    imap_mbox_subscribe(handle, mailbox->path, FALSE);
    II(rc,handle,
       imap_mbox_delete(handle, mailbox->path));
    if(rc != IMR_OK) {
        gchar *msg = imap_mbox_handle_get_last_msg(handle);
        g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                    LIBBALSA_MAILBOX_DELETE_ERROR,
                    "%s", msg);
        g_free(msg);
    }

    libbalsa_mailbox_imap_release_handle(mailbox);
    return rc == IMR_OK;
}

gchar *
libbalsa_imap_url(LibBalsaServer * server, const gchar * path)
{
    gchar *enc = libbalsa_urlencode(server->user);
    gchar *url = g_strdup_printf("imap%s://%s@%s/%s",
#ifdef USE_SSL_TO_SET_IMAPS_IN_URL
                                 server->use_ssl ? "s" : "",
#else
                                 "",
#endif
                                 enc, server->host,
                                 path ? path : "");
    g_free(enc);

    return url;
}

gboolean
libbalsa_mailbox_imap_sync(LibBalsaMailbox * mailbox, gboolean expunge)
{
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    gboolean res = TRUE;

    g_return_val_if_fail(mimap->opened, FALSE);
    /* we are always in sync, we need only to do expunge now and then */
    if(expunge) {
        ImapResponse rc;
        II(rc,mimap->handle,
           imap_mbox_expunge(mimap->handle));
        res = (rc == IMR_OK);
    }
    return res;
}

static InternetAddress *
internet_address_new_from_imap_address(ImapAddress *addr)
{
    InternetAddress *address;

    if (!addr || (addr->name==NULL && addr->addr_spec==NULL))
       return NULL;

    address = internet_address_new();

    /* it will be owned by the caller */

    if (addr->name) {
      gchar *tmp = g_mime_utils_header_decode_text(addr->name);
      internet_address_set_name(address, tmp);
      g_free(tmp);
    }
    if (addr->addr_spec) {
      gchar *tmp = g_mime_utils_header_decode_text(addr->addr_spec);
      internet_address_set_addr(address, tmp);
      g_free(tmp);
    } else { /* FIXME: is that a right thing? */
        internet_address_unref(address);
        address = NULL;
    }
    return address;
}

static InternetAddressList *
internet_address_new_list_from_imap_address_list(ImapAddress *list)
{
    InternetAddress *addr;
    InternetAddressList *res = NULL;

    for (; list; list = list->next) {
       addr = internet_address_new_from_imap_address(list);
       if (addr) {
           res = internet_address_list_append(res, addr);
         internet_address_unref(addr);
       }
    }
    return res;
}

static void
lb_set_headers(LibBalsaMessageHeaders *headers, ImapEnvelope *  envelope,
               gboolean is_embedded)
{
    headers->date = envelope->date;
    headers->from =
      internet_address_new_list_from_imap_address_list(envelope->from);
    headers->reply_to =
        internet_address_new_list_from_imap_address_list(envelope->replyto);
    headers->to_list =
      internet_address_new_list_from_imap_address_list(envelope->to);
    headers->cc_list =
      internet_address_new_list_from_imap_address_list(envelope->cc);
    headers->bcc_list =
      internet_address_new_list_from_imap_address_list(envelope->bcc);

    if(is_embedded) {
        headers->subject = 
            g_mime_utils_header_decode_text(envelope->subject);
        libbalsa_utf8_sanitize(&headers->subject, TRUE, NULL);
    }
}

static gboolean
libbalsa_mailbox_imap_load_envelope(LibBalsaMailboxImap *mimap,
                            LibBalsaMessage *message)
{
    ImapEnvelope *envelope;
    ImapMessage* imsg;
    gchar *hdr;
    
    g_return_val_if_fail(mimap->opened, FALSE);
    imsg = mi_get_imsg(mimap, message->msgno);

    if(!imsg || !imsg->envelope) /* Connection severed and and restore
                                 *  failed - deal with it! */
        return FALSE;

    lbimap_update_flags(message, imsg);

    lb_set_headers(message->headers, imsg->envelope, FALSE);
    if ((hdr = imsg->fetched_header_fields) && *hdr && *hdr != '\r')
      libbalsa_message_set_headers_from_string(message, hdr);
    envelope        = imsg->envelope;
    message->length = imsg->rfc822size;
    message->subj   = g_mime_utils_header_decode_text(envelope->subject);
    libbalsa_utf8_sanitize(&message->subj, TRUE, NULL);
    message->sender =
      internet_address_new_list_from_imap_address_list(envelope->sender);
    libbalsa_message_set_in_reply_to_from_string(message,
                                     envelope->in_reply_to);
    if (envelope->message_id) {
      message->message_id =
          g_mime_utils_decode_message_id(envelope->message_id);
    }

    return TRUE;
}

/* converts the backend data to LibBalsaMessage object */
static LibBalsaMessage*
libbalsa_mailbox_imap_get_message(LibBalsaMailbox * mailbox, guint msgno)
{
    struct message_info *msg_info;
    LibBalsaMailboxImap *mimap = (LibBalsaMailboxImap *) mailbox;

    msg_info = message_info_from_msgno(mimap, msgno);

    if (!msg_info->message) {
        LibBalsaMessage *msg = libbalsa_message_new();
        msg->msgno   = msgno;
        msg->mailbox = mailbox;
        if (libbalsa_mailbox_imap_load_envelope(mimap, msg)) {
          gchar *id;
            msg_info->message  = msg;
          if (libbalsa_message_is_partial(msg, &id)) {
            libbalsa_mailbox_try_reassemble(mailbox, id);
            g_free(id);
          }
      } else 
            g_object_unref(G_OBJECT(msg));
    }
    if (msg_info->message)
      g_object_ref(msg_info->message); /* we want to keep one copy */
    return msg_info->message;
}

static void
libbalsa_mailbox_imap_prepare_threading(LibBalsaMailbox *mailbox, 
                                        guint lo, guint hi)
{
    g_warning("%s not implemented yet.\n", __func__);
}

static void
lbm_imap_construct_body(LibBalsaMessageBody *lbbody, ImapBody *imap_body)
{
    const char *str;
    g_return_if_fail(lbbody);
    g_return_if_fail(imap_body);

    switch(imap_body->media_basic) {
    case IMBMEDIA_MULTIPART:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART; break;
    case IMBMEDIA_APPLICATION:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_APPLICATION; break;
    case IMBMEDIA_AUDIO:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_AUDIO; break;
    case IMBMEDIA_IMAGE:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_IMAGE; break;
    case IMBMEDIA_MESSAGE_RFC822:
    case IMBMEDIA_MESSAGE_OTHER:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE; break;
    case IMBMEDIA_TEXT:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_TEXT; break;
    default:
    case IMBMEDIA_OTHER:
        lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_OTHER; break;
    }

    switch (imap_body->content_dsp) {
    case IMBDISP_INLINE:
      lbbody->content_dsp = GMIME_DISPOSITION_INLINE; break;
    case IMBDISP_ATTACHMENT:
      lbbody->content_dsp = GMIME_DISPOSITION_ATTACHMENT; break;
    case IMBDISP_OTHER:
      lbbody->content_dsp = imap_body->content_dsp_other; break;
    }
    lbbody->content_id = imap_body->content_id;
    lbbody->content_type = imap_body_get_content_type(imap_body);
    /* get the name in the same way as g_mime_part_get_filename() does */
    str = imap_body_get_dsp_param(imap_body, "filename");
    if(!str) str = imap_body_get_param(imap_body, "name");
    if(str) {
        lbbody->filename  = g_mime_utils_header_decode_text(str);
        libbalsa_utf8_sanitize(&lbbody->filename, TRUE, NULL);
    }
    lbbody->charset   = g_strdup(imap_body_get_param(imap_body, "charset"));
    if(imap_body->envelope) {
        lbbody->embhdrs = g_new0(LibBalsaMessageHeaders, 1);
        lb_set_headers(lbbody->embhdrs, imap_body->envelope, TRUE);
    }
    if(imap_body->next) {
        LibBalsaMessageBody *body = libbalsa_message_body_new(lbbody->message);
        lbm_imap_construct_body(body, imap_body->next);
        lbbody->next = body;
    }
    if(imap_body->child) {
        LibBalsaMessageBody *body = libbalsa_message_body_new(lbbody->message);
        lbm_imap_construct_body(body, imap_body->child);
        lbbody->parts = body;
    }
}

static gboolean
get_struct_from_cache(LibBalsaMailbox *mailbox, LibBalsaMessage *message,
                      LibBalsaFetchFlag flags)
{
    gchar **pair, *filename;
    int fd;
    GMimeStream *stream = NULL;
    GMimeParser *mime_parser;

    g_return_val_if_fail(message->mime_msg == NULL, FALSE);

    pair =  get_cache_name_pair(LIBBALSA_MAILBOX_IMAP(mailbox), "body", 
                                IMAP_MESSAGE_UID(message));

    filename = g_build_filename(pair[0], pair[1], NULL);
    g_strfreev(pair);
    fd = open(filename, O_RDONLY);
    g_free(filename);
    if(fd == -1) return FALSE;

    stream = g_mime_stream_fs_new(fd);

    mime_parser = g_mime_parser_new_with_stream(stream);
    g_mime_parser_set_scan_from(mime_parser, FALSE);
    message->mime_msg = g_mime_parser_construct_message(mime_parser);
    g_object_unref(mime_parser);
    g_object_unref(stream);
    
    /* follow libbalsa_mailbox_local_fetch_structure here;
     * perhaps create common helper */
    if(flags & LB_FETCH_STRUCTURE) {
        LibBalsaMessageBody *body = libbalsa_message_body_new(message);
        libbalsa_message_body_set_mime_body(body,
                                            message->mime_msg->mime_part);
        libbalsa_message_append_part(message, body);
        libbalsa_message_headers_from_gmime(message->headers,
                                            message->mime_msg);
    }
    if(flags & LB_FETCH_RFC822_HEADERS) {
        message->headers->user_hdrs = 
            libbalsa_message_user_hdrs_from_gmime(message->mime_msg);
        message->has_all_headers = 1;
    }
    return TRUE;
}

static gboolean
libbalsa_mailbox_imap_fetch_structure(LibBalsaMailbox *mailbox,
                                      LibBalsaMessage *message,
                                      LibBalsaFetchFlag flags)
{
    ImapResponse rc;
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    LibBalsaServer *server;
    ImapFetchType ift = 0;
    g_return_val_if_fail(mimap->opened, FALSE);

    /* work around some server bugs... */
    server = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox);
    if(!imap_mbox_handle_can_do(mimap->handle, IMCAP_FETCHBODY) ||
       libbalsa_imap_server_has_bug(LIBBALSA_IMAP_SERVER(server),
                                    ISBUG_FETCH)){
        /* we could optimize this part a little bit: we do not need to
         * keep reopening the stream. */
        GMimeStream *stream = 
            libbalsa_mailbox_imap_get_message_stream(mailbox, message);
        if(stream)
            g_object_unref(stream);
    }

    if(get_struct_from_cache(mailbox, message, flags))
        return TRUE;

    if(flags & LB_FETCH_RFC822_HEADERS) ift |= IMFETCH_RFC822HEADERS_SELECTED;
    if(flags & LB_FETCH_STRUCTURE)      ift |= IMFETCH_BODYSTRUCT;

    II(rc, mimap->handle,
       imap_mbox_handle_fetch_range(mimap->handle, message->msgno,
                                    message->msgno, ift));
    if(rc == IMR_OK) { /* translate ImapData to LibBalsaMessage */
        gchar *hdr;
        ImapMessage *im = imap_mbox_handle_get_msg(mimap->handle,
                                                   message->msgno);
      /* in case of msg number discrepancies: */
        g_return_val_if_fail(im != NULL, FALSE);
        if(flags & LB_FETCH_STRUCTURE) {
            LibBalsaMessageBody *body = libbalsa_message_body_new(message);
            lbm_imap_construct_body(body, im->body);
            libbalsa_message_append_part(message, body);
        }
        if( (flags & LB_FETCH_RFC822_HEADERS) &&
            (hdr = im->fetched_header_fields) && *hdr && *hdr != '\r') {
            libbalsa_message_set_headers_from_string(message, hdr);
            message->has_all_headers = 1;
        }
        return TRUE;
    }

    return FALSE;
}

static void
libbalsa_mailbox_imap_fetch_headers(LibBalsaMailbox *mailbox,
                                    LibBalsaMessage *message)
{
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    ImapResponse rc;

    II(rc,mimap->handle,
       imap_mbox_handle_fetch_range(mimap->handle,
                                    message->msgno,
                                    message->msgno,
                                    IMFETCH_RFC822HEADERS_SELECTED));
    if(rc == IMR_OK) { /* translate ImapData to LibBalsaMessage */
        const gchar *hdr;
        ImapMessage *im = imap_mbox_handle_get_msg(mimap->handle,
                                                   message->msgno);
        if ((hdr = im->fetched_header_fields) && *hdr && *hdr != '\r')
            libbalsa_message_set_headers_from_string(message, hdr);
    }
}

static gboolean
is_child_of(LibBalsaMessageBody *body, LibBalsaMessageBody *child,
            GString *s, gboolean modify)
{
    int i = 1;
    gboolean do_mod;
    for(i=1; body; body = body->next) {
        if(body==child) {
            g_string_printf(s, "%u", i);
            return TRUE;
        }
        do_mod = !(body->body_type == LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE &&
                   body->parts &&
                   body->parts->body_type ==
                   LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART);
        if(is_child_of(body->parts, child, s, do_mod)){
            char buf[12];
            if(modify) {
                snprintf(buf, sizeof(buf), "%u.", i);
                g_string_prepend(s, buf);
            }
            return TRUE;
        }
        i++;
    }
    return FALSE;
}

#if 0
static void
print_structure(LibBalsaMessageBody *part, LibBalsaMessageBody* m, int ind)
{
    static const char *t[] = {
        "LIBBALSA_MESSAGE_BODY_TYPE_OTHER",
        "LIBBALSA_MESSAGE_BODY_TYPE_AUDIO",
        "LIBBALSA_MESSAGE_BODY_TYPE_APPLICATION",
        "LIBBALSA_MESSAGE_BODY_TYPE_IMAGE",
        "LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE",
        "LIBBALSA_MESSAGE_BODY_TYPE_MODEL",
        "LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART",
        "LIBBALSA_MESSAGE_BODY_TYPE_TEXT",
        "LIBBALSA_MESSAGE_BODY_TYPE_VIDEO" };

    int j,i=1;
    while(part) {
        for(j=0; j<ind; j++) putchar(' ');
        printf("%d: %s%s\n", i++, t[part->body_type],
               part == m ? " <--" : "");
        if(part->parts)
            print_structure(part->parts, m, ind+2);
        part = part->next;
    }
}
#endif
static gchar*
get_section_for(LibBalsaMessage *msg, LibBalsaMessageBody *part)
{
    GString *section = g_string_new("");
    LibBalsaMessageBody *parent;

    parent = msg->body_list;
    if (libbalsa_message_body_is_multipart(parent))
      parent = parent->parts;

    if(!is_child_of(parent, part, section, TRUE)) {
        g_warning("Internal error, part %p not found in msg %p.\n",
                  part, msg);
        g_string_free(section, TRUE);
        return g_strdup("1");
    }
#if 0
    print_structure(msg->body_list, part, 0);
    printf("section: %s\n", section->str);
#endif
    return g_string_free(section, FALSE);
}
struct part_data { char *block; unsigned pos; ImapBody *body; };
static void
append_str(const char *buf, int buflen, void *arg)
{
    struct part_data *dt = (struct part_data*)arg;

    if(dt->pos + buflen > dt->body->octets) {
        /* 
        fprintf(stderr, "IMAP server sends too much data but we just "
                "reallocate the block.\n"); */
      dt->body->octets = dt->pos + buflen;
      dt->block = g_realloc(dt->block, dt->body->octets);
    }
    memcpy(dt->block + dt->pos, buf, buflen);
    dt->pos += buflen;
}

static const char*
encoding_names(ImapBodyEncoding enc)
{
    switch(enc) {
    case IMBENC_7BIT:   return "7bit";
    default:
    case IMBENC_8BIT:   return "8bit";
    case IMBENC_BINARY: return "binary";
    case IMBENC_BASE64: return "base64";
    case IMBENC_QUOTED: return "quoted-printable";
    }
}
static LibBalsaMessageBody*
get_parent(LibBalsaMessageBody *root, LibBalsaMessageBody *part,
           LibBalsaMessageBody *parent)
{
    while(root) {
        LibBalsaMessageBody *res;
        if(root == part)
            return parent;
        if(root->parts &&
           (res = get_parent(root->parts, part, root)) != NULL)
            return res;
        root = root->next;
    }
    return NULL;
}
static gboolean
lbm_imap_get_msg_part_from_cache(LibBalsaMessage * msg,
                                 LibBalsaMessageBody * part)
{
    GMimeStream *partstream = NULL;

    gchar **pair,  *part_name;
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(msg->mailbox);
    FILE *fp;
    gchar *section;

   /* look for a part cache */
    section = get_section_for(msg, part);
    pair = get_cache_name_pair(mimap, "part", IMAP_MESSAGE_UID(msg));
    part_name   = g_strconcat(pair[0], "/", pair[1], "-", section, NULL);
    fp = fopen(part_name,"rb+");
    
    if(!fp) { /* no cache element */
        struct part_data dt;
        LibBalsaMailboxImap* mimap;
        ImapFetchBodyOptions ifbo;
        ImapMessage *im;
        ImapResponse rc;

        libbalsa_lock_mailbox(msg->mailbox);
        mimap = LIBBALSA_MAILBOX_IMAP(msg->mailbox);
        im = mi_get_imsg(mimap, msg->msgno);
        
        dt.body  = imap_message_get_body_from_section(im, section);
        dt.block = g_malloc(dt.body->octets+1);
        dt.pos   = 0;
        if(dt.body->octets>SizeMsgThreshold)
            libbalsa_information(LIBBALSA_INFORMATION_MESSAGE, 
                                 _("Downloading %ld kB"),
                                 dt.body->octets/1024);
      /* Imap_mbox_handle_fetch_body fetches the MIME headers of the
         * section, followed by the text. We write this unfiltered to
         * the cache. The probably only exception is the main body
         * which has no headers. In this case, we have to fake them.
         * We could and probably should dump there first the headers 
         * that we have already fetched... */
        if(strcmp(section, "1") == 0)
            ifbo = IMFB_NONE;
        else {
            LibBalsaMessageBody *parent =
                get_parent(msg->body_list, part, NULL);
            if(parent->body_type == LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE)
                ifbo = IMFB_HEADER;
            else
                ifbo = IMFB_MIME;
        }
        II(rc,mimap->handle,
           imap_mbox_handle_fetch_body(mimap->handle, msg->msgno,
                                       section, ifbo, append_str, &dt));
        if(rc != IMR_OK)
            g_warning("FIXME: consider adding FETCH error handling!\n");
        libbalsa_unlock_mailbox(msg->mailbox);
        
        libbalsa_assure_balsa_dir();
        mkdir(pair[0], S_IRUSR|S_IWUSR|S_IXUSR); /* ignore errors */
        fp = fopen(part_name, "wb+");
        if(!fp) {
            g_free(section); 
            g_strfreev(pair);
            g_free(part_name);
            return FALSE; /* something better ? */
        }
        if(ifbo == IMFB_NONE) {
            fprintf(fp,"MIME-version: 1.0\r\ncontent-type: %s\r\n"
                    "Content-Transfer-Encoding: %s\r\n\r\n",
                    part->content_type ? part->content_type : "text/plain",
                    encoding_names(dt.body->encoding));
        }
      fwrite(dt.block, dt.body->octets, 1, fp);
      fseek(fp, 0, SEEK_SET);
    }
    partstream = g_mime_stream_file_new (fp);

    {
        GMimeParser *parser =  
            g_mime_parser_new_with_stream (partstream);
        part->mime_part = g_mime_parser_construct_part (parser);
        g_object_unref (parser);
    }
    g_object_unref (partstream);
    g_free(section); 
    g_strfreev(pair);
    g_free(part_name);

    return TRUE;
}

/* Recursive helper for libbalsa_mailbox_imap_get_msg_part: ensure that
 * we have a mime_part, and if we are in a multipart/signed or
 * multipart/encrypted, ensure that all needed children are also
 * created. */
static gboolean
lbm_imap_get_msg_part(LibBalsaMessage * msg, LibBalsaMessageBody * part,
                      gboolean need_children, GMimeObject * parent_part)
{
    if (!part)
        return FALSE;

    if (!part->mime_part) {
        GMimeContentType *type =
            g_mime_content_type_new_from_string(part->content_type);
        if (g_mime_content_type_is_type(type, "multipart", "*")) {
            if (g_mime_content_type_is_type(type, "multipart", "signed"))
                part->mime_part =
                    GMIME_OBJECT(g_mime_multipart_signed_new());
            else if (g_mime_content_type_is_type(type, "multipart",
                                                 "encrypted"))
                part->mime_part =
                    GMIME_OBJECT(g_mime_multipart_encrypted_new());
            else
                part->mime_part = GMIME_OBJECT(g_mime_multipart_new());
            g_mime_object_set_content_type(part->mime_part, type);
        } else {
            g_mime_content_type_destroy(type);
            if (!lbm_imap_get_msg_part_from_cache(msg, part))
                return FALSE;
        }
    }

    if (parent_part) {
        /* GMime will unref and so will we. */
        g_object_ref(part->mime_part);
      g_mime_multipart_add_part(GMIME_MULTIPART(parent_part),
                                  part->mime_part);
    }

    if (GMIME_IS_MULTIPART_SIGNED(part->mime_part)
        || GMIME_IS_MULTIPART_ENCRYPTED(part->mime_part))
        need_children = TRUE;

    if (need_children) {
      /* Get the children, if any,... */
        if (GMIME_IS_MULTIPART(part->mime_part))
            lbm_imap_get_msg_part(msg, part->parts, TRUE, part->mime_part);
      /* ...and siblings. */
        lbm_imap_get_msg_part(msg, part->next, TRUE, parent_part);
      /* FIXME if GMIME_IS_MESSAGE_PART? */
    }

    return GMIME_IS_PART(part->mime_part);
}

static gboolean
libbalsa_mailbox_imap_get_msg_part(LibBalsaMessage *msg,
                                   LibBalsaMessageBody *part)
{
    if (part->mime_part)
        return GMIME_IS_PART(part->mime_part);

    return lbm_imap_get_msg_part(msg, part, FALSE, NULL);
}

/* libbalsa_mailbox_imap_add_message: 
   can be called for a closed mailbox.
   Called with mailbox locked.
*/
int
libbalsa_mailbox_imap_add_message(LibBalsaMailbox * mailbox,
                                  LibBalsaMessage * message, GError **err)
{
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    ImapMsgFlags imap_flags = IMAP_FLAGS_EMPTY;
    ImapResponse rc;
    GMimeStream *stream;
    GMimeStream *tmpstream;
    GMimeFilter *crlffilter;
    ImapMboxHandle *handle;
    gint outfd;
    gchar *outfile;
    GMimeStream *outstream;
    gssize len;

    if(message->mailbox &&
       LIBBALSA_IS_MAILBOX_IMAP(message->mailbox) &&
       LIBBALSA_MAILBOX_REMOTE(message->mailbox)->server ==
       LIBBALSA_MAILBOX_REMOTE(mailbox)->server) {
        ImapMboxHandle *handle = mimap->handle;
        unsigned msgno = message->msgno;
        g_return_val_if_fail(handle, -1); /* message is there but
                                           * the source mailbox closed! */
        /* FIXME: reconnect? The msgno number might have changed.
         * we should probably reconnect and return a retriable error. */
        rc = imap_mbox_handle_copy(handle, 1, &msgno, mimap->path);
        if(rc != IMR_OK) {
            gchar *msg = imap_mbox_handle_get_last_msg(mimap->handle);
            g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                        LIBBALSA_MAILBOX_COPY_ERROR,
                        "%s", msg);
            g_free(msg);
        }
        return rc == IMR_OK ? 1 : -1;
    }

    if (!LIBBALSA_MESSAGE_IS_UNREAD(message) )
      IMSG_FLAG_SET(imap_flags,IMSGF_SEEN);
    if ( LIBBALSA_MESSAGE_IS_DELETED(message ) ) 
      IMSG_FLAG_SET(imap_flags,IMSGF_DELETED);
    if ( LIBBALSA_MESSAGE_IS_FLAGGED(message) )
      IMSG_FLAG_SET(imap_flags,IMSGF_FLAGGED);
    if ( LIBBALSA_MESSAGE_IS_REPLIED(message) )
      IMSG_FLAG_SET(imap_flags,IMSGF_ANSWERED);

    stream = libbalsa_mailbox_get_message_stream(message->mailbox, message);
    tmpstream = g_mime_stream_filter_new_with_stream(stream);
    g_object_unref(stream);

    crlffilter =
      g_mime_filter_crlf_new(GMIME_FILTER_CRLF_ENCODE,
                         GMIME_FILTER_CRLF_MODE_CRLF_ONLY);
    g_mime_stream_filter_add(GMIME_STREAM_FILTER(tmpstream), crlffilter);
    g_object_unref(crlffilter);

    outfd = g_file_open_tmp("balsa-tmp-file-XXXXXX", &outfile, err);
    if (outfd < 0) {
      g_warning("Could not create temporary file: %s", (*err)->message);
      g_object_unref(tmpstream);
      return -1;
    }

    outstream = g_mime_stream_fs_new(outfd);
    g_mime_stream_write_to_stream(tmpstream, outstream);
    g_object_unref(tmpstream);

    len = g_mime_stream_tell(outstream);
    g_mime_stream_reset(outstream);

    if(len>(signed)SizeMsgThreshold)
        libbalsa_information(LIBBALSA_INFORMATION_MESSAGE, 
                             _("Uploading %ld kB"),
                             (long)len/1024);
    handle = libbalsa_mailbox_imap_get_handle(mimap, NULL);
    rc = imap_mbox_append_stream(handle, mimap->path,
                         imap_flags, outstream, len);
    if(rc != IMR_OK) {
        gchar *msg = imap_mbox_handle_get_last_msg(mimap->handle);
        g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                    LIBBALSA_MAILBOX_APPEND_ERROR,
                    "%s", msg);
        g_free(msg);
    }
    libbalsa_mailbox_imap_release_handle(mimap);

    g_object_unref(outstream);
    unlink(outfile);
    g_free(outfile);

    return rc == IMR_OK ? 1 : -1;
}

static void
transform_flags(LibBalsaMessageFlag set, LibBalsaMessageFlag clr,
                ImapMsgFlag *flg_set, ImapMsgFlag *flg_clr)
{
    *flg_set = 0;
    *flg_clr = 0;

    if (set & LIBBALSA_MESSAGE_FLAG_REPLIED)
        *flg_set |= IMSGF_ANSWERED;
    if (clr & LIBBALSA_MESSAGE_FLAG_REPLIED)
      *flg_clr |= IMSGF_ANSWERED;
    if (set & LIBBALSA_MESSAGE_FLAG_NEW)
      *flg_clr |= IMSGF_SEEN;
    if (clr & LIBBALSA_MESSAGE_FLAG_NEW)
      *flg_set |= IMSGF_SEEN;
    if (set & LIBBALSA_MESSAGE_FLAG_FLAGGED)
      *flg_set |= IMSGF_FLAGGED;
    if (clr & LIBBALSA_MESSAGE_FLAG_FLAGGED)
      *flg_clr |= IMSGF_FLAGGED;
    if (set & LIBBALSA_MESSAGE_FLAG_DELETED)
      *flg_set |= IMSGF_DELETED;
    if (clr & LIBBALSA_MESSAGE_FLAG_DELETED)
      *flg_clr |= IMSGF_DELETED;
}

static void
lbm_imap_change_user_flags(LibBalsaMailbox * mailbox, GArray * seqno,
                           LibBalsaMessageFlag set,
                     LibBalsaMessageFlag clear)
{
    gint i;
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);

    set   &= ~LIBBALSA_MESSAGE_FLAGS_REAL;
    clear &= ~LIBBALSA_MESSAGE_FLAGS_REAL;
    if (!set && !clear)
      return;

    for (i = seqno->len; --i >= 0;) {
      guint msgno = g_array_index(seqno, guint, i);
        struct message_info *msg_info = 
            message_info_from_msgno(mimap, msgno);
      msg_info->user_flags |= set;
      msg_info->user_flags &= ~clear;
    }
}

static gboolean
lbm_imap_messages_change_flags(LibBalsaMailbox * mailbox, GArray * seqno,
                         LibBalsaMessageFlag set,
                         LibBalsaMessageFlag clear)
{
    ImapMsgFlag flag_set, flag_clr;
    ImapResponse rc = IMR_OK;
    ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(mailbox)->handle;

    lbm_imap_change_user_flags(mailbox, seqno, set, clear);

    if (!((set | clear) & LIBBALSA_MESSAGE_FLAGS_REAL))
      /* No real flags. */
      return TRUE;

    g_array_sort(seqno, cmp_msgno);
    transform_flags(set, clear, &flag_set, &flag_clr);
    if (flag_set)
        II(rc, handle,
           imap_mbox_store_flag(handle,
                                seqno->len, (guint *) seqno->data,
                                flag_set, TRUE));
    if (rc == IMR_OK && flag_clr)
        II(rc, handle,
           imap_mbox_store_flag(handle,
                                seqno->len, (guint *) seqno->data,
                                flag_clr, FALSE));
    return rc == IMR_OK;
}

static gboolean
libbalsa_mailbox_imap_msgno_has_flags(LibBalsaMailbox * m, unsigned msgno,
                                      LibBalsaMessageFlag set,
                                      LibBalsaMessageFlag unset)
{
    LibBalsaMessageFlag user_set, user_unset;
    ImapMsgFlag flag_set, flag_unset;
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(m);
    ImapMboxHandle *handle;

    user_set   = set   & ~LIBBALSA_MESSAGE_FLAGS_REAL;
    user_unset = unset & ~LIBBALSA_MESSAGE_FLAGS_REAL;
    if (user_set || user_unset) {
        struct message_info *msg_info =
            message_info_from_msgno(mimap, msgno);
        if ((msg_info->user_flags & user_set) != user_set ||
            (msg_info->user_flags & user_unset) != 0)
            return FALSE;
    }

    transform_flags(set, unset, &flag_set, &flag_unset);
    if (!flag_set && !flag_unset)
        return TRUE;

    handle = mimap->handle;
    g_return_val_if_fail(handle, FALSE);
    return imap_mbox_handle_msgno_has_flags(handle, msgno, flag_set,
                                            flag_unset);
}

static gboolean
libbalsa_mailbox_imap_can_do(LibBalsaMailbox* mbox,
                             enum LibBalsaMailboxCapability c)
{
    LibBalsaMailboxImap *mimap;
    if(!mbox)
        return TRUE;
    mimap = LIBBALSA_MAILBOX_IMAP(mbox);
    switch(c) {
    case LIBBALSA_MAILBOX_CAN_SORT:
        return imap_mbox_handle_can_do(mimap->handle, IMCAP_SORT);
    case LIBBALSA_MAILBOX_CAN_THREAD:
        return imap_mbox_handle_can_do(mimap->handle, IMCAP_THREAD_REFERENCES);
    default:
        return TRUE;
    }
}

static ImapSortKey
lbmi_get_imap_sort_key(LibBalsaMailbox *mbox)
{
    ImapSortKey key = LB_MBOX_FROM_COL;

    switch (mbox->view->sort_field) {
    default:
    case LB_MAILBOX_SORT_NO:    key = IMSO_MSGNO;   break;
    case LB_MAILBOX_SORT_SENDER:    
        key = mbox->view->show == LB_MAILBOX_SHOW_TO
            ? IMSO_TO : IMSO_FROM;              break;
    case LB_MAILBOX_SORT_SUBJECT: key = IMSO_SUBJECT; break;
    case LB_MAILBOX_SORT_DATE:    key = IMSO_DATE;    break;
    case LB_MAILBOX_SORT_SIZE:    key = IMSO_SIZE;    break;
    }

    return key;
}
     
static void
libbalsa_mailbox_imap_set_threading(LibBalsaMailbox *mailbox,
                            LibBalsaMailboxThreadingType thread_type)
{
    LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
    GNode * new_tree = NULL;
    guint msgno;
    ImapSearchKey *filter = lbmi_build_imap_query(mailbox->view_filter, NULL);
    ImapResponse rc;
    
    mailbox->view->threading_type = thread_type;
    switch(thread_type) {
    case LB_MAILBOX_THREADING_SIMPLE:
    case LB_MAILBOX_THREADING_JWZ:
        II(rc,mimap->handle,
           imap_mbox_thread(mimap->handle, "REFERENCES", filter));
        if(rc == IMR_OK) {
            new_tree =
                g_node_copy(imap_mbox_handle_get_thread_root(mimap->handle));
            break;
        } else 
            libbalsa_information(LIBBALSA_INFORMATION_WARNING,
                       _("Server-side threading not supported."));
        /* fall through */
    case LB_MAILBOX_THREADING_FLAT:
        if(filter) {
            II(rc,mimap->handle,
               imap_mbox_sort_filter(mimap->handle,
                                     lbmi_get_imap_sort_key(mailbox),
                                     mailbox->view->sort_type ==
                                     LB_MAILBOX_SORT_TYPE_ASC,
                                     filter));
            if(rc == IMR_OK)
                new_tree =
                    g_node_copy
                    (imap_mbox_handle_get_thread_root(mimap->handle));
        }
        if(!new_tree) { /* fall back */
            new_tree = g_node_new(NULL);
            for(msgno = 1; msgno <= mimap->messages_info->len; msgno++)
                g_node_append_data(new_tree, GUINT_TO_POINTER(msgno));
        }
        break;
    default:
      g_assert_not_reached();
      new_tree = NULL;
    }
    imap_search_key_free(filter);

    if(!mailbox->msg_tree) /* first reference */
        mailbox->msg_tree = g_node_new(NULL);
    if (new_tree)
      libbalsa_mailbox_set_msg_tree(mailbox, new_tree);
}

static void
lbm_imap_update_view_filter(LibBalsaMailbox   *mailbox,
                            LibBalsaCondition *view_filter)
{
    if(mailbox->view_filter)
        libbalsa_condition_free(mailbox->view_filter);
    mailbox->view_filter = view_filter;
}

/* Sorting
 *
 * To avoid multiple server queries when the view is threaded, we sort
 * the whole mailbox, find the rank of each message, and cache the
 * result in LibBalsaMailboxImap::rank.  When libbalsa_mailbox_imap_sort()
 * is called on a subset of messages, it can use their ranks to sort
 * them.  The sort-field is also cached, so we can refresh the ranks
 * when the sort-field is changed.  The cached sort-field is invalidated
 * (set to -1) whenever the cached ranks might be out of date.
 */
static gint
lbmi_compare_func(const SortTuple * a,
              const SortTuple * b,
              LibBalsaMailboxImap * mimap)
{
    unsigned seqnoa, seqnob;
    int retval;
    LibBalsaMailbox *mbox = (LibBalsaMailbox *) mimap;

    seqnoa = GPOINTER_TO_UINT(a->node->data);
    g_assert(seqnoa <= mimap->sort_ranks->len);
    seqnob = GPOINTER_TO_UINT(b->node->data);
    g_assert(seqnob <= mimap->sort_ranks->len);
    retval = g_array_index(mimap->sort_ranks, guint, seqnoa - 1) -
      g_array_index(mimap->sort_ranks, guint, seqnob - 1);

    return mbox->view->sort_type == LB_MAILBOX_SORT_TYPE_ASC ?
        retval : -retval;
}

static void
libbalsa_mailbox_imap_sort(LibBalsaMailbox *mbox, GArray *array)
{
    unsigned *msgno_arr, i;
    ImapResponse rc;
    LibBalsaMailboxImap *mimap;

    mimap = LIBBALSA_MAILBOX_IMAP(mbox);
    if (mimap->sort_field != mbox->view->sort_field) {
      /* Cached ranks are invalid. */
        guint len = mimap->messages_info->len;
        msgno_arr = g_malloc(len * sizeof(unsigned));
        for (i = 0; i < len; i++)
            msgno_arr[i] = i + 1;
        if (mbox->view->sort_field != LB_MAILBOX_SORT_NO)
          /* Server-side sort of the whole mailbox. */
            II(rc, LIBBALSA_MAILBOX_IMAP(mbox)->handle,
               imap_mbox_sort_msgno(LIBBALSA_MAILBOX_IMAP(mbox)->handle,
                                    lbmi_get_imap_sort_key(mbox), TRUE,
                                    msgno_arr, len)); /* ignore errors */
        g_array_set_size(mimap->sort_ranks, len);
        for (i = 0; i < len; i++)
          g_array_index(mimap->sort_ranks, guint, msgno_arr[i] - 1) = i;
      g_free(msgno_arr);
      /* Validate the cache. */
        mimap->sort_field = mbox->view->sort_field;
    }
    g_array_sort_with_data(array, (GCompareDataFunc) lbmi_compare_func,
                           mimap);
}

static guint
libbalsa_mailbox_imap_total_messages(LibBalsaMailbox * mailbox)
{
    LibBalsaMailboxImap *mimap = (LibBalsaMailboxImap *) mailbox;

    return mimap->messages_info ? mimap->messages_info->len : 0;
}

/* Copy messages in the list to dest; use server-side copy if mailbox
 * and dest are on the same server, fall back to parent method
 * otherwise.
 */
static gboolean
libbalsa_mailbox_imap_messages_copy(LibBalsaMailbox * mailbox,
                            GArray * msgnos,
                            LibBalsaMailbox * dest, GError **err)
{
    if (LIBBALSA_IS_MAILBOX_IMAP(dest) &&
      LIBBALSA_MAILBOX_REMOTE(dest)->server ==
      LIBBALSA_MAILBOX_REMOTE(mailbox)->server) {
        gboolean ret;
      ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(mailbox)->handle;
      g_return_val_if_fail(handle, FALSE);

      /* User server-side copy. */
      g_array_sort(msgnos, cmp_msgno);
      ret = imap_mbox_handle_copy(handle, msgnos->len,
                                    (guint *) msgnos->data,
                                    LIBBALSA_MAILBOX_IMAP(dest)->path)
          == IMR_OK;
        if(!ret) {
            gchar *msg = imap_mbox_handle_get_last_msg(handle);
            g_set_error(err, LIBBALSA_MAILBOX_ERROR,
                        LIBBALSA_MAILBOX_COPY_ERROR,
                        "%s", msg);
            g_free(msg);
        }
        return ret;
    }

    /* Couldn't use server-side copy, fall back to default method. */
    return parent_class->messages_copy(mailbox, msgnos, dest, err);
}

void
libbalsa_imap_set_cache_size(off_t cache_size)
{
    ImapCacheSize = cache_size;
}

/* libbalsa_imap_purge_temp_dir() purges the temporary directory used
   for non-persistent message caching. */
void
libbalsa_imap_purge_temp_dir(off_t cache_size)
{
    gchar *dir_name = get_cache_dir(NULL, NULL, FALSE);
    clean_dir(dir_name, cache_size);
    g_free(dir_name);
}

/* ===================================================================
   ImapCacheManager implementation.  The main task of the
   ImapCacheManager is to reuse msgno->UID mappings. This is useful
   mostly for operations when the imap connection cannot last as long
   as it should due to external constraints (flaky connection,
   pay-by-the-minute connections). The goal is to extract any UID
   information that the low level libimap handle might have on
   connection close and provide it to the new one whenever one is
   created.

   The general scheme is that libimap does not cache any data
   persistently based on UIDs - this task is left to ImapCacheManager.
 .
   ICM provides two main functions:

   - init_on_select() - preloads ImapMboxHandle msgno-based cache with
     all available information. It may ask ImapMboxHandle to provide
     UID->msgno maps if it considers it necessary.

   - store_cached_data() - extracts all information that is known to
     ImapMboxHandle and can be potentially used in future sessions -
     mostly all ImapMessage and ImapEnvelope structures.

     Current implementation stores the information in memory but an
     implementation storing data on disk is possible, too.

 */
struct ImapCacheManager {
    GHashTable *headers;
    GArray     *uidmap;
    uint32_t    uidvalidity;
    uint32_t    uidnext;
    uint32_t    exists;
};

static struct ImapCacheManager*
imap_cache_manager_new(guint cnt)
{
    struct ImapCacheManager *icm = g_new0(struct ImapCacheManager, 1);
    icm->exists = cnt;
    icm->headers = 
        g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
    icm->uidmap = g_array_sized_new(FALSE,  TRUE, sizeof(uint32_t), cnt);
    return icm;
}

static void
icm_destroy(struct ImapCacheManager *icm)
{
    g_hash_table_destroy(icm->headers);
    g_array_free(icm->uidmap, TRUE);
}

/* icm_init_on_select_() preloads header cache of the ImapMboxHandle object.
   It currently handles following cases:
   a). uidvalidity different - entire cache has to be invalidated.
   b). cache->exists == h->exists && cache->uidnext == h->uidnext:
   nothing has changed - feed entire cache.
   else fetch the message numbers for the UIDs in cache (not yet handled).
   Problem: what to do about UID fragmentation?
*/
static void
icm_restore_from_cache(ImapMboxHandle *h, struct ImapCacheManager *icm)
{
    unsigned exists, uidvalidity, uidnext;
    unsigned i;

    if(!icm || ! h)
        return;
    uidvalidity = imap_mbox_handle_get_validity(h);
    exists  = imap_mbox_handle_get_exists(h);
    uidnext = imap_mbox_handle_get_uidnext(h);
    if(icm->uidvalidity != uidvalidity) {
        printf("different validities %u %u- cache invalidated\n",
               icm->uidvalidity, uidvalidity);
        return;
    }

    /* detect unhandled "else" case */
    if(exists - icm->exists !=  uidnext - icm->uidnext) {
        printf("mailbox modified exists: %u %u uidnext: %u %u\n",
               icm->exists, exists, icm->uidnext, uidnext);
        return;
    }
    for(i=1; i<=icm->exists; i++) {
        uint32_t uid = g_array_index(icm->uidmap, uint32_t, i-1);
        void *data = g_hash_table_lookup(icm->headers,
                                         GUINT_TO_POINTER(uid));
        if(data) /* if uid known */
            imap_mbox_handle_msg_deserialize(h, i, data);
    }
}
static struct ImapCacheManager*
icm_store_cached_data(ImapMboxHandle *handle)
{
    struct ImapCacheManager *icm;
    unsigned cnt, i;

    if(!handle)
        return NULL;

    cnt = imap_mbox_handle_get_exists(handle);
    icm = imap_cache_manager_new(cnt);
    icm->uidvalidity = imap_mbox_handle_get_validity(handle);
    icm->uidnext     = imap_mbox_handle_get_uidnext(handle);
    for(i=1; i<=cnt; i++) {
        void *ptr;
        uint32_t *uid = &g_array_index(icm->uidmap, uint32_t, i-1);
        ImapMessage *imsg = imap_mbox_handle_get_msg(handle, i);
        if(imsg && (ptr = imap_message_serialize(imsg)) != NULL) {
            g_hash_table_insert(icm->headers,
                                GUINT_TO_POINTER(imsg->uid), ptr);
            *uid = imsg->uid;
        } else *uid = 0;
    }
    return icm;
}

Generated by  Doxygen 1.6.0   Back to index