diff options
Diffstat (limited to 'libmailutils/amd.c')
-rw-r--r-- | libmailutils/amd.c | 1925 |
1 files changed, 1925 insertions, 0 deletions
diff --git a/libmailutils/amd.c b/libmailutils/amd.c new file mode 100644 index 000000000..3cf3dde6c --- /dev/null +++ b/libmailutils/amd.c @@ -0,0 +1,1925 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + 2008, 2009, 2010 Free Software Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA */ + +/* Mailutils Abstract Mail Directory Layer + First draft by Sergey Poznyakoff. + Thanks Tang Yong Ping <yongping.tang@radixs.com> for initial + patch (although not used here). + + This module provides basic support for "MH" and "Maildir" formats. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> + +#ifdef WITH_PTHREAD +# ifdef HAVE_PTHREAD_H +# ifndef _XOPEN_SOURCE +# define _XOPEN_SOURCE 500 +# endif +# include <pthread.h> +# endif +#endif + +#include <string.h> +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif + +#include <mailutils/cctype.h> +#include <mailutils/cstr.h> +#include <mailutils/attribute.h> +#include <mailutils/body.h> +#include <mailutils/debug.h> +#include <mailutils/envelope.h> +#include <mailutils/error.h> +#include <mailutils/errno.h> +#include <mailutils/header.h> +#include <mailutils/locker.h> +#include <mailutils/message.h> +#include <mailutils/mutil.h> +#include <mailutils/property.h> +#include <mailutils/stream.h> +#include <mailutils/url.h> +#include <mailutils/observer.h> +#include <mailutils/sys/stream.h> +#include <mailutils/sys/mailbox.h> +#include <mailutils/sys/registrar.h> +#include <mailutils/sys/url.h> +#include <mailutils/sys/amd.h> + +static void amd_destroy (mu_mailbox_t mailbox); +static int amd_open (mu_mailbox_t, int); +static int amd_close (mu_mailbox_t); +static int amd_get_message (mu_mailbox_t, size_t, mu_message_t *); +static int amd_quick_get_message (mu_mailbox_t mailbox, mu_message_qid_t qid, + mu_message_t *pmsg); +static int amd_append_message (mu_mailbox_t, mu_message_t); +static int amd_messages_count (mu_mailbox_t, size_t *); +static int amd_messages_recent (mu_mailbox_t, size_t *); +static int amd_message_unseen (mu_mailbox_t, size_t *); +static int amd_expunge (mu_mailbox_t); +static int amd_sync (mu_mailbox_t); +static int amd_uidnext (mu_mailbox_t mailbox, size_t *puidnext); +static int amd_uidvalidity (mu_mailbox_t, unsigned long *); +static int amd_scan (mu_mailbox_t, size_t, size_t *); +static int amd_is_updated (mu_mailbox_t); +static int amd_get_size (mu_mailbox_t, mu_off_t *); + +static int amd_body_size (mu_body_t body, size_t *psize); +static int amd_body_lines (mu_body_t body, size_t *plines); + +static int amd_header_fill (void *data, char **pbuf, size_t *plen); + +static int amd_get_attr_flags (mu_attribute_t attr, int *pflags); +static int amd_set_attr_flags (mu_attribute_t attr, int flags); +static int amd_unset_attr_flags (mu_attribute_t attr, int flags); + +static void _amd_message_delete (struct _amd_data *amd, + struct _amd_message *msg); +static int amd_pool_open (struct _amd_message *mhm); +static int amd_pool_open_count (struct _amd_data *amd); +static void amd_pool_flush (struct _amd_data *amd); +static struct _amd_message **amd_pool_lookup (struct _amd_message *mhm); + +static int amd_envelope_date (mu_envelope_t envelope, char *buf, size_t len, + size_t *psize); +static int amd_envelope_sender (mu_envelope_t envelope, char *buf, size_t len, + size_t *psize); + + +static int amd_body_stream_read (mu_stream_t str, char *buffer, + size_t buflen, + size_t *pnread); +static int amd_body_stream_readdelim (mu_stream_t is, + char *buffer, size_t buflen, + int delim, + size_t *pnread); +static int amd_body_stream_size (mu_stream_t str, mu_off_t *psize); +static int amd_body_stream_seek (mu_stream_t str, mu_off_t off, + mu_off_t *presult); + +struct _amd_body_stream +{ + struct _mu_stream stream; + mu_body_t body; + mu_off_t off; +}; + + +/* Operations on message array */ + +/* Perform binary search for message MSG on a segment of message array + of AMD between the indexes FIRST and LAST inclusively. + If found, return 0 and store index of the located entry in the + variable PRET. Otherwise, return 1 and place into PRET index of + the nearest array element that is less than MSG (in the sense of + amd->msg_cmp) + Indexes are zero-based. */ + +static int +amd_msg_bsearch (struct _amd_data *amd, mu_off_t first, mu_off_t last, + struct _amd_message *msg, + mu_off_t *pret) +{ + mu_off_t mid; + int rc; + + if (last < first) + return 1; + + mid = (first + last) / 2; + rc = amd->msg_cmp (amd->msg_array[mid], msg); + if (rc > 0) + return amd_msg_bsearch (amd, first, mid-1, msg, pret); + *pret = mid; + if (rc < 0) + return amd_msg_bsearch (amd, mid+1, last, msg, pret); + /* else */ + return 0; +} + +/* Search for message MSG in the message array of AMD. + If found, return 0 and store index of the located entry in the + variable PRET. Otherwise, return 1 and place into PRET index of + the array element that is less than MSG (in the sense of + amd->msg_cmp) + Index returned in PRET is 1-based, so *PRET == 0 means that MSG + is less than the very first element of the message array. + + In other words, when amd_msg_lookup() returns 1, the value in *PRET + can be regarded as a 0-based index of the array slot where MSG can + be inserted */ + +int +amd_msg_lookup (struct _amd_data *amd, struct _amd_message *msg, + size_t *pret) +{ + int rc; + mu_off_t i; + + if (amd->msg_count == 0) + { + *pret = 0; + return 1; + } + + rc = amd->msg_cmp (msg, amd->msg_array[0]); + if (rc < 0) + { + *pret = 0; + return 1; + } + else if (rc == 0) + { + *pret = 1; + return 0; + } + + rc = amd->msg_cmp (msg, amd->msg_array[amd->msg_count - 1]); + if (rc > 0) + { + *pret = amd->msg_count; + return 1; + } + else if (rc == 0) + { + *pret = amd->msg_count; + return 0; + } + + rc = amd_msg_bsearch (amd, 0, amd->msg_count - 1, msg, &i); + *pret = i + 1; + return rc; +} + +#define AMD_MSG_INC 64 + +/* Prepare the message array for insertion of a new message + at position INDEX (zero based), by moving its contents + one slot to the right. If necessary, expand the array by + AMD_MSG_INC */ +int +amd_array_expand (struct _amd_data *amd, size_t index) +{ + if (amd->msg_count == amd->msg_max) + { + struct _amd_message **p; + + amd->msg_max += AMD_MSG_INC; /* FIXME: configurable? */ + p = realloc (amd->msg_array, amd->msg_max * amd->msg_size); + if (!p) + { + amd->msg_max -= AMD_MSG_INC; + return ENOMEM; + } + amd->msg_array = p; + } + memmove (&amd->msg_array[index+1], &amd->msg_array[index], + (amd->msg_count-index) * amd->msg_size); + amd->msg_count++; + return 0; +} + +/* Shrink the message array by removing element at INDEX-1 and + shifting left by one position all the elements on the right of + it. */ +int +amd_array_shrink (struct _amd_data *amd, size_t index) +{ + memmove (&amd->msg_array[index-1], &amd->msg_array[index], + (amd->msg_count-index) * amd->msg_size); + amd->msg_count--; + return 0; +} + + +int +amd_init_mailbox (mu_mailbox_t mailbox, size_t amd_size, + struct _amd_data **pamd) +{ + int status; + struct _amd_data *amd; + + if (mailbox == NULL) + return MU_ERR_MBX_NULL; + if (amd_size < sizeof (*amd)) + return EINVAL; + + amd = mailbox->data = calloc (1, amd_size); + if (mailbox->data == NULL) + return ENOMEM; + + /* Back pointer. */ + amd->mailbox = mailbox; + + status = mu_url_aget_path (mailbox->url, &amd->name); + if (status) + { + free (amd); + mailbox->data = NULL; + return status; + } + + /* Overloading the defaults. */ + mailbox->_destroy = amd_destroy; + + mailbox->_open = amd_open; + mailbox->_close = amd_close; + + /* Overloading of the entire mailbox object methods. */ + mailbox->_get_message = amd_get_message; + mailbox->_quick_get_message = amd_quick_get_message; + mailbox->_append_message = amd_append_message; + mailbox->_messages_count = amd_messages_count; + mailbox->_messages_recent = amd_messages_recent; + mailbox->_message_unseen = amd_message_unseen; + mailbox->_expunge = amd_expunge; + mailbox->_sync = amd_sync; + mailbox->_uidvalidity = amd_uidvalidity; + mailbox->_uidnext = amd_uidnext; + + mailbox->_scan = amd_scan; + mailbox->_is_updated = amd_is_updated; + + mailbox->_get_size = amd_get_size; + + MU_DEBUG1 (mailbox->debug, MU_DEBUG_TRACE1, "amd_init(%s)\n", amd->name); + *pamd = amd; + return 0; +} + +static void +amd_destroy (mu_mailbox_t mailbox) +{ + struct _amd_data *amd = mailbox->data; + size_t i; + + if (!amd) + return; + + amd_pool_flush (amd); + mu_monitor_wrlock (mailbox->monitor); + for (i = 0; i < amd->msg_count; i++) + { + mu_message_destroy (&amd->msg_array[i]->message, amd->msg_array[i]); + free (amd->msg_array[i]); + } + free (amd->msg_array); + + if (amd->name) + free (amd->name); + + free (amd); + mailbox->data = NULL; + mu_monitor_unlock (mailbox->monitor); +} + +static int +amd_open (mu_mailbox_t mailbox, int flags) +{ + struct _amd_data *amd = mailbox->data; + struct stat st; + + mailbox->flags = flags; + if (stat (amd->name, &st) < 0) + { + if ((flags & MU_STREAM_CREAT) && errno == ENOENT) + { + int rc; + int perms = mu_stream_flags_to_mode (flags, 1); + if (mkdir (amd->name, S_IRUSR|S_IWUSR|S_IXUSR|perms)) + return errno; + if (stat (amd->name, &st) < 0) + return errno; + if (amd->create && (rc = amd->create (amd, flags))) + return rc; + } + else + return errno; + } + + if (!S_ISDIR (st.st_mode)) + return EINVAL; + + if (mailbox->locker == NULL) + mu_locker_create (&mailbox->locker, "/dev/null", 0); + + return 0; +} + +static int +amd_close (mu_mailbox_t mailbox) +{ + struct _amd_data *amd; + int i; + + if (!mailbox) + return MU_ERR_MBX_NULL; + + amd = mailbox->data; + + /* Destroy all cached data */ + amd_pool_flush (amd); + mu_monitor_wrlock (mailbox->monitor); + for (i = 0; i < amd->msg_count; i++) + { + mu_message_destroy (&amd->msg_array[i]->message, amd->msg_array[i]); + free (amd->msg_array[i]); + } + free (amd->msg_array); + amd->msg_array = NULL; + + amd->msg_count = 0; /* number of messages in the list */ + amd->msg_max = 0; /* maximum message buffer capacity */ + + amd->uidvalidity = 0; + mu_monitor_unlock (mailbox->monitor); + + return 0; +} + +static int +amd_message_qid (mu_message_t msg, mu_message_qid_t *pqid) +{ + struct _amd_message *mhm = mu_message_get_owner (msg); + + return mhm->amd->cur_msg_file_name (mhm, pqid); +} + +struct _amd_message * +_amd_get_message (struct _amd_data *amd, size_t msgno) +{ + msgno--; + if (msgno >= amd->msg_count) + return NULL; + return amd->msg_array[msgno]; +} + +static int +_amd_attach_message (mu_mailbox_t mailbox, struct _amd_message *mhm, + mu_message_t *pmsg) +{ + int status; + mu_message_t msg; + + /* Check if we already have it. */ + if (mhm->message) + { + if (pmsg) + *pmsg = mhm->message; + return 0; + } + + /* Get an empty message struct. */ + status = mu_message_create (&msg, mhm); + if (status != 0) + return status; + + /* Set the header. */ + { + mu_header_t header = NULL; + status = mu_header_create (&header, NULL, 0); + if (status != 0) + { + mu_message_destroy (&msg, mhm); + return status; + } + mu_header_set_fill (header, amd_header_fill, msg); + /*FIXME: + mu_header_set_get_fvalue (header, amd_header_get_fvalue, msg); + */ + mu_message_set_header (msg, header, mhm); + } + + /* Set the attribute. */ + { + mu_attribute_t attribute; + status = mu_attribute_create (&attribute, msg); + if (status != 0) + { + mu_message_destroy (&msg, mhm); + return status; + } + mu_attribute_set_get_flags (attribute, amd_get_attr_flags, msg); + mu_attribute_set_set_flags (attribute, amd_set_attr_flags, msg); + mu_attribute_set_unset_flags (attribute, amd_unset_attr_flags, msg); + mu_message_set_attribute (msg, attribute, mhm); + } + + /* Prepare the body. */ + { + mu_body_t body = NULL; + struct _amd_body_stream *str; + + if ((status = mu_body_create (&body, msg)) != 0) + return status; + + str = (struct _amd_body_stream *) + _mu_stream_create (sizeof (*str), + mailbox->flags | MU_STREAM_SEEK); + if (!str) + { + mu_body_destroy (&body, msg); + mu_message_destroy (&msg, mhm); + return ENOMEM; + } + str->stream.read = amd_body_stream_read; + str->stream.readdelim = amd_body_stream_readdelim; + str->stream.size = amd_body_stream_size; + str->stream.seek = amd_body_stream_seek; + mu_body_set_stream (body, (mu_stream_t) str, msg); + mu_body_clear_modified (body); + mu_body_set_size (body, amd_body_size, msg); + mu_body_set_lines (body, amd_body_lines, msg); + mu_message_set_body (msg, body, mhm); + str->body = body; + } + + /* Set the envelope. */ + { + mu_envelope_t envelope = NULL; + status = mu_envelope_create (&envelope, msg); + if (status != 0) + { + mu_message_destroy (&msg, mhm); + return status; + } + mu_envelope_set_sender (envelope, amd_envelope_sender, msg); + mu_envelope_set_date (envelope, amd_envelope_date, msg); + mu_message_set_envelope (msg, envelope, mhm); + } + + /* Set the UID. */ + if (mhm->amd->message_uid) + mu_message_set_uid (msg, mhm->amd->message_uid, mhm); + mu_message_set_qid (msg, amd_message_qid, mhm); + + /* Attach the message to the mailbox mbox data. */ + mhm->message = msg; + mu_message_set_mailbox (msg, mailbox, mhm); + + /* Some of mu_message_set_ functions above mark message as modified. + Undo it now. + + FIXME: Marking message as modified is not always appropriate. Find + a better way. */ + + mu_message_clear_modified (msg); + + if (pmsg) + *pmsg = msg; + + return 0; +} + +static int +amd_get_message (mu_mailbox_t mailbox, size_t msgno, mu_message_t *pmsg) +{ + int status; + struct _amd_data *amd = mailbox->data; + struct _amd_message *mhm; + + /* Sanity checks. */ + if (pmsg == NULL) + return MU_ERR_OUT_PTR_NULL; + if (amd == NULL) + return EINVAL; + + /* If we did not start a scanning yet do it now. */ + if (amd->msg_count == 0) + { + status = amd->scan0 (mailbox, 1, NULL, 0); + if (status != 0) + return status; + } + + if ((mhm = _amd_get_message (amd, msgno)) == NULL) + return EINVAL; + return _amd_attach_message (mailbox, mhm, pmsg); +} + +static int +amd_quick_get_message (mu_mailbox_t mailbox, mu_message_qid_t qid, + mu_message_t *pmsg) +{ + int status; + struct _amd_data *amd = mailbox->data; + if (amd->msg_count) + { + mu_message_qid_t vqid; + mu_message_t msg = amd->msg_array[0]->message; + status = mu_message_get_qid (msg, &vqid); + if (status) + return status; + status = strcmp (qid, vqid); + free (vqid); + if (status) + return MU_ERR_EXISTS; + *pmsg = msg; + } + else if (amd->qfetch) + { + status = amd->qfetch (amd, qid); + if (status) + return status; + return _amd_attach_message (mailbox, amd->msg_array[0], pmsg); + } + + return ENOSYS; +} + +static FILE * +_amd_tempfile(struct _amd_data *amd, char **namep) +{ + int fd = mu_tempfile (amd->name, namep); + if (fd == -1) + return NULL; + return fdopen (fd, "w"); +} + +static int +_amd_delim (char *str) +{ + if (str[0] == '-') + { + for (; *str == '-'; str++) + ; + for (; *str == ' ' || *str == '\t'; str++) + ; + } + return str[0] == '\n'; +} + +static int +_amd_message_save (struct _amd_data *amd, struct _amd_message *mhm, + int expunge) +{ + mu_stream_t stream = NULL; + char *name = NULL, *buf = NULL, *msg_name, *old_name; + size_t n; + size_t bsize; + size_t nlines, nbytes; + size_t new_body_start, new_header_lines; + FILE *fp; + mu_message_t msg = mhm->message; + mu_header_t hdr; + int status; + mu_attribute_t attr; + mu_body_t body; + const char *sbuf; + mu_envelope_t env = NULL; + char statbuf[MU_STATUS_BUF_SIZE]; + + status = mu_message_size (msg, &bsize); + if (status) + return status; + + status = amd->new_msg_file_name (mhm, mhm->attr_flags, expunge, &msg_name); + if (status) + return status; + if (!msg_name) + { + /* Unlink the original file */ + char *old_name; + status = amd->cur_msg_file_name (mhm, &old_name); + free (msg_name); + if (status == 0 && unlink (old_name)) + status = errno; + free (old_name); + return status; + } + + fp = _amd_tempfile (mhm->amd, &name); + if (!fp) + { + free (msg_name); + return errno; + } + + /* Try to allocate large buffer */ + for (; bsize > 1; bsize /= 2) + if ((buf = malloc (bsize))) + break; + + if (!bsize) + { + unlink (name); + free (name); + free (msg_name); + return ENOMEM; + } + + /* Copy flags */ + mu_message_get_header (msg, &hdr); + mu_header_get_streamref (hdr, &stream); + status = mu_stream_seek (stream, 0, MU_SEEK_SET, NULL); + if (status) + { + /* FIXME: Provide a common exit point for all error + cases */ + unlink (name); + free (name); + free (msg_name); + mu_stream_destroy (&stream); + return status; + } + + nlines = nbytes = 0; + while ((status = mu_stream_readline (stream, buf, bsize, &n)) == 0 + && n != 0) + { + if (_amd_delim (buf)) + break; + + if (!(mu_c_strncasecmp (buf, "status:", 7) == 0 + || mu_c_strncasecmp (buf, "x-imapbase:", 11) == 0 + || mu_c_strncasecmp (buf, "x-uid:", 6) == 0 + || mu_c_strncasecmp (buf, + MU_HEADER_ENV_DATE ":", sizeof (MU_HEADER_ENV_DATE)) == 0 + || mu_c_strncasecmp (buf, + MU_HEADER_ENV_SENDER ":", sizeof (MU_HEADER_ENV_SENDER)) == 0)) + { + nlines++; + nbytes += fprintf (fp, "%s", buf); + } + } + mu_stream_destroy (&stream); + + /* Add imapbase */ + if (!(amd->mailbox->flags & MU_STREAM_APPEND) + && amd->next_uid + && (!amd->msg_array || (amd->msg_array[0] == mhm))) /*FIXME*/ + { + nbytes += fprintf (fp, "X-IMAPbase: %lu %u\n", + (unsigned long) amd->uidvalidity, + (unsigned) amd->next_uid (amd)); + nlines++; + } + + mu_message_get_envelope (msg, &env); + if (mu_envelope_sget_date (env, &sbuf) == 0) + { + /* NOTE: buffer might be terminated with \n */ + while (*sbuf && mu_isspace (*sbuf)) + sbuf++; + nbytes += fprintf (fp, "%s: %s", MU_HEADER_ENV_DATE, sbuf); + + if (*sbuf && sbuf[strlen (sbuf) - 1] != '\n') + nbytes += fprintf (fp, "\n"); + + nlines++; + } + + if (mu_envelope_sget_sender (env, &sbuf) == 0) + { + fprintf (fp, "%s: %s\n", MU_HEADER_ENV_SENDER, sbuf); + nlines++; + } + + /* Add status */ + mu_message_get_attribute (msg, &attr); + mu_attribute_to_string (attr, statbuf, sizeof (statbuf), &n); + if (n) + { + nbytes += fprintf (fp, "Status: %s\n", statbuf); + nlines++; + } + nbytes += fprintf (fp, "\n"); + nlines++; + + new_header_lines = nlines; + new_body_start = nbytes; + + /* Copy message body */ + + mu_message_get_body (msg, &body); + mu_body_get_streamref (body, &stream); + status = mu_stream_seek (stream, 0, MU_SEEK_SET, NULL); + if (status) + { + unlink (name); + free (name); + free (msg_name); + mu_stream_destroy (&stream); + return status; + } + + nlines = 0; + while (mu_stream_read (stream, buf, bsize, &n) == 0 && n != 0) + { + char *p; + for (p = buf; p < buf + n; p++) + if (*p == '\n') + nlines++; + fwrite (buf, 1, n, fp); + nbytes += n; + } + mu_stream_destroy (&stream); + + mhm->header_lines = new_header_lines; + mhm->body_start = new_body_start; + mhm->body_lines = nlines; + mhm->body_end = nbytes; + + free (buf); + fclose (fp); + + status = amd->cur_msg_file_name (mhm, &old_name); + if (status == 0) + { + if (rename (name, msg_name)) + status = errno; + else + { + mode_t perms; + + perms = mu_stream_flags_to_mode (amd->mailbox->flags, 0); + if (perms != 0) + { + /* It is documented that the mailbox permissions are + affected by the current umask, so take it into account + here. + FIXME: I'm still not sure we should honor umask, though. + --gray + */ + mode_t mask = umask (0); + chmod (msg_name, (0600 | perms) & ~mask); + umask (mask); + } + if (strcmp (old_name, msg_name)) + /* Unlink original message */ + unlink (old_name); + } + free (old_name); + + mhm->orig_flags = mhm->attr_flags; + } + free (msg_name); + free (name); + + return status; +} + +static int +amd_append_message (mu_mailbox_t mailbox, mu_message_t msg) +{ + int status; + struct _amd_data *amd = mailbox->data; + struct _amd_message *mhm; + + if (!mailbox) + return MU_ERR_MBX_NULL; + if (!msg) + return EINVAL; + + mhm = calloc (1, amd->msg_size); + if (!mhm) + return ENOMEM; + + /* If we did not start a scanning yet do it now. */ + if (amd->msg_count == 0) + { + status = amd->scan0 (mailbox, 1, NULL, 0); + if (status != 0) + { + free (mhm); + return status; + } + } + + amd->has_new_msg = 1; + + mhm->amd = amd; + if (amd->msg_init_delivery) + { + status = amd->msg_init_delivery (amd, mhm); + if (status) + { + free (mhm); + return status; + } + } + + mhm->message = msg; + status = _amd_message_save (amd, mhm, 0); + if (status) + { + free (mhm); + return status; + } + + mhm->message = NULL; + /* Insert and re-scan the message */ + status = _amd_message_insert (amd, mhm); + if (status) + { + free (mhm); + return status; + } + + if (amd->msg_finish_delivery) + status = amd->msg_finish_delivery (amd, mhm, msg); + + if (status == 0 && mailbox->observable) + { + char *qid; + if (amd->cur_msg_file_name (mhm, &qid) == 0) + { + mu_observable_notify (mailbox->observable, MU_EVT_MESSAGE_APPEND, + qid); + free (qid); + } + } + + return status; +} + +static int +amd_messages_count (mu_mailbox_t mailbox, size_t *pcount) +{ + struct _amd_data *amd = mailbox->data; + + if (amd == NULL) + return EINVAL; + + if (!amd_is_updated (mailbox)) + return amd->scan0 (mailbox, amd->msg_count, pcount, 0); + + if (pcount) + *pcount = amd->msg_count; + + return 0; +} + +/* A "recent" message is the one not marked with MU_ATTRIBUTE_SEEN + ('O' in the Status header), i.e. a message that is first seen + by the current session (see attributes.h) */ +static int +amd_messages_recent (mu_mailbox_t mailbox, size_t *pcount) +{ + struct _amd_data *amd = mailbox->data; + size_t count, i; + + /* If we did not start a scanning yet do it now. */ + if (amd->msg_count == 0) + { + int status = amd->scan0 (mailbox, 1, NULL, 0); + if (status != 0) + return status; + } + count = 0; + for (i = 0; i < amd->msg_count; i++) + { + if (MU_ATTRIBUTE_IS_UNSEEN(amd->msg_array[i]->attr_flags)) + count++; + } + *pcount = count; + return 0; +} + +/* An "unseen" message is the one that has not been read yet */ +static int +amd_message_unseen (mu_mailbox_t mailbox, size_t *pmsgno) +{ + struct _amd_data *amd = mailbox->data; + size_t i; + + /* If we did not start a scanning yet do it now. */ + if (amd->msg_count == 0) + { + int status = amd->scan0 (mailbox, 1, NULL, 0); + if (status != 0) + return status; + } + + for (i = 0; i < amd->msg_count; i++) + { + if (MU_ATTRIBUTE_IS_UNREAD(amd->msg_array[0]->attr_flags)) + { + *pmsgno = i + 1; + break; + } + } + return 0; +} + +static char * +make_size_file_name (struct _amd_data *amd) +{ + size_t size = strlen (amd->name) + 1 + sizeof (MU_AMD_SIZE_FILE_NAME); + char *name = malloc (size); + if (name) + { + strcpy (name, amd->name); + strcat (name, "/"); + strcat (name, MU_AMD_SIZE_FILE_NAME); + } + return name; +} + +static int +read_size_file (struct _amd_data *amd, mu_off_t *psize) +{ + FILE *fp; + int rc; + char *name = make_size_file_name (amd); + if (!name) + return 1; + fp = fopen (name, "r"); + if (fp) + { + unsigned long size; + if (fscanf (fp, "%lu", &size) == 1) + { + *psize = size; + rc = 0; + } + else + rc = 1; + fclose (fp); + } + else + rc = 1; + free (name); + return rc; +} + +static int +write_size_file (struct _amd_data *amd, mu_off_t size) +{ + FILE *fp; + int rc; + char *name = make_size_file_name (amd); + if (!name) + return 1; + fp = fopen (name, "w"); + if (fp) + { + fprintf (fp, "%lu", (unsigned long) size); + fclose (fp); + rc = 0; + } + else + rc = 1; + free (name); + return rc; +} + +static int +compute_mailbox_size (struct _amd_data *amd, const char *name, mu_off_t *psize) +{ + DIR *dir; + struct dirent *entry; + char *buf; + size_t bufsize; + size_t dirlen; + size_t flen; + int status = 0; + struct stat sb; + + dir = opendir (name); + if (!dir) + return errno; + + dirlen = strlen (name); + bufsize = dirlen + 32; + buf = malloc (bufsize); + if (!buf) + { + closedir (dir); + return ENOMEM; + } + + strcpy (buf, name); + if (buf[dirlen-1] != '/') + buf[++dirlen - 1] = '/'; + + while ((entry = readdir (dir))) + { + switch (entry->d_name[0]) + { + case '.': + break; + + default: + flen = strlen (entry->d_name); + if (dirlen + flen + 1 > bufsize) + { + bufsize = dirlen + flen + 1; + buf = realloc (buf, bufsize); + if (!buf) + { + status = ENOMEM; + break; + } + } + strcpy (buf + dirlen, entry->d_name); + if (stat (buf, &sb) == 0) + { + if (S_ISREG (sb.st_mode)) + *psize += sb.st_size; + else if (S_ISDIR (sb.st_mode)) + compute_mailbox_size (amd, buf, psize); + } + /* FIXME: else? */ + break; + } + } + + free (buf); + + closedir (dir); + return 0; +} + +static int +amd_expunge (mu_mailbox_t mailbox) +{ + struct _amd_data *amd = mailbox->data; + struct _amd_message *mhm; + size_t i; + int updated = amd->has_new_msg; + + if (amd == NULL) + return EINVAL; + + if (amd->msg_count == 0) + return 0; + + /* Find the first dirty(modified) message. */ + for (i = 0; i < amd->msg_count; i++) + { + mhm = amd->msg_array[i]; + if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED) || + (mhm->attr_flags & MU_ATTRIBUTE_DELETED) || + (mhm->message && mu_message_is_modified (mhm->message))) + break; + } + + while (i < amd->msg_count) + { + mhm = amd->msg_array[i]; + + if (mhm->attr_flags & MU_ATTRIBUTE_DELETED) + { + int rc; + char *old_name; + char *new_name; + + rc = amd->cur_msg_file_name (mhm, &old_name); + if (rc) + return rc; + rc = amd->new_msg_file_name (mhm, mhm->attr_flags, 1, + &new_name); + if (rc) + { + free (old_name); + return rc; + } + + if (new_name) + { + /* FIXME: It may be a good idea to have a capability flag + in struct _amd_data indicating that no actual removal + is needed (e.g. for traditional MH). It will allow to + bypass lots of no-op code here. */ + if (strcmp (old_name, new_name)) + /* Rename original message */ + rename (old_name, new_name); + } + else + /* Unlink original file */ + unlink (old_name); + + free (old_name); + free (new_name); + + _amd_message_delete (amd, mhm); + updated = 1; + /* Do not increase i! */ + } + else + { + if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED) + || (mhm->message && mu_message_is_modified (mhm->message))) + { + _amd_attach_message (mailbox, mhm, NULL); + _amd_message_save (amd, mhm, 1); + updated = 1; + } + i++; /* Move to the next message */ + } + } + + if (updated && !amd->mailbox_size) + { + mu_off_t size = 0; + int rc = compute_mailbox_size (amd, amd->name, &size); + if (rc == 0) + write_size_file (amd, size); + } + return 0; +} + +static int +amd_sync (mu_mailbox_t mailbox) +{ + struct _amd_data *amd = mailbox->data; + struct _amd_message *mhm; + size_t i; + int updated = amd->has_new_msg; + + if (amd == NULL) + return EINVAL; + + if (amd->msg_count == 0) + return 0; + + /* Find the first dirty(modified) message. */ + for (i = 0; i < amd->msg_count; i++) + { + mhm = amd->msg_array[i]; + if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED) + || (mhm->message && mu_message_is_modified (mhm->message))) + break; + } + + for ( ; i < amd->msg_count; i++) + { + mhm = amd->msg_array[i]; + + if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED) + || (mhm->message && mu_message_is_modified (mhm->message))) + { + _amd_attach_message (mailbox, mhm, NULL); + _amd_message_save (amd, mhm, 0); + updated = 1; + } + } + + if (updated && !amd->mailbox_size) + { + mu_off_t size = 0; + int rc = compute_mailbox_size (amd, amd->name, &size); + if (rc == 0) + write_size_file (amd, size); + } + + return 0; +} + +static int +amd_uidvalidity (mu_mailbox_t mailbox, unsigned long *puidvalidity) +{ + struct _amd_data *amd = mailbox->data; + int status = amd_messages_count (mailbox, NULL); + if (status != 0) + return status; + /* If we did not start a scanning yet do it now. */ + if (amd->msg_count == 0) + { + status = amd->scan0 (mailbox, 1, NULL, 0); + if (status != 0) + return status; + } + if (puidvalidity) + *puidvalidity = amd->uidvalidity; + return 0; +} + |