From: Thomas Roessler Date: Tue, 7 Sep 1999 06:53:08 +0000 (+0000) Subject: * redoes the folder update optimisation I did yesterday. It's somewhat X-Git-Tag: mutt-0-96-7-rel~13 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=458307e72af511d2fad7961fdd180f02b9f77256;p=mutt * redoes the folder update optimisation I did yesterday. It's somewhat cleaner and less invasive, and I'm not so worried about memory leaks now. * Fixes the bug where mutt would append a '/' to $folder even if it was only {mailhost}, causing mutt to browse the root directory instead of the home directory. * includes a first stab at preserving the D flag on the IMAP server. Now if you answer no to 'Purge deleted', the server still stores the messages as deleted, but doesn't expunge them on exit. NOTE: this is a first attempt. Play around, but don't mark things as deleted that you'd be sorry to see disappear. (From: Brendan Cully ) --- diff --git a/complete.c b/complete.c index d9e1c011..272b263b 100644 --- a/complete.c +++ b/complete.c @@ -51,20 +51,22 @@ int mutt_complete (char *s, size_t slen) if (*s == '=' || *s == '+') { if (s[1]) - snprintf (imap_path, sizeof(imap_path), "%s/%s", NONULL(Maildir), - s+1); + { + /* don't append '/' if Maildir is {host} only */ + if (mx_is_imap (NONULL (Maildir)) && Maildir[strlen (Maildir)-1] == '}') + snprintf (imap_path, sizeof (imap_path), "%s%s", Maildir, s+1); + else + snprintf (imap_path, sizeof (imap_path), "%s/%s", NONULL (Maildir), + s+1); + } else strfcpy (imap_path, NONULL(Maildir), sizeof(imap_path)); } else - { strfcpy (imap_path, s, sizeof(imap_path)); - } if (mx_is_imap (imap_path)) - { return imap_complete (s, slen, imap_path); - } #endif if (*s == '=' || *s == '+') diff --git a/flags.c b/flags.c index 0f62e6e1..0a454ba7 100644 --- a/flags.c +++ b/flags.c @@ -39,6 +39,15 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) { h->deleted = 1; if (upd_ctx) ctx->deleted++; +#ifdef USE_IMAP + /* deleted messages aren't treated as changed elsewhere so that the + * purge-on-sync option works correctly. This isn't applicable here */ + if (ctx->magic == M_IMAP) + { + h->changed = 1; + if (upd_ctx) ctx->changed = 1; + } +#endif } } else if (h->deleted) @@ -46,7 +55,7 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx) h->deleted = 0; if (upd_ctx) ctx->deleted--; #ifdef USE_IMAP -/* if you undelete a message, the imap server will probably need to know. */ + /* see my comment above */ if (ctx->magic == M_IMAP) { h->changed = 1; diff --git a/imap/BUGS b/imap/BUGS index b75eb413..d52db96e 100644 --- a/imap/BUGS +++ b/imap/BUGS @@ -1,8 +1,6 @@ -* Mutt doesn't handle timeouts or dropped connections gracefully. +In no particular order: -* Mutt appends a '/' to $folder even when it is just '{server}', causing some - servers to look for folders off the root of the server instead of in the - home namespace. +* Mutt doesn't handle timeouts or dropped connections gracefully. * Have a hard time when the home namespace isn't default "". @@ -13,4 +11,4 @@ * The mutt_pretty routines don't work well when the delimiter isn't '/'. Brendan Cully -Updated 19990903 +Updated 19990906 diff --git a/imap/Makefile.am b/imap/Makefile.am index 33eca53e..c69a17e0 100644 --- a/imap/Makefile.am +++ b/imap/Makefile.am @@ -7,5 +7,4 @@ EXTRA_DIST = BUGS TODO noinst_LIBRARIES = libimap.a noinst_HEADERS = imap_private.h imap_socket.h md5.h message.h -libimap_a_SOURCES = imap.c imap.h auth.c browse.c md5c.c socket.c \ - message.c +libimap_a_SOURCES = auth.c browse.c imap.c imap.h md5c.c message.c socket.c diff --git a/imap/TODO b/imap/TODO index 340071d7..28b96712 100644 --- a/imap/TODO +++ b/imap/TODO @@ -1,4 +1,4 @@ -IMAP enhancements/fixes, by priority: +IMAP enhancements, by priority: [ -- socket -- ] * Smarter connection code. Mutt should handle dropped connections/routing @@ -18,23 +18,17 @@ IMAP enhancements/fixes, by priority: Possibly we could use a tree-view in the browser, w/ expand, collapse. For low-bandwidth lines we could defer getting subfolder lists until the folder is expanded. - -[ -- new features -- ] -* Implement server message COPY, instead of FETCH/APPEND. - - PRIORITY: [** ] - -* Implement the received folder on IMAP. (Wait on COPY). - + Current thought is that will select a mailbox if it doesn't have + subfolders or enter the folder if it does. If it has messages and subfolders, + we'll use a new key to select it as a mailbox. + We should maybe add a new imap_format string for IMAP browsing, without all + the stat variables but with tags like how many messages are in the folders, + how many subfolders, that weird \Marked tag, etc. + PRIORITY: [** ] -* Commands for creating/deleting folders on the server, since users may not - otherwise be able to do this on IMAP servers. - - PRIORITY: [** ] - -* Implement READ-ONLY support, and the x (quit without saving changes) - command. +[ -- speed -- ] +* Implement server message COPY, instead of FETCH/APPEND. PRIORITY: [** ] @@ -54,5 +48,20 @@ IMAP enhancements/fixes, by priority: PRIORITY: [* ] +[ -- new features -- ] +* Implement the received folder on IMAP. (Wait on COPY). + + PRIORITY: [** ] + +* Commands for creating/deleting folders on the server, since users may not + otherwise be able to do this on IMAP servers. + + PRIORITY: [** ] + +* Implement READ-ONLY support, and the x (quit without saving changes) + command. + + PRIORITY: [** ] + Brendan Cully -Updated: 19990904 +Updated: 19990906 diff --git a/imap/imap.c b/imap/imap.c index ea66c688..b577de91 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -50,7 +50,6 @@ static void imap_parse_capabilities (IMAP_DATA *idata, char *s); static int imap_reopen_mailbox (CONTEXT *ctx, int *index_hint); static int imap_get_delim (IMAP_DATA *idata, CONNECTION *conn); static char* imap_get_flags (LIST** hflags, char* s); -static int imap_has_flag (LIST* flag_list, const char* flag); static int imap_check_acl (IMAP_DATA *idata); static int imap_check_capabilities (IMAP_DATA *idata); static int imap_create_mailbox (IMAP_DATA *idata, char *mailbox); @@ -1007,10 +1006,10 @@ int imap_open_mailbox (CONTEXT *ctx) else if (mutt_strncasecmp ("FLAGS", pc, 5) == 0) { /* don't override PERMANENTFLAGS */ - if (!idata->mailbox_flags) + if (!idata->flags) { dprint (2, (debugfile, "Getting mailbox FLAGS\n")); - if ((pc = imap_get_flags (&(idata->mailbox_flags), pc)) == NULL) + if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL) return -1; } } @@ -1020,10 +1019,10 @@ int imap_open_mailbox (CONTEXT *ctx) dprint (2, (debugfile, "Getting mailbox PERMANENTFLAGS\n")); /* safe to call on NULL */ - mutt_free_list (&(idata->mailbox_flags)); + mutt_free_list (&(idata->flags)); /* skip "OK [PERMANENT" so syntax is the same as FLAGS */ pc += 13; - if ((pc = imap_get_flags (&(idata->mailbox_flags), pc)) == NULL) + if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL) return -1; } else if (imap_handle_untagged (idata, buf) != 0) @@ -1035,11 +1034,11 @@ int imap_open_mailbox (CONTEXT *ctx) /* dump the mailbox flags we've found */ if (debuglevel > 2) { - if (!idata->mailbox_flags) + if (!idata->flags) dprint (3, (debugfile, "No folder flags found\n")); else { - LIST* t = idata->mailbox_flags; + LIST* t = idata->flags; dprint (3, (debugfile, "Mailbox flags: ")); @@ -1118,7 +1117,7 @@ int imap_select_mailbox (CONTEXT* ctx, const char* path) return -1; } - if (imap_sync_mailbox (ctx, M_NO) < 0) + if (imap_sync_mailbox (ctx, 0) < 0) return -1; idata = CTX_DATA; @@ -1261,44 +1260,6 @@ int imap_close_connection (CONTEXT *ctx) return 0; } -/* imap_has_flag: do a caseless comparison of the flag against a flag list, - * return 1 if found or flag list has '\*', 0 otherwise */ -static int imap_has_flag (LIST* flag_list, const char* flag) -{ - if (!flag_list) - return 0; - - flag_list = flag_list->next; - while (flag_list) - { - if (!mutt_strncasecmp (flag_list->data, flag, strlen (flag_list->data))) - return 1; - - flag_list = flag_list->next; - } - - return 0; -} - -/* imap_stringify_flaglist: concatenate custom IMAP tags to list, if they - * appear in the folder flags list. Why wouldn't they? */ -static void imap_stringify_flaglist (LIST* flags, LIST* mailbox_flags, char *s) -{ - if (!mailbox_flags || !flags) - return; - - flags = flags->next; - while (flags) - { - if (imap_has_flag (mailbox_flags, flags->data)) - { - strcat (s, flags->data); - strcat (s, " "); - } - flags = flags->next; - } -} - static void imap_set_flag (CONTEXT *ctx, int aclbit, int flag, const char *str, char *flags) { @@ -1310,7 +1271,7 @@ static void imap_set_flag (CONTEXT *ctx, int aclbit, int flag, const char *str, /* update the IMAP server to reflect message changes done within mutt. * Arguments * ctx: the current context - * expunge: M_YES or M_NO - do expunge? */ + * expunge: 0 or 1 - do expunge? */ int imap_sync_mailbox (CONTEXT *ctx, int expunge) { char seq[8]; @@ -1321,7 +1282,7 @@ int imap_sync_mailbox (CONTEXT *ctx, int expunge) /* save status changes */ for (n = 0; n < ctx->msgcount; n++) { - if (ctx->hdrs[n]->deleted || ctx->hdrs[n]->changed) + if (ctx->hdrs[n]->changed) { snprintf (buf, sizeof (buf), _("Saving message status flags... [%d/%d]"), n+1, ctx->msgcount); @@ -1339,23 +1300,42 @@ int imap_sync_mailbox (CONTEXT *ctx, int expunge) flags); /* now make sure we don't lose custom tags */ - imap_stringify_flaglist (ctx->hdrs[n]->server_flags, - CTX_DATA->mailbox_flags, flags); + imap_add_keywords (flags, ctx->hdrs[n], CTX_DATA->flags); mutt_remove_trailing_ws (flags); imap_make_sequence (seq, sizeof (seq)); - snprintf (buf, sizeof (buf), "%s STORE %d FLAGS.SILENT (%s)\r\n", seq, - ctx->hdrs[n]->index + 1, NONULL(flags)); + /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to + * explicitly revoke all system flags (if we have permission) */ + if (!*flags) + { + imap_set_flag (ctx, IMAP_ACL_SEEN, 1, "\\Seen ", flags); + imap_set_flag (ctx, IMAP_ACL_WRITE, 1, "\\Flagged ", flags); + imap_set_flag (ctx, IMAP_ACL_WRITE, 1, "\\Answered ", flags); + imap_set_flag (ctx, IMAP_ACL_DELETE, 1, "\\Deleted ", flags); + + mutt_remove_trailing_ws (flags); + + snprintf (buf, sizeof (buf), "%s STORE %d -FLAGS.SILENT (%s)\r\n", seq, + ctx->hdrs[n]->index + 1, flags); + } + else + snprintf (buf, sizeof (buf), "%s STORE %d FLAGS.SILENT (%s)\r\n", seq, + ctx->hdrs[n]->index + 1, flags); if (imap_exec (buf, sizeof (buf), CTX_DATA, seq, buf, 0) != 0) { imap_error ("imap_sync_mailbox()", buf); - return (-1); + /* Give up on this message if we pass here again */ + ctx->hdrs[n]->changed = 0; + return -1; } + + ctx->hdrs[n]->changed = 0; } } + ctx->changed = 0; - if (expunge == M_YES) + if (expunge == 1) { if (mutt_bit_isset(CTX_DATA->rights, IMAP_ACL_DELETE)) { @@ -1384,27 +1364,6 @@ int imap_sync_mailbox (CONTEXT *ctx, int expunge) return 0; } -/* commit changes and terminate connection */ -static int imap_close_mailbox (IMAP_DATA *idata) -{ - char seq[8]; - char buf[LONG_STRING]; - - /* tell the server to commit changes */ - mutt_message _("Closing mailbox..."); - imap_make_sequence (seq, sizeof (seq)); - snprintf (buf, sizeof (buf), "%s CLOSE\r\n", seq); - if (imap_exec (buf, sizeof (buf), idata, seq, buf, 0) != 0) - { - imap_error ("imap_close_mailbox()", buf); - idata->status = IMAP_FATAL; - return (-1); - } - idata->state = IMAP_AUTHENTICATED; - return 0; -} - - void imap_fastclose_mailbox (CONTEXT *ctx) { int i; @@ -1414,8 +1373,11 @@ void imap_fastclose_mailbox (CONTEXT *ctx) return; if ((CTX_DATA->state == IMAP_SELECTED) && (ctx == CTX_DATA->selected_ctx)) - if (imap_close_mailbox (CTX_DATA) != 0) - return; + CTX_DATA->state = IMAP_AUTHENTICATED; + + /* free IMAP part of headers */ + for (i = 0; i < ctx->msgcount; i++) + imap_free_header_data (&(ctx->hdrs[i]->data)); for (i = 0; i < IMAP_CACHE_LEN; i++) { diff --git a/imap/imap_private.h b/imap/imap_private.h index 6b10bcbc..3fcfbc0d 100644 --- a/imap/imap_private.h +++ b/imap/imap_private.h @@ -135,7 +135,8 @@ typedef struct unsigned char rights[(RIGHTSMAX + 7)/8]; unsigned int newMailCount; IMAP_CACHE cache[IMAP_CACHE_LEN]; - LIST *mailbox_flags; + /* all folder flags - system flags AND keywords */ + LIST *flags; } IMAP_DATA; /* -- macros -- */ @@ -166,6 +167,8 @@ void imap_unquote_string (char *s); int imap_authenticate (IMAP_DATA *idata, CONNECTION *conn); /* message.c */ +void imap_add_keywords (char* s, HEADER* keywords, LIST* mailbox_flags); +void imap_free_header_data (void** data); int imap_read_headers (CONTEXT* ctx, int msgbegin, int msgend); #endif diff --git a/imap/message.c b/imap/message.c index 5789d5da..6b0faf3e 100644 --- a/imap/message.c +++ b/imap/message.c @@ -32,9 +32,11 @@ #include "pgp.h" #endif -static void flush_buffer(char *buf, size_t *len, CONNECTION *conn); -static int msg_parse_fetch (IMAP_HEADER_INFO *h, char *s); -static char* msg_parse_flags (IMAP_FLAGS* flags, char* s); +static void flush_buffer(char* buf, size_t* len, CONNECTION* conn); +static int msg_has_flag (LIST* flag_list, const char* flag); +static IMAP_HEADER* msg_new_header (void); +static int msg_parse_fetch (IMAP_HEADER* h, char* s); +static char* msg_parse_flags (IMAP_HEADER* h, char* s); /* imap_read_headers: * Changed to read many headers instead of just one. It will return the @@ -52,25 +54,41 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) long ploc; long bytes = 0; int msgno,fetchlast; - IMAP_HEADER_INFO *h0,*h,*htemp; -#define WANT_HEADERS "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE IN-REPLY-TO REPLY-TO" - const char *want_headers = WANT_HEADERS; + IMAP_HEADER *h, *h0; + const char *want_headers = "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE IN-REPLY-TO REPLY-TO"; int using_body_peek = 0; fetchlast = 0; + /* define search string */ + if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4REV1)) + { + snprintf(hdrreq, sizeof (hdrreq), "BODY.PEEK[HEADER.FIELDS (%s)]", + want_headers); + using_body_peek = 1; + } + else if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4)) + { + snprintf(hdrreq, sizeof (hdrreq), "RFC822.HEADER.LINES (%s)", + want_headers); + } + else + { /* Unable to fetch headers for lower versions */ + mutt_error _("Unable to fetch headers from this IMAP server version."); + sleep (1); /* pause a moment to let the user see the error */ + return -1; + } + /* * We now download all of the headers into one file. This should be * faster on most systems. */ mutt_mktemp (tempfile); if (!(fp = safe_fopen (tempfile, "w+"))) - { - return (-1); - } + return -1; - h0 = (IMAP_HEADER_INFO*) safe_malloc(sizeof(IMAP_HEADER_INFO)); - h = h0; - for (msgno=msgbegin; msgno <= msgend ; msgno++) + h = msg_new_header (); + h0 = h; + for (msgno = msgbegin; msgno <= msgend ; msgno++) { snprintf (buf, sizeof (buf), _("Fetching message headers... [%d/%d]"), msgno + 1, msgend + 1); @@ -86,26 +104,9 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) * If we get more messages while doing this, we make another * request for all the new messages. */ - if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4REV1)) - { - snprintf(hdrreq, sizeof (hdrreq), "BODY.PEEK[HEADER.FIELDS (%s)]", - want_headers); - using_body_peek = 1; - } - else if (mutt_bit_isset(CTX_DATA->capabilities,IMAP4)) - { - snprintf(hdrreq, sizeof (hdrreq), "RFC822.HEADER.LINES (%s)", - want_headers); - } - else - { /* Unable to fetch headers for lower versions */ - mutt_error _("Unable to fetch headers from this IMAP server version."); - sleep (1); /* pause a moment to let the user see the error */ - return (-1); - } snprintf (buf, sizeof (buf), - "%s FETCH %d:%d (FLAGS INTERNALDATE RFC822.SIZE %s)\r\n", - seq, msgno + 1, msgend + 1, hdrreq); + "%s FETCH %d:%d (UID FLAGS INTERNALDATE RFC822.SIZE %s)\r\n", + seq, msgno + 1, msgend + 1, hdrreq); mutt_socket_write (CTX_DATA->conn, buf); fetchlast = msgend + 1; @@ -114,9 +115,7 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) do { if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - return (-1); - } + return -1; if (buf[0] == '*') { @@ -143,7 +142,7 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) if (!hdr) { imap_error ("imap_read_headers()", buf); - return (-1); + return -1; } strncpy(fpc,pc,hdr-pc); fpc += hdr-pc; @@ -153,25 +152,24 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) if (imap_get_literal_count(buf, &bytes) < 0) { imap_error ("imap_read_headers()", buf); - return (-1); + return -1; } imap_read_bytes (fp, CTX_DATA->conn, bytes); if (mutt_socket_read_line_d (buf, sizeof (buf), CTX_DATA->conn) < 0) - { - return (-1); - } + return -1; + pc = buf; } } else if (imap_handle_untagged (CTX_DATA, buf) != 0) - return (-1); + return -1; } } while ((msgno + 1) >= fetchlast && mutt_strncmp (seq, buf, SEQLEN) != 0); h->content_length = -bytes; if (msg_parse_fetch (h, fetchbuf) == -1) - return (-1); + return -1; /* subtract the header length; the total message size will be added to this */ @@ -185,12 +183,12 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) ((IMAP_DATA *) ctx->data)->status = 0; } - h->next=safe_malloc(sizeof(IMAP_HEADER_INFO)); - h=h->next; + h->next = msg_new_header (); + h = h->next; } rewind(fp); - h=h0; + h = h0; /* * Now that we have all the header information, we can tell mutt about @@ -204,21 +202,22 @@ int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend) ctx->hdrs[msgno]->env = mutt_read_rfc822_header (fp, ctx->hdrs[msgno], 0); ploc=ftell(fp); - ctx->hdrs[msgno]->read = h->flags.read; - ctx->hdrs[msgno]->old = h->flags.old; - ctx->hdrs[msgno]->deleted = h->flags.deleted; - ctx->hdrs[msgno]->flagged = h->flags.flagged; - ctx->hdrs[msgno]->replied = h->flags.replied; - ctx->hdrs[msgno]->changed = h->flags.changed; - ctx->hdrs[msgno]->server_flags = h->flags.server_flags; + ctx->hdrs[msgno]->read = h->read; + ctx->hdrs[msgno]->old = h->old; + ctx->hdrs[msgno]->deleted = h->deleted; + ctx->hdrs[msgno]->flagged = h->flagged; + ctx->hdrs[msgno]->replied = h->replied; + ctx->hdrs[msgno]->changed = h->changed; ctx->hdrs[msgno]->received = h->received; ctx->hdrs[msgno]->content->length = h->content_length; + ctx->hdrs[msgno]->data = (void *) (h->data); mx_update_context (ctx); /* increments ->msgcount */ - htemp=h; - h=h->next; - safe_free ((void **) &htemp); + h0 = h; + h = h->next; + /* hdata is freed later */ + safe_free ((void **) &h0); } fclose(fp); unlink(tempfile); @@ -318,35 +317,38 @@ int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) pc = buf; } /* UW-IMAP will provide a FLAGS update here if the FETCH causes a - * change (eg from \Unseen to \Seen) */ - else if (strncasecmp ("FLAGS", pc, 5) == 0) + * change (eg from \Unseen to \Seen). + * Uncommitted changes in mutt take precedence. If we decide to + * incrementally update flags later, this won't stop us syncing */ + else if ((strncasecmp ("FLAGS", pc, 5) == 0) && + !ctx->hdrs[msgno]->changed) { - IMAP_FLAGS flags; + IMAP_HEADER* newh; HEADER* h = ctx->hdrs[msgno]; - flags.server_flags = NULL; + newh = msg_new_header (); dprint (2, (debugfile, "imap_fetch_message: parsing FLAGS\n")); - if ((pc = msg_parse_flags (&flags, pc)) == NULL) + if ((pc = msg_parse_flags (newh, pc)) == NULL) return -1; /* update context sums */ - ctx->new += ((flags.read | flags.old) ? 0 : 1) - + ctx->new += ((newh->read | newh->old) ? 0 : 1) - ((h->read | h->old) ? 0 : 1); - ctx->unread += h->read - flags.read; - ctx->deleted += h->deleted = flags.deleted; - ctx->flagged += h->flagged - flags.flagged; + ctx->unread += h->read - newh->read; + ctx->deleted += h->deleted = newh->deleted; + ctx->flagged += h->flagged - newh->flagged; /* now commit the new flags */ - ctx->hdrs[msgno]->read = flags.read; - ctx->hdrs[msgno]->old = flags.old; - ctx->hdrs[msgno]->deleted = flags.deleted; - ctx->hdrs[msgno]->flagged = flags.flagged; - ctx->hdrs[msgno]->replied = flags.replied; - ctx->hdrs[msgno]->changed = flags.changed; - - mutt_free_list (&(h->server_flags)); - h->server_flags = flags.server_flags; + ctx->hdrs[msgno]->read = newh->read; + ctx->hdrs[msgno]->old = newh->old; + ctx->hdrs[msgno]->deleted = newh->deleted; + ctx->hdrs[msgno]->flagged = newh->flagged; + ctx->hdrs[msgno]->replied = newh->replied; + + mutt_free_list (&(HEADER_DATA(h)->keywords)); + HEADER_DATA(h)->keywords = newh->data->keywords; + safe_free ((void**) &newh); } } } @@ -505,8 +507,79 @@ int imap_append_message (CONTEXT *ctx, MESSAGE *msg) return 0; } +/* imap_add_keywords: concatenate custom IMAP tags to list, if they + * appear in the folder flags list. Why wouldn't they? */ +void imap_add_keywords (char* s, HEADER* h, LIST* mailbox_flags) +{ + LIST *keywords; + + if (!mailbox_flags || !HEADER_DATA(h) || !HEADER_DATA(h)->keywords) + return; + + keywords = HEADER_DATA(h)->keywords->next; + + while (keywords) + { + if (msg_has_flag (mailbox_flags, keywords->data)) + { + strcat (s, keywords->data); + strcat (s, " "); + } + keywords = keywords->next; + } +} + +/* imap_free_header_data: free IMAP_HEADER structure */ +void imap_free_header_data (void** data) +{ + /* this should be safe even if the list wasn't used */ + mutt_free_list (&(((IMAP_HEADER_DATA*) *data)->keywords)); + + safe_free (data); +} + +/* msg_new_header: allocate and initialise a new IMAP_HEADER structure */ +static IMAP_HEADER* msg_new_header (void) +{ + IMAP_HEADER* h; + + h = (IMAP_HEADER*) safe_malloc (sizeof (IMAP_HEADER)); + h->data = (IMAP_HEADER_DATA*) safe_malloc (sizeof (IMAP_HEADER_DATA)); + + /* lists aren't allocated unless they're used */ + h->data->keywords = NULL; + + h->deleted = 0; + h->flagged = 0; + h->replied = 0; + h->read = 0; + h->old = 0; + h->changed = 0; + + return h; +} + +/* msg_has_flag: do a caseless comparison of the flag against a flag list, + * return 1 if found or flag list has '\*', 0 otherwise */ +static int msg_has_flag (LIST* flag_list, const char* flag) +{ + if (!flag_list) + return 0; + + flag_list = flag_list->next; + while (flag_list) + { + if (!mutt_strncasecmp (flag_list->data, flag, strlen (flag_list->data))) + return 1; + + flag_list = flag_list->next; + } + + return 0; +} + /* msg_parse_fetch: handle headers returned from header fetch */ -static int msg_parse_fetch (IMAP_HEADER_INFO *h, char *s) +static int msg_parse_fetch (IMAP_HEADER *h, char *s) { char tmp[SHORT_STRING]; char *ptmp; @@ -520,10 +593,17 @@ static int msg_parse_fetch (IMAP_HEADER_INFO *h, char *s) if (mutt_strncasecmp ("FLAGS", s, 5) == 0) { - h->flags.server_flags = NULL; - if ((s = msg_parse_flags (&(h->flags), s)) == NULL) + if ((s = msg_parse_flags (h, s)) == NULL) return -1; } + else if (mutt_strncasecmp ("UID", s, 3) == 0) + { + s += 3; + SKIPWS (s); + h->data->uid = (unsigned int) atoi (s); + + s = imap_next_word (s); + } else if (mutt_strncasecmp ("INTERNALDATE", s, 12) == 0) { s += 12; @@ -568,7 +648,7 @@ static int msg_parse_fetch (IMAP_HEADER_INFO *h, char *s) /* 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 ...)" */ -static char* msg_parse_flags (IMAP_FLAGS* flags, char* s) +static char* msg_parse_flags (IMAP_HEADER* h, char* s) { int recent = 0; @@ -589,36 +669,28 @@ static char* msg_parse_flags (IMAP_FLAGS* flags, char* s) } s++; - /* reset the current flags */ - flags->deleted = 0; - flags->flagged = 0; - flags->replied = 0; - flags->read = 0; - flags->old = 0; - flags->changed = 0; - /* start parsing */ while (*s && *s != ')') { if (mutt_strncasecmp ("\\deleted", s, 8) == 0) { s += 8; - flags->deleted = 1; + h->deleted = 1; } else if (mutt_strncasecmp ("\\flagged", s, 8) == 0) { s += 8; - flags->flagged = 1; + h->flagged = 1; } else if (mutt_strncasecmp ("\\answered", s, 9) == 0) { s += 9; - flags->replied = 1; + h->replied = 1; } else if (mutt_strncasecmp ("\\seen", s, 5) == 0) { s += 5; - flags->read = 1; + h->read = 1; } else if (mutt_strncasecmp ("\\recent", s, 5) == 0) { @@ -631,14 +703,14 @@ static char* msg_parse_flags (IMAP_FLAGS* flags, char* s) char ctmp; char* flag_word = s; - if (!flags->server_flags) - flags->server_flags = mutt_new_list (); + if (!h->data->keywords) + h->data->keywords = mutt_new_list (); while (*s && !ISSPACE (*s) && *s != ')') s++; ctmp = *s; *s = '\0'; - mutt_add_list (flags->server_flags, flag_word); + mutt_add_list (h->data->keywords, flag_word); *s = ctmp; } SKIPWS(s); @@ -648,8 +720,8 @@ static char* msg_parse_flags (IMAP_FLAGS* flags, char* s) if (*s == ')') { /* if a message is neither seen nor recent, it is OLD. */ - if (option (OPTMARKOLD) && !recent && !(flags->read)) - flags->old = 1; + if (option (OPTMARKOLD) && !recent && !(h->read)) + h->old = 1; s++; } else diff --git a/imap/message.h b/imap/message.h index 1669d8c6..ab31fd8f 100644 --- a/imap/message.h +++ b/imap/message.h @@ -22,8 +22,17 @@ #ifndef MESSAGE_H #define MESSAGE_H 1 -/* Data from the FLAGS response */ -typedef struct imap_flags +/* -- data structures -- */ +/* IMAP-specific header data, stored as HEADER->data */ +typedef struct imap_header_data +{ + unsigned int uid; /* 32-bit Message UID */ + LIST *keywords; +} IMAP_HEADER_DATA; + +/* Linked list to hold header information while downloading message + * headers */ +typedef struct imap_header { unsigned int read : 1; unsigned int old : 1; @@ -32,19 +41,14 @@ typedef struct imap_flags unsigned int replied : 1; unsigned int changed : 1; - LIST* server_flags; /* flags mutt doesn't use, but the server does */ -} IMAP_FLAGS; - -/* Linked list to hold header information while downloading message - * headers */ -typedef struct imap_header_info -{ - IMAP_FLAGS flags; + IMAP_HEADER_DATA* data; unsigned int number; time_t received; long content_length; - struct imap_header_info *next; -} IMAP_HEADER_INFO; + struct imap_header *next; +} IMAP_HEADER; +/* -- macros -- */ +#define HEADER_DATA(ph) ((IMAP_HEADER_DATA*) ((ph)->data)) #endif diff --git a/mutt.h b/mutt.h index a3a5110d..508b1f47 100644 --- a/mutt.h +++ b/mutt.h @@ -585,7 +585,7 @@ typedef struct header #endif #ifdef USE_IMAP - LIST *server_flags; /* server custom flags, which should be preserved */ + void *data; /* driver-specific data (only used by IMAP */ #endif } HEADER; diff --git a/muttlib.c b/muttlib.c index c8cdedaa..1b995803 100644 --- a/muttlib.c +++ b/muttlib.c @@ -158,9 +158,6 @@ void mutt_free_header (HEADER **h) safe_free ((void **) &(*h)->path); #ifdef MIXMASTER mutt_free_list (&(*h)->chain); -#endif -#ifdef USE_IMAP - mutt_free_list (&(*h)->server_flags); #endif safe_free ((void **) h); } @@ -229,7 +226,15 @@ char *mutt_expand_path (char *s, size_t slen) } } else if (*s == '=' || *s == '+') + { +#ifdef USE_IMAP + /* special case: folder = {host}: don't append slash */ + if (mx_is_imap (NONULL (Maildir)) && Maildir[strlen (Maildir) - 1] == '}') + snprintf (p, sizeof (p), "%s%s", NONULL (Maildir), s + 1); + else +#endif snprintf (p, sizeof (p), "%s/%s", NONULL (Maildir), s + 1); + } else if (*s == '@') { /* elm compatibility, @ expands alias to user name */ @@ -912,4 +917,3 @@ int state_printf(STATE *s, const char *fmt, ...) return rv; } - diff --git a/mx.c b/mx.c index 606db587..b1ec0fd2 100644 --- a/mx.c +++ b/mx.c @@ -730,7 +730,7 @@ static int sync_mailbox (CONTEXT *ctx) #ifdef USE_IMAP case M_IMAP: /* extra argument means EXPUNGE */ - rc = imap_sync_mailbox (ctx, M_YES); + rc = imap_sync_mailbox (ctx, 1); break; #endif /* USE_IMAP */ } @@ -862,6 +862,14 @@ int mx_close_mailbox (CONTEXT *ctx) return 0; } +#ifdef USE_IMAP + /* allow IMAP to preserve the deleted flag across sessions */ + if (ctx->magic == M_IMAP) + { + if (imap_sync_mailbox (ctx, purge) == -1) + return -1; + } +#endif if (!purge) { for (i = 0; i < ctx->msgcount; i++) @@ -875,6 +883,11 @@ int mx_close_mailbox (CONTEXT *ctx) return (-1); } +#ifdef USE_IMAP + if (ctx->magic == M_IMAP && !purge) + mutt_message (_("%d kept."), ctx->msgcount); + else +#endif if (move_messages) mutt_message (_("%d kept, %d moved, %d deleted."), ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted); @@ -979,6 +992,7 @@ void mx_update_tables(CONTEXT *ctx, int committing) int mx_sync_mailbox (CONTEXT *ctx) { int rc, i; + int purge = 1; if (ctx->dontwrite) { @@ -1011,22 +1025,40 @@ int mx_sync_mailbox (CONTEXT *ctx) snprintf (buf, sizeof (buf), ctx->deleted == 1 ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"), ctx->deleted); - if ((rc = query_quadoption (OPT_DELETE, buf)) < 0) + if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) return (-1); - else if (rc == M_NO) + else if (purge == M_NO) { if (!ctx->changed) return 0; /* nothing to do! */ - for (i = 0 ; i < ctx->msgcount ; i++) - ctx->hdrs[i]->deleted = 0; - ctx->deleted = 0; +#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; + } } } - if ((rc = sync_mailbox (ctx)) == 0) +#ifdef USE_IMAP + if (ctx->magic == M_IMAP) + rc = imap_sync_mailbox (ctx, purge); + else +#endif + rc = sync_mailbox (ctx); + if (rc == 0) { +#ifdef USE_IMAP + if (ctx->magic == M_IMAP && !purge) + mutt_message (_("%d kept."), ctx->msgcount); + else +#endif mutt_message (_("%d kept, %d deleted."), ctx->msgcount - ctx->deleted, - ctx->deleted); + ctx->deleted); + sleep (1); /* allow the user time to read the message */ if (ctx->msgcount == ctx->deleted && @@ -1038,8 +1070,12 @@ int mx_sync_mailbox (CONTEXT *ctx) return 0; } - mx_update_tables(ctx, 1); - mutt_sort_headers (ctx, 1); /* rethread from scratch */ + /* if we haven't deleted any messages, we don't need to resort */ + if (purge) + { + mx_update_tables(ctx, 1); + mutt_sort_headers (ctx, 1); /* rethread from scratch */ + } } return (rc); diff --git a/status.c b/status.c index 20553862..1f11fb27 100644 --- a/status.c +++ b/status.c @@ -21,6 +21,7 @@ #include "mutt_curses.h" #include "sort.h" #include "mapping.h" +#include "mx.h" #include #include @@ -204,8 +205,13 @@ status_format_str (char *buf, size_t buflen, char op, const char *src, if (Context) { - i = option(OPTATTACHMSG) ? 3 : ((Context->readonly || Context->dontwrite) ? 2 : - (Context->changed || Context->deleted) ? 1 : 0); + i = option(OPTATTACHMSG) ? 3 : ((Context->readonly || + Context->dontwrite) ? 2 : (Context->changed || ( +#ifdef USE_IMAP + /* deleted doesn't necessarily mean changed in IMAP */ + Context->magic != M_IMAP && +#endif + Context->deleted)) ? 1 : 0); } if (!StChars)