"Fossies" - the Fresh Open Source Software archive 
Member "balsa-2.5.0/libbalsa/mailbox_imap.c" of archive balsa-2.5.0.tar.gz:
/* -*-mode:c; c-basic-offset:4; -*- */
/* Balsa E-Mail Client
* Copyright (C) 1997-2013 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.
*/
#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
# include "config.h"
#endif /* HAVE_CONFIG_H */
#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 "filter-funcs.h"
#include "filter.h"
#include "imap-commands.h"
#include "imap-handle.h"
#include "imap-server.h"
#include "libbalsa-conf.h"
#include "libbalsa_private.h"
#include "libimap.h"
#include "mailbox-filter.h"
#include "message.h"
#include "mime-stream-shared.h"
#include "misc.h"
#include "server.h"
#include <glib/gi18n.h>
#define ENABLE_CLIENT_SIDE_SORT 1
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;
GPtrArray *msgids; /* message-ids */
GArray *sort_ranks;
guint unread_update_id;
LibBalsaMailboxSortFields sort_field;
unsigned opened:1;
ImapAclType rights; /* RFC 4314 'myrights' */
GList *acls; /* RFC 4314 acl's */
};
struct _LibBalsaMailboxImapClass {
LibBalsaMailboxRemoteClass klass;
};
struct message_info {
LibBalsaMessage *message;
LibBalsaMessageFlag user_flags;
};
static LibBalsaMailboxClass *parent_class = NULL;
static off_t ImapCacheSize = 30*1024*1024; /* 30MB */
/* 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,
guint msgno,
gboolean peek);
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 gboolean libbalsa_mailbox_imap_prepare_threading(LibBalsaMailbox *
mailbox,
guint start);
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 *,
GError **err);
static GArray *libbalsa_mailbox_imap_duplicate_msgnos(LibBalsaMailbox *
mailbox);
static guint libbalsa_mailbox_imap_add_messages(LibBalsaMailbox *mailbox,
LibBalsaAddMessageIterator mi,
void *mi_arg,
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,
LibBalsaMailbox * mailbox);
static struct message_info *message_info_from_msgno(
LibBalsaMailboxImap * mimap,
guint msgno)
{
struct message_info *msg_info;
if (msgno > mimap->messages_info->len) {
printf("%s %s msgno %d > messages_info len %d\n", __func__,
LIBBALSA_MAILBOX(mimap)->name, msgno,
mimap->messages_info->len);
msg_info = NULL;
} else
msg_info =
&g_array_index(mimap->messages_info, struct message_info,
msgno - 1);
return msg_info;
}
#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->duplicate_msgnos =
libbalsa_mailbox_imap_duplicate_msgnos;
libbalsa_mailbox_class->add_messages = libbalsa_mailbox_imap_add_messages;
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(mailbox->unread_update_id) {
g_source_remove(mailbox->unread_update_id);
mailbox->unread_update_id = 0;
}
g_list_foreach(mailbox->acls, (GFunc)imap_user_acl_free, NULL);
g_list_free(mailbox->acls);
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);
}
gboolean
libbalsa_imap_get_quota(LibBalsaMailboxImap * mailbox,
gulong *max_kbyte, gulong *used_kbyte)
{
g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mailbox), FALSE);
return imap_mbox_get_quota(mailbox->handle, mailbox->path,
max_kbyte, used_kbyte) == IMR_OK;
}
/* converts ACL's to a standard RFC 4314 acl string */
static char *
imap_acl_to_str(ImapAclType acl)
{
GString *rights;
/* include "cd" for RFC 2086 support: */
static const char * flags = "lrswipkxteacd";
unsigned n;
rights = g_string_new("");
for (n = 0; n < strlen(flags); n++)
if (acl & (1 << n))
rights = g_string_append_c(rights, flags[n]);
return g_string_free(rights, FALSE);
}
gchar *
libbalsa_imap_get_rights(LibBalsaMailboxImap * mailbox)
{
g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mailbox), NULL);
if (mailbox->rights == IMAP_ACL_NONE)
return NULL;
else
return imap_acl_to_str(mailbox->rights);
}
gchar **
libbalsa_imap_get_acls(LibBalsaMailboxImap * mailbox)
{
gchar ** acls;
guint n;
GList *p;
g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mailbox), NULL);
if (!mailbox->acls)
return NULL;
acls = g_new0(char *, 2 * g_list_length(mailbox->acls) + 1);
n = 0;
for (p = g_list_first(mailbox->acls); p; p = g_list_next(p), n += 2) {
acls[n] = g_strdup(((ImapUserAclType *)p->data)->uid);
acls[n + 1] = imap_acl_to_str(((ImapUserAclType *)p->data)->acl);
}
return acls;
}
const gchar*
libbalsa_mailbox_imap_get_path(LibBalsaMailboxImap * mailbox)
{
return mailbox->path;
}
static void
server_host_settings_changed_cb(LibBalsaServer * server,
LibBalsaMailbox * mailbox)
{
libbalsa_mailbox_imap_update_url(LIBBALSA_MAILBOX_IMAP(mailbox));
}
static gchar*
get_cache_dir(gboolean is_persistent)
{
gchar *fname;
if(is_persistent) {
const gchar *home = g_get_home_dir();
fname = g_strconcat(home, G_DIR_SEPARATOR_S ".balsa"
G_DIR_SEPARATOR_S "imap-cache", NULL);
} else
fname = g_strconcat(g_get_tmp_dir(),
G_DIR_SEPARATOR_S "/balsa-",
g_get_user_name(), NULL);
return fname;
}
static gchar*
get_header_cache_path(LibBalsaMailboxImap *mimap)
{
LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE(mimap)->server;
gchar *cache_dir = get_cache_dir(TRUE); /* FIXME */
gchar *header_file = g_strdup_printf("%s@%s-%s-%u-headers2",
s->user, s->host,
(mimap->path ? mimap->path : "INBOX"),
mimap->uid_validity);
gchar *encoded_path = libbalsa_urlencode(header_file);
g_free(header_file);
header_file = g_build_filename(cache_dir, encoded_path, NULL);
g_free(cache_dir);
g_free(encoded_path);
return header_file;
}
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;
gchar *fname;
res[0] = get_cache_dir(is_persistent);
fname = g_strdup_printf("%s@%s-%s-%u-%u-%s",
s->user, s->host,
(mailbox->path ? mailbox->path : "INBOX"),
uid_validity, uid, type);
res[1] = libbalsa_urlencode(fname);
g_free(fname);
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_build_filename(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(is_persistent);
clean_dir(dir, ImapCacheSize);
g_free(dir);
return TRUE;
}
struct ImapCacheManager;
static struct ImapCacheManager*imap_cache_manager_new_from_file(const char *header_cache_path);
static void imap_cache_manager_free(struct ImapCacheManager *icm);
static struct ImapCacheManager *icm_store_cached_data(ImapMboxHandle *h);
static void icm_restore_from_cache(ImapMboxHandle *h,
struct ImapCacheManager *icm);
static gboolean icm_save_to_file(struct ImapCacheManager *icm,
const gchar *path);
static ImapResult
mi_reconnect(ImapMboxHandle *h)
{
struct ImapCacheManager *icm = icm_store_cached_data(h);
ImapResult r;
unsigned old_cnt = imap_mbox_handle_get_exists(h);
unsigned old_next = imap_mbox_handle_get_uidnext(h);
r = imap_mbox_handle_reconnect(h, NULL);
if(r==IMAP_SUCCESS) icm_restore_from_cache(h, icm);
imap_cache_manager_free(icm);
if(imap_mbox_handle_get_exists(h) != old_cnt ||
imap_mbox_handle_get_uidnext(h) != old_next)
g_signal_emit_by_name(h, "exists-notify", 0);
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(imap_handle_op_cancelled(h))\
break;\
else if(rc==IMR_SEVERED) \
libbalsa_information(LIBBALSA_INFORMATION_WARNING, \
_("IMAP connection has been severed. Reconnecting...")); \
else if(rc==IMR_PROTOCOL) \
libbalsa_information(LIBBALSA_INFORMATION_WARNING, \
_("IMAP protocol error. Try enabling bug workarounds.")); \
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);}
#define IIA(rc,h,line,cmd) \
{int trials=2;do{\
if(imap_mbox_is_disconnected(h) &&mi_reconnect(h)!=IMAP_SUCCESS)\
{rc=0;break;};\
rc=line; \
if(imap_mbox_op_cancelled(h))\
break;\
else if(rc != IMR_OK) \
libbalsa_information(LIBBALSA_INFORMATION_WARNING, \
_("Async IMAP cmd %s could not be executed. Reconnecting..."),cmd); \
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 = 20;
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);
}
/* Forward reference. */
static void lbm_imap_get_unseen(LibBalsaMailboxImap * mimap);
/** 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.
OBSERVE: it must not trigger any IMAP activity under NO circumstances!
*/
static gboolean
idle_unread_update_cb(LibBalsaMailbox *mailbox)
{
glong unread;
libbalsa_lock_mailbox(mailbox);
gdk_threads_enter();
unread = mailbox->unread_messages;
lbm_imap_get_unseen(LIBBALSA_MAILBOX_IMAP(mailbox));
if(unread != mailbox->unread_messages)
libbalsa_mailbox_set_unread_messages_flag(mailbox,
mailbox->unread_messages>0);
LIBBALSA_MAILBOX_IMAP(mailbox)->unread_update_id = 0;
gdk_threads_leave();
libbalsa_unlock_mailbox(mailbox);
return FALSE;
}
static void
imap_flags_cb(unsigned cnt, const unsigned seqno[], LibBalsaMailboxImap *mimap)
{
LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap);
unsigned i;
libbalsa_lock_mailbox(mailbox);
for(i=0; i<cnt; i++) {
struct message_info *msg_info =
message_info_from_msgno(mimap, seqno[i]);
if(msg_info && msg_info->message) {
LibBalsaMessageFlag flags;
/* 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);
++mimap->search_stamp;
}
}
if(!mimap->unread_update_id)
mimap->unread_update_id =
g_idle_add((GSourceFunc)idle_unread_update_cb, mailbox);
libbalsa_unlock_mailbox(mailbox);
}
static gboolean
imap_exists_idle(gpointer data)
{
LibBalsaMailboxImap *mimap = (LibBalsaMailboxImap*)data;
LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap);
unsigned cnt;
libbalsa_lock_mailbox(mailbox);
gdk_threads_enter();
mimap->sort_field = -1; /* Invalidate. */
if(mimap->handle && /* was it closed in meantime? */
(cnt = imap_mbox_handle_get_exists(mimap->handle))
!= mimap->messages_info->len) {
unsigned i;
struct message_info a = {0};
GNode *sibling = NULL;
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... -
We 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);
for(i=0; i<mimap->messages_info->len; i++) {
gchar *msgid;
struct message_info *msg_info =
message_info_from_msgno(mimap, i+1);
if(!msg_info)
continue;
if(msg_info->message)
g_object_unref(msg_info->message);
msg_info->message = NULL;
msgid = g_ptr_array_index(mimap->msgids, i);
if(msgid) {
g_free(msgid);
g_ptr_array_index(mimap->msgids, i) = NULL;
}
libbalsa_mailbox_index_entry_clear(mailbox, i + 1);
}
for(i=mimap->messages_info->len; i>cnt; i--) {
g_array_remove_index(mimap->messages_info, i-1);
g_ptr_array_remove_index(mimap->msgids, i-1);
libbalsa_mailbox_msgno_removed(mailbox, i);
}
}
if (mailbox->msg_tree)
sibling = g_node_last_child(mailbox->msg_tree);
for(i=mimap->messages_info->len+1; i <= cnt; i++) {
g_array_append_val(mimap->messages_info, a);
g_ptr_array_add(mimap->msgids, NULL);
libbalsa_mailbox_msgno_inserted(mailbox, i, mailbox->msg_tree,
&sibling);
}
++mimap->search_stamp;
libbalsa_mailbox_run_filters_on_reception(mailbox);
lbm_imap_get_unseen(LIBBALSA_MAILBOX_IMAP(mailbox));
}
gdk_threads_leave();
libbalsa_unlock_mailbox(mailbox);
g_object_unref(G_OBJECT(mailbox));
return FALSE;
}
static void
imap_exists_cb(ImapMboxHandle *handle, LibBalsaMailboxImap *mimap)
{
g_object_ref(G_OBJECT(mimap));
g_idle_add(imap_exists_idle, mimap);
}
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;
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_build_filename(pair[0], pair[1], NULL);
unlink(fn); /* ignore error; perhaps the message
* was not in the cache. */
g_free(fn);
g_strfreev(pair);
}
msg_info = message_info_from_msgno(mimap, seqno);
if (msg_info) {
if (msg_info->message)
g_object_unref(msg_info->message);
g_array_remove_index(mimap->messages_info, seqno-1);
}
if (seqno <= mimap->msgids->len) {
gchar *msgid;
msgid = g_ptr_array_index(mimap->msgids, seqno - 1);
g_free(msgid);
g_ptr_array_remove_index(mimap->msgids, 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;
}
/* check if we have RFC 4314 acl's for the selected mailbox */
if (imap_mbox_get_my_rights(mimap->handle, &mimap->rights, FALSE) ==
IMR_OK) {
if (!IMAP_RIGHTS_CAN_WRITE(mimap->rights))
LIBBALSA_MAILBOX(mimap)->readonly = TRUE;
if (mimap->rights & IMAP_ACL_ADMIN)
imap_mbox_get_acl(mimap->handle, mimap->path, &mimap->acls);
}
/* 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;
}
}
if(count == 0)
mailbox->first_unread = 0;
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;
unsigned i;
guint total_messages;
struct ImapCacheManager *icm;
gboolean from_file = FALSE;
g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), FALSE);
mimap = LIBBALSA_MAILBOX_IMAP(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);
mimap->msgids = g_ptr_array_sized_new(total_messages);
for(i=0; i < total_messages; i++) {
struct message_info a = {0};
g_array_append_val(mimap->messages_info, a);
g_ptr_array_add(mimap->msgids, NULL);
}
icm = g_object_get_data(G_OBJECT(mailbox), "cache-manager");
if(!icm) { /* Try restoring from file... */
gchar *header_cache_path = get_header_cache_path(mimap);
icm = imap_cache_manager_new_from_file(header_cache_path);
g_free(header_cache_path);
from_file = TRUE;
}
if (icm) {
icm_restore_from_cache(mimap->handle, icm);
if (from_file)
imap_cache_manager_free(icm);
else
g_object_set_data(G_OBJECT(mailbox), "cache-manager", NULL);
}
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;
if(messages_info->len != mbox->msgids->len)
g_warning("free_messages_info: array sizes do not match.");
for (i = 0; i < messages_info->len; i++) {
gchar *msgid;
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);
}
msgid = g_ptr_array_index(mbox->msgids, i);
if(msgid) g_free(msgid);
}
g_array_free(mbox->messages_info, TRUE);
mbox->messages_info = NULL;
g_ptr_array_free(mbox->msgids, TRUE);
mbox->msgids = NULL;
}
static void
libbalsa_mailbox_imap_close(LibBalsaMailbox * mailbox, gboolean expunge)
{
LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE(mailbox)->server;
LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s);
gboolean is_persistent = libbalsa_imap_server_has_persistent_cache(is);
LibBalsaMailboxImap *mbox = LIBBALSA_MAILBOX_IMAP(mailbox);
struct ImapCacheManager *icm = icm_store_cached_data(mbox->handle);
mbox->opened = FALSE;
g_object_set_data_full(G_OBJECT(mailbox), "cache-manager", icm,
(GDestroyNotify) imap_cache_manager_free);
/* we do not attempt to reconnect here */
if (expunge) {
if (is_persistent) { /* We appreciate expunge info to simplify
next resync. */
imap_mbox_expunge_a(mbox->handle);
}
imap_mbox_close(mbox->handle);
} else
imap_mbox_unselect(mbox->handle);
/* We have received last notificiations, we can save the cache now. */
if(is_persistent) {
/* Implement only for persistent. Cache dir is shared for all
non-persistent caches. */
gchar *header_file = get_header_cache_path(mbox);
icm_save_to_file(icm, header_file);
g_free(header_file);
}
clean_cache(mailbox);
free_messages_info(mbox);
libbalsa_mailbox_imap_release_handle(mbox);
mbox->sort_field = -1; /* Invalidate. */
}
static FILE*
get_cache_stream(LibBalsaMailboxImap *mimap, guint uid, gboolean peek)
{
FILE *stream;
gchar **pair, *path;
pair = get_cache_name_pair(mimap, "body", uid);
path = g_build_filename(pair[0], pair[1], NULL);
stream = fopen(path, "rb");
if(!stream) {
FILE *cache;
ImapResponse rc;
g_mkdir_with_parents(pair[0], S_IRUSR|S_IWUSR|S_IXUSR);
#if 0
if(msg->length>(signed)SizeMsgThreshold)
libbalsa_information(LIBBALSA_INFORMATION_MESSAGE,
_("Downloading %ld kB"),
msg->length/1024);
#endif
cache = fopen(path, "wb");
if(cache) {
int ferr;
II(rc,mimap->handle,
imap_mbox_handle_fetch_rfc822_uid(mimap->handle, uid, peek,
cache));
ferr = ferror(cache);
fclose(cache);
if(ferr || rc != IMR_OK) {
printf("Error fetching RFC822 message, removing cache.\n");
unlink(path);
}
}
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,
guint msgno, gboolean peek)
{
FILE *stream;
ImapMessage *imsg;
LibBalsaMailboxImap *mimap;
g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), NULL);
libbalsa_lock_mailbox(mailbox);
/* this may get called when the mailbox is being opened ie,
open_ref==0 */
mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
if (!mimap->handle) {
libbalsa_unlock_mailbox(mailbox);
return NULL;
}
imsg = mi_get_imsg(mimap, msgno);
stream = imsg ? get_cache_stream(mimap, imsg->uid, peek) : NULL;
libbalsa_unlock_mailbox(mailbox);
return stream ? g_mime_stream_file_new(stream) : NULL;
}
/* 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);
LibBalsaServer *server = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox);
ImapMboxHandle *handle;
gulong id;
handle = libbalsa_mailbox_imap_get_handle(mimap, NULL);
if (!handle)
return FALSE;
if(libbalsa_imap_server_get_use_status(LIBBALSA_IMAP_SERVER(server))) {
static struct ImapStatusResult info[] = {
{ IMSTAT_UNSEEN, 0 }, { IMSTAT_NONE, 0 } };
/* cannot do status on an open mailbox */
g_return_val_if_fail(!mimap->opened, FALSE);
if(imap_mbox_status(handle, mimap->path, info) != IMR_OK)
return FALSE;
libbalsa_mailbox_imap_release_handle(mimap);
return info[0].result > 0;
} else {
struct mark_info info;
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)
return FALSE;
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;
ImapResponse 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;
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) {
query = imap_search_key_new_date
(IMSE_D_BEFORE, FALSE, cond->match.date.date_high);
if(slo)
imap_search_key_set_next(query, slo);
}
/* 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 occurred
*/
GHashTable * libbalsa_mailbox_imap_get_matchings(LibBalsaMailboxImap* mbox,
LibBalsaCondition *ct,
gboolean only_recent,
gboolean * err)
{
ImapSearchKey* query;
ImapResponse 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);
ImapMessage *imsg = mi_get_imsg(mbox, m->msgno);
if (imsg) {
g_hash_table_insert(cbdata->uids,
GUINT_TO_POINTER(imsg->uid), m);
} else
g_warning("Msg %d out of range\n", m->msgno);
}
#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), "config-changed",
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)
{
ImapResponse 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... */
;
}
void
libbalsa_mailbox_imap_force_disconnect(LibBalsaMailboxImap* mimap)
{
g_return_if_fail(mimap != NULL);
if (mimap->handle) {/* we do not attempt to reconnect here */
printf("Disconnecting %s (%u)\n", LIBBALSA_MAILBOX(mimap)->name,
(unsigned)time(NULL));
imap_handle_force_disconnect(mimap->handle);
printf("Disconnected %s (%u)\n", LIBBALSA_MAILBOX(mimap)->name,
(unsigned)time(NULL));
}
}
void
libbalsa_mailbox_imap_reconnect(LibBalsaMailboxImap* mimap)
{
g_return_if_fail(mimap != NULL);
if (mimap->handle &&
imap_mbox_is_disconnected (mimap->handle)) {
printf("Reconnecting %s (%u)\n",
LIBBALSA_MAILBOX_REMOTE_SERVER(mimap)->host,
(unsigned)time(NULL));
if (imap_mbox_handle_reconnect
(mimap->handle, &(LIBBALSA_MAILBOX(mimap)->readonly))
== IMAP_SUCCESS)
printf("Reconnected %s (%u)\n",
LIBBALSA_MAILBOX_REMOTE_SERVER(mimap)->host,
(unsigned)time(NULL));
}
}
gboolean
libbalsa_mailbox_imap_is_connected(LibBalsaMailboxImap* mimap)
{
return mimap->handle && !imap_mbox_is_disconnected(mimap->handle);
}
/* 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)
{
ImapResponse rc;
ImapMboxHandle* handle;
gchar *new_path;
char delim[2];
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));
delim[0] = imap_mbox_handle_get_delim(handle, new_parent);
delim[1] = '\0';
new_path = g_build_path(delim, 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)
{
ImapResponse 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) {
char delim[2];
delim[0] = imap_mbox_handle_get_delim(handle, parent);
delim[1] = '\0';
new_path = g_build_path(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*
imap_address_to_gmime_mailbox(ImapAddress *addr)
{
gchar *tmp = g_mime_utils_header_decode_text(addr->addr_spec);
InternetAddress *address = internet_address_mailbox_new(NULL, tmp);
g_free(tmp);
if (addr->name) {
tmp = g_mime_utils_header_decode_text(addr->name);
internet_address_set_name(address, tmp);
g_free(tmp);
}
return address;
}
static InternetAddressList *
internet_address_new_list_from_imap_address(ImapAddress *list,
ImapAddress **tail)
{
InternetAddress *addr;
InternetAddressList *res;
if (!list)
return NULL;
res = internet_address_list_new();
do {
if (list->addr_spec) {
addr = imap_address_to_gmime_mailbox(list);
} else {
/* Group */
if (list->name) {
/* Group head */
ImapAddress *tail = NULL;
InternetAddressList *l;
gchar *tmp = g_mime_utils_header_decode_text(list->name);
addr = internet_address_group_new(tmp);
g_free(tmp);
l = internet_address_new_list_from_imap_address(list->next,
&tail);
if (l) {
internet_address_group_set_members
(INTERNET_ADDRESS_GROUP(addr), l);
g_object_unref(l);
}
list = tail;
} else {
/* tail */
if (tail)
*tail = list;
return res;
}
}
internet_address_list_add(res, addr);
g_object_unref(addr);
} while (list && (list = list->next) != NULL);
return res;
}
static InternetAddressList *
internet_address_new_list_from_imap_address_list(ImapAddress *list)
{
return internet_address_new_list_from_imap_address(list, NULL);
}
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! */
fprintf(stderr, "load_envelope failed!\n");
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;
libbalsa_message_set_subject_from_header(message, envelope->subject);
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;
libbalsa_lock_mailbox(mailbox);
msg_info = message_info_from_msgno(mimap, msgno);
if (!msg_info) {
libbalsa_unlock_mailbox(mailbox);
return NULL;
}
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 */
libbalsa_unlock_mailbox(mailbox);
return msg_info->message;
}
static gboolean
libbalsa_mailbox_imap_prepare_threading(LibBalsaMailbox * mailbox,
guint start)
{
/* Nothing to do. */
return TRUE;
}
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)
{
if (!message->mime_msg) {
gchar **pair, *filename;
int fd;
GMimeStream *stream, *fstream;
GMimeFilter *filter;
GMimeParser *mime_parser;
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
ImapMessage *imsg = mi_get_imsg(mimap, message->msgno);
if (!imsg)
return FALSE;
pair = get_cache_name_pair(mimap, "body", imsg->uid);
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);
fstream = g_mime_stream_filter_new(stream);
g_object_unref(stream);
filter = g_mime_filter_crlf_new(FALSE, FALSE);
g_mime_stream_filter_add(GMIME_STREAM_FILTER(fstream), filter);
g_object_unref(filter);
mime_parser = g_mime_parser_new_with_stream(fstream);
g_object_unref(fstream);
g_mime_parser_set_scan_from(mime_parser, FALSE);
message->mime_msg = g_mime_parser_construct_message(mime_parser);
g_object_unref(mime_parser);
}
/* 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 by fetching the RFC2822 form of
the message. This is used also to save one RTT for one part
messages with a part that can certainly be displayed, there is
no reason to fetch the structure and the only part
separately... Observe, that the only part can be in principle
something else, like "audio", "*" - we do not prefetch such
parts yet. Also, we save some RTTS for very small messages by
fetching them in their entirety. */
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) ||
LIBBALSA_MESSAGE_GET_LENGTH(message)<8192 ||
(message->headers &&
(!message->headers->content_type ||
g_mime_content_type_is_type(message->headers->content_type, "text", "*"))) ){
/* 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->msgno,
FALSE);
if(!stream) /* oops, connection broken or the message disappeared? */
return FALSE;
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)){
if(modify) {
char buf[12];
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(unsigned seqno, const char *buf, size_t 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,
GError **err)
{
GMimeStream *partstream = NULL;
gchar **pair, *part_name;
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(msg->mailbox);
FILE *fp;
gchar *section;
ImapMessage *imsg = mi_get_imsg(mimap, msg->msgno);
if (!imsg) {
g_set_error(err,
LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_ACCESS_ERROR,
_("Error fetching message from IMAP server: %s"),
imap_mbox_handle_get_last_msg(mimap->handle));
return FALSE;
}
/* look for a part cache */
section = get_section_for(msg, part);
pair = get_cache_name_pair(mimap, "part", imsg->uid);
part_name = g_strconcat(pair[0], G_DIR_SEPARATOR_S,
pair[1], "-", section, NULL);
fp = fopen(part_name,"rb+");
if(!fp) { /* no cache element */
struct part_data dt;
ImapFetchBodyOptions ifbo;
ImapResponse rc;
LibBalsaMessageBody *parent;
libbalsa_lock_mailbox(msg->mailbox);
mimap = LIBBALSA_MAILBOX_IMAP(msg->mailbox);
dt.body = imap_message_get_body_from_section(imsg, section);
if(!dt.body) {
/* This may happen if we reconnect the data dropping the
body structures but still try refetching the
message. This can be simulated by randomly
disconnecting from the IMAP server. */
fprintf(stderr, "Cannot find data for section %s\n", section);
return FALSE;
}
dt.block = g_malloc(dt.body->octets+1);
dt.pos = 0;
if(dt.body->octets>SizeMsgThreshold)
libbalsa_information(LIBBALSA_INFORMATION_MESSAGE,
_("Downloading %u 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... */
parent = get_parent(msg->body_list, part, NULL);
if(parent == NULL)
ifbo = IMFB_NONE;
else {
if(parent->body_type == LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE)
ifbo = IMFB_HEADER;
else
ifbo = IMFB_MIME;
}
rc = IMR_OK;
if (dt.body->octets > 0)
II(rc,mimap->handle,
imap_mbox_handle_fetch_body(mimap->handle, msg->msgno,
section, FALSE, ifbo, append_str, &dt));
libbalsa_unlock_mailbox(msg->mailbox);
if(rc != IMR_OK) {
fprintf(stderr, "Error fetching imap message no %lu section %s\n",
msg->msgno, section);
g_free(dt.block);
g_free(section);
g_strfreev(pair);
g_free(part_name);
g_set_error(err,
LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_ACCESS_ERROR,
_("Error fetching message from IMAP server: %s"),
imap_mbox_handle_get_last_msg(mimap->handle));
return FALSE;
}
g_mkdir_with_parents(pair[0], S_IRUSR|S_IWUSR|S_IXUSR);
fp = fopen(part_name, "wb+");
if(!fp) {
g_free(section);
g_strfreev(pair);
g_free(part_name);
g_set_error(err,
LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_ACCESS_ERROR,
_("Cannot create temporary file"));
return FALSE;
}
if(ifbo == IMFB_NONE || dt.body->octets == 0) {
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));
}
/* Carefully save number of bytes actually read from the file. */
if (dt.pos) {
if(fwrite(dt.block, 1, dt.pos, fp) != dt.pos
|| fflush(fp) != 0) {
fclose(fp);
/* we do not want to have an incomplete part in the cache
so that the user still can try again later when the
problem with writing (disk space?) is removed */
unlink(part_name);
g_set_error(err,
LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_ACCESS_ERROR,
_("Cannot write to temporary file %s"), part_name);
g_free(section);
g_strfreev(pair);
g_free(part_name);
g_free(dt.block);
return FALSE; /* something better ? */
}
}
g_free(dt.block);
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,
GError **err)
{
g_return_val_if_fail(part, 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_object_unref(type);
if (!lbm_imap_get_msg_part_from_cache(msg, part, err))
return FALSE;
}
}
if (parent_part) {
/* GMime will unref and so will we. */
g_object_ref(part->mime_part);
g_mime_multipart_add(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)) {
if(!lbm_imap_get_msg_part(msg, part->parts, TRUE,
part->mime_part, err))
return FALSE;
}
/* ...and siblings. */
if(part->next &&
!lbm_imap_get_msg_part(msg, part->next, TRUE, parent_part, err))
return FALSE;
/* FIXME if GMIME_IS_MESSAGE_PART? */
}
return GMIME_IS_PART(part->mime_part)
|| GMIME_IS_MULTIPART(part->mime_part)
|| GMIME_IS_MESSAGE_PART(part->mime_part);
}
static gboolean
libbalsa_mailbox_imap_get_msg_part(LibBalsaMessage *msg,
LibBalsaMessageBody *part,
GError **err)
{
if (part->mime_part)
return GMIME_IS_PART(part->mime_part)
|| GMIME_IS_MULTIPART(part->mime_part)
|| GMIME_IS_MESSAGE_PART(part->mime_part);
return lbm_imap_get_msg_part(msg, part, FALSE, NULL, err);
}
/* libbalsa_mailbox_imap_duplicate_msgnos: identify messages with same
non-empty message-ids. An efficient implementation requires that a
list of msgids is maintained client side. The algorithm consists of
four phases:
a). identify x=largest UID in the message-id hash.
b). complete the message-id hash from (possibly new) envelopes.
c). check for any missing message-ids by fetching the header for
UID>x.
d). identify duplicates in the message hash. When a duplicate
is encountered, keep the one with lower MSGNO/UID.
*/
static GArray*
libbalsa_mailbox_imap_duplicate_msgnos(LibBalsaMailbox *mailbox)
{
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
unsigned first_to_fetch = 1;
GHashTable *dupes;
GArray *res;
unsigned i;
/* a+b. */
for(i=mimap->msgids->len; i>=1; i--) {
ImapMessage *imsg;
gchar *msg_id = g_ptr_array_index(mimap->msgids, i-1);
if(msg_id) {
first_to_fetch = i+1;
break;
} else {
imsg = imap_mbox_handle_get_msg(mimap->handle, i);
if(imsg && imsg->envelope)
g_ptr_array_index(mimap->msgids, i-1) =
g_strdup(imsg->envelope->message_id);
}
}
/* c. */
if(imap_mbox_complete_msgids(mimap->handle, mimap->msgids,
first_to_fetch) != IMR_OK)
return NULL;
/* d. */
dupes = g_hash_table_new(g_str_hash, g_str_equal);
res = g_array_new(FALSE, FALSE, sizeof(unsigned));
for(i=1; i<=mimap->msgids->len; i++) {
gchar *msg_id = g_ptr_array_index(mimap->msgids, i-1);
if(!msg_id) { /* g_warning("msgid not completed %u", i); */continue; }
if(!*msg_id || *msg_id == '\r' || *msg_id == '\n') continue;
if(!g_hash_table_lookup(dupes, msg_id))
g_hash_table_insert(dupes, msg_id, GINT_TO_POINTER(1));
else {
g_array_append_val(res, i);
}
}
g_hash_table_destroy(dupes);
printf("total elements: %d\n", res->len);
for(i=0; i<res->len; i++)
printf("%u ", GPOINTER_TO_UINT(g_array_index(res, unsigned, i)));
puts("");
return res;
}
/** Adds given set of messages to given imap mailbox.
Method can be called on a closed mailbox.
Called with mailbox locked.
*/
#if 0
static guint
libbalsa_mailbox_imap_add_messages(LibBalsaMailbox * mailbox,
LibBalsaAddMessageIterator msg_iterator,
void *arg, GError ** err)
{
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
LibBalsaMessageFlag flags;
GMimeStream *stream;
unsigned successfully_copied = 0;
while( msg_iterator(&flags, &stream, arg) ) {
ImapMsgFlags imap_flags = IMAP_FLAGS_EMPTY;
ImapResponse rc;
GMimeStream *tmpstream;
GMimeFilter *crlffilter;
ImapMboxHandle *handle;
gint outfd;
gchar *outfile;
GMimeStream *outstream;
gssize len;
if (!(flags & LIBBALSA_MESSAGE_FLAG_NEW))
IMSG_FLAG_SET(imap_flags, IMSGF_SEEN);
if (flags & LIBBALSA_MESSAGE_FLAG_DELETED)
IMSG_FLAG_SET(imap_flags, IMSGF_DELETED);
if (flags & LIBBALSA_MESSAGE_FLAG_FLAGGED)
IMSG_FLAG_SET(imap_flags, IMSGF_FLAGGED);
if (flags & LIBBALSA_MESSAGE_FLAG_REPLIED)
IMSG_FLAG_SET(imap_flags, IMSGF_ANSWERED);
tmpstream = g_mime_stream_filter_new(stream);
crlffilter =
g_mime_filter_crlf_new(TRUE,
FALSE);
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);
g_object_unref(stream);
return successfully_copied;
}
handle = libbalsa_mailbox_imap_get_handle(mimap, err);
if (!handle)
/* Perhaps the mailbox was closed and the authentication
failed or was cancelled? err is set already, we just
return. */
return successfully_copied;
outstream = g_mime_stream_fs_new(outfd);
libbalsa_mime_stream_shared_lock(stream);
g_mime_stream_write_to_stream(tmpstream, outstream);
libbalsa_mime_stream_shared_unlock(stream);
g_object_unref(tmpstream);
g_object_unref(stream);
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);
rc = imap_mbox_append_stream(handle, mimap->path,
imap_flags, outstream, len);
if (rc != IMR_OK) {
gchar *msg = imap_mbox_handle_get_last_msg(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);
if(rc != IMR_OK)
return successfully_copied;
successfully_copied++;
}
return successfully_copied;
}
#else
struct MultiAppendCbData {
LibBalsaAddMessageIterator msg_iterator;
void *iterator_data;
GMimeStream *outstream;
GList *outfiles;
GError **err;
guint copied;
};
static void
macd_clear(struct MultiAppendCbData *macd)
{
if(macd->outstream) {
g_object_unref(macd->outstream);
macd->outstream = NULL;
}
}
static void
macd_destroy(struct MultiAppendCbData *macd)
{
GList *outmsgs;
for(outmsgs = macd->outfiles; outmsgs; outmsgs = outmsgs->next) {
unlink(outmsgs->data);
g_free(outmsgs->data);
}
g_list_free(macd->outfiles);
}
static size_t
multi_append_cb(char * buf, size_t buflen,
ImapAppendMultiStage stage,
ImapMsgFlags *return_flags, void *arg)
{
struct MultiAppendCbData *macd = (struct MultiAppendCbData*)arg;
switch(stage) {
case IMA_STAGE_NEW_MSG: {
ImapMsgFlags imap_flags = IMAP_FLAGS_EMPTY;
GMimeStream *tmpstream;
GMimeFilter *crlffilter;
gint outfd;
GMimeStream *stream = NULL;
gint64 len;
LibBalsaMessageFlag flags;
GError**err = macd->err;
gchar *outf = NULL;
macd_clear(macd);
while( macd->msg_iterator(&flags, &stream, macd->iterator_data) &&
!stream)
;
if(!stream) /* No more messages to append! */
return 0;
if (!(flags & LIBBALSA_MESSAGE_FLAG_NEW))
IMSG_FLAG_SET(imap_flags, IMSGF_SEEN);
if (flags & LIBBALSA_MESSAGE_FLAG_DELETED)
IMSG_FLAG_SET(imap_flags, IMSGF_DELETED);
if (flags & LIBBALSA_MESSAGE_FLAG_FLAGGED)
IMSG_FLAG_SET(imap_flags, IMSGF_FLAGGED);
if (flags & LIBBALSA_MESSAGE_FLAG_REPLIED)
IMSG_FLAG_SET(imap_flags, IMSGF_ANSWERED);
tmpstream = g_mime_stream_filter_new(stream);
crlffilter =
g_mime_filter_crlf_new(TRUE,
FALSE);
g_mime_stream_filter_add(GMIME_STREAM_FILTER(tmpstream), crlffilter);
g_object_unref(crlffilter);
outfd = g_file_open_tmp("balsa-tmp-file-XXXXXX", &outf, err);
if (outfd < 0) {
g_warning("Could not create temporary file: %s", (*err)->message);
g_object_unref(tmpstream);
g_object_unref(stream);
return 0;
}
macd->outstream = g_mime_stream_fs_new(outfd);
macd->outfiles = g_list_append(macd->outfiles, outf);
libbalsa_mime_stream_shared_lock(stream);
g_mime_stream_write_to_stream(tmpstream, macd->outstream);
libbalsa_mime_stream_shared_unlock(stream);
g_object_unref(tmpstream);
g_object_unref(stream);
len = g_mime_stream_tell(macd->outstream);
g_mime_stream_reset(macd->outstream);
if (len > (signed) SizeMsgThreshold)
libbalsa_information(LIBBALSA_INFORMATION_MESSAGE,
_("Uploading %ld kB"),
(long int) ((len + 512) / 1024));
*return_flags = imap_flags;
macd->copied++;
return g_mime_stream_length(macd->outstream);
}
break;
case IMA_STAGE_PASS_DATA:
return g_mime_stream_read(macd->outstream, buf, buflen);
}
g_assert_not_reached();
return 0;
}
struct append_to_cache_data {
const gchar *user, *host, *path, *cache_dir;
GList *curr_name;
unsigned uid_validity;
};
static void
create_cache_copy(const gchar *src, const gchar *cache_dir, const gchar *name)
{
gchar *fname = libbalsa_urlencode(name);
gchar *dst = g_build_filename(cache_dir, fname, NULL);
if(link(src, dst) != 0) {
/* Link failed possibly because the two caches reside on
different file systems. We attempt to copy the cache instead. */
FILE *in = fopen(src, "r");
if(in) {
FILE *out = fopen(dst, "w");
if(out) {
size_t sz;
char buf[65536];
gboolean err;
while( (sz=fread(buf, 1, sizeof(buf), in)) > 0)
if(fwrite(buf, 1, sz, out) != sz)
break;
err = ferror(in) || ferror(out);
fclose(out);
if(err)
unlink(dst);
}
fclose(in);
}
}
g_free(fname);
g_free(dst);
}
static void
append_to_cache(unsigned uid, void *arg)
{
struct append_to_cache_data *atcd = (struct append_to_cache_data*)arg;
gchar *name = g_strdup_printf("%s@%s-%s-%u-%u-%s",
atcd->user, atcd->host, atcd->path,
atcd->uid_validity,
uid, "body");
gchar *msg = atcd->curr_name->data;
atcd->curr_name = g_list_next(atcd->curr_name);
g_return_if_fail(msg);
create_cache_copy(msg, atcd->cache_dir, name);
g_free(name);
}
static guint
libbalsa_mailbox_imap_add_messages(LibBalsaMailbox * mailbox,
LibBalsaAddMessageIterator msg_iterator,
void *arg, GError ** err)
{
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
ImapMboxHandle *handle = libbalsa_mailbox_imap_get_handle(mimap, err);
struct MultiAppendCbData macd;
ImapResponse rc;
ImapSequence uid_sequence;
if (!handle) {
/* Perhaps the mailbox was closed and the authentication
failed or was cancelled? err is set already, we just
return. */
return 0;
}
macd.msg_iterator = msg_iterator;
macd.iterator_data = arg;
macd.outstream = NULL;
macd.outfiles = NULL;
macd.err = err;
macd.copied = 0;
imap_sequence_init(&uid_sequence);
rc = imap_mbox_append_multi(handle, mimap->path,
multi_append_cb, &macd, &uid_sequence);
libbalsa_mailbox_imap_release_handle(mimap);
macd_clear(&macd);
if(!imap_sequence_empty(&uid_sequence) &&
g_list_length(macd.outfiles) == imap_sequence_length(&uid_sequence)) {
/* Hurray, server returned UID data on appended messages! */
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE(mailbox)->server;
LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s);
gboolean is_persistent = libbalsa_imap_server_has_persistent_cache(is);
struct append_to_cache_data atcd;
gchar *cache_dir;
atcd.user = s->user;
atcd.host = s->host;
atcd.path = mimap->path ? mimap->path : "INBOX";
atcd.cache_dir = cache_dir = get_cache_dir(is_persistent);
atcd.curr_name = macd.outfiles;
atcd.uid_validity = uid_sequence.uid_validity;
imap_sequence_foreach(&uid_sequence, append_to_cache, &atcd);
imap_sequence_release(&uid_sequence);
g_free(cache_dir);
}
macd_destroy(&macd);
return rc == IMR_OK ? macd.copied : 0;
}
#endif
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);
if (msg_info) {
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;
if(seqno->len == 0) return TRUE;
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);
/* Do not use the asynchronous versions until the issues related
to unsolicited EXPUNGE responses are resolved. The issues are
pretty much of a theoretical character but we do not want to
risk the mail store integrity, do we? */
if (flag_set)
II(rc, handle,
imap_mbox_store_flag(handle,
seqno->len, (guint *) seqno->data,
flag_set, TRUE));
if (rc && 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 ||
(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) {
#if !ENABLE_CLIENT_SIDE_SORT
case LIBBALSA_MAILBOX_CAN_SORT:
return imap_mbox_handle_can_do(mimap->handle, IMCAP_SORT);
#endif
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);
libbalsa_mailbox_set_msg_tree(mailbox, new_tree);
}
static void
lbm_imap_update_view_filter(LibBalsaMailbox *mailbox,
LibBalsaCondition *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)
{
LibBalsaMailboxImap *mimap;
mimap = LIBBALSA_MAILBOX_IMAP(mbox);
if (mimap->sort_field != mbox->view->sort_field) {
/* Cached ranks are invalid. */
unsigned *msgno_arr;
guint i, len;
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) {
ImapResponse rc;
/* 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;
guint cnt;
cnt = mimap->messages_info ? mimap->messages_info->len : 0;
return cnt;
}
/* 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;
LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(mailbox)->handle;
ImapSequence uid_sequence;
unsigned *seqno = (unsigned*)msgnos->data, *uids;
unsigned im;
g_return_val_if_fail(handle, FALSE);
imap_sequence_init(&uid_sequence);
/* User server-side copy. */
g_array_sort(msgnos, cmp_msgno);
uids = g_new(unsigned, msgnos->len);
for(im=0; im<msgnos->len; im++) {
ImapMessage * imsg = imap_mbox_handle_get_msg(handle, seqno[im]);
uids[im] = imsg ? imsg->uid : 0;
}
ret = imap_mbox_handle_copy(handle, msgnos->len,
(guint *) msgnos->data,
LIBBALSA_MAILBOX_IMAP(dest)->path,
&uid_sequence)
== 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);
} else if(!imap_sequence_empty(&uid_sequence)) {
/* Copy cache files. */
GDir *dir;
LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE(mailbox)->server;
LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s);
LibBalsaMailboxImap *dst_imap = LIBBALSA_MAILBOX_IMAP(dest);
gboolean is_persistent =
libbalsa_imap_server_has_persistent_cache(is);
gchar *dir_name = get_cache_dir(is_persistent);
gchar *src_prefix = g_strdup_printf("%s@%s-%s-%u-",
s->user, s->host,
(mimap->path
? mimap->path : "INBOX"),
mimap->uid_validity);
gchar *encoded_path = libbalsa_urlencode(src_prefix);
g_free(src_prefix);
dir = g_dir_open(dir_name, 0, NULL);
if(dir) {
const gchar *filename;
size_t prefix_length = strlen(encoded_path);
unsigned nth;
while ((filename = g_dir_read_name(dir)) != NULL) {
unsigned msg_uid;
gchar *tail;
if(strncmp(encoded_path, filename, prefix_length))
continue;
msg_uid = strtol(filename + prefix_length, &tail, 10);
for(im = 0; im<msgnos->len; im++) {
if(uids[im]>msg_uid) break;
else if(uids[im]==msg_uid &&
(nth = imap_sequence_nth(&uid_sequence, im))
) {
gchar *src =
g_build_filename(dir_name, filename, NULL);
gchar *dst_prefix =
g_strdup_printf("%s@%s-%s-%u-%u%s",
s->user, s->host,
(dst_imap->path
? dst_imap->path : "INBOX"),
uid_sequence.uid_validity,
nth, tail);
create_cache_copy(src, dir_name, dst_prefix);
g_free(dst_prefix);
break;
}
}
}
g_dir_close(dir);
}
g_free(dir_name);
}
g_free(uids);
imap_sequence_release(&uid_sequence);
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;
}
/** 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(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 struct ImapCacheManager*
imap_cache_manager_new_from_file(const char *header_cache_path)
{
/* The cache data should be transferable between 32- and 64-bit
systems. */
uint32_t i;
ImapUID uid;
struct ImapCacheManager *icm;
FILE *f = fopen(header_cache_path, "rb");
if(!f) {
return NULL;
}
if(fread(&i, sizeof(i), 1, f) != 1) {
printf("Could not read cache table size.\n");
fclose(f);
return NULL;
}
icm = imap_cache_manager_new(i);
if(fread(&icm->uidvalidity, sizeof(uint32_t), 1, f) != 1 ||
fread(&icm->uidnext, sizeof(uint32_t), 1, f) != 1 ||
fread(&icm->exists, sizeof(uint32_t), 1, f) != 1) {
imap_cache_manager_free(icm);
printf("Couldn't read cache - aborting...\n");
fclose(f);
return NULL;
}
i = 0;
while(fread(&uid, sizeof(uid), 1, f) == 1) {
if(uid) {
uint32_t slen; /* Architecture-independent size */
gchar *s;
if(fread(&slen, sizeof(slen), 1, f) != 1)
break;
s = g_malloc(slen+1); /* slen would be sufficient? */
if(fread(s, 1, slen, f) != slen)
break;
s[slen] = '\0'; /* Unneeded? */
g_hash_table_insert(icm->headers, GUINT_TO_POINTER(uid), s);
}
g_array_append_val(icm->uidmap, uid);
i++;
}
fclose(f);
return icm;
}
static void
imap_cache_manager_free(struct ImapCacheManager *icm)
{
g_hash_table_destroy(icm->headers);
g_array_free(icm->uidmap, TRUE);
g_free(icm);
}
/* 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.
*/
static void
set_uid(ImapMboxHandle *handle, unsigned seqno, void *arg)
{
GArray *a = (GArray*)arg;
g_array_append_val(a, seqno);
}
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 old: %u new: %u - cache invalidated\n",
icm->uidvalidity, uidvalidity);
return;
}
/* There were some modifications to the mailbox but the situation
* is not hopeless, we just need to get the seqnos of messages in
* the cache. */
if(exists - icm->exists != uidnext - icm->uidnext) {
ImapResponse rc;
GArray *uidmap = g_array_sized_new(FALSE, TRUE,
sizeof(uint32_t), icm->exists);
ImapSearchKey *k;
unsigned lo = icm->uidmap->len+1, hi = 0, i;
/* printf("UIDSYNC:Searching range [1:%u]\n", icm->uidmap->len); */
for(i=1; i<=icm->uidmap->len; i++)
if(g_array_index(icm->uidmap, uint32_t, i-1)) {lo=i; break; }
for(i=icm->uidmap->len; i>=lo; i--)
if(g_array_index(icm->uidmap, uint32_t, i-1)) {hi=i; break; }
k = imap_search_key_new_range(FALSE, FALSE, lo, hi);
/* printf("UIDSYNC: Old vs new: exists: %u %u uidnext: %u %u "
"- syncing uid map for msgno [%u:%u].\n",
icm->exists, exists, icm->uidnext, uidnext, lo, hi); */
if(k) {
uidmap->len = lo-1;
rc = imap_search_exec(h, TRUE, k, set_uid, uidmap);
imap_search_key_free(k);
} else rc = IMR_NO;
if(rc != IMR_OK) {
g_array_free(uidmap, TRUE);
return;
}
g_array_free(icm->uidmap, TRUE); icm->uidmap = uidmap;
/* printf("New uidmap has length: %u\n", icm->uidmap->len); */
}
/* One way or another, we have a valid uid->seqno map now;
* The mailbox data can be resynced easily. */
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);
}
}
/** Stores (possibly persistently) data associated with given handle.
This allows for quick restore between IMAP sessions and reduces
synchronization overhead. */
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=0; i<cnt; i++) {
void *ptr;
ImapMessage *imsg = imap_mbox_handle_get_msg(handle, i+1);
unsigned uid;
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;
g_array_append_val(icm->uidmap, uid);
}
return icm;
}
static gboolean
icm_save_header(uint32_t uid, gpointer value, FILE *f)
{
if(fwrite(&uid, sizeof(uid), 1, f) != 1) return FALSE;
if(uid) {
uint32_t slen = imap_serialized_message_size(value);
if(fwrite(&slen, sizeof(slen), 1, f) != 1 ||
fwrite(value, 1, slen, f) != slen)
return FALSE;
}
return TRUE;
}
static gboolean
icm_save_to_file(struct ImapCacheManager *icm, const gchar *file_name)
{
gboolean success;
FILE *f = fopen(file_name, "wb");
success = f != NULL;
if(success) {
uint32_t i = icm->uidmap->len;
if(fwrite(&i, sizeof(i), 1, f) != 1 ||
fwrite(&icm->uidvalidity, sizeof(uint32_t), 1, f) != 1 ||
fwrite(&icm->uidnext, sizeof(uint32_t), 1, f) != 1 ||
fwrite(&icm->exists, sizeof(uint32_t), 1, f) != 1) {
success = FALSE;
} else {
for(i = 0; i<icm->uidmap->len; i++) {
uint32_t uid = g_array_index(icm->uidmap, uint32_t, i);
gpointer value = g_hash_table_lookup(icm->headers,
GUINT_TO_POINTER(uid));
if(!icm_save_header(uid, value, f)) {
success = FALSE;
break;
}
}
}
fclose(f);
}
return success;
}