/*
* Copyright notice from original mutt:
* Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
*
* This file is part of mutt-ng, see http://www.muttng.org/.
* It's licensed under the GNU General Public License,
* please see the file GPL in the top level source directory.
*/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include "mutt.h"
#include "buffy.h"
#include "ascii.h"
#include "mx.h"
#include "mbox.h"
#include "mh.h"
#include "rfc2047.h"
#include "sort.h"
#include "thread.h"
#include "copy.h"
#include "keymap.h"
#include "url.h"
#include "sidebar.h"
#ifdef USE_COMPRESSED
#include "compress.h"
#endif
#ifdef USE_IMAP
#include "imap/imap.h"
#include "imap/mx_imap.h"
#endif
#ifdef USE_POP
#include "pop/pop.h"
#include "pop/mx_pop.h"
#endif
#ifdef USE_NNTP
#include "nntp/nntp.h"
#include "nntp/mx_nntp.h"
#endif
#ifdef USE_DOTLOCK
#include "dotlock.h"
#endif
#include "mutt_crypt.h"
#include "lib/mem.h"
#include "lib/intl.h"
#include "lib/str.h"
#include "lib/list.h"
#include "lib/debug.h"
#include <dirent.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <utime.h>
static list2_t* MailboxFormats = NULL;
#define MX_COMMAND(idx,cmd) ((mx_t*) MailboxFormats->data[idx])->cmd
#define MX_IDX(idx) (idx >= 0 && idx < MailboxFormats->length)
#define mutt_is_spool(s) (str_cmp (Spoolfile, s) == 0)
#ifdef USE_DOTLOCK
/* parameters:
* path - file to lock
* retry - should retry if unable to lock?
*/
#ifdef DL_STANDALONE
static int invoke_dotlock (const char *path, int dummy, int flags, int retry)
{
char cmd[LONG_STRING + _POSIX_PATH_MAX];
char f[SHORT_STRING + _POSIX_PATH_MAX];
char r[SHORT_STRING];
if (flags & DL_FL_RETRY)
snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
mutt_quote_filename (f, sizeof (f), path);
snprintf (cmd, sizeof (cmd),
"%s %s%s%s%s%s%s%s",
NONULL (MuttDotlock),
flags & DL_FL_TRY ? "-t " : "",
flags & DL_FL_UNLOCK ? "-u " : "",
flags & DL_FL_USEPRIV ? "-p " : "",
flags & DL_FL_FORCE ? "-f " : "",
flags & DL_FL_UNLINK ? "-d " : "",
flags & DL_FL_RETRY ? r : "", f);
return mutt_system (cmd);
}
#else
#define invoke_dotlock dotlock_invoke
#endif
static int dotlock_file (const char *path, int fd, int retry)
{
int r;
int flags = DL_FL_USEPRIV | DL_FL_RETRY;
if (retry)
retry = 1;
retry_lock:
if ((r = invoke_dotlock (path, fd, flags, retry)) == DL_EX_EXIST) {
if (!option (OPTNOCURSES)) {
char msg[LONG_STRING];
snprintf (msg, sizeof (msg),
_("Lock count exceeded, remove lock for %s?"), path);
if (retry && mutt_yesorno (msg, M_YES) == M_YES) {
flags |= DL_FL_FORCE;
retry--;
mutt_clear_error ();
goto retry_lock;
}
}
else {
mutt_error (_("Can't dotlock %s.\n"), path);
}
}
return (r == DL_EX_OK ? 0 : -1);
}
static int undotlock_file (const char *path, int fd)
{
return (invoke_dotlock (path, fd, DL_FL_USEPRIV | DL_FL_UNLOCK, 0) ==
DL_EX_OK ? 0 : -1);
}
#endif /* USE_DOTLOCK */
/* looks up index of type for path in MailboxFormats */
static int mx_get_idx (const char* path) {
int i = 0, t = 0;
struct stat st;
/* first, test all non-local folders to avoid stat() call */
for (i = 0; i < MailboxFormats->length; i++) {
if (!MX_COMMAND(i,local))
t = MX_COMMAND(i,mx_is_magic)(path, NULL);
if (t >= 1)
return (t-1);
}
if (stat (path, &st) == 0) {
/* if stat() succeeded, keep testing until success and
* pass stat() info so that we only need to do it once */
for (i = 0; i < MailboxFormats->length; i++) {
if (MX_COMMAND(i,local))
t = MX_COMMAND(i,mx_is_magic)(path, &st);
if (t >= 1)
return (t-1);
}
}
return (-1);
}
/* Args:
* excl if excl != 0, request an exclusive lock
* dot if dot != 0, try to dotlock the file
* timeout should retry locking?
*/
int mx_lock_file (const char *path, int fd, int excl, int dot, int timeout)
{
#if defined (USE_FCNTL) || defined (USE_FLOCK)
int count;
int attempt;
struct stat prev_sb;
#endif
int r = 0;
#ifdef USE_FCNTL
struct flock lck;
memset (&lck, 0, sizeof (struct flock));
lck.l_type = excl ? F_WRLCK : F_RDLCK;
lck.l_whence = SEEK_SET;
count = 0;
attempt = 0;
prev_sb.st_size = 0;
while (fcntl (fd, F_SETLK, &lck) == -1) {
struct stat sb;
debug_print (1, ("fcntl errno %d.\n", errno));
if (errno != EAGAIN && errno != EACCES) {
mutt_perror ("fcntl");
return (-1);
}
if (fstat (fd, &sb) != 0)
sb.st_size = 0;
if (count == 0)
prev_sb = sb;
/* only unlock file if it is unchanged */
if (prev_sb.st_size == sb.st_size
&& ++count >= (timeout ? MAXLOCKATTEMPT : 0)) {
if (timeout)
mutt_error _("Timeout exceeded while attempting fcntl lock!");
return (-1);
}
prev_sb = sb;
mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
sleep (1);
}
#endif /* USE_FCNTL */
#ifdef USE_FLOCK
count = 0;
attempt = 0;
while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
struct stat sb;
if (errno != EWOULDBLOCK) {
mutt_perror ("flock");
r = -1;
break;
}
if (fstat (fd, &sb) != 0)
sb.st_size = 0;
if (count == 0)
prev_sb = sb;
/* only unlock file if it is unchanged */
if (prev_sb.st_size == sb.st_size
&& ++count >= (timeout ? MAXLOCKATTEMPT : 0)) {
if (timeout)
mutt_error _("Timeout exceeded while attempting flock lock!");
r = -1;
break;
}
prev_sb = sb;
mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
sleep (1);
}
#endif /* USE_FLOCK */
#ifdef USE_DOTLOCK
if (r == 0 && dot)
r = dotlock_file (path, fd, timeout);
#endif /* USE_DOTLOCK */
if (r == -1) {
/* release any other locks obtained in this routine */
#ifdef USE_FCNTL
lck.l_type = F_UNLCK;
fcntl (fd, F_SETLK, &lck);
#endif /* USE_FCNTL */
#ifdef USE_FLOCK
flock (fd, LOCK_UN);
#endif /* USE_FLOCK */
return (-1);
}
return 0;
}
int mx_unlock_file (const char *path, int fd, int dot)
{
#ifdef USE_FCNTL
struct flock unlockit = { F_UNLCK, 0, 0, 0 };
memset (&unlockit, 0, sizeof (struct flock));
unlockit.l_type = F_UNLCK;
unlockit.l_whence = SEEK_SET;
fcntl (fd, F_SETLK, &unlockit);
#endif
#ifdef USE_FLOCK
flock (fd, LOCK_UN);
#endif
#ifdef USE_DOTLOCK
if (dot)
undotlock_file (path, fd);
#endif
return 0;
}
void mx_unlink_empty (const char *path)
{
int fd;
#ifndef USE_DOTLOCK
struct stat sb;
#endif
if ((fd = open (path, O_RDWR)) == -1)
return;
if (mx_lock_file (path, fd, 1, 0, 1) == -1) {
close (fd);
return;
}
#ifdef USE_DOTLOCK
invoke_dotlock (path, fd, DL_FL_UNLINK, 1);
#else
if (fstat (fd, &sb) == 0 && sb.st_size == 0)
unlink (path);
#endif
mx_unlock_file (path, fd, 0);
close (fd);
}
/* try to figure out what type of mailbox ``path'' is */
int mx_get_magic (const char *path) {
int i = 0;
if (str_len (path) == 0)
return (-1);
if ((i = mx_get_idx (path)) >= 0)
return (MX_COMMAND(i,type));
return (-1);
}
int mx_is_local (int m) {
if (!MX_IDX(m))
return (0);
return (MX_COMMAND(m,local));
}
/*
* set DefaultMagic to the given value
*/
int mx_set_magic (const char *s)
{
if (ascii_strcasecmp (s, "mbox") == 0)
DefaultMagic = M_MBOX;
else if (ascii_strcasecmp (s, "mmdf") == 0)
DefaultMagic = M_MMDF;
else if (ascii_strcasecmp (s, "mh") == 0)
DefaultMagic = M_MH;
else if (ascii_strcasecmp (s, "maildir") == 0)
DefaultMagic = M_MAILDIR;
else
return (-1);
return 0;
}
/* mx_access: Wrapper for access, checks permissions on a given mailbox.
* We may be interested in using ACL-style flags at some point, currently
* we use the normal access() flags. */
int mx_access (const char *path, int flags)
{
int i = 0;
if ((i = mx_get_idx (path)) >= 0 && MX_COMMAND(i,mx_access))
return (MX_COMMAND(i,mx_access)(path,flags));
return (0);
}
static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
{
struct stat sb;
#ifdef USE_COMPRESSED
/* special case for appending to compressed folders -
* even if we can not open them for reading */
if (mutt_can_append_compressed (ctx->path))
mutt_open_append_compressed (ctx);
#endif
ctx->append = 1;
#ifdef USE_IMAP
if (mx_get_magic (ctx->path) == M_IMAP)
return imap_open_mailbox_append (ctx);
#endif
if (stat (ctx->path, &sb) == 0) {
ctx->magic = mx_get_magic (ctx->path);
switch (ctx->magic) {
case 0:
mutt_error (_("%s is not a mailbox."), ctx->path);
/* fall through */
case -1:
return (-1);
}
}
else if (errno == ENOENT) {
ctx->magic = DefaultMagic;
if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
char tmp[_POSIX_PATH_MAX];
if (mkdir (ctx->path, S_IRWXU)) {
mutt_perror (ctx->path);
return (-1);
}
if (ctx->magic == M_MAILDIR) {
snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
if (mkdir (tmp, S_IRWXU)) {
mutt_perror (tmp);
rmdir (ctx->path);
return (-1);
}
snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
if (mkdir (tmp, S_IRWXU)) {
mutt_perror (tmp);
snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
rmdir (tmp);
rmdir (ctx->path);
return (-1);
}
snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
if (mkdir (tmp, S_IRWXU)) {
mutt_perror (tmp);
snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
rmdir (tmp);
snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
rmdir (tmp);
rmdir (ctx->path);
return (-1);
}
}
else {
int i;
snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
if ((i = creat (tmp, S_IRWXU)) == -1) {
mutt_perror (tmp);
rmdir (ctx->path);
return (-1);
}
close (i);
}
}
}
else {
mutt_perror (ctx->path);
return (-1);
}
switch (ctx->magic) {
case M_MBOX:
case M_MMDF:
if ((ctx->fp =
safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
|| mbox_lock_mailbox (ctx, 1, 1) != 0) {
if (!ctx->fp)
mutt_perror (ctx->path);
else {
mutt_error (_("Couldn't lock %s\n"), ctx->path);
safe_fclose (&ctx->fp);
}
return (-1);
}
fseeko (ctx->fp, 0, 2);
break;
case M_MH:
case M_MAILDIR:
/* nothing to do */
break;
default:
return (-1);
}
return 0;
}
/*
* open a mailbox and parse it
*
* Args:
* flags M_NOSORT do not sort mailbox
* M_APPEND open mailbox for appending
* M_READONLY open mailbox in read-only mode
* M_QUIET only print error messages
* ctx if non-null, context struct to use
*/
CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
{
CONTEXT *ctx = pctx;
int rc;
if (!ctx)
ctx = mem_malloc (sizeof (CONTEXT));
memset (ctx, 0, sizeof (CONTEXT));
ctx->path = str_dup (path);
ctx->msgnotreadyet = -1;
ctx->collapsed = 0;
if (flags & M_QUIET)
ctx->quiet = 1;
if (flags & M_READONLY)
ctx->readonly = 1;
if (flags & M_COUNT)
ctx->counting = 1;
if (flags & (M_APPEND | M_NEWFOLDER)) {
if (mx_open_mailbox_append (ctx, flags) != 0) {
mx_fastclose_mailbox (ctx);
if (!pctx)
mem_free (&ctx);
return NULL;
}
return ctx;
}
if (!MX_IDX(ctx->magic-1))
ctx->magic = mx_get_magic (path);
#ifdef USE_COMPRESSED
if (ctx->magic == M_COMPRESSED)
mutt_open_read_compressed (ctx);
#endif
if (ctx->magic == 0)
mutt_error (_("%s is not a mailbox."), path);
if (ctx->magic == -1)
mutt_perror (path);
if (ctx->magic <= 0) {
mx_fastclose_mailbox (ctx);
if (!pctx)
mem_free (&ctx);
return (NULL);
}
/* if the user has a `push' command in their .muttrc, or in a folder-hook,
* it will cause the progress messages not to be displayed because
* mutt_refresh() will think we are in the middle of a macro. so set a
* flag to indicate that we should really refresh the screen.
*/
set_option (OPTFORCEREFRESH);
if (!ctx->quiet)
mutt_message (_("Reading %s..."), ctx->path);
rc = MX_COMMAND(ctx->magic-1,mx_open_mailbox)(ctx);
if (rc == 0) {
if ((flags & M_NOSORT) == 0) {
/* avoid unnecessary work since the mailbox is completely unthreaded
to begin with */
unset_option (OPTSORTSUBTHREADS);
unset_option (OPTNEEDRESCORE);
mutt_sort_headers (ctx, 1);
}
if (!ctx->quiet)
mutt_clear_error ();
}
else {
mx_fastclose_mailbox (ctx);
if (!pctx)
mem_free (&ctx);
}
unset_option (OPTFORCEREFRESH);
return (ctx);
}
/* free up memory associated with the mailbox context */
void mx_fastclose_mailbox (CONTEXT * ctx)
{
int i;
if (!ctx)
return;
if (MX_IDX(ctx->magic-1) && MX_COMMAND(ctx->magic-1,mx_fastclose_mailbox))
MX_COMMAND(ctx->magic-1,mx_fastclose_mailbox(ctx));
if (ctx->subj_hash)
hash_destroy (&ctx->subj_hash, NULL);
if (ctx->id_hash)
hash_destroy (&ctx->id_hash, NULL);
mutt_clear_threads (ctx);
for (i = 0; i < ctx->msgcount; i++)
mutt_free_header (&ctx->hdrs[i]);
mem_free (&ctx->hdrs);
mem_free (&ctx->v2r);
#ifdef USE_COMPRESSED
if (ctx->compressinfo)
mutt_fast_close_compressed (ctx);
#endif
mem_free (&ctx->path);
mem_free (&ctx->pattern);
if (ctx->limit_pattern)
mutt_pattern_free (&ctx->limit_pattern);
safe_fclose (&ctx->fp);
memset (ctx, 0, sizeof (CONTEXT));
}
/* save changes to disk */
static int sync_mailbox (CONTEXT * ctx, int *index_hint)
{
int rc = -1;
if (!ctx->quiet)
mutt_message (_("Writing %s..."), ctx->path);
if (MX_IDX(ctx->magic-1))
/* the 1 is only of interest for IMAP and means EXPUNGE */
rc = MX_COMMAND(ctx->magic-1,mx_sync_mailbox(ctx,1,index_hint));
#ifdef USE_COMPRESSED
if (rc == 0 && ctx->compressinfo)
return mutt_sync_compressed (ctx);
#endif
return rc;
}
/* move deleted mails to the trash folder */
static int trash_append (CONTEXT * ctx)
{
CONTEXT *ctx_trash;
int i = 0;
struct stat st, stc;
if (!TrashPath || !ctx->deleted ||
(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
return 0;
for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
ctx->hdrs[i]->appended); i++);
if (i == ctx->msgcount)
return 0; /* nothing to be done */
if (mutt_save_confirm (TrashPath, &st) != 0) {
mutt_error _("message(s) not deleted");
return -1;
}
if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
&& stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
return 0; /* we are in the trash folder: simple sync */
if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
for (i = 0; i < ctx->msgcount; i++)
if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
&& !ctx->hdrs[i]->purged
&& mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
mx_close_mailbox (ctx_trash, NULL);
return -1;
}
mx_close_mailbox (ctx_trash, NULL);
}
else {
mutt_error _("Can't open trash folder");
return -1;
}
return 0;
}
/* save changes and close mailbox */
static int _mx_close_mailbox (CONTEXT * ctx, int *index_hint)
{
int i, move_messages = 0, purge = 1, read_msgs = 0;
int check;
int isSpool = 0;
CONTEXT f;
char mbox[_POSIX_PATH_MAX];
char buf[SHORT_STRING];
if (!ctx)
return 0;
ctx->closing = 1;
#ifdef USE_NNTP
if (ctx->magic == M_NNTP) {
int ret;
ret = nntp_close_mailbox (ctx);
mx_fastclose_mailbox (ctx);
return ret;
}
#endif
if (ctx->readonly || ctx->dontwrite) {
/* mailbox is readonly or we don't want to write */
mx_fastclose_mailbox (ctx);
return 0;
}
if (ctx->append) {
/* mailbox was opened in write-mode */
if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
mbox_close_mailbox (ctx);
else
mx_fastclose_mailbox (ctx);
return 0;
}
for (i = 0; i < ctx->msgcount; i++) {
if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
&& !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
read_msgs++;
}
if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
char *p;
if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
isSpool = 1;
strfcpy (mbox, p, sizeof (mbox));
}
else {
strfcpy (mbox, NONULL (Inbox), sizeof (mbox));
isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
}
mutt_expand_path (mbox, sizeof (mbox));
if (isSpool) {
snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
ctx->closing = 0;
return (-1);
}
}
}
/*
* There is no point in asking whether or not to purge if we are
* just marking messages as "trash".
*/
if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
snprintf (buf, sizeof (buf), ctx->deleted == 1
? _("Purge %d deleted message?") :
_("Purge %d deleted messages?"), ctx->deleted);
if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
ctx->closing = 0;
return (-1);
}
}
#ifdef USE_IMAP
/* IMAP servers manage the OLD flag themselves */
if (ctx->magic != M_IMAP)
#endif
if (option (OPTMARKOLD)) {
for (i = 0; i < ctx->msgcount; i++) {
if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
}
}
if (move_messages) {
mutt_message (_("Moving read messages to %s..."), mbox);
#ifdef USE_IMAP
/* try to use server-side copy first */
i = 1;
if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
/* tag messages for moving, and clear old tags, if any */
for (i = 0; i < ctx->msgcount; i++)
if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
&& !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
ctx->hdrs[i]->tagged = 1;
else
ctx->hdrs[i]->tagged = 0;
i = imap_copy_messages (ctx, NULL, mbox, 1);
}
if (i == 0) /* success */
mutt_clear_error ();
else if (i == -1) { /* horrible error, bail */
ctx->closing = 0;
return -1;
}
else /* use regular append-copy mode */
#endif
{
if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
ctx->closing = 0;
return -1;
}
for (i = 0; i < ctx->msgcount; i++) {
if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
&& !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
0) {
mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
}
else {
mx_close_mailbox (&f, NULL);
ctx->closing = 0;
return -1;
}
}
}
mx_close_mailbox (&f, NULL);
}
}
else if (!ctx->changed && ctx->deleted == 0) {
mutt_message _("Mailbox is unchanged.");
mx_fastclose_mailbox (ctx);
return 0;
}
/* copy mails to the trash before expunging */
if (purge && ctx->deleted)
if (trash_append (ctx) != 0) {
ctx->closing = 0;
return -1;
}
#ifdef USE_IMAP
/* allow IMAP to preserve the deleted flag across sessions */
if (ctx->magic == M_IMAP) {
if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
ctx->closing = 0;
return check;
}
}
else
#endif
{
if (!purge) {
for (i = 0; i < ctx->msgcount; i++)
ctx->hdrs[i]->deleted = 0;
ctx->deleted = 0;
}
if (ctx->changed || ctx->deleted) {
if ((check = sync_mailbox (ctx, index_hint)) != 0) {
ctx->closing = 0;
return check;
}
}
}
if (move_messages)
mutt_message (_("%d kept, %d moved, %d deleted."),
ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
else
mutt_message (_("%d kept, %d deleted."),
ctx->msgcount - ctx->deleted, ctx->deleted);
if (ctx->msgcount == ctx->deleted &&
(ctx->magic == M_MMDF || ctx->magic == M_MBOX) &&
!mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
mx_unlink_empty (ctx->path);
#ifdef USE_COMPRESSED
if (ctx->compressinfo && mutt_slow_close_compressed (ctx))
return (-1);
#endif
mx_fastclose_mailbox (ctx);
return 0;
}
int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
int ret = 0;
if (!ctx)
return (0);
ret = _mx_close_mailbox (ctx, index_hint);
sidebar_set_buffystats (ctx);
return (ret);
}
/* update a Context structure's internal tables. */
void mx_update_tables (CONTEXT * ctx, int committing)
{
int i, j;
/* update memory to reflect the new state of the mailbox */
ctx->vcount = 0;
ctx->vsize = 0;
ctx->tagged = 0;
ctx->deleted = 0;
ctx->new = 0;
ctx->unread = 0;
ctx->changed = 0;
ctx->flagged = 0;
#define this_body ctx->hdrs[j]->content
for (i = 0, j = 0; i < ctx->msgcount; i++) {
if ((committing && (!ctx->hdrs[i]->deleted ||
(ctx->magic == M_MAILDIR
&& option (OPTMAILDIRTRASH)))) || (!committing
&& ctx->hdrs[i]->
active)) {
if (i != j) {
ctx->hdrs[j] = ctx->hdrs[i];
ctx->hdrs[i] = NULL;
}
ctx->hdrs[j]->msgno = j;
if (ctx->hdrs[j]->virtual != -1) {
ctx->v2r[ctx->vcount] = j;
ctx->hdrs[j]->virtual = ctx->vcount++;
ctx->vsize += this_body->length + this_body->offset -
this_body->hdr_offset;
}
if (committing)
ctx->hdrs[j]->changed = 0;
else if (ctx->hdrs[j]->changed)
ctx->changed++;
if (!committing
|| (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
if (ctx->hdrs[j]->deleted)
ctx->deleted++;
}
if (ctx->hdrs[j]->tagged)
ctx->tagged++;
if (ctx->hdrs[j]->flagged)
ctx->flagged++;
if (!ctx->hdrs[j]->read) {
ctx->unread++;
if (!ctx->hdrs[j]->old)
ctx->new++;
}
j++;
}
else {
if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
ctx->size -= (ctx->hdrs[i]->content->length +
ctx->hdrs[i]->content->offset -
ctx->hdrs[i]->content->hdr_offset);
/* remove message from the hash tables */
if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
ctx->hdrs[i], NULL);
if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id,
ctx->hdrs[i], NULL);
mutt_free_header (&ctx->hdrs[i]);
}
}
#undef this_body
ctx->msgcount = j;
/* update sidebar count */
sidebar_set_buffystats (ctx);
}
/* save changes to mailbox
*
* return values:
* 0 success
* -1 error
*/
static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
{
int rc, i;
int purge = 1;
int msgcount, deleted;
if (ctx->dontwrite) {
char buf[STRING], tmp[STRING];
if (km_expand_key (buf, sizeof (buf),
km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
else
strfcpy (tmp, _("Use 'toggle-write' to re-enable write!"),
sizeof (tmp));
mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
return -1;
}
else if (ctx->readonly) {
mutt_error _("Mailbox is read-only.");
return -1;
}
if (!ctx->changed && !ctx->deleted) {
mutt_message _("Mailbox is unchanged.");
return (0);
}
if (ctx->deleted) {
char buf[SHORT_STRING];
snprintf (buf, sizeof (buf), ctx->deleted == 1
? _("Purge %d deleted message?") :
_("Purge %d deleted messages?"), ctx->deleted);
if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
return (-1);
else if (purge == M_NO) {
if (!ctx->changed)
return 0; /* nothing to do! */
#ifdef USE_IMAP
/* let IMAP servers hold on to D flags */
if (ctx->magic != M_IMAP)
#endif
{
for (i = 0; i < ctx->msgcount; i++)
ctx->hdrs[i]->deleted = 0;
ctx->deleted = 0;
}
}
else if (ctx->last_tag && ctx->last_tag->deleted)
ctx->last_tag = NULL; /* reset last tagged msg now useless */
}
/* really only for IMAP - imap_sync_mailbox results in a call to
* mx_update_tables, so ctx->deleted is 0 when it comes back */
msgcount = ctx->msgcount;
deleted = ctx->deleted;
if (purge && ctx->deleted) {
if (trash_append (ctx) == -1)
return -1;
}
#ifdef USE_IMAP
if (ctx->magic == M_IMAP)
rc = imap_sync_mailbox (ctx, purge, index_hint);
else
#endif
rc = sync_mailbox (ctx, index_hint);
if (rc == 0) {
#ifdef USE_IMAP
if (ctx->magic == M_IMAP && !purge)
mutt_message (_("Mailbox checkpointed."));
else
#endif
mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
mutt_sleep (0);
if (ctx->msgcount == ctx->deleted &&
(ctx->magic == M_MBOX || ctx->magic == M_MMDF) &&
!mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY)) {
unlink (ctx->path);
mx_fastclose_mailbox (ctx);
return 0;
}
/* if we haven't deleted any messages, we don't need to resort */
/* ... except for certain folder formats which need "unsorted"
* sort order in order to synchronize folders.
*
* MH and maildir are safe. mbox-style seems to need re-sorting,
* at least with the new threading code.
*/
if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
#ifdef USE_IMAP
/* IMAP does this automatically after handling EXPUNGE */
if (ctx->magic != M_IMAP)
#endif
{
mx_update_tables (ctx, 1);
mutt_sort_headers (ctx, 1); /* rethread from scratch */
}
}
}
return (rc);
}
int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
int ret = _mx_sync_mailbox (ctx, index_hint);
sidebar_set_buffystats (ctx);
return (ret);
}
/* args:
* dest destintation mailbox
* hdr message being copied (required for maildir support, because
* the filename depends on the message flags)
*/
MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
{
MESSAGE *msg;
ADDRESS *p = NULL;
if (!MX_IDX(dest->magic-1)) {
debug_print (1, ("function unimplemented for mailbox type %d.\n", dest->magic));
return (NULL);
}
msg = mem_calloc (1, sizeof (MESSAGE));
msg->magic = dest->magic;
msg->write = 1;
if (hdr) {
msg->flags.flagged = hdr->flagged;
msg->flags.replied = hdr->replied;
msg->flags.read = hdr->read;
msg->received = hdr->received;
}
if (msg->received == 0)
time (&msg->received);
if (MX_COMMAND(dest->magic-1,mx_open_new_message)(msg, dest, hdr) == 0) {
if (dest->magic == M_MMDF)
fputs (MMDF_SEP, msg->fp);
if ((msg->magic == M_MBOX || msg->magic == M_MMDF) && flags & M_ADD_FROM) {
if (hdr) {
if (hdr->env->return_path)
p = hdr->env->return_path;
else if (hdr->env->sender)
p = hdr->env->sender;
else
p = hdr->env->from;
}
fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL (Username),
ctime (&msg->received));
}
}
else
mem_free (&msg);
return msg;
}
/* check for new mail */
int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
#ifdef USE_COMPRESSED
if (ctx->compressinfo)
return mutt_check_mailbox_compressed (ctx);
#endif
if (ctx) {
if (ctx->locked)
lock = 0;
if (MX_IDX(ctx->magic-1) && MX_COMMAND(ctx->magic-1,mx_check_mailbox))
return (MX_COMMAND(ctx->magic-1,mx_check_mailbox)(ctx, index_hint, lock));
}
debug_print (1, ("null or invalid context.\n"));
return (-1);
}
/* return a stream pointer for a message */
MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
{
MESSAGE *msg;
msg = mem_calloc (1, sizeof (MESSAGE));
switch (msg->magic = ctx->magic) {
case M_MBOX:
case M_MMDF:
msg->fp = ctx->fp;
break;
case M_MH:
case M_MAILDIR:
{
HEADER *cur = ctx->hdrs[msgno];
char path[_POSIX_PATH_MAX];
snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
ctx->magic == M_MAILDIR)
msg->fp = maildir_open_find_message (ctx->path, cur->path);
if (msg->fp == NULL) {
mutt_perror (path);
debug_print (1, ("fopen: %s: %s (errno %d).\n", path, strerror (errno), errno));
mem_free (&msg);
}
}
break;
#ifdef USE_IMAP
case M_IMAP:
{
if (imap_fetch_message (msg, ctx, msgno) != 0)
mem_free (&msg);
break;
}
#endif /* USE_IMAP */
#ifdef USE_POP
case M_POP:
{
if (pop_fetch_message (msg, ctx, msgno) != 0)
mem_free (&msg);
break;
}
#endif /* USE_POP */
#ifdef USE_NNTP
case M_NNTP:
{
if (nntp_fetch_message (msg, ctx, msgno) != 0)
mem_free (&msg);
break;
}
#endif /* USE_NNTP */
default:
debug_print (1, ("function not implemented for mailbox type %d.\n", ctx->magic));
mem_free (&msg);
break;
}
return (msg);
}
/* commit a message to a folder */
int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
if (!(msg->write && ctx->append)) {
debug_print (1, ("msg->write = %d, ctx->append = %d\n", msg->write, ctx->append));
return -1;
}
if (!ctx || !MX_IDX(ctx->magic-1) || !MX_COMMAND(ctx->magic-1,mx_commit_message))
return (-1);
return (MX_COMMAND(ctx->magic-1,mx_commit_message) (msg, ctx));
}
/* close a pointer to a message */
int mx_close_message (MESSAGE ** msg)
{
int r = 0;
if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
#ifdef USE_IMAP
|| (*msg)->magic == M_IMAP
#endif
#ifdef USE_POP
|| (*msg)->magic == M_POP
#endif
#ifdef USE_NNTP
|| (*msg)->magic == M_NNTP
#endif
) {
r = safe_fclose (&(*msg)->fp);
}
else
(*msg)->fp = NULL;
if ((*msg)->path) {
debug_print (1, ("unlinking %s\n", (*msg)->path));
unlink ((*msg)->path);
mem_free (&(*msg)->path);
}
mem_free (msg);
return (r);
}
void mx_alloc_memory (CONTEXT * ctx)
{
int i;
size_t s = MAX (sizeof (HEADER *), sizeof (int));
if ((ctx->hdrmax + 25) * s < ctx->hdrmax * s) {
mutt_error _("Integer overflow -- can't allocate memory.");
sleep (1);
mutt_exit (1);
}
if (ctx->hdrs) {
mem_realloc (&ctx->hdrs, sizeof (HEADER *) * (ctx->hdrmax += 25));
mem_realloc (&ctx->v2r, sizeof (int) * ctx->hdrmax);
}
else {
ctx->hdrs = mem_calloc ((ctx->hdrmax += 25), sizeof (HEADER *));
ctx->v2r = mem_calloc (ctx->hdrmax, sizeof (int));
}
for (i = ctx->msgcount; i < ctx->hdrmax; i++) {
ctx->hdrs[i] = NULL;
ctx->v2r[i] = -1;
}
}
/* this routine is called to update the counts in the context structure for
* the last message header parsed.
*/
void mx_update_context (CONTEXT * ctx, int new_messages)
{
HEADER *h;
int msgno;
for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
h = ctx->hdrs[msgno];
if (WithCrypto) {
/* NOTE: this _must_ be done before the check for mailcap! */
h->security = crypt_query (h->content);
}
if (!ctx->pattern) {
ctx->v2r[ctx->vcount] = msgno;
h->virtual = ctx->vcount++;
}
else
h->virtual = -1;
h->msgno = msgno;
if (h->env->supersedes) {
HEADER *h2;
if (!ctx->id_hash)
ctx->id_hash = mutt_make_id_hash (ctx);
h2 = hash_find (ctx->id_hash, h->env->supersedes);
/* mem_free (&h->env->supersedes); should I ? */
if (h2) {
h2->superseded = 1;
if (!ctx->counting && option (OPTSCORE))
mutt_score_message (ctx, h2, 1);
}
}
/* add this message to the hash tables */
if (ctx->id_hash && h->env->message_id)
hash_insert (ctx->id_hash, h->env->message_id, h, 0);
if (!ctx->counting) {
if (ctx->subj_hash && h->env->real_subj)
hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
if (option (OPTSCORE))
mutt_score_message (ctx, h, 0);
}
if (h->changed)
ctx->changed = 1;
if (h->flagged)
ctx->flagged++;
if (h->deleted)
ctx->deleted++;
if (!h->read) {
ctx->unread++;
if (!h->old)
ctx->new++;
}
}
/* update sidebar count */
sidebar_set_buffystats (ctx);
}
/*
* Return:
* 1 if the specified mailbox contains 0 messages.
* 0 if the mailbox contains messages
* -1 on error
*/
int mx_check_empty (const char *path)
{
int i = 0;
if ((i = mx_get_idx (path)) >= 0 && MX_COMMAND(i,mx_check_empty))
return (MX_COMMAND(i,mx_check_empty)(path));
errno = EINVAL;
return (-1);
}
int mx_acl_check (CONTEXT* ctx, int flag) {
if (!ctx || !MX_IDX(ctx->magic-1))
return (0);
/* if no acl_check defined for module, assume permission is granted */
if (!MX_COMMAND(ctx->magic-1,mx_acl_check))
return (1);
return (MX_COMMAND(ctx->magic-1,mx_acl_check)(ctx,flag));
}
void mx_init (void) {
#ifdef DEBUG
int i = 0;
#endif
list_push_back (&MailboxFormats, (void*) mbox_reg_mx ());
list_push_back (&MailboxFormats, (void*) mmdf_reg_mx ());
list_push_back (&MailboxFormats, (void*) mh_reg_mx ());
list_push_back (&MailboxFormats, (void*) maildir_reg_mx ());
#ifdef USE_IMAP
list_push_back (&MailboxFormats, (void*) imap_reg_mx ());
#endif
#ifdef USE_POP
list_push_back (&MailboxFormats, (void*) pop_reg_mx ());
#endif
#ifdef USE_NNTP
list_push_back (&MailboxFormats, (void*) nntp_reg_mx ());
#endif
#ifdef USE_COMPRESSED
list_push_back (&MailboxFormats, (void*) compress_reg_mx ());
#endif
#ifdef DEBUG
/* check module registration for completeness with debug versions */
#define EXITWITHERR(m) do { fprintf(stderr, "error: incomplete mx module: %s is missing for type %i\n",m,i);exit(1); } while (0)
for (i = 0; i < MailboxFormats->length; i++) {
if (MX_COMMAND(i,type) < 1) EXITWITHERR("type");
if (!MX_COMMAND(i,mx_is_magic)) EXITWITHERR("mx_is_magic");
if (!MX_COMMAND(i,mx_open_mailbox)) EXITWITHERR("mx_open_mailbox");
/* if (!MX_COMMAND(i,mx_sync_mailbox)) EXITWITHERR("mx_sync_mailbox");*/
}
#undef EXITWITHERR
#endif /* DEBUG */
}
int mx_rebuild_cache (void) {
#ifndef USE_HCACHE
mutt_error (_("Support for header caching was not build in."));
return (1);
#else
int i = 0, magic = 0;
CONTEXT* ctx = NULL;
BUFFY* b = NULL;
if (list_empty(Incoming)) {
mutt_error (_("No mailboxes defined."));
return (1);
}
for (i = 0; i < Incoming->length; i++) {
b = (BUFFY*) Incoming->data[i];
magic = mx_get_magic (b->path);
if (magic != M_MAILDIR && magic != M_MH
#ifdef USE_IMAP
&& magic != M_IMAP
#endif
)
continue;
sidebar_set_current (b->path);
sidebar_draw (CurrentMenu);
if ((ctx = mx_open_mailbox (b->path,
M_READONLY | M_NOSORT | M_COUNT,
NULL)) != NULL)
mx_close_mailbox (ctx, 0);
}
mutt_clear_error ();
if (Context && Context->path)
sidebar_set_current (Context->path);
sidebar_draw (CurrentMenu);
return (0);
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1