/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */ /* Balsa E-Mail Client * Copyright (C) 1997-2003 Stuart Parmenter and others, * See the file AUTHORS for a list. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /* NOTES: CACHING: persistent cache is implemented using a directory. CONNECTIONS: there is always one connection per opened mailbox to keep track of untagged responses. Understand idea of untagged responses particularly for shared mailboxes before you try messing with this. */ #include "config.h" #define _XOPEN_SOURCE 500 #include #include /* for open() */ #include #include #ifdef BALSA_USE_THREADS #include #endif #include /* for gnome-i18n.h, gnome-config and gnome-util */ #include "libimap.h" #include "filter-funcs.h" #include "filter.h" #include "mailbox-filter.h" #include "message.h" #include "libbalsa_private.h" #include "misc.h" #include "imap-handle.h" #include "imap-commands.h" #include "imap-server.h" struct _LibBalsaMailboxImap { LibBalsaMailboxRemote mailbox; ImapMboxHandle *handle; /* stream that has this mailbox selected */ guint handle_refs; /* reference counter */ gint search_stamp; /* search result validator */ gchar *path; /* Imap local path (third part of URL) */ ImapUID uid_validity; GArray* messages_info; gboolean opened; }; struct _LibBalsaMailboxImapClass { LibBalsaMailboxRemoteClass klass; }; struct message_info { LibBalsaMessage *message; }; static LibBalsaMailboxClass *parent_class = NULL; static off_t ImapCacheSize = 15*1024*1024; /* 15MB */ /* issue message if downloaded part has more than this size */ static unsigned SizeMsgThreshold = 50*1024; static void libbalsa_mailbox_imap_finalize(GObject * object); static void libbalsa_mailbox_imap_class_init(LibBalsaMailboxImapClass * klass); static void libbalsa_mailbox_imap_init(LibBalsaMailboxImap * mailbox); static gboolean libbalsa_mailbox_imap_open(LibBalsaMailbox * mailbox, GError **err); static void libbalsa_mailbox_imap_close(LibBalsaMailbox * mailbox, gboolean expunge); static GMimeStream *libbalsa_mailbox_imap_get_message_stream(LibBalsaMailbox * mailbox, LibBalsaMessage * message); static void libbalsa_mailbox_imap_check(LibBalsaMailbox * mailbox); static void libbalsa_mailbox_imap_search_iter_free(LibBalsaMailboxSearchIter * iter); static gboolean libbalsa_mailbox_imap_message_match(LibBalsaMailbox* mailbox, guint msgno, LibBalsaMailboxSearchIter * search_iter); static gboolean libbalsa_mailbox_imap_can_match(LibBalsaMailbox *mbox, LibBalsaCondition *condition); static void libbalsa_mailbox_imap_save_config(LibBalsaMailbox * mailbox, const gchar * prefix); static void libbalsa_mailbox_imap_load_config(LibBalsaMailbox * mailbox, const gchar * prefix); static gboolean libbalsa_mailbox_imap_sync(LibBalsaMailbox * mailbox, gboolean expunge); static LibBalsaMessage* libbalsa_mailbox_imap_get_message(LibBalsaMailbox* mailbox, guint msgno); static void libbalsa_mailbox_imap_prepare_threading(LibBalsaMailbox *mailbox, guint lo, guint hi); static void libbalsa_mailbox_imap_fetch_structure(LibBalsaMailbox *mailbox, LibBalsaMessage *message, LibBalsaFetchFlag flags); static void libbalsa_mailbox_imap_fetch_headers(LibBalsaMailbox *mailbox, LibBalsaMessage *message); static const gchar* libbalsa_mailbox_imap_get_msg_part(LibBalsaMessage *msg, LibBalsaMessageBody *, ssize_t *); static int libbalsa_mailbox_imap_add_message(LibBalsaMailbox * mailbox, LibBalsaMessage * message, GError **err); static void libbalsa_mailbox_imap_change_message_flags(LibBalsaMailbox * mailbox, guint msgno, LibBalsaMessageFlag set, LibBalsaMessageFlag clear); static gboolean lbm_imap_messages_change_flags(LibBalsaMailbox * mailbox, GArray * seqno, LibBalsaMessageFlag set, LibBalsaMessageFlag clear); static gboolean libbalsa_mailbox_imap_can_do(LibBalsaMailbox* mbox, enum LibBalsaMailboxCapability c); static void libbalsa_mailbox_imap_set_threading(LibBalsaMailbox *mailbox, LibBalsaMailboxThreadingType thread_type); static void lbm_imap_update_view_filter(LibBalsaMailbox *mailbox, LibBalsaCondition *view_filter); static void libbalsa_mailbox_imap_sort(LibBalsaMailbox *mailbox, GArray *array); static guint libbalsa_mailbox_imap_total_messages(LibBalsaMailbox * mailbox); static gboolean libbalsa_mailbox_imap_messages_copy(LibBalsaMailbox * mailbox, GArray * msgnos, LibBalsaMailbox * dest, GError **err); static void server_host_settings_changed_cb(LibBalsaServer * server, gchar * host, #ifdef USE_SSL gboolean use_ssl, #endif LibBalsaMailbox * mailbox); static struct message_info *message_info_from_msgno( LibBalsaMailboxImap * mimap, guint msgno) { struct message_info *msg_info = &g_array_index(mimap->messages_info, struct message_info, msgno - 1); return msg_info; } #define IMAP_MESSAGE_UID(msg) \ (mi_get_imsg(LIBBALSA_MAILBOX_IMAP((msg)->mailbox),\ (msg)->msgno)->uid) #define IMAP_MAILBOX_UID_VALIDITY(mailbox) (LIBBALSA_MAILBOX_IMAP(mailbox)->uid_validity) GType libbalsa_mailbox_imap_get_type(void) { static GType mailbox_type = 0; if (!mailbox_type) { static const GTypeInfo mailbox_info = { sizeof(LibBalsaMailboxImapClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) libbalsa_mailbox_imap_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(LibBalsaMailboxImap), 0, /* n_preallocs */ (GInstanceInitFunc) libbalsa_mailbox_imap_init }; mailbox_type = g_type_register_static(LIBBALSA_TYPE_MAILBOX_REMOTE, "LibBalsaMailboxImap", &mailbox_info, 0); } return mailbox_type; } static void libbalsa_mailbox_imap_class_init(LibBalsaMailboxImapClass * klass) { GObjectClass *object_class; LibBalsaMailboxClass *libbalsa_mailbox_class; object_class = G_OBJECT_CLASS(klass); libbalsa_mailbox_class = LIBBALSA_MAILBOX_CLASS(klass); parent_class = g_type_class_peek_parent(klass); object_class->finalize = libbalsa_mailbox_imap_finalize; libbalsa_mailbox_class->open_mailbox = libbalsa_mailbox_imap_open; libbalsa_mailbox_class->close_mailbox = libbalsa_mailbox_imap_close; libbalsa_mailbox_class->check = libbalsa_mailbox_imap_check; libbalsa_mailbox_class->search_iter_free = libbalsa_mailbox_imap_search_iter_free; libbalsa_mailbox_class->message_match = libbalsa_mailbox_imap_message_match; libbalsa_mailbox_class->can_match = libbalsa_mailbox_imap_can_match; libbalsa_mailbox_class->save_config = libbalsa_mailbox_imap_save_config; libbalsa_mailbox_class->load_config = libbalsa_mailbox_imap_load_config; libbalsa_mailbox_class->sync = libbalsa_mailbox_imap_sync; libbalsa_mailbox_class->get_message = libbalsa_mailbox_imap_get_message; libbalsa_mailbox_class->prepare_threading = libbalsa_mailbox_imap_prepare_threading; libbalsa_mailbox_class->fetch_message_structure = libbalsa_mailbox_imap_fetch_structure; libbalsa_mailbox_class->fetch_headers = libbalsa_mailbox_imap_fetch_headers; libbalsa_mailbox_class->get_message_part = libbalsa_mailbox_imap_get_msg_part; libbalsa_mailbox_class->get_message_stream = libbalsa_mailbox_imap_get_message_stream; libbalsa_mailbox_class->add_message = libbalsa_mailbox_imap_add_message; libbalsa_mailbox_class->change_message_flags = libbalsa_mailbox_imap_change_message_flags; libbalsa_mailbox_class->messages_change_flags = lbm_imap_messages_change_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; } /* 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; } if (G_OBJECT_CLASS(parent_class)->finalize) G_OBJECT_CLASS(parent_class)->finalize(object); } LibBalsaMailboxImap* libbalsa_mailbox_imap_new(void) { LibBalsaMailboxImap *mailbox; mailbox = g_object_new(LIBBALSA_TYPE_MAILBOX_IMAP, NULL); return mailbox; } /* libbalsa_mailbox_imap_update_url: this is to be used only by mailboxImap functions, with exception for the folder scanner, which has to go around libmutt limitations. */ void libbalsa_mailbox_imap_update_url(LibBalsaMailboxImap* mailbox) { LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox); g_free(LIBBALSA_MAILBOX(mailbox)->url); LIBBALSA_MAILBOX(mailbox)->url = libbalsa_imap_url(s, mailbox->path); } void libbalsa_mailbox_imap_set_path(LibBalsaMailboxImap* mailbox, const gchar* path) { g_return_if_fail(mailbox); g_free(mailbox->path); mailbox->path = g_strdup(path); libbalsa_mailbox_imap_update_url(mailbox); } const gchar* libbalsa_mailbox_imap_get_path(LibBalsaMailboxImap * mailbox) { return mailbox->path; } static void server_host_settings_changed_cb(LibBalsaServer * server, gchar * host, #ifdef USE_SSL gboolean use_ssl, #endif LibBalsaMailbox * mailbox) { libbalsa_mailbox_imap_update_url(LIBBALSA_MAILBOX_IMAP(mailbox)); } static gchar* get_cache_dir(LibBalsaMailboxImap* mailbox, const gchar* type, gboolean is_persistent) { gchar *fname; if(mailbox && is_persistent) { LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox); gchar *start; const gchar *home = g_get_home_dir(); fname = g_strconcat(home, "/.balsa/", s->user, "@", s->host, "-", (mailbox->path ? mailbox->path : "INBOX"), "-", type, ".dir", NULL); for(start=fname+strlen(home)+8; *start; start++) if(*start == '/') *start = '-'; } else fname = g_strconcat("/tmp/balsa-", g_get_user_name(), NULL); return fname; } static gchar** get_cache_name_pair(LibBalsaMailboxImap* mailbox, const gchar *type, ImapUID uid) { LibBalsaServer *s = LIBBALSA_MAILBOX_REMOTE(mailbox)->server; LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s); gboolean is_persistent = libbalsa_imap_server_has_persistent_cache(is); gchar **res = g_malloc(3*sizeof(gchar*)); ImapUID uid_validity = LIBBALSA_MAILBOX_IMAP(mailbox)->uid_validity; res[0] = get_cache_dir(mailbox, type, is_persistent); if(is_persistent) res[1] = g_strdup_printf("%u-%u", uid_validity, uid); else { gchar *start; res[1] = g_strdup_printf("%s@%s-%s-%s-%u-%u", s->user, s->host, (mailbox->path ? mailbox->path : "INBOX"), type, uid_validity, uid); for(start=res[1]; *start; start++) if(*start == '/') *start = '-'; } res[2] = NULL; return res; } /* clean_cache: removes unused entries from the cache file. */ struct file_info { char *name; off_t size; time_t time; }; static gint cmp_by_time (gconstpointer a, gconstpointer b) { return ((const struct file_info*)b)->time -((const struct file_info*)a)->time; } static void clean_dir(const char *dir_name, off_t cache_size) { DIR* dir; struct dirent* key; GList *list, *lst; off_t sz; dir = opendir(dir_name); if(!dir) return; list = NULL; while ( (key=readdir(dir)) != NULL) { struct stat st; struct file_info *fi; gchar *fname = g_strconcat(dir_name, "/", key->d_name, NULL); if(stat(fname, &st) == -1 || !S_ISREG(st.st_mode)) { g_free(fname); continue; } fi = g_new(struct file_info,1); fi->name = fname; fi->size = st.st_size; fi->time = st.st_atime; list = g_list_prepend(list, fi); } closedir(dir); list = g_list_sort(list, cmp_by_time); sz = 0; for(lst = list; lst; lst = lst->next) { struct file_info *fi = (struct file_info*)(lst->data); sz += fi->size; if(sz>cache_size) { printf("removing %s\n", fi->name); unlink(fi->name); } g_free(fi->name); g_free(fi); } g_list_free(list); } static gboolean clean_cache(LibBalsaMailbox* mailbox) { LibBalsaServer *s= LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox); gboolean is_persistent = libbalsa_imap_server_has_persistent_cache(LIBBALSA_IMAP_SERVER(s)); gchar* dir; dir = get_cache_dir(LIBBALSA_MAILBOX_IMAP(mailbox), "body", is_persistent); clean_dir(dir, ImapCacheSize); g_free(dir); /* the body and part dirs are different only with persistent caching. */ if(is_persistent) { dir = get_cache_dir(LIBBALSA_MAILBOX_IMAP(mailbox), "part", is_persistent); clean_dir(dir, ImapCacheSize); g_free(dir); } return TRUE; } struct ImapCacheManager; static void icm_destroy(struct ImapCacheManager *icm); static struct ImapCacheManager *icm_store_cached_data(ImapMboxHandle *h); static void icm_restore_from_cache(ImapMboxHandle *h, struct ImapCacheManager *icm); static ImapResult mi_reconnect(ImapMboxHandle *h) { struct ImapCacheManager *icm = icm_store_cached_data(h); ImapResult r = imap_mbox_handle_reconnect(h, NULL); if(r==IMAP_SUCCESS) icm_restore_from_cache(h, icm); icm_destroy(icm); return r; } /* ImapIssue macro handles reconnecting. We might issue a LIBBALSA_INFORMATION_MESSAGE here but it would be overwritten by login information... */ #define II(rc,h,line) \ {int trials=2;do{\ if(imap_mbox_is_disconnected(h)) mi_reconnect(h);\ rc=line; \ if(rc==IMR_SEVERED) \ libbalsa_information(LIBBALSA_INFORMATION_WARNING, \ _("IMAP connection has been severed. Reconnecting...")); \ else if(rc==IMR_BYE) {char *msg = imap_mbox_handle_get_last_msg(h); \ libbalsa_information(LIBBALSA_INFORMATION_WARNING, \ _("IMAP server has shut the connection: %s Reconnecting..."), msg); \ g_free(msg);}\ else break;}while(trials-->0);} static ImapMboxHandle * libbalsa_mailbox_imap_get_handle(LibBalsaMailboxImap *mimap, GError **err) { g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mimap), NULL); if(!mimap->handle) { LibBalsaServer *server = LIBBALSA_MAILBOX_REMOTE_SERVER(mimap); LibBalsaImapServer *imap_server; if (!LIBBALSA_IS_IMAP_SERVER(server)) return NULL; imap_server = LIBBALSA_IMAP_SERVER(server); mimap->handle = libbalsa_imap_server_get_handle(imap_server, err); mimap->handle_refs = 1; } else ++mimap->handle_refs; return mimap->handle; } #define RELEASE_HANDLE(mailbox,handle) \ libbalsa_imap_server_release_handle( \ LIBBALSA_IMAP_SERVER(LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox)),\ handle) static void lbimap_update_flags(LibBalsaMessage *message, ImapMessage *imsg) { message->flags = 0; if (!IMSG_FLAG_SEEN(imsg->flags)) message->flags |= LIBBALSA_MESSAGE_FLAG_NEW; if (IMSG_FLAG_DELETED(imsg->flags)) message->flags |= LIBBALSA_MESSAGE_FLAG_DELETED; if (IMSG_FLAG_FLAGGED(imsg->flags)) message->flags |= LIBBALSA_MESSAGE_FLAG_FLAGGED; if (IMSG_FLAG_ANSWERED(imsg->flags)) message->flags |= LIBBALSA_MESSAGE_FLAG_REPLIED; if (IMSG_FLAG_RECENT(imsg->flags)) message->flags |= LIBBALSA_MESSAGE_FLAG_RECENT; } /* mi_get_imsg is a thin wrapper around imap_mbox_handle_get_msg(). We wrap around imap_mbox_handle_get_msg() in case the libimap data was invalidated by eg. disconnect. */ struct collect_seq_data { unsigned *msgno_arr; unsigned cnt; unsigned needed_msgno; unsigned has_it; }; static const unsigned MAX_CHUNK_LENGTH = 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; ihandle, imap_mbox_handle_fetch_set(mimap->handle, csd.msgno_arr, csd.cnt, IMFETCH_FLAGS | IMFETCH_UID | IMFETCH_ENV | IMFETCH_RFC822SIZE | IMFETCH_CONTENT_TYPE)); g_free(csd.msgno_arr); if (rc != IMR_OK) return FALSE; return imap_mbox_handle_get_msg(mimap->handle, msgno); } /** imap_flags_cb() is called by the imap backend when flags are fetched. Note that we may not have yet the preprocessed data in LibBalsaMessage. We ignore the info in this case. */ static void imap_flags_cb(unsigned cnt, const unsigned seqno[], LibBalsaMailboxImap *mimap) { LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap); unsigned i; libbalsa_lock_mailbox(mailbox); for(i=0; imessage) { /* 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]); LibBalsaMessageFlag old_flags = msg_info->message->flags; GList *list; if(!imsg) continue; lbimap_update_flags(msg_info->message, imsg); libbalsa_mailbox_index_set_flags(mailbox, seqno[i], msg_info->message->flags); libbalsa_mailbox_msgno_changed(mailbox, seqno[i]); ++mimap->search_stamp; list = g_list_prepend(NULL, msg_info->message); libbalsa_mailbox_messages_status_changed (mailbox, list, (old_flags ^ msg_info->message->flags)); g_list_free_1(list); } } libbalsa_unlock_mailbox(mailbox); } /* Forward reference. */ static void lbm_imap_get_unseen(LibBalsaMailboxImap * mimap); static gboolean update_counters_and_filter(void *data) { LibBalsaMailbox *mailbox= (LibBalsaMailbox*)data; gdk_threads_enter(); libbalsa_lock_mailbox(mailbox); libbalsa_mailbox_run_filters_on_reception(mailbox); lbm_imap_get_unseen(LIBBALSA_MAILBOX_IMAP(mailbox)); libbalsa_unlock_mailbox(mailbox); gdk_threads_leave(); g_object_unref(G_OBJECT(mailbox)); return FALSE; } static void imap_exists_cb(ImapMboxHandle *handle, LibBalsaMailboxImap *mimap) { unsigned cnt = imap_mbox_handle_get_exists(mimap->handle); LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap); if(cntmessages_info->len) { /* remove messages */ printf("%s: expunge ignored?\n", __func__); } else if (cnt > mimap->messages_info->len) { /* new messages arrived */ unsigned i; struct message_info a = {0}; /* EXISTS response may result from any IMAP action. */ libbalsa_lock_mailbox(mailbox); i=mimap->messages_info->len+1; do { g_array_append_val(mimap->messages_info, a); /* dummy entry in mindex for now */ g_ptr_array_add(mailbox->mindex, NULL); libbalsa_mailbox_msgno_inserted(mailbox, i); } while (++i <= cnt); ++mimap->search_stamp; /* we run filters and get unseen messages in a idle callback: * these things do not need to be done immediately and we do * not want to issue too many new overlapping IMAP requests. */ g_object_ref(G_OBJECT(mailbox)); g_idle_add(update_counters_and_filter, mailbox); libbalsa_unlock_mailbox(mailbox); } } static void imap_expunge_cb(ImapMboxHandle *handle, unsigned seqno, LibBalsaMailboxImap *mimap) { guint i; LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap); struct message_info *msg_info = message_info_from_msgno(mimap, seqno); libbalsa_lock_mailbox(mailbox); libbalsa_mailbox_msgno_removed(mailbox, seqno); ++mimap->search_stamp; if(msg_info->message) { gchar **pair = get_cache_name_pair(mimap, "body", IMAP_MESSAGE_UID(msg_info->message)); gchar *fn = g_strconcat(pair[0], "/", pair[1], NULL); unlink(fn); /* ignore error; perhaps the message * was not in the cache. */ g_free(fn); g_strfreev(pair); g_object_unref(G_OBJECT(msg_info->message)); } g_array_remove_index(mimap->messages_info, seqno-1); for (i = seqno - 1; i < mimap->messages_info->len; i++) { struct message_info *msg_info = &g_array_index(mimap->messages_info, struct message_info, i); if (msg_info->message) msg_info->message->msgno = i + 1; } libbalsa_unlock_mailbox(mailbox); } static void libbalsa_mailbox_imap_release_handle(LibBalsaMailboxImap * mimap) { g_assert(mimap->handle != NULL); g_assert(mimap->handle_refs > 0); if (--mimap->handle_refs == 0) { /* Only selected handles have these signal handlers, but we'll * disconnect them anyway. */ g_signal_handlers_disconnect_matched(mimap->handle, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, mimap); imap_handle_set_flagscb(mimap->handle, NULL, NULL); RELEASE_HANDLE(mimap, mimap->handle); mimap->handle = NULL; } } static ImapMboxHandle * libbalsa_mailbox_imap_get_selected_handle(LibBalsaMailboxImap *mimap, GError **err) { LibBalsaServer *server; LibBalsaImapServer *imap_server; ImapResponse rc; unsigned uidval; g_return_val_if_fail(LIBBALSA_MAILBOX_IMAP(mimap), NULL); server = LIBBALSA_MAILBOX_REMOTE_SERVER(mimap); if (!LIBBALSA_IS_IMAP_SERVER(server)) return NULL; imap_server = LIBBALSA_IMAP_SERVER(server); if(!mimap->handle) { mimap->handle = libbalsa_imap_server_get_handle_with_user(imap_server, mimap, err); if (!mimap->handle) return NULL; } II(rc,mimap->handle, imap_mbox_select(mimap->handle, mimap->path, &(LIBBALSA_MAILBOX(mimap)->readonly))); if (rc != IMR_OK) { gchar *msg = imap_mbox_handle_get_last_msg(mimap->handle); g_set_error(err, LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_OPEN_ERROR, "%s", msg); g_free(msg); RELEASE_HANDLE(mimap, mimap->handle); mimap->handle = NULL; return NULL; } /* test validity */ uidval = imap_mbox_handle_get_validity(mimap->handle); if (mimap->uid_validity != uidval) { mimap->uid_validity = uidval; /* FIXME: update/remove msg uids */ } imap_handle_set_flagscb(mimap->handle, (ImapFlagsCb)imap_flags_cb, mimap); g_signal_connect(G_OBJECT(mimap->handle), "exists-notify", G_CALLBACK(imap_exists_cb), mimap); g_signal_connect(G_OBJECT(mimap->handle), "expunge-notify", G_CALLBACK(imap_expunge_cb), mimap); mimap->handle_refs = 1; return mimap->handle; } /* Get the list of unseen messages from the server and set * the unread-messages count. */ static void lbm_imap_get_unseen(LibBalsaMailboxImap * mimap) { LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mimap); guint i, count, total = imap_mbox_handle_get_exists(mimap->handle); mailbox->first_unread = total; for(i=count=0; ihandle, i+1, 0, IMSGF_SEEN|IMSGF_DELETED)) { count++; if(mailbox->first_unread>i) mailbox->first_unread = i+1; } } mailbox->unread_messages = count; libbalsa_mailbox_set_unread_messages_flag(mailbox, count > 0); } /* libbalsa_mailbox_imap_open: opens IMAP mailbox. On failure leaves the object in sane state. */ static gboolean libbalsa_mailbox_imap_open(LibBalsaMailbox * mailbox, GError **err) { LibBalsaMailboxImap *mimap; LibBalsaServer *server; unsigned i; guint total_messages; struct ImapCacheManager *icm; g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), FALSE); mimap = LIBBALSA_MAILBOX_IMAP(mailbox); server = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox); mimap->handle = libbalsa_mailbox_imap_get_selected_handle(mimap, err); if (!mimap->handle) { mimap->opened = FALSE; mailbox->disconnected = TRUE; return FALSE; } mimap->opened = TRUE; mailbox->disconnected = FALSE; total_messages = imap_mbox_handle_get_exists(mimap->handle); mimap->messages_info = g_array_sized_new(FALSE, TRUE, sizeof(struct message_info), total_messages); for(i=0; i < total_messages; i++) { struct message_info a = {0}; g_array_append_val(mimap->messages_info, a); /* dummy entry in mindex for now */ g_ptr_array_add(mailbox->mindex, NULL); } icm = g_object_get_data(G_OBJECT(mailbox), "cache-manager"); if(icm) icm_restore_from_cache(mimap->handle, icm); mailbox->first_unread = imap_mbox_handle_first_unseen(mimap->handle); libbalsa_mailbox_run_filters_on_reception(mailbox); lbm_imap_get_unseen(mimap); if (mimap->search_stamp) ++mimap->search_stamp; else mimap->search_stamp = mailbox->stamp; #ifdef DEBUG g_print(_("%s: Opening %s Refcount: %d\n"), "LibBalsaMailboxImap", mailbox->name, mailbox->open_ref); #endif return TRUE; } static void free_messages_info(LibBalsaMailboxImap * mbox) { guint i; GArray *messages_info = mbox->messages_info; for (i = 0; i < messages_info->len; i++) { struct message_info *msg_info = &g_array_index(messages_info, struct message_info, i); if (msg_info->message) { msg_info->message->mailbox = NULL; g_object_unref(msg_info->message); } } g_array_free(mbox->messages_info, TRUE); mbox->messages_info = NULL; } static void libbalsa_mailbox_imap_close(LibBalsaMailbox * mailbox, gboolean expunge) { LibBalsaMailboxImap *mbox = LIBBALSA_MAILBOX_IMAP(mailbox); /* FIXME: save headers differently: save_to_cache(mailbox); */ clean_cache(mailbox); mbox->opened = FALSE; g_object_set_data(G_OBJECT(mailbox), "cache-manager", icm_store_cached_data(mbox->handle)); /* we do not attempt to reconnect here */ if (expunge) imap_mbox_close(mbox->handle); else imap_mbox_unselect(mbox->handle); free_messages_info(mbox); libbalsa_mailbox_imap_release_handle(mbox); } static FILE* get_cache_stream(LibBalsaMailbox *mailbox, LibBalsaMessage *msg) { unsigned uid = IMAP_MESSAGE_UID(msg); LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox); FILE *stream; gchar **pair, *path; g_assert(mimap->handle); pair = get_cache_name_pair(mimap, "body", uid); path = g_strconcat(pair[0], "/", pair[1], NULL); stream = fopen(path, "rb"); if(!stream) { FILE *cache; ImapResponse rc; libbalsa_assure_balsa_dir(); mkdir(pair[0], S_IRUSR|S_IWUSR|S_IXUSR); /* ignore errors */ if(msg->length>(signed)SizeMsgThreshold) libbalsa_information(LIBBALSA_INFORMATION_MESSAGE, _("Downloading %ld kB"), msg->length/1024); cache = fopen(path, "wb"); II(rc,mimap->handle, imap_mbox_handle_fetch_rfc822_uid(mimap->handle, uid, cache)); fclose(cache); stream = fopen(path,"rb"); } g_free(path); g_strfreev(pair); return stream; } /* libbalsa_mailbox_imap_get_message_stream: Fetch data from cache first, if available. When calling imap_fetch_message(), we make use of fact that imap_fetch_message doesn't set msg->path field. */ static GMimeStream * libbalsa_mailbox_imap_get_message_stream(LibBalsaMailbox * mailbox, LibBalsaMessage * message) { FILE *stream; g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), NULL); g_return_val_if_fail(LIBBALSA_IS_MESSAGE(message), NULL); libbalsa_lock_mailbox(mailbox); /* this may get called when the mailbox is being opened ie, open_ref==0 */ if(!LIBBALSA_MAILBOX_IMAP(mailbox)->handle) { libbalsa_unlock_mailbox(mailbox); return NULL; } stream = get_cache_stream(mailbox, message); libbalsa_unlock_mailbox(mailbox); return g_mime_stream_file_new(stream); } /* libbalsa_mailbox_imap_check: checks imap mailbox for new messages. Called with the mailbox locked. */ struct mark_info { const gchar *path; gboolean marked; }; static void lbm_imap_list_cb(ImapMboxHandle * handle, int delim, ImapMboxFlags * flags, char *folder, gpointer data) { struct mark_info *info = data; if (strcmp(folder, info->path) == 0 && IMAP_MBOX_HAS_FLAG(*flags, IMLIST_MARKED)) info->marked = TRUE; } static gboolean lbm_imap_check(LibBalsaMailbox * mailbox) { LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox); ImapMboxHandle *handle; gulong id; struct mark_info info; handle = libbalsa_mailbox_imap_get_handle(mimap, NULL); if (!handle) return FALSE; info.path = mimap->path; info.marked = FALSE; id = g_signal_connect(G_OBJECT(handle), "list-response", G_CALLBACK(lbm_imap_list_cb), &info); if (imap_mbox_list(handle, mimap->path) != IMR_OK) info.marked = FALSE; g_signal_handler_disconnect(G_OBJECT(handle), id); libbalsa_mailbox_imap_release_handle(mimap); return info.marked; } static void libbalsa_mailbox_imap_check(LibBalsaMailbox * mailbox) { g_assert(LIBBALSA_IS_MAILBOX_IMAP(mailbox)); if (!MAILBOX_OPEN(mailbox)) { libbalsa_mailbox_set_unread_messages_flag(mailbox, lbm_imap_check(mailbox)); return; } if (LIBBALSA_MAILBOX_IMAP(mailbox)->handle) libbalsa_mailbox_imap_noop(LIBBALSA_MAILBOX_IMAP(mailbox)); else g_warning("mailbox has open_ref>0 but no handle!\n"); } /* Search iters */ static ImapSearchKey *lbmi_build_imap_query(const LibBalsaCondition * cond, ImapSearchKey * last); static gboolean libbalsa_mailbox_imap_message_match(LibBalsaMailbox* mailbox, guint msgno, LibBalsaMailboxSearchIter * search_iter) { LibBalsaMailboxImap *mimap; struct message_info *msg_info; GHashTable *matchings; mimap = LIBBALSA_MAILBOX_IMAP(mailbox); msg_info = message_info_from_msgno(mimap, msgno); if (msg_info->message) g_object_ref(msg_info->message); else if (imap_mbox_handle_get_msg(mimap->handle, msgno) && imap_mbox_handle_get_msg(mimap->handle, msgno)->envelope) /* The backend has already downloaded the data, we can just * convert it to LibBalsaMessage. */ libbalsa_mailbox_imap_get_message(mailbox, msgno); if (msg_info->message) { if (libbalsa_condition_can_match(search_iter->condition, msg_info->message)) { gboolean retval = libbalsa_condition_matches(search_iter->condition, msg_info->message); g_object_unref(msg_info->message); return retval; } g_object_unref(msg_info->message); } if (search_iter->stamp != mimap->search_stamp && search_iter->mailbox && LIBBALSA_MAILBOX_GET_CLASS(search_iter->mailbox)-> search_iter_free) LIBBALSA_MAILBOX_GET_CLASS(search_iter->mailbox)-> search_iter_free(search_iter); matchings = search_iter->user_data; if (!matchings) { ImapSearchKey* query; ImapResult rc; matchings = g_hash_table_new(NULL, NULL); query = lbmi_build_imap_query(search_iter->condition, NULL); II(rc,mimap->handle, imap_mbox_filter_msgnos(mimap->handle, query, matchings)); imap_search_key_free(query); if (rc != IMR_OK) { g_hash_table_destroy(matchings); return FALSE; } search_iter->user_data = matchings; search_iter->mailbox = mailbox; search_iter->stamp = mimap->search_stamp; } return g_hash_table_lookup(matchings, GUINT_TO_POINTER(msgno)) != NULL; } static void libbalsa_mailbox_imap_search_iter_free(LibBalsaMailboxSearchIter * iter) { GHashTable *matchings = iter->user_data; if (matchings) { g_hash_table_destroy(matchings); iter->user_data = NULL; } /* iter->condition and iter are freed in the LibBalsaMailbox method. */ } /* add_or_query() adds a new term to an set of eqs. that can be or-ed. There are at least two ways to do it: a). transform a and b to NOT (NOT a NOT b) b). transform a and b to OR a b We keep it simple. */ static ImapSearchKey* add_or_query(ImapSearchKey *or_query, gboolean neg, ImapSearchKey *new_term) { if(!or_query) return new_term; if(neg) { imap_search_key_set_next(new_term, or_query); return new_term; } else return imap_search_key_new_or(FALSE, new_term, or_query); } static ImapSearchKey* lbmi_build_imap_query(const LibBalsaCondition* cond, ImapSearchKey *next) { gboolean neg; ImapSearchKey *query = NULL; int cnt=0; if(!cond) return NULL; neg = cond->negate; switch (cond->type) { case CONDITION_STRING: if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_TO)) cnt++,query = add_or_query (query, neg, imap_search_key_new_string (neg, IMSE_S_TO, cond->match.string.string, NULL)); if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_FROM)) cnt++,query = add_or_query (query, neg, imap_search_key_new_string (neg, IMSE_S_FROM, cond->match.string.string, NULL)); if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_SUBJECT)) cnt++,query = add_or_query (query, neg, imap_search_key_new_string (neg, IMSE_S_SUBJECT,cond->match.string.string, NULL)); if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_CC)) cnt++,query = add_or_query (query, neg, imap_search_key_new_string (neg, IMSE_S_CC, cond->match.string.string, NULL)); if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_BODY)) cnt++,query = add_or_query (query, neg, imap_search_key_new_string (neg, IMSE_S_BODY, cond->match.string.string, NULL)); if (CONDITION_CHKMATCH(cond,CONDITION_MATCH_US_HEAD)) cnt++,query = add_or_query (query, neg, imap_search_key_new_string (neg, IMSE_S_HEADER, cond->match.string.string, cond->match.string.user_header)); if(neg && cnt>1) query = imap_search_key_new_not(FALSE, query); imap_search_key_set_next(query, next); break; case CONDITION_DATE: { ImapSearchKey *slo = NULL, *shi = NULL; if (cond->match.date.date_low) query = slo = imap_search_key_new_date (IMSE_D_SINCE, FALSE, cond->match.date.date_low); if (cond->match.date.date_high) { shi = imap_search_key_new_date (IMSE_D_BEFORE, FALSE, cond->match.date.date_high); imap_search_key_set_next(query, shi); } /* this might be redundant if only one limit was specified. */ if(query) query = imap_search_key_new_not(neg, query); break; } case CONDITION_FLAG: if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_REPLIED) cnt++,query = add_or_query (query, neg, imap_search_key_new_flag (neg, IMSGF_ANSWERED)); if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_NEW) cnt++,query = add_or_query (query, neg, imap_search_key_new_flag (!neg, IMSGF_SEEN)); if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_DELETED) cnt++,query = add_or_query (query, neg, imap_search_key_new_flag (neg, IMSGF_DELETED)); if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_FLAGGED) cnt++,query = add_or_query (query, neg, imap_search_key_new_flag (neg, IMSGF_FLAGGED)); if (cond->match.flags & LIBBALSA_MESSAGE_FLAG_RECENT) cnt++, query = add_or_query (query, neg, imap_search_key_new_flag (neg, IMSGF_RECENT)); if(neg && cnt>1) query = imap_search_key_new_not(FALSE, query); imap_search_key_set_next(query, next); break; case CONDITION_AND: if(neg) { query = imap_search_key_new_not (TRUE, lbmi_build_imap_query (cond->match.andor.left, lbmi_build_imap_query (cond->match.andor.right, NULL))); imap_search_key_set_next(query, next); } else query = lbmi_build_imap_query (cond->match.andor.left, lbmi_build_imap_query (cond->match.andor.right, next)); break; case CONDITION_OR: query = imap_search_key_new_or (neg, lbmi_build_imap_query(cond->match.andor.left, NULL), lbmi_build_imap_query(cond->match.andor.right, NULL)); imap_search_key_set_next(query, next); break; case CONDITION_NONE: case CONDITION_REGEX: default: break; } return query; } typedef struct { GHashTable * uids; GHashTable * res; } ImapSearchData; static void imap_matched(unsigned uid, ImapSearchData* data) { LibBalsaMessage* m = g_hash_table_lookup(data->uids,GUINT_TO_POINTER(uid)); if(m) g_hash_table_insert(data->res, m, m); else printf("Could not find UID: %u in message list\n", uid); } /* Gets the messages matching the conditions via the IMAP search command error is put to TRUE if an error occured */ GHashTable * libbalsa_mailbox_imap_get_matchings(LibBalsaMailboxImap* mbox, LibBalsaCondition *ct, gboolean only_recent, gboolean * err) { ImapSearchKey* query; ImapResult rc = IMR_NO; ImapSearchData * cbdata; *err = FALSE; cbdata = g_new( ImapSearchData, 1 ); cbdata->uids = g_hash_table_new(NULL, NULL); cbdata->res = g_hash_table_new(NULL, NULL); query = lbmi_build_imap_query(ct /* FIXME: ONLY RECENT! */, NULL); if (query) { #ifdef UID_SEARCH_IMPLEMENTED for(msgs= LIBBALSA_MAILBOX(mbox)->message_list; msgs; msgs = msgs->next){ LibBalsaMessage *m = LIBBALSA_MESSAGE(msgs->data); ImapUID uid = IMAP_MESSAGE_UID(m); g_hash_table_insert(cbdata->uids, GUINT_TO_POINTER(uid), m); } #else g_warning("Search results ignored. Fixme!"); #endif II(rc,mbox->handle, imap_mbox_uid_search(mbox->handle, query, (void(*)(unsigned,void*))imap_matched, cbdata)); imap_search_key_free(query); } g_hash_table_destroy(cbdata->uids); /* Clean up on error */ if (rc != IMR_OK) { g_hash_table_destroy(cbdata->res); cbdata->res = NULL; *err = TRUE; libbalsa_information(LIBBALSA_INFORMATION_DEBUG, _("IMAP SEARCH command failed for mailbox %s\n" "falling back to default searching method"), LIBBALSA_MAILBOX(mbox)->url); }; return cbdata->res; } /* Returns false if the conditions contain regex matches User must be informed that regex match on IMAP will be done by default filters functions hence leading to SLOW match */ gboolean libbalsa_mailbox_imap_can_match(LibBalsaMailbox *mailbox, LibBalsaCondition *condition) { #if 0 GSList *cnds; for (cnds = conditions->cond_list; cnds; cnds = g_slist_next(cnds)) { LibBalsaCondition * cnd = cnds->data; if (cnd->type==CONDITION_REGEX) return FALSE; } #endif return TRUE; } static void libbalsa_mailbox_imap_save_config(LibBalsaMailbox * mailbox, const gchar * prefix) { LibBalsaMailboxImap *mimap; g_return_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox)); mimap = LIBBALSA_MAILBOX_IMAP(mailbox); gnome_config_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 = gnome_config_get_string("Path"); remote = LIBBALSA_MAILBOX_REMOTE(mailbox); remote->server = LIBBALSA_SERVER(libbalsa_imap_server_new_from_config()); g_signal_connect(G_OBJECT(remote->server), "set-host", G_CALLBACK(server_host_settings_changed_cb), (gpointer) mailbox); if (LIBBALSA_MAILBOX_CLASS(parent_class)->load_config) LIBBALSA_MAILBOX_CLASS(parent_class)->load_config(mailbox, prefix); libbalsa_mailbox_imap_update_url(mimap); } gboolean libbalsa_mailbox_imap_subscribe(LibBalsaMailboxImap * mailbox, gboolean subscribe) { ImapResult rc; g_return_val_if_fail(LIBBALSA_IS_MAILBOX_IMAP(mailbox), FALSE); II(rc,mailbox->handle, imap_mbox_subscribe(mailbox->handle, mailbox->path, subscribe)); return rc == IMR_OK; } /* libbalsa_mailbox_imap_noop: * pings the connection with NOOP for an open IMAP mailbox. * this keeps the connections alive. * new messages are loaded in exists-notify signal handler. * * FIXME: shift responsibility for keeping the connection alive to * LibBalsaImapServer. */ void libbalsa_mailbox_imap_noop(LibBalsaMailboxImap* mimap) { g_return_if_fail(mimap != NULL); if (mimap->handle) /* we do not attempt to reconnect here */ if (imap_mbox_handle_noop(mimap->handle) != IMR_OK) /* FIXME: report error... */ ; } /* imap_close_all_connections: close all connections to leave the place cleanly. */ void libbalsa_imap_close_all_connections(void) { libbalsa_imap_server_close_all_connections(); } /* libbalsa_imap_rename_subfolder: dir+parent determine current name. folder - new name. Can be called for a closed mailbox. */ gboolean libbalsa_imap_rename_subfolder(LibBalsaMailboxImap* imap, const gchar *new_parent, const gchar *folder, gboolean subscribe) { ImapResult rc; ImapMboxHandle* handle; gchar *new_path; handle = libbalsa_mailbox_imap_get_handle(imap, NULL); if (!handle) return FALSE; II(rc,handle, imap_mbox_subscribe(handle, imap->path, FALSE)); /* FIXME: should use imap server folder separator */ new_path = g_strjoin("/", new_parent, folder, NULL); rc = imap_mbox_rename(handle, imap->path, new_path); if (subscribe && rc == IMR_OK) rc = imap_mbox_subscribe(handle, new_path, TRUE); g_free(new_path); libbalsa_mailbox_imap_release_handle(imap); return rc == IMR_OK; } void libbalsa_imap_new_subfolder(const gchar *parent, const gchar *folder, gboolean subscribe, LibBalsaServer *server) { ImapResult rc; ImapMboxHandle* handle; gchar *new_path; char delim[2]; if (!LIBBALSA_IS_IMAP_SERVER(server)) return; handle = libbalsa_imap_server_get_handle(LIBBALSA_IMAP_SERVER(server), NULL); if (!handle) return; delim[0] = imap_mbox_handle_get_delim(handle, parent); delim[1] = '\0'; new_path = g_strjoin(delim, parent, folder, NULL); 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); libbalsa_imap_server_release_handle(LIBBALSA_IMAP_SERVER(server), handle); } gboolean libbalsa_imap_delete_folder(LibBalsaMailboxImap *mailbox) { 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)); libbalsa_mailbox_imap_release_handle(mailbox); return rc == IMR_OK; } gchar * libbalsa_imap_path(LibBalsaServer * server, const gchar * path) { gchar *imap_path = path && *path ? g_strdup_printf("imap%s://%s/%s/", #ifdef USE_SSL server->use_ssl ? "s" : "", #else "", #endif server->host, path) : g_strdup_printf("imap%s://%s/", #ifdef USE_SSL server->use_ssl ? "s" : "", #else "", #endif server->host); return imap_path; } 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 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 LibBalsaAddress * libbalsa_address_new_from_imap_address(ImapAddress *addr) { LibBalsaAddress *address; if (!addr || (addr->name==NULL && addr->addr_spec==NULL)) return NULL; address = libbalsa_address_new(); /* it will be owned by the caller */ if (addr->name) address->full_name = g_mime_utils_8bit_header_decode(addr->name); if (addr->addr_spec) address->address_list = g_list_append(address->address_list, g_mime_utils_8bit_header_decode(addr-> addr_spec)); else { /* FIXME: is that a right thing? */ g_object_unref(G_OBJECT(address)); address = NULL; } return address; } static GList* libbalsa_address_new_list_from_imap_address_list(ImapAddress *list) { LibBalsaAddress* addr; GList *res = NULL; for (; list; list = list->next) { addr = libbalsa_address_new_from_imap_address(list); if (addr) res = g_list_prepend(res, addr); } return g_list_reverse(res); } static void lb_set_headers(LibBalsaMessageHeaders *headers, ImapEnvelope * envelope, gboolean is_embedded) { headers->date = envelope->date; headers->from = libbalsa_address_new_from_imap_address(envelope->from); headers->reply_to = libbalsa_address_new_from_imap_address(envelope->replyto); headers->to_list = libbalsa_address_new_list_from_imap_address_list(envelope->to); headers->cc_list = libbalsa_address_new_list_from_imap_address_list(envelope->cc); headers->bcc_list = libbalsa_address_new_list_from_imap_address_list(envelope->bcc); if(is_embedded) { headers->subject = g_mime_utils_8bit_header_decode(envelope->subject); libbalsa_utf8_sanitize(&headers->subject, TRUE, NULL); } } static gboolean libbalsa_mailbox_imap_load_envelope(LibBalsaMailboxImap *mimap, LibBalsaMessage *message) { ImapEnvelope *envelope; ImapMessage* imsg; gchar *hdr; g_return_val_if_fail(mimap->opened, FALSE); imsg = mi_get_imsg(mimap, message->msgno); if(!imsg || !imsg->envelope) /* Connection severed and and restore * failed - deal with it! */ return FALSE; lbimap_update_flags(message, imsg); lb_set_headers(message->headers, imsg->envelope, FALSE); if ((hdr = imsg->fetched_header_fields) && *hdr && *hdr != '\r') libbalsa_message_set_headers_from_string(message, hdr); envelope = imsg->envelope; message->length = imsg->rfc822size; message->subj = g_mime_utils_8bit_header_decode(envelope->subject); libbalsa_utf8_sanitize(&message->subj, TRUE, NULL); message->sender = libbalsa_address_new_from_imap_address(envelope->sender); libbalsa_message_set_in_reply_to_from_string(message, envelope->in_reply_to); if (envelope->message_id) { message->message_id = g_mime_utils_decode_message_id(envelope->message_id); } return TRUE; } /* converts the backend data to LibBalsaMessage object */ static LibBalsaMessage* libbalsa_mailbox_imap_get_message(LibBalsaMailbox * mailbox, guint msgno) { struct message_info *msg_info; LibBalsaMailboxImap *mimap = (LibBalsaMailboxImap *) mailbox; msg_info = message_info_from_msgno(mimap, msgno); if (!msg_info->message) { LibBalsaMessage *msg = libbalsa_message_new(); msg->msgno = msgno; msg->mailbox = mailbox; if (libbalsa_mailbox_imap_load_envelope(mimap, msg)) { gchar *id; msg_info->message = msg; if (libbalsa_message_is_partial(msg, &id)) { libbalsa_mailbox_try_reassemble(mailbox, id); g_free(id); } } else g_object_unref(G_OBJECT(msg)); } if (msg_info->message) g_object_ref(msg_info->message); /* we want to keep one copy */ return msg_info->message; } static void libbalsa_mailbox_imap_prepare_threading(LibBalsaMailbox *mailbox, guint lo, guint hi) { g_warning("%s not implemented yet.\n", __func__); } static void lbm_imap_construct_body(LibBalsaMessageBody *lbbody, ImapBody *imap_body) { const char *str; g_return_if_fail(lbbody); g_return_if_fail(imap_body); switch(imap_body->media_basic) { case IMBMEDIA_MULTIPART: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART; break; case IMBMEDIA_APPLICATION: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_APPLICATION; break; case IMBMEDIA_AUDIO: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_AUDIO; break; case IMBMEDIA_IMAGE: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_IMAGE; break; case IMBMEDIA_MESSAGE_RFC822: case IMBMEDIA_MESSAGE_OTHER: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE; break; case IMBMEDIA_TEXT: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_TEXT; break; default: case IMBMEDIA_OTHER: lbbody->body_type = LIBBALSA_MESSAGE_BODY_TYPE_OTHER; break; } switch (imap_body->content_dsp) { case IMBDISP_INLINE: lbbody->content_dsp = GMIME_DISPOSITION_INLINE; break; case IMBDISP_ATTACHMENT: lbbody->content_dsp = GMIME_DISPOSITION_ATTACHMENT; break; case IMBDISP_OTHER: lbbody->content_dsp = imap_body->content_dsp_other; break; } lbbody->content_id = imap_body->content_id; lbbody->content_type = imap_body_get_content_type(imap_body); /* get the name in the same way as g_mime_part_get_filename() does */ str = imap_body_get_dsp_param(imap_body, "filename"); if(!str) str = imap_body_get_param(imap_body, "name"); if(str) { lbbody->filename = g_mime_utils_8bit_header_decode(str); libbalsa_utf8_sanitize(&lbbody->filename, TRUE, NULL); } lbbody->charset = g_strdup(imap_body_get_param(imap_body, "charset")); if(imap_body->envelope) { lbbody->embhdrs = g_new0(LibBalsaMessageHeaders, 1); lb_set_headers(lbbody->embhdrs, imap_body->envelope, TRUE); } if(imap_body->next) { LibBalsaMessageBody *body = libbalsa_message_body_new(lbbody->message); lbm_imap_construct_body(body, imap_body->next); lbbody->next = body; } if(imap_body->child) { LibBalsaMessageBody *body = libbalsa_message_body_new(lbbody->message); lbm_imap_construct_body(body, imap_body->child); lbbody->parts = body; } } static gboolean get_struct_from_cache(LibBalsaMailbox *mailbox, LibBalsaMessage *message, LibBalsaFetchFlag flags) { gchar **pair, *filename; int fd; GMimeStream *stream = NULL; GMimeParser *mime_parser; g_return_val_if_fail(message->mime_msg == NULL, FALSE); pair = get_cache_name_pair(LIBBALSA_MAILBOX_IMAP(mailbox), "body", IMAP_MESSAGE_UID(message)); filename = g_build_filename(pair[0], pair[1], NULL); g_strfreev(pair); fd = open(filename, O_RDONLY); g_free(filename); if(fd == -1) return FALSE; stream = g_mime_stream_fs_new(fd); mime_parser = g_mime_parser_new_with_stream(stream); g_mime_parser_set_scan_from(mime_parser, FALSE); message->mime_msg = g_mime_parser_construct_message(mime_parser); g_object_unref(mime_parser); g_mime_stream_unref(stream); /* follow libbalsa_mailbox_local_fetch_structure here; * perhaps create common helper */ if(flags & LB_FETCH_STRUCTURE) { LibBalsaMessageBody *body = libbalsa_message_body_new(message); libbalsa_message_body_set_mime_body(body, message->mime_msg->mime_part); libbalsa_message_append_part(message, body); libbalsa_message_headers_from_gmime(message->headers, message->mime_msg); } if(flags & LB_FETCH_RFC822_HEADERS) { message->headers->user_hdrs = libbalsa_message_user_hdrs_from_gmime(message->mime_msg); message->has_all_headers = 1; } return TRUE; } static void 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_if_fail(mimap->opened); /* work around some server bugs... */ server = LIBBALSA_MAILBOX_REMOTE_SERVER(mailbox); if(!imap_mbox_handle_can_do(mimap->handle, IMCAP_FETCHBODY) || libbalsa_imap_server_has_bug(LIBBALSA_IMAP_SERVER(server), ISBUG_FETCH)){ /* we could optimize this part a little bit: we do not need to * keep reopening the stream. */ GMimeStream *stream = libbalsa_mailbox_imap_get_message_stream(mailbox, message); if(stream) g_object_unref(stream); } if(get_struct_from_cache(mailbox, message, flags)) return; 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); g_return_if_fail(im); /* in case of msg number discrepancies */ 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; } } } 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; for(i=1; body; body = body->next) { if(body==child) { g_string_printf(s, "%u", i); return TRUE; } if(is_child_of(body->parts, child, s, body->body_type != LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE)){ char buf[12]; if(modify) { snprintf(buf, sizeof(buf), "%u.", i); g_string_prepend(s, buf); } return TRUE; } i++; } return FALSE; } 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"); } return g_string_free(section, FALSE); } struct part_data { char *block; unsigned pos; ImapBody *body; }; static void append_str(const char *buf, int buflen, void *arg) { struct part_data *dt = (struct part_data*)arg; if(dt->pos + buflen > dt->body->octets) { fprintf(stderr, "IMAP server sends too much data but we just " "ignore that looser.\n"); buflen = dt->body->octets-dt->pos; } memcpy(dt->block + dt->pos, buf, buflen); dt->pos += buflen; } static const gchar* libbalsa_mailbox_imap_get_msg_part(LibBalsaMessage *msg, LibBalsaMessageBody *part, ssize_t *sz) { GMimeStream *partstream = NULL; gchar **pair, *part_name; LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(msg->mailbox); FILE *fp; gchar *section; if(part->mime_part) return g_mime_part_get_content(GMIME_PART(part->mime_part), sz); /* look for a part cache */ section = get_section_for(msg, part); pair = get_cache_name_pair(mimap, "part", IMAP_MESSAGE_UID(msg)); part_name = g_strconcat(pair[0], "/", pair[1], "-", section, NULL); fp = fopen(part_name,"rb+"); if(!fp) { /* no cache element */ struct part_data dt; GMimePart *prefilt; GMimeStream *gms; GMimePartEncodingType gmt; LibBalsaMailboxImap* mimap; ImapMessage *im; ImapResponse rc; libbalsa_lock_mailbox(msg->mailbox); mimap = LIBBALSA_MAILBOX_IMAP(msg->mailbox); im = mi_get_imsg(mimap, msg->msgno); dt.body = imap_message_get_body_from_section(im, section); dt.block = g_malloc(dt.body->octets+1); dt.pos = 0; if(dt.body->octets>SizeMsgThreshold) libbalsa_information(LIBBALSA_INFORMATION_MESSAGE, _("Downloading %ld kB"), dt.body->octets/1024); II(rc,mimap->handle, imap_mbox_handle_fetch_body(mimap->handle, msg->msgno, section, append_str, &dt)); if(rc != IMR_OK) g_warning("FIXME: error handling here!\n"); libbalsa_unlock_mailbox(msg->mailbox); prefilt = g_mime_part_new_with_type (dt.body->media_basic_name, dt.body->media_subtype); gmt = dt.body->encoding == IMBENC_OTHER ? GMIME_PART_ENCODING_DEFAULT : dt.body->encoding; g_mime_part_set_pre_encoded_content ( prefilt, dt.block, dt.body->octets, gmt ); g_free(dt.block); libbalsa_assure_balsa_dir(); mkdir(pair[0], S_IRUSR|S_IWUSR|S_IXUSR); /* ignore errors */ /* printf("%s path: %s\n", __FUNCTION__, part_name); */ fp = fopen(part_name, "wb+"); if(!fp) { g_free(section); g_strfreev(pair); g_free(part_name); return FALSE; /* something better ? */ } if( (dt.body->media_basic == IMBMEDIA_TEXT) && !g_ascii_strcasecmp( "plain", dt.body->media_subtype ) ) { GMimeFilter *crlffilter; gms = g_mime_stream_file_new (fp); crlffilter = g_mime_filter_crlf_new ( GMIME_FILTER_CRLF_DECODE, GMIME_FILTER_CRLF_MODE_CRLF_ONLY ); partstream = g_mime_stream_filter_new_with_stream (gms); g_mime_stream_filter_add (GMIME_STREAM_FILTER(partstream) , crlffilter); g_object_unref (gms); g_object_unref (crlffilter); } else partstream = g_mime_stream_file_new (fp); g_mime_part_set_encoding (prefilt, GMIME_PART_ENCODING_8BIT ); g_mime_part_write_to_stream (prefilt, partstream ); g_mime_stream_flush (partstream); g_object_unref (prefilt); /* aparently the parser doesn't like the stream_filters .. */ g_object_unref (partstream); fp = fopen(part_name, "r"); } 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); if (GMIME_IS_PART(part->mime_part)) return g_mime_part_get_content(GMIME_PART(part->mime_part), sz); *sz = -1; return NULL; } /* libbalsa_mailbox_imap_add_message: can be called for a closed mailbox. Called with mailbox locked. */ int libbalsa_mailbox_imap_add_message(LibBalsaMailbox * mailbox, LibBalsaMessage * message, GError **err) { LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox); ImapMsgFlags imap_flags = IMAP_FLAGS_EMPTY; ImapResponse rc; GMimeStream *stream; GMimeStream *tmpstream; GMimeFilter *crlffilter; ImapMboxHandle *handle; gint outfd; gchar *outfile; GMimeStream *outstream; gssize len; if(message->mailbox && LIBBALSA_IS_MAILBOX_IMAP(message->mailbox) && LIBBALSA_MAILBOX_REMOTE(message->mailbox)->server == LIBBALSA_MAILBOX_REMOTE(mailbox)->server) { ImapMboxHandle *handle = mimap->handle; unsigned msgno = message->msgno; g_return_val_if_fail(handle, -1); /* message is there but * the source mailbox closed! */ /* FIXME: reconnect? The msgno number might have changed. * we should probably reconnect and return a retriable error. */ rc = imap_mbox_handle_copy(handle, 1, &msgno, mimap->path); if(rc != IMR_OK) { gchar *msg = imap_mbox_handle_get_last_msg(mimap->handle); g_set_error(err, LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_COPY_ERROR, "%s", msg); g_free(msg); } return rc == IMR_OK ? 1 : -1; } if (!LIBBALSA_MESSAGE_IS_UNREAD(message) ) IMSG_FLAG_SET(imap_flags,IMSGF_SEEN); if ( LIBBALSA_MESSAGE_IS_DELETED(message ) ) IMSG_FLAG_SET(imap_flags,IMSGF_DELETED); if ( LIBBALSA_MESSAGE_IS_FLAGGED(message) ) IMSG_FLAG_SET(imap_flags,IMSGF_FLAGGED); if ( LIBBALSA_MESSAGE_IS_REPLIED(message) ) IMSG_FLAG_SET(imap_flags,IMSGF_ANSWERED); stream = libbalsa_mailbox_get_message_stream(message->mailbox, message); tmpstream = g_mime_stream_filter_new_with_stream(stream); g_mime_stream_unref(stream); crlffilter = g_mime_filter_crlf_new(GMIME_FILTER_CRLF_ENCODE, GMIME_FILTER_CRLF_MODE_CRLF_ONLY); g_mime_stream_filter_add(GMIME_STREAM_FILTER(tmpstream), crlffilter); g_object_unref(crlffilter); outfd = g_file_open_tmp("balsa-tmp-file-XXXXXX", &outfile, err); if (outfd < 0) { g_warning("Could not create temporary file: %s", (*err)->message); g_mime_stream_unref(tmpstream); return -1; } outstream = g_mime_stream_fs_new(outfd); g_mime_stream_write_to_stream(tmpstream, outstream); g_mime_stream_unref(tmpstream); len = g_mime_stream_tell(outstream); g_mime_stream_reset(outstream); if(len>(signed)SizeMsgThreshold) libbalsa_information(LIBBALSA_INFORMATION_MESSAGE, _("Uploading %ld kB"), (long)len/1024); handle = libbalsa_mailbox_imap_get_handle(mimap, NULL); rc = imap_mbox_append_stream(handle, mimap->path, imap_flags, outstream, len); if(rc != IMR_OK) { gchar *msg = imap_mbox_handle_get_last_msg(mimap->handle); g_set_error(err, LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_APPEND_ERROR, "%s", msg); g_free(msg); } libbalsa_mailbox_imap_release_handle(mimap); g_mime_stream_unref(outstream); unlink(outfile); g_free(outfile); return rc == IMR_OK ? 1 : -1; } static void transform_flags(LibBalsaMessageFlag set, LibBalsaMessageFlag clr, ImapMsgFlag *flg_set, ImapMsgFlag *flg_clr) { *flg_set = 0; *flg_clr = 0; if (set & LIBBALSA_MESSAGE_FLAG_REPLIED) *flg_set |= IMSGF_ANSWERED; if (clr & LIBBALSA_MESSAGE_FLAG_REPLIED) *flg_clr |= IMSGF_ANSWERED; if (set & LIBBALSA_MESSAGE_FLAG_NEW) *flg_clr |= IMSGF_SEEN; if (clr & LIBBALSA_MESSAGE_FLAG_NEW) *flg_set |= IMSGF_SEEN; if (set & LIBBALSA_MESSAGE_FLAG_FLAGGED) *flg_set |= IMSGF_FLAGGED; if (clr & LIBBALSA_MESSAGE_FLAG_FLAGGED) *flg_clr |= IMSGF_FLAGGED; if (set & LIBBALSA_MESSAGE_FLAG_DELETED) *flg_set |= IMSGF_DELETED; if (clr & LIBBALSA_MESSAGE_FLAG_DELETED) *flg_clr |= IMSGF_DELETED; } static void libbalsa_mailbox_imap_change_message_flags(LibBalsaMailbox * mailbox, guint msgno, LibBalsaMessageFlag set, LibBalsaMessageFlag clear) { #if 0 LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox); ImapMboxHandle *handle = mimap->handle; ImapMsgFlag flag_set, flag_clr; transform_flags(set, clear, &flag_set, &flag_clr); if(flag_set) imap_mbox_store_flag(handle, 1, &msgno, flag_set, 1); if(flag_clr) imap_mbox_store_flag(handle, 1, &msgno, flag_clr, 0); #else g_assert_not_reached(); #endif } static gboolean lbm_imap_messages_change_flags(LibBalsaMailbox * mailbox, GArray * seqno, LibBalsaMessageFlag set, LibBalsaMessageFlag clear) { ImapMsgFlag flag_set, flag_clr; ImapResponse rc1 = IMR_OK, rc2 = IMR_OK; ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(mailbox)->handle; g_array_sort(seqno, cmp_msgno); transform_flags(set, clear, &flag_set, &flag_clr); if(flag_set) II(rc1,handle, imap_mbox_store_flag(handle, seqno->len, (guint *) seqno->data, flag_set, TRUE)); if(flag_clr) II(rc2,handle, imap_mbox_store_flag(LIBBALSA_MAILBOX_IMAP(mailbox)->handle, seqno->len, (guint *) seqno->data, flag_clr, FALSE)); if (rc1 == IMR_OK && rc2 == IMR_OK) { LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox); unsigned i; ++mimap->search_stamp; for (i = 0; i < seqno->len; i++) { guint msgno = g_array_index(seqno, guint, i); struct message_info *msg_info = message_info_from_msgno(mimap, msgno); if (msg_info->message) { libbalsa_message_set_msg_flags(msg_info->message, set, clear); libbalsa_mailbox_index_set_flags(mailbox, msgno, msg_info->message->flags); libbalsa_mailbox_msgno_changed(mailbox, msgno); } } return TRUE; } else return FALSE; } gboolean libbalsa_mailbox_msgno_has_flags(LibBalsaMailbox *m, unsigned msgno, LibBalsaMessageFlag set, LibBalsaMessageFlag unset); gboolean libbalsa_mailbox_msgno_has_flags(LibBalsaMailbox *m, unsigned msgno, LibBalsaMessageFlag set, LibBalsaMessageFlag unset) { ImapMsgFlag flag_set, flag_unset; ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(m)->handle; g_return_val_if_fail(handle, FALSE); transform_flags(set, unset, &flag_set, &flag_unset); return imap_mbox_handle_msgno_has_flags(handle, msgno, flag_set, flag_unset); } static gboolean libbalsa_mailbox_imap_can_do(LibBalsaMailbox* mbox, enum LibBalsaMailboxCapability c) { LibBalsaMailboxImap *mimap; if(!mbox) return TRUE; mimap = LIBBALSA_MAILBOX_IMAP(mbox); switch(c) { case LIBBALSA_MAILBOX_CAN_SORT: return imap_mbox_handle_can_do(mimap->handle, IMCAP_SORT); case LIBBALSA_MAILBOX_CAN_THREAD: return imap_mbox_handle_can_do(mimap->handle, IMCAP_THREAD_REFERENCES); default: return TRUE; } } static ImapSortKey lbmi_get_imap_sort_key(LibBalsaMailbox *mbox) { ImapSortKey key = LB_MBOX_FROM_COL; switch (mbox->view->sort_field) { default: case LB_MAILBOX_SORT_NO: key = IMSO_MSGNO; break; case LB_MAILBOX_SORT_SENDER: key = mbox->view->show == LB_MAILBOX_SHOW_TO ? IMSO_TO : IMSO_FROM; break; case LB_MAILBOX_SORT_SUBJECT: key = IMSO_SUBJECT; break; case LB_MAILBOX_SORT_DATE: key = IMSO_DATE; break; case LB_MAILBOX_SORT_SIZE: key = IMSO_SIZE; break; } return key; } static void libbalsa_mailbox_imap_set_threading(LibBalsaMailbox *mailbox, LibBalsaMailboxThreadingType thread_type) { LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox); GNode * new_tree = NULL; guint msgno; ImapSearchKey *filter = lbmi_build_imap_query(mailbox->view_filter, NULL); ImapResponse rc; mailbox->view->threading_type = thread_type; switch(thread_type) { case LB_MAILBOX_THREADING_SIMPLE: case LB_MAILBOX_THREADING_JWZ: II(rc,mimap->handle, imap_mbox_thread(mimap->handle, "REFERENCES", filter)); if(rc == IMR_OK) { new_tree = g_node_copy(imap_mbox_handle_get_thread_root(mimap->handle)); break; } else libbalsa_information(LIBBALSA_INFORMATION_WARNING, _("Server-side threading not supported.")); /* fall through */ case LB_MAILBOX_THREADING_FLAT: if(filter) { II(rc,mimap->handle, imap_mbox_sort_filter(mimap->handle, lbmi_get_imap_sort_key(mailbox), mailbox->view->sort_type == LB_MAILBOX_SORT_TYPE_ASC, filter)); if(rc == IMR_OK) new_tree = g_node_copy (imap_mbox_handle_get_thread_root(mimap->handle)); } if(!new_tree) { /* fall back */ new_tree = g_node_new(NULL); for(msgno = 1; msgno <= mimap->messages_info->len; msgno++) g_node_append_data(new_tree, GUINT_TO_POINTER(msgno)); } break; default: g_assert_not_reached(); new_tree = NULL; } imap_search_key_free(filter); if(!mailbox->msg_tree) /* first reference */ mailbox->msg_tree = g_node_new(NULL); if (new_tree) libbalsa_mailbox_set_msg_tree(mailbox, new_tree); } static void lbm_imap_update_view_filter(LibBalsaMailbox *mailbox, LibBalsaCondition *view_filter) { if(mailbox->view_filter) libbalsa_condition_free(mailbox->view_filter); mailbox->view_filter = view_filter; } static gint lbmi_compare_func(const SortTuple * a, const SortTuple * b, gboolean ascending) { if(ascending) return GPOINTER_TO_INT(a->node->data) - GPOINTER_TO_INT(b->node->data); else return GPOINTER_TO_INT(b->node->data) - GPOINTER_TO_INT(a->node->data); } static void libbalsa_mailbox_imap_sort(LibBalsaMailbox *mbox, GArray *array) { unsigned *msgno_arr, *msgno_map, len, i, no_max; GArray *tmp; len = array->len; ImapResponse rc; if(mbox->view->sort_field == LB_MAILBOX_SORT_NO) { g_array_sort_with_data(array, (GCompareDataFunc)lbmi_compare_func, GINT_TO_POINTER(mbox->view->sort_type == LB_MAILBOX_SORT_TYPE_ASC)); return; } if(mbox->view->threading_type != LB_MAILBOX_THREADING_FLAT) return; /* IMAP threading has an own way of sorting messages. * We could possibly disable sort buttons if threading * is used. */ msgno_arr = g_malloc(len*sizeof(unsigned)); no_max = 0; for(i=0; idata); if(msgno_arr[i]> no_max) no_max = msgno_arr[i]; } msgno_map = g_malloc(no_max*sizeof(unsigned)); for(i=0; ihandle, imap_mbox_sort_msgno(LIBBALSA_MAILBOX_IMAP(mbox)->handle, lbmi_get_imap_sort_key(mbox), mbox->view->sort_type == LB_MAILBOX_SORT_TYPE_ASC, msgno_arr, len)); /* ignore errors */ tmp = g_array_new(FALSE,FALSE, sizeof(SortTuple)); g_array_append_vals(tmp, array->data, array->len); g_array_set_size(array, 0); /* truncate */ for(i=0; imessages_info ? mimap->messages_info->len : 0; } /* Copy messages in the list to dest; use server-side copy if mailbox * and dest are on the same server, fall back to parent method * otherwise. */ static gboolean libbalsa_mailbox_imap_messages_copy(LibBalsaMailbox * mailbox, GArray * msgnos, LibBalsaMailbox * dest, GError **err) { if (LIBBALSA_IS_MAILBOX_IMAP(dest) && LIBBALSA_MAILBOX_REMOTE(dest)->server == LIBBALSA_MAILBOX_REMOTE(mailbox)->server) { gboolean ret; ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(mailbox)->handle; g_return_val_if_fail(handle, FALSE); /* User server-side copy. */ g_array_sort(msgnos, cmp_msgno); ret = imap_mbox_handle_copy(handle, msgnos->len, (guint *) msgnos->data, LIBBALSA_MAILBOX_IMAP(dest)->path) == IMR_OK; if(!ret) { gchar *msg = imap_mbox_handle_get_last_msg(handle); g_set_error(err, LIBBALSA_MAILBOX_ERROR, LIBBALSA_MAILBOX_COPY_ERROR, "%s", msg); g_free(msg); } return ret; } /* Couldn't use server-side copy, fall back to default method. */ return parent_class->messages_copy(mailbox, msgnos, dest, err); } void libbalsa_imap_set_cache_size(off_t cache_size) { ImapCacheSize = cache_size; } /* libbalsa_imap_purge_temp_dir() purges the temporary directory used for non-persistent message caching. */ void libbalsa_imap_purge_temp_dir(off_t cache_size) { gchar *dir_name = get_cache_dir(NULL, NULL, FALSE); clean_dir(dir_name, cache_size); g_free(dir_name); } /* =================================================================== ImapCacheManager implementation. The main task of the ImapCacheManager is to reuse msgno->UID mappings. This is useful mostly for operations when the imap connection cannot last as long as it should due to external constraints (flaky connection, pay-by-the-minute connections). The goal is to extract any UID information that the low level libimap handle might have on connection close and provide it to the new one whenever one is created. The general scheme is that libimap does not cache any data persistently based on UIDs - this task is left to ImapCacheManager. . ICM provides two main functions: - init_on_select() - preloads ImapMboxHandle msgno-based cache with all available information. It may ask ImapMboxHandle to provide UID->msgno maps if it considers it necessary. - store_cached_data() - extracts all information that is known to ImapMboxHandle and can be potentially used in future sessions - mostly all ImapMessage and ImapEnvelope structures. Current implementation stores the information in memory but an implementation storing data on disk is possible, too. */ struct ImapCacheManager { GHashTable *headers; GArray *uidmap; uint32_t uidvalidity; uint32_t uidnext; uint32_t exists; }; static struct ImapCacheManager* imap_cache_manager_new(guint cnt) { struct ImapCacheManager *icm = g_new0(struct ImapCacheManager, 1); icm->exists = cnt; icm->headers = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); icm->uidmap = g_array_sized_new(FALSE, TRUE, sizeof(uint32_t), cnt); return icm; } static void icm_destroy(struct ImapCacheManager *icm) { g_hash_table_destroy(icm->headers); g_array_free(icm->uidmap, TRUE); } /* icm_init_on_select_() preloads header cache of the ImapMboxHandle object. It currently handles following cases: a). uidvalidity different - entire cache has to be invalidated. b). cache->exists == h->exists && cache->uidnext == h->uidnext: nothing has changed - feed entire cache. else fetch the message numbers for the UIDs in cache (not yet handled). Problem: what to do about UID fragmentation? */ static void icm_restore_from_cache(ImapMboxHandle *h, struct ImapCacheManager *icm) { unsigned exists, uidvalidity, uidnext; unsigned i; if(!icm || ! h) return; uidvalidity = imap_mbox_handle_get_validity(h); exists = imap_mbox_handle_get_exists(h); uidnext = imap_mbox_handle_get_uidnext(h); if(icm->uidvalidity != uidvalidity) { printf("different validities %u %u- cache invalidated\n", icm->uidvalidity, uidvalidity); return; } /* detect unhandled "else" case */ if(exists - icm->exists != uidnext - icm->uidnext) { printf("mailbox modified exists: %u %u uidnext: %u %u\n", icm->exists, exists, icm->uidnext, uidnext); return; } for(i=1; i<=icm->exists; i++) { uint32_t uid = g_array_index(icm->uidmap, uint32_t, i-1); void *data = g_hash_table_lookup(icm->headers, GUINT_TO_POINTER(uid)); if(data) /* if uid known */ imap_mbox_handle_msg_deserialize(h, i, data); } } static struct ImapCacheManager* icm_store_cached_data(ImapMboxHandle *handle) { struct ImapCacheManager *icm; unsigned cnt, i; if(!handle) return NULL; cnt = imap_mbox_handle_get_exists(handle); icm = imap_cache_manager_new(cnt); icm->uidvalidity = imap_mbox_handle_get_validity(handle); icm->uidnext = imap_mbox_handle_get_uidnext(handle); for(i=1; i<=cnt; i++) { uint32_t *uid = &g_array_index(icm->uidmap, uint32_t, i-1); ImapMessage *imsg = imap_mbox_handle_get_msg(handle, i); if(imsg) { void *ptr = imap_message_serialize(imsg); g_hash_table_insert(icm->headers, GUINT_TO_POINTER(imsg->uid), ptr); *uid = imsg->uid; } else *uid = 0; } return icm; }