Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib-imap/imap-message-cache.c @ 22:a946ce1f09b7 HEAD
mbox fixes, not fully working yet but almost :)
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sat, 24 Aug 2002 05:04:45 +0300 |
parents | 82b7de533f98 |
children | d493b9cc265e |
line wrap: on
line source
/* Copyright (C) 2002 Timo Sirainen */ #include "lib.h" #include "iobuffer.h" #include "temp-string.h" #include "mmap-util.h" #include "message-parser.h" #include "message-size.h" #include "imap-bodystructure.h" #include "imap-envelope.h" #include "imap-message-cache.h" #include <unistd.h> /* It's not very useful to cache lots of messages, as they're mostly wanted just once. The biggest reason for this cache to exist is to get just the latest message. */ #define MAX_CACHED_MESSAGES 16 #define DEFAULT_MESSAGE_POOL_SIZE 4096 typedef struct _CachedMessage CachedMessage; struct _CachedMessage { CachedMessage *next; Pool pool; unsigned int uid; MessagePart *part; MessageSize *hdr_size; MessageSize *body_size; MessageSize *partial_size; char *cached_body; char *cached_bodystructure; char *cached_envelope; MessagePartEnvelopeData *envelope; }; struct _ImapMessageCache { CachedMessage *messages; int messages_count; CachedMessage *open_msg; IOBuffer *open_inbuf; off_t open_virtual_size; IOBuffer *(*inbuf_rewind)(IOBuffer *inbuf, void *context); void *context; }; ImapMessageCache *imap_msgcache_alloc(void) { return i_new(ImapMessageCache, 1); } static void cached_message_free(CachedMessage *msg) { pool_unref(msg->pool); } void imap_msgcache_clear(ImapMessageCache *cache) { CachedMessage *next; imap_msgcache_close(cache); while (cache->messages != NULL) { next = cache->messages->next; cached_message_free(cache->messages); cache->messages = next; } } void imap_msgcache_free(ImapMessageCache *cache) { imap_msgcache_clear(cache); i_free(cache); } static CachedMessage *cache_new(ImapMessageCache *cache, unsigned int uid) { CachedMessage *msg, **msgp; Pool pool; if (cache->messages_count < MAX_CACHED_MESSAGES) cache->messages_count++; else { /* remove the last message from cache */ msgp = &cache->messages; while ((*msgp)->next != NULL) msgp = &(*msgp)->next; cached_message_free(*msgp); *msgp = NULL; } pool = pool_create("CachedMessage", DEFAULT_MESSAGE_POOL_SIZE, FALSE); msg = p_new(pool, CachedMessage, 1); msg->pool = pool; msg->uid = uid; msg->next = cache->messages; cache->messages = msg; return msg; } static CachedMessage *cache_open_or_create(ImapMessageCache *cache, unsigned int uid) { CachedMessage **pos, *msg; pos = &cache->messages; for (; *pos != NULL; pos = &(*pos)->next) { if ((*pos)->uid == uid) break; } if (*pos == NULL) { /* not found, add it */ msg = cache_new(cache, uid); } else if (*pos != cache->messages) { /* move it to first in list */ msg = *pos; *pos = msg->next; msg->next = cache->messages; cache->messages = msg; } else { msg = *pos; } return msg; } static void parse_envelope_header(MessagePart *part, const char *name, unsigned int name_len, const char *value, unsigned int value_len, void *context) { CachedMessage *msg = context; if (part == NULL || part->parent == NULL) { /* parse envelope headers if we're at the root message part */ imap_envelope_parse_header(msg->pool, &msg->envelope, t_strndup(name, name_len), value, value_len); } } static CachedMessage *cache_find(ImapMessageCache *cache, unsigned int uid) { CachedMessage *msg; for (msg = cache->messages; msg != NULL; msg = msg->next) { if (msg->uid == uid) return msg; } return NULL; } static void imap_msgcache_get_inbuf(ImapMessageCache *cache, off_t offset) { if (offset < cache->open_inbuf->offset) { /* need to rewind */ cache->open_inbuf = cache->inbuf_rewind(cache->open_inbuf, cache->context); if (cache->open_inbuf == NULL) i_fatal("Can't rewind message buffer"); } io_buffer_skip(cache->open_inbuf, offset - cache->open_inbuf->offset); } int imap_msgcache_is_cached(ImapMessageCache *cache, unsigned int uid, ImapCacheField fields) { CachedMessage *msg; if (cache->open_msg != NULL && cache->open_msg->uid == uid) return TRUE; /* not open, see if the wanted fields are cached */ msg = cache_find(cache, uid); if (msg == NULL) return FALSE; if ((fields & IMAP_CACHE_BODY) && msg->cached_body == NULL) return FALSE; if ((fields & IMAP_CACHE_BODYSTRUCTURE) && msg->cached_bodystructure == NULL) return FALSE; if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL) return FALSE; if ((fields & IMAP_CACHE_MESSAGE_OPEN) && msg != cache->open_msg) return FALSE; if ((fields & IMAP_CACHE_MESSAGE_PART) && msg->part == NULL) return FALSE; if ((fields & IMAP_CACHE_MESSAGE_HDR_SIZE) && msg->hdr_size == NULL) return FALSE; if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) && msg->body_size == NULL) return FALSE; return TRUE; } /* Caches the fields for given message if possible */ static void cache_fields(ImapMessageCache *cache, CachedMessage *msg, ImapCacheField fields) { const char *value; t_push(); if ((fields & IMAP_CACHE_BODY) && msg->cached_body == NULL && msg == cache->open_msg) { imap_msgcache_get_inbuf(cache, 0); value = imap_part_get_bodystructure(msg->pool, &msg->part, cache->open_inbuf, FALSE); msg->cached_body = p_strdup(msg->pool, value); } if ((fields & IMAP_CACHE_BODYSTRUCTURE) && msg->cached_bodystructure == NULL && msg == cache->open_msg) { imap_msgcache_get_inbuf(cache, 0); value = imap_part_get_bodystructure(msg->pool, &msg->part, cache->open_inbuf, TRUE); msg->cached_bodystructure = p_strdup(msg->pool, value); } if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL) { if (msg->envelope == NULL && msg == cache->open_msg) { /* envelope isn't parsed yet, do it. header size is calculated anyway so save it */ if (msg->hdr_size == NULL) { msg->hdr_size = p_new(msg->pool, MessageSize, 1); } imap_msgcache_get_inbuf(cache, 0); message_parse_header(NULL, cache->open_inbuf, msg->hdr_size, parse_envelope_header, msg); } if (msg->envelope != NULL) { value = imap_envelope_get_part_data(msg->envelope); msg->cached_envelope = p_strdup(msg->pool, value); } } if ((fields & IMAP_CACHE_MESSAGE_PART) && msg->part == NULL && msg == cache->open_msg) { /* we need to parse the message */ MessageHeaderFunc func; if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL) { /* we need envelope too, fill the info while parsing headers */ func = parse_envelope_header; } else { func = NULL; } imap_msgcache_get_inbuf(cache, 0); msg->part = message_parse(msg->pool, cache->open_inbuf, func, msg); } if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) && msg->body_size == NULL && (msg == cache->open_msg || msg->part != NULL)) { /* fill the body size, and while at it fill the header size as well */ if (msg->hdr_size == NULL) msg->hdr_size = p_new(msg->pool, MessageSize, 1); msg->body_size = p_new(msg->pool, MessageSize, 1); if (msg->part != NULL) { /* easy, get it from root part */ *msg->hdr_size = msg->part->header_size; *msg->body_size = msg->part->body_size; } else { /* first get the header's size, then calculate the body size from it and the total virtual size */ imap_msgcache_get_inbuf(cache, 0); message_get_header_size(cache->open_inbuf, msg->hdr_size); i_assert((off_t)msg->hdr_size->physical_size < cache->open_inbuf->size); i_assert((off_t)msg->hdr_size->virtual_size < cache->open_virtual_size); msg->body_size->lines = 0; msg->body_size->physical_size = cache->open_inbuf->size - msg->hdr_size->physical_size; msg->body_size->virtual_size = cache->open_virtual_size - msg->hdr_size->virtual_size; } } if ((fields & IMAP_CACHE_MESSAGE_HDR_SIZE) && msg->hdr_size == NULL && (msg == cache->open_msg || msg->part != NULL)) { msg->hdr_size = p_new(msg->pool, MessageSize, 1); if (msg->part != NULL) { /* easy, get it from root part */ *msg->hdr_size = msg->part->header_size; } else { /* need to do some light parsing */ imap_msgcache_get_inbuf(cache, 0); message_get_header_size(cache->open_inbuf, msg->hdr_size); } } t_pop(); } void imap_msgcache_message(ImapMessageCache *cache, unsigned int uid, ImapCacheField fields, off_t virtual_size, off_t pv_headers_size, off_t pv_body_size, IOBuffer *inbuf, IOBuffer *(*inbuf_rewind)(IOBuffer *inbuf, void *context), void *context) { CachedMessage *msg; msg = cache_open_or_create(cache, uid); if (cache->open_msg != msg) { imap_msgcache_close(cache); cache->open_msg = msg; cache->open_inbuf = inbuf; cache->open_virtual_size = virtual_size; cache->inbuf_rewind = inbuf_rewind; cache->context = context; } if (pv_headers_size != 0 && msg->hdr_size == NULL) { /* physical size == virtual size */ msg->hdr_size = p_new(msg->pool, MessageSize, 1); msg->hdr_size->physical_size = msg->hdr_size->virtual_size = pv_headers_size; } if (pv_body_size != 0 && msg->body_size == NULL) { /* physical size == virtual size */ msg->body_size = p_new(msg->pool, MessageSize, 1); msg->body_size->physical_size = msg->body_size->virtual_size = pv_body_size; } cache_fields(cache, msg, fields); } void imap_msgcache_close(ImapMessageCache *cache) { if (cache->open_inbuf != NULL) { (void)close(cache->open_inbuf->fd); io_buffer_destroy(cache->open_inbuf); cache->open_inbuf = NULL; } cache->open_msg = NULL; cache->open_virtual_size = 0; } void imap_msgcache_set(ImapMessageCache *cache, unsigned int uid, ImapCacheField field, const char *value) { CachedMessage *msg; msg = cache_find(cache, uid); if (msg == NULL) msg = cache_new(cache, uid); switch (field) { case IMAP_CACHE_BODY: msg->cached_body = p_strdup(msg->pool, value); break; case IMAP_CACHE_BODYSTRUCTURE: msg->cached_bodystructure = p_strdup(msg->pool, value); break; case IMAP_CACHE_ENVELOPE: msg->cached_envelope = p_strdup(msg->pool, value); break; default: i_assert(0); } } const char *imap_msgcache_get(ImapMessageCache *cache, unsigned int uid, ImapCacheField field) { CachedMessage *msg; msg = cache_find(cache, uid); if (msg == NULL) return NULL; switch (field) { case IMAP_CACHE_BODY: if (msg->cached_body == NULL) cache_fields(cache, msg, field); return msg->cached_body; case IMAP_CACHE_BODYSTRUCTURE: if (msg->cached_bodystructure == NULL) cache_fields(cache, msg, field); return msg->cached_bodystructure; case IMAP_CACHE_ENVELOPE: if (msg->cached_envelope == NULL) cache_fields(cache, msg, field); return msg->cached_envelope; default: i_assert(0); } return NULL; } MessagePart *imap_msgcache_get_parts(ImapMessageCache *cache, unsigned int uid) { CachedMessage *msg; msg = cache_find(cache, uid); if (msg == NULL) return NULL; if (msg->part == NULL) cache_fields(cache, msg, IMAP_CACHE_MESSAGE_PART); return msg->part; } int imap_msgcache_get_rfc822(ImapMessageCache *cache, unsigned int uid, MessageSize *hdr_size, MessageSize *body_size, IOBuffer **inbuf) { CachedMessage *msg; off_t offset; if (inbuf != NULL) { if (cache->open_msg == NULL || cache->open_msg->uid != uid) return FALSE; msg = cache->open_msg; offset = hdr_size != NULL ? 0 : msg->hdr_size->physical_size; imap_msgcache_get_inbuf(cache, offset); *inbuf = cache->open_inbuf; } else { msg = cache_find(cache, uid); if (msg == NULL) return FALSE; } if (body_size != NULL) { if (msg->body_size == NULL) cache_fields(cache, msg, IMAP_CACHE_MESSAGE_BODY_SIZE); if (msg->body_size == NULL) return FALSE; *body_size = *msg->body_size; } if (hdr_size != NULL) { if (msg->hdr_size == NULL) cache_fields(cache, msg, IMAP_CACHE_MESSAGE_HDR_SIZE); if (msg->hdr_size == NULL) return FALSE; *hdr_size = *msg->hdr_size; } return TRUE; } static void get_partial_size(IOBuffer *inbuf, off_t virtual_skip, off_t max_virtual_size, MessageSize *partial, MessageSize *dest) { unsigned char *msg; unsigned int size; int cr_skipped; /* see if we can use the existing partial */ if ((off_t)partial->virtual_size > virtual_skip) memset(partial, 0, sizeof(MessageSize)); else { io_buffer_skip(inbuf, partial->physical_size); virtual_skip -= partial->virtual_size; } message_skip_virtual(inbuf, virtual_skip, partial, &cr_skipped); if (!cr_skipped) { /* see if we need to add virtual CR */ while (io_buffer_read_data(inbuf, &msg, &size, 0) >= 0) { if (size > 0) { if (msg[0] == '\n') dest->virtual_size++; break; } } } message_get_body_size(inbuf, dest, max_virtual_size); } int imap_msgcache_get_rfc822_partial(ImapMessageCache *cache, unsigned int uid, off_t virtual_skip, off_t max_virtual_size, int get_header, MessageSize *size, IOBuffer **inbuf) { CachedMessage *msg; off_t physical_skip; int size_got; msg = cache->open_msg; if (msg == NULL || msg->uid != uid) return FALSE; if (msg->hdr_size == NULL) { msg->hdr_size = p_new(msg->pool, MessageSize, 1); imap_msgcache_get_inbuf(cache, 0); message_get_header_size(cache->open_inbuf, msg->hdr_size); } physical_skip = get_header ? 0 : msg->hdr_size->physical_size; /* see if we can do this easily */ size_got = FALSE; if (virtual_skip == 0) { if (max_virtual_size < 0 && msg->body_size == NULL) { msg->body_size = p_new(msg->pool, MessageSize, 1); msg->body_size->physical_size = cache->open_inbuf->size - msg->hdr_size->physical_size; msg->body_size->virtual_size = cache->open_virtual_size - msg->hdr_size->virtual_size; } if (msg->body_size != NULL && (max_virtual_size < 0 || max_virtual_size >= (off_t)msg->body_size->virtual_size)) { *size = *msg->body_size; size_got = TRUE; } } if (!size_got) { if (msg->partial_size == NULL) msg->partial_size = p_new(msg->pool, MessageSize, 1); imap_msgcache_get_inbuf(cache, msg->hdr_size->physical_size); get_partial_size(cache->open_inbuf, virtual_skip, max_virtual_size, msg->partial_size, size); physical_skip += msg->partial_size->physical_size; } if (get_header) message_size_add(size, msg->hdr_size); /* seek to wanted position */ imap_msgcache_get_inbuf(cache, physical_skip); *inbuf = cache->open_inbuf; return TRUE; } int imap_msgcache_get_data(ImapMessageCache *cache, unsigned int uid, IOBuffer **inbuf) { if (cache->open_msg == NULL || cache->open_msg->uid != uid) return FALSE; imap_msgcache_get_inbuf(cache, 0); *inbuf = cache->open_inbuf; return TRUE; }