Commit 4e1fb3cf authored by Thomas Roessler's avatar Thomas Roessler

More concurrent IMAP modification handling from Brendan Cully.

parent dfef7a5a
......@@ -461,19 +461,21 @@ int mutt_index_menu (void)
set_option (OPTSEARCHINVALID);
}
else if (check == M_NEW_MAIL || check == M_REOPENED)
else if (check == M_NEW_MAIL || check == M_REOPENED || check == M_FLAGS)
{
update_index (menu, Context, check, oldcount, index_hint);
/* notify the user of new mail */
if (check == M_REOPENED)
mutt_error _("Mailbox was externally modified. Flags may be wrong.");
else
else if (check == M_NEW_MAIL)
{
mutt_message _("New mail in this mailbox.");
if (option (OPTBEEPNEW))
beep ();
}
} else if (check == M_FLAGS)
mutt_message _("Mailbox was externally modified.");
/* avoid the message being overwritten by buffy */
do_buffy_notify = 0;
......
......@@ -32,12 +32,12 @@
#define IMAP_CMD_BUFSIZE 512
/* forward declarations */
static void cmd_finish (IMAP_DATA* idata);
static void cmd_handle_fatal (IMAP_DATA* idata);
static int cmd_handle_untagged (IMAP_DATA* idata);
static void cmd_make_sequence (IMAP_DATA* idata);
static void cmd_parse_capabilities (IMAP_DATA* idata, char* s);
static void cmd_parse_expunge (IMAP_DATA* idata, char* s);
static void cmd_parse_expunge (IMAP_DATA* idata, const char* s);
static void cmd_parse_fetch (IMAP_DATA* idata, char* s);
static void cmd_parse_myrights (IMAP_DATA* idata, char* s);
static char *Capabilities[] = {
......@@ -148,7 +148,7 @@ int imap_cmd_step (IMAP_DATA* idata)
/* tagged completion code */
if (!mutt_strncmp (cmd->buf, cmd->seq, SEQLEN))
{
cmd_finish (idata);
imap_cmd_finish (idata);
return imap_code (cmd->buf) ? IMAP_CMD_OK : IMAP_CMD_NO;
}
......@@ -229,16 +229,16 @@ int imap_cmd_running (IMAP_DATA* idata)
return 0;
}
/* cmd_finish: When the caller has finished reading command responses,
* it must call this routine to perform cleanup (eg fetch new mail if
* detected, do expunge). Called automatically by imap_cmd_step */
static void cmd_finish (IMAP_DATA* idata)
/* imap_cmd_finish: Attempts to perform cleanup (eg fetch new mail if
* detected, do expunge). Called automatically by imap_cmd_step, but
* may be called at any time. Called by imap_check_mailbox just before
* the index is refreshed, for instance. */
void imap_cmd_finish (IMAP_DATA* idata)
{
if (!(idata->state == IMAP_SELECTED) || idata->ctx->closing)
return;
if ((idata->reopen & IMAP_REOPEN_ALLOW) &&
(idata->reopen & (IMAP_EXPUNGE_PENDING|IMAP_NEWMAIL_PENDING)))
if (idata->reopen & IMAP_REOPEN_ALLOW)
{
int count = idata->newMailCount;
......@@ -247,16 +247,16 @@ static void cmd_finish (IMAP_DATA* idata)
&& count > idata->ctx->msgcount)
{
/* read new mail messages */
dprint (2, (debugfile, "cmd_finish: Fetching new mail\n"));
dprint (2, (debugfile, "imap_cmd_finish: Fetching new mail\n"));
/* check_status: curs_main uses imap_check_mailbox to detect
* whether the index needs updating */
idata->check_status = IMAP_NEWMAIL_PENDING;
idata->reopen &= ~IMAP_NEWMAIL_PENDING;
count = imap_read_headers (idata, idata->ctx->msgcount, count-1)+1;
}
else
else if (idata->reopen & IMAP_EXPUNGE_PENDING)
{
dprint (2, (debugfile, "cmd_finish: Expunging mailbox\n"));
dprint (2, (debugfile, "imap_cmd_finish: Expunging mailbox\n"));
imap_expunge_mailbox (idata);
/* Detect whether we've gotten unexpected EXPUNGE messages */
if (idata->reopen & IMAP_EXPUNGE_PENDING &&
......@@ -334,9 +334,11 @@ static int cmd_handle_untagged (IMAP_DATA* idata)
idata->newMailCount = count;
}
}
/* pn vs. s: need initial seqno */
else if (ascii_strncasecmp ("EXPUNGE", s, 7) == 0)
/* pn vs. s: need initial seqno */
cmd_parse_expunge (idata, pn);
else if (ascii_strncasecmp ("FETCH", s, 5) == 0)
cmd_parse_fetch (idata, pn);
}
else if (ascii_strncasecmp ("CAPABILITY", s, 10) == 0)
cmd_parse_capabilities (idata, s);
......@@ -407,7 +409,7 @@ static void cmd_parse_capabilities (IMAP_DATA* idata, char* s)
/* cmd_parse_expunge: mark headers with new sequence ID and mark idata to
* be reopened at our earliest convenience */
static void cmd_parse_expunge (IMAP_DATA* idata, char* s)
static void cmd_parse_expunge (IMAP_DATA* idata, const char* s)
{
int expno, cur;
HEADER* h;
......@@ -433,6 +435,65 @@ static void cmd_parse_expunge (IMAP_DATA* idata, char* s)
idata->reopen |= IMAP_EXPUNGE_PENDING;
}
/* cmd_parse_fetch: Load fetch response into IMAP_DATA. Currently only
* handles unanticipated FETCH responses, and only FLAGS data. We get
* these if another client has changed flags for a mailbox we've selected.
* Of course, a lot of code here duplicates code in message.c. */
static void cmd_parse_fetch (IMAP_DATA* idata, char* s)
{
int msgno, cur;
HEADER* h = NULL;
dprint (2, (debugfile, "Handling FETCH\n"));
msgno = atoi (s);
/* see cmd_parse_expunge */
for (cur = 0; cur < idata->ctx->msgcount; cur++)
{
h = idata->ctx->hdrs[cur];
if (h->active && h->index+1 == msgno)
{
dprint (2, (debugfile, "Message UID %d updated\n", HEADER_DATA(h)->uid));
break;
}
h = NULL;
}
if (!h)
{
dprint (1, (debugfile, "FETCH response ignored for this message\n"));
return;
}
/* skip FETCH */
s = imap_next_word (s);
s = imap_next_word (s);
if (*s != '(')
{
dprint (1, (debugfile, "Malformed FETCH response"));
return;
}
s++;
if (ascii_strncasecmp ("FLAGS", s, 5) != 0)
{
dprint (2, (debugfile, "Only handle FLAGS updates\n"));
return;
}
/* If server flags could conflict with mutt's flags, reopen the mailbox. */
if (h->changed)
idata->reopen |= IMAP_EXPUNGE_PENDING;
else {
imap_set_flags (idata, h, s);
idata->check_status = IMAP_FLAGS_PENDING;
}
}
/* cmd_parse_myrights: set rights bits according to MYRIGHTS response */
static void cmd_parse_myrights (IMAP_DATA* idata, char* s)
{
......
......@@ -652,7 +652,7 @@ int imap_open_mailbox (CONTEXT* ctx)
ctx->msgcount = 0;
count = imap_read_headers (idata, 0, count - 1) + 1;
dprint (1, (debugfile, "imap_open_mailbox(): msgcount is %d\n", ctx->msgcount));
dprint (2, (debugfile, "imap_open_mailbox: msgcount is %d\n", ctx->msgcount));
FREE (&mx.mbox);
return 0;
......@@ -1076,6 +1076,7 @@ int imap_check_mailbox (CONTEXT *ctx, int *index_hint)
IMAP_DATA* idata;
time_t now;
int result = 0;
idata = (IMAP_DATA*) ctx->data;
......@@ -1087,19 +1088,21 @@ int imap_check_mailbox (CONTEXT *ctx, int *index_hint)
if (imap_exec (idata, "NOOP", 0) != 0)
return -1;
}
/* We call this even when we haven't run NOOP in case we have pending
* changes to process, since we can reopen here. */
imap_cmd_finish (idata);
if (idata->check_status & IMAP_NEWMAIL_PENDING)
{
idata->check_status &= ~IMAP_NEWMAIL_PENDING;
return M_NEW_MAIL;
}
if (idata->check_status & IMAP_EXPUNGE_PENDING)
{
idata->check_status &= ~IMAP_EXPUNGE_PENDING;
return M_REOPENED;
}
result = M_NEW_MAIL;
else if (idata->check_status & IMAP_EXPUNGE_PENDING)
result = M_REOPENED;
else if (idata->check_status & IMAP_FLAGS_PENDING)
result = M_FLAGS;
return 0;
idata->check_status = 0;
return result;
}
/* returns count of recent messages if new = 1, else count of total messages.
......
......@@ -50,9 +50,10 @@
#define SEQLEN 5
#define IMAP_REOPEN_ALLOW (1<<0)
#define IMAP_EXPUNGE_PENDING (1<<1)
#define IMAP_NEWMAIL_PENDING (1<<2)
#define IMAP_EXPUNGE_EXPECTED (1<<3)
#define IMAP_EXPUNGE_EXPECTED (1<<1)
#define IMAP_EXPUNGE_PENDING (1<<2)
#define IMAP_NEWMAIL_PENDING (1<<3)
#define IMAP_FLAGS_PENDING (1<<4)
/* imap_exec flags (see imap_exec) */
#define IMAP_CMD_FAIL_OK (1<<0)
......@@ -205,6 +206,7 @@ int imap_authenticate (IMAP_DATA* idata);
/* command.c */
int imap_cmd_start (IMAP_DATA* idata, const char* cmd);
int imap_cmd_step (IMAP_DATA* idata);
void imap_cmd_finish (IMAP_DATA* idata);
int imap_code (const char* s);
int imap_exec (IMAP_DATA* idata, const char* cmd, int flags);
......@@ -212,6 +214,7 @@ int imap_exec (IMAP_DATA* idata, const char* cmd, int flags);
void imap_add_keywords (char* s, HEADER* keywords, LIST* mailbox_flags, size_t slen);
void imap_free_header_data (void** data);
int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend);
char* imap_set_flags (IMAP_DATA* idata, HEADER* h, char* s);
/* util.c */
int imap_continue (const char* msg, const char* resp);
......
......@@ -58,7 +58,6 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend)
ctx = idata->ctx;
/* define search string */
if (mutt_bit_isset (idata->capabilities,IMAP4REV1))
{
snprintf (hdrreq, sizeof (hdrreq), "BODY.PEEK[HEADER.FIELDS (%s)]",
......@@ -302,48 +301,8 @@ int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno)
else if ((ascii_strncasecmp ("FLAGS", pc, 5) == 0) &&
!ctx->hdrs[msgno]->changed)
{
IMAP_HEADER newh;
unsigned char readonly;
h = ctx->hdrs[msgno];
memset (&newh, 0, sizeof (newh));
newh.data = safe_calloc (1, sizeof (IMAP_HEADER_DATA));
dprint (2, (debugfile, "imap_fetch_message: parsing FLAGS\n"));
if ((pc = msg_parse_flags (&newh, pc)) == NULL)
if ((pc = imap_set_flags (idata, ctx->hdrs[msgno], pc)) == NULL)
goto bail;
/* this is less efficient than the code which used to be here,
* but (1) this is only invoked when fetching messages, and (2)
* this way, we can make sure that side effects of flag changes
* are taken account of the proper way.
*/
/* YAUH (yet another ugly hack): temporarily set context to
* read-write even if it's read-only, so *server* updates of
* flags can be processed by mutt_set_flag. ctx->changed must
* be restored afterwards */
readonly = ctx->readonly;
ctx->readonly = 0;
mutt_set_flag (ctx, h, M_NEW,
!(newh.read || newh.old || h->read || h->old));
mutt_set_flag (ctx, h, M_OLD, newh.old);
mutt_set_flag (ctx, h, M_READ, h->read || newh.read);
mutt_set_flag (ctx, h, M_DELETE, h->deleted || newh.deleted);
mutt_set_flag (ctx, h, M_FLAG, h->flagged || newh.flagged);
mutt_set_flag (ctx, h, M_REPLIED, h->replied || newh.replied);
/* this message is now definitively *not* changed (mutt_set_flag
* marks things changed as a side-effect) */
h->changed = 0;
ctx->changed &= ~readonly;
ctx->readonly = readonly;
mutt_free_list (&(HEADER_DATA(h)->keywords));
HEADER_DATA(h)->keywords = newh.data->keywords;
FREE(&newh.data);
}
}
}
......@@ -694,6 +653,51 @@ void imap_free_header_data (void** data)
safe_free (data);
}
/* imap_set_flags: fill out the message header according to the flags from
* the server. Expects a flags line of the form "FLAGS (flag flag ...)" */
char* imap_set_flags (IMAP_DATA* idata, HEADER* h, char* s)
{
CONTEXT* ctx = idata->ctx;
IMAP_HEADER newh;
unsigned char readonly;
memset (&newh, 0, sizeof (newh));
newh.data = safe_calloc (1, sizeof (IMAP_HEADER_DATA));
dprint (2, (debugfile, "imap_fetch_message: parsing FLAGS\n"));
if ((s = msg_parse_flags (&newh, s)) == NULL)
{
FREE (&newh.data);
return NULL;
}
/* YAUH (yet another ugly hack): temporarily set context to
* read-write even if it's read-only, so *server* updates of
* flags can be processed by mutt_set_flag. ctx->changed must
* be restored afterwards */
readonly = ctx->readonly;
ctx->readonly = 0;
mutt_set_flag (ctx, h, M_NEW, !(newh.read || newh.old));
mutt_set_flag (ctx, h, M_OLD, newh.old);
mutt_set_flag (ctx, h, M_READ, newh.read);
mutt_set_flag (ctx, h, M_DELETE, newh.deleted);
mutt_set_flag (ctx, h, M_FLAG, newh.flagged);
mutt_set_flag (ctx, h, M_REPLIED, newh.replied);
/* this message is now definitively *not* changed (mutt_set_flag
* marks things changed as a side-effect) */
h->changed = 0;
ctx->changed &= ~readonly;
ctx->readonly = readonly;
mutt_free_list (&(HEADER_DATA(h)->keywords));
HEADER_DATA(h)->keywords = newh.data->keywords;
FREE(&newh.data);
return s;
}
/* msg_fetch_header: import IMAP FETCH response into an IMAP_HEADER.
* Expects string beginning with * n FETCH.
* Returns:
......@@ -846,8 +850,7 @@ static int msg_parse_fetch (IMAP_HEADER *h, char *s)
return 0;
}
/* msg_parse_flags: fill out the message header according to the flags from the
* server. Expects a flags line of the form "FLAGS (flag flag ...)" */
/* msg_parse_flags: read a FLAGS token into an IMAP_HEADER */
static char* msg_parse_flags (IMAP_HEADER* h, char* s)
{
int recent = 0;
......
......@@ -33,7 +33,8 @@ enum
{
M_NEW_MAIL = 1, /* new mail received in mailbox */
M_LOCKED, /* couldn't lock the mailbox */
M_REOPENED /* mailbox was reopened */
M_REOPENED, /* mailbox was reopened */
M_FLAGS /* nondestructive flags change (IMAP) */
};
typedef struct
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment