Mercurial > dovecot > original-hg > dovecot-1.2
changeset 7620:4b8c1c164d8f HEAD
Initial CONDSTORE support.
line wrap: on
line diff
--- a/src/imap/client.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/client.c Sat Mar 15 09:59:56 2008 +0200 @@ -775,6 +775,13 @@ } } +void client_enable(struct client *client, enum mailbox_feature features) +{ + client->enabled_features |= features; + if (client->mailbox != NULL) + mailbox_enable(client->mailbox, features); +} + void clients_init(void) { my_client = NULL;
--- a/src/imap/client.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/client.h Sat Mar 15 09:59:56 2008 +0200 @@ -69,6 +69,7 @@ unsigned int select_counter; /* increased when mailbox is changed */ unsigned int sync_counter; uint32_t messages_count, recent_count, uidvalidity; + enum mailbox_feature enabled_features; time_t last_input, last_output; unsigned int bad_counter; @@ -124,6 +125,8 @@ bool client_read_string_args(struct client_command_context *cmd, unsigned int count, ...); +void client_enable(struct client *client, enum mailbox_feature features); + void clients_init(void); void clients_deinit(void);
--- a/src/imap/cmd-append.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/cmd-append.c Sat Mar 15 09:59:56 2008 +0200 @@ -446,6 +446,8 @@ client_send_storage_error(cmd, storage); return NULL; } + if (cmd->client->enabled_features != 0) + mailbox_enable(box, cmd->client->enabled_features); return box; }
--- a/src/imap/cmd-copy.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/cmd-copy.c Sat Mar 15 09:59:56 2008 +0200 @@ -128,6 +128,8 @@ client_send_storage_error(cmd, storage); return TRUE; } + if (client->enabled_features != 0) + mailbox_enable(destbox, client->enabled_features); } t = mailbox_transaction_begin(destbox,
--- a/src/imap/cmd-fetch.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/cmd-fetch.c Sat Mar 15 09:59:56 2008 +0200 @@ -7,6 +7,8 @@ #include "imap-search.h" #include "mail-search.h" +#include <stdlib.h> + const char *all_macro[] = { "FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", NULL }; @@ -66,6 +68,52 @@ return TRUE; } +static bool +fetch_add_unchanged_since(struct imap_fetch_context *ctx, uint64_t modseq) +{ + struct mail_search_arg *search_arg; + + search_arg = p_new(ctx->cmd->pool, struct mail_search_arg, 1); + search_arg->type = SEARCH_MODSEQ; + search_arg->value.modseq = + p_new(ctx->cmd->pool, struct mail_search_modseq, 1); + search_arg->value.modseq->modseq = modseq; + + search_arg->next = ctx->search_args->next; + ctx->search_args->next = search_arg; + + return imap_fetch_init_handler(ctx, "MODSEQ", NULL); +} + +static bool +fetch_parse_modifiers(struct imap_fetch_context *ctx, + const struct imap_arg *args) +{ + const char *name; + unsigned long long num; + + for (; args->type != IMAP_ARG_EOL; args++) { + if (args->type != IMAP_ARG_ATOM || + args[1].type != IMAP_ARG_ATOM) { + client_send_command_error(ctx->cmd, + "FETCH modifiers contain non-atoms."); + return FALSE; + } + name = IMAP_ARG_STR(args); + if (strcasecmp(name, "CHANGEDSINCE") == 0) { + args++; + num = strtoull(imap_arg_string(args), NULL, 10); + if (!fetch_add_unchanged_since(ctx, num)) + return FALSE; + } else { + client_send_command_error(ctx->cmd, + "Unknown FETCH modifier"); + return FALSE; + } + } + return TRUE; +} + static bool cmd_fetch_finish(struct imap_fetch_context *ctx) { struct client_command_context *cmd = ctx->cmd; @@ -128,9 +176,11 @@ if (!client_verify_open_mailbox(cmd)) return TRUE; + /* <messageset> <field(s)> [(CHANGEDSINCE <modseq>)] */ messageset = imap_arg_string(&args[0]); if (messageset == NULL || - (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM)) { + (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM) || + (args[2].type != IMAP_ARG_EOL && args[2].type != IMAP_ARG_LIST)) { client_send_command_error(cmd, "Invalid arguments."); return TRUE; } @@ -142,13 +192,16 @@ ctx = imap_fetch_init(cmd); if (ctx == NULL) return TRUE; + ctx->search_args = search_arg; - if (!fetch_parse_args(ctx, &args[1])) { + if (!fetch_parse_args(ctx, &args[1]) || + (args[2].type == IMAP_ARG_LIST && + !fetch_parse_modifiers(ctx, IMAP_ARG_LIST_ARGS(&args[2])))) { imap_fetch_deinit(ctx); return TRUE; } - imap_fetch_begin(ctx, search_arg); + imap_fetch_begin(ctx); if ((ret = imap_fetch(ctx)) == 0) { /* unfinished */ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
--- a/src/imap/cmd-search.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/cmd-search.c Sat Mar 15 09:59:56 2008 +0200 @@ -4,6 +4,7 @@ #include "ostream.h" #include "str.h" #include "commands.h" +#include "mail-search.h" #include "mail-search-build.h" #include "imap-search.h" @@ -19,11 +20,31 @@ struct timeout *to; string_t *output_buf; + uint64_t highest_seen_modseq; struct timeval start_time; unsigned int output_sent:1; + unsigned int show_highest_modseq:1; }; +static bool imap_search_args_have_modseq(const struct mail_search_arg *sargs) +{ + for (; sargs->type != IMAP_ARG_EOL; sargs++) { + switch (sargs->type) { + case SEARCH_MODSEQ: + return TRUE; + case SEARCH_OR: + case SEARCH_SUB: + if (imap_search_args_have_modseq(sargs->value.subargs)) + return TRUE; + break; + default: + break; + } + } + return FALSE; +} + static struct imap_search_context * imap_search_init(struct client_command_context *cmd, struct mailbox *box, const char *charset, struct mail_search_arg *sargs) @@ -31,13 +52,17 @@ struct imap_search_context *ctx; ctx = p_new(cmd->pool, struct imap_search_context, 1); + if (imap_search_args_have_modseq(sargs)) { + ctx->show_highest_modseq = TRUE; + client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE); + } + ctx->box = box; ctx->trans = mailbox_transaction_begin(cmd->client->mailbox, 0); ctx->sargs = sargs; ctx->search_ctx = mailbox_search_init(ctx->trans, charset, sargs, NULL); ctx->mail = mail_alloc(ctx->trans, 0, NULL); (void)gettimeofday(&ctx->start_time, NULL); - ctx->output_buf = str_new(default_pool, OUTBUF_SIZE); str_append(ctx->output_buf, "* SEARCH"); return ctx; @@ -73,6 +98,7 @@ { struct imap_search_context *ctx = cmd->context; struct timeval end_time; + uint64_t modseq; bool tryagain; int ret; @@ -92,6 +118,11 @@ str_truncate(ctx->output_buf, 0); ctx->output_sent = TRUE; } + if (ctx->show_highest_modseq) { + modseq = mail_get_modseq(ctx->mail); + if (ctx->highest_seen_modseq < modseq) + ctx->highest_seen_modseq = modseq; + } str_printfa(ctx->output_buf, " %u", cmd->uid ? ctx->mail->uid : ctx->mail->seq); @@ -99,6 +130,11 @@ if (tryagain) return FALSE; + if (ctx->highest_seen_modseq != 0) { + str_printfa(ctx->output_buf, " (MODSEQ %llu)", + (unsigned long long)ctx->highest_seen_modseq); + } + if (gettimeofday(&end_time, NULL) < 0) memset(&end_time, 0, sizeof(end_time)); end_time.tv_sec -= ctx->start_time.tv_sec;
--- a/src/imap/cmd-select.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/cmd-select.c Sat Mar 15 09:59:56 2008 +0200 @@ -10,11 +10,30 @@ struct mail_storage *storage; struct mailbox *box; struct mailbox_status status; - const char *mailbox; + enum mailbox_open_flags open_flags = 0; + const struct imap_arg *args, *list_args; + const char *mailbox, *str; + + /* <mailbox> [(CONDSTORE)] */ + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; - /* <mailbox> */ - if (!client_read_string_args(cmd, 1, &mailbox)) + if (!IMAP_ARG_TYPE_IS_STRING(args[0].type)) { + client_send_command_error(cmd, "Invalid arguments."); return FALSE; + } + mailbox = IMAP_ARG_STR(&args[0]); + + if (args[1].type == IMAP_ARG_LIST) { + list_args = IMAP_ARG_LIST_ARGS(&args[1]); + for (; list_args->type != IMAP_ARG_EOL; list_args++) { + str = imap_arg_string(list_args); + if (str != NULL && strcasecmp(str, "CONDSTORE") == 0) { + client_enable(client, + MAILBOX_FEATURE_CONDSTORE); + } + } + } if (client->mailbox != NULL) { box = client->mailbox; @@ -29,17 +48,22 @@ if (storage == NULL) return TRUE; - box = mailbox_open(storage, mailbox, NULL, !readonly ? 0 : - (MAILBOX_OPEN_READONLY | MAILBOX_OPEN_KEEP_RECENT)); + if (readonly) + open_flags |= MAILBOX_OPEN_READONLY | MAILBOX_OPEN_KEEP_RECENT; + + box = mailbox_open(storage, mailbox, NULL, open_flags); if (box == NULL) { client_send_storage_error(cmd, storage); return TRUE; } + if (client->enabled_features != 0) + mailbox_enable(box, client->enabled_features); if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ, STATUS_MESSAGES | STATUS_RECENT | STATUS_FIRST_UNSEEN_SEQ | STATUS_UIDVALIDITY | - STATUS_UIDNEXT | STATUS_KEYWORDS, &status) < 0) { + STATUS_UIDNEXT | STATUS_KEYWORDS | + STATUS_HIGHESTMODSEQ, &status) < 0) { client_send_storage_error(cmd, storage); mailbox_close(&box); return TRUE; @@ -76,6 +100,15 @@ t_strdup_printf("* OK [UIDNEXT %u] Predicted next UID", status.uidnext)); + if (status.highest_modseq == 0) { + client_send_line(client, + "* OK [NOMODSEQ] No permanent modsequences"); + } else { + client_send_line(client, + t_strdup_printf("* OK [HIGHESTMODSEQ %llu]", + (unsigned long long)status.highest_modseq)); + } + client_send_tagline(cmd, mailbox_is_readonly(box) ? "OK [READ-ONLY] Select completed." : "OK [READ-WRITE] Select completed.");
--- a/src/imap/cmd-store.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/cmd-store.c Sat Mar 15 09:59:56 2008 +0200 @@ -1,38 +1,119 @@ /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */ #include "common.h" +#include "seq-range-array.h" #include "str.h" #include "commands.h" #include "imap-search.h" #include "imap-util.h" +#include <stdlib.h> + +struct imap_store_context { + struct client_command_context *cmd; + const char *messageset; + uint64_t max_modseq; + + enum mail_flags flags; + struct mail_keywords *keywords; + + enum modify_type modify_type; + bool silent; +}; + static bool -get_modify_type(struct client_command_context *cmd, const char *item, - enum modify_type *modify_type, bool *silent) +get_modify_type(struct imap_store_context *ctx, const char *type) { - if (*item == '+') { - *modify_type = MODIFY_ADD; - item++; - } else if (*item == '-') { - *modify_type = MODIFY_REMOVE; - item++; + if (*type == '+') { + ctx->modify_type = MODIFY_ADD; + type++; + } else if (*type == '-') { + ctx->modify_type = MODIFY_REMOVE; + type++; } else { - *modify_type = MODIFY_REPLACE; + ctx->modify_type = MODIFY_REPLACE; } - if (strncasecmp(item, "FLAGS", 5) != 0) { - client_send_tagline(cmd, t_strconcat( - "NO Invalid item ", item, NULL)); + if (strncasecmp(type, "FLAGS", 5) != 0) + return FALSE; + + ctx->silent = strcasecmp(type+5, ".SILENT") == 0; + if (!ctx->silent && type[5] != '\0') + return FALSE; + return TRUE; +} + +static bool +store_parse_modifiers(struct imap_store_context *ctx, + const struct imap_arg *args) +{ + const char *name; + + for (; args->type != IMAP_ARG_EOL; args++) { + if (args->type != IMAP_ARG_ATOM || + args[1].type != IMAP_ARG_ATOM) { + client_send_command_error(ctx->cmd, + "STORE modifiers contain non-atoms."); + return FALSE; + } + name = IMAP_ARG_STR(args); + if (strcasecmp(name, "UNCHANGEDSINCE") == 0) { + args++; + ctx->max_modseq = + strtoull(imap_arg_string(args), NULL, 10); + client_enable(ctx->cmd->client, + MAILBOX_FEATURE_CONDSTORE); + } else { + client_send_command_error(ctx->cmd, + "Unknown STORE modifier"); + return FALSE; + } + } + return TRUE; +} + +static bool +store_parse_args(struct imap_store_context *ctx, const struct imap_arg *args) +{ + struct client_command_context *cmd = ctx->cmd; + const char *type; + const char *const *keywords_list = NULL; + + ctx->max_modseq = (uint64_t)-1; + ctx->messageset = imap_arg_string(args++); + + if (args->type == IMAP_ARG_LIST) { + if (!store_parse_modifiers(ctx, IMAP_ARG_LIST_ARGS(args))) + return FALSE; + args++; + } + + type = imap_arg_string(args++); + if (ctx->messageset == NULL || type == NULL || + !get_modify_type(ctx, type)) { + client_send_command_error(cmd, "Invalid arguments."); return FALSE; } - *silent = strcasecmp(item+5, ".SILENT") == 0; - if (!*silent && item[5] != '\0') { - client_send_tagline(cmd, t_strconcat( - "NO Invalid item ", item, NULL)); - return FALSE; + if (args->type == IMAP_ARG_LIST) { + if (!client_parse_mail_flags(cmd, IMAP_ARG_LIST_ARGS(args), + &ctx->flags, &keywords_list)) + return FALSE; + } else { + if (!client_parse_mail_flags(cmd, args, + &ctx->flags, &keywords_list)) + return FALSE; } + if (keywords_list != NULL || ctx->modify_type == MODIFY_REPLACE) { + if (mailbox_keywords_create(cmd->client->mailbox, keywords_list, + &ctx->keywords) < 0) { + /* invalid keywords */ + client_send_storage_error(cmd, + mailbox_get_storage(cmd->client->mailbox)); + return FALSE; + } + } return TRUE; } @@ -40,17 +121,15 @@ { struct client *client = cmd->client; const struct imap_arg *args; - enum mail_flags flags; - const char *const *keywords_list; - struct mail_keywords *keywords; - enum modify_type modify_type; - struct mailbox *box; struct mail_search_arg *search_arg; struct mail_search_context *search_ctx; struct mailbox_transaction_context *t; struct mail *mail; - const char *messageset, *item; - bool silent, failed; + struct imap_store_context ctx; + ARRAY_TYPE(seq_range) modified_set = ARRAY_INIT; + const char *tagged_reply; + string_t *str; + bool failed; if (!client_read_args(cmd, 0, 0, &args)) return FALSE; @@ -58,57 +137,52 @@ if (!client_verify_open_mailbox(cmd)) return TRUE; - /* validate arguments */ - messageset = imap_arg_string(&args[0]); - item = imap_arg_string(&args[1]); - - if (messageset == NULL || item == NULL) { - client_send_command_error(cmd, "Invalid arguments."); - return TRUE; - } - - if (!get_modify_type(cmd, item, &modify_type, &silent)) + memset(&ctx, 0, sizeof(ctx)); + ctx.cmd = cmd; + if (!store_parse_args(&ctx, args)) return TRUE; - if (args[2].type == IMAP_ARG_LIST) { - if (!client_parse_mail_flags(cmd, - IMAP_ARG_LIST_ARGS(&args[2]), - &flags, &keywords_list)) - return TRUE; - } else { - if (!client_parse_mail_flags(cmd, args+2, - &flags, &keywords_list)) - return TRUE; - } - - box = client->mailbox; - search_arg = imap_search_get_arg(cmd, messageset, cmd->uid); + search_arg = imap_search_get_arg(cmd, ctx.messageset, cmd->uid); if (search_arg == NULL) return TRUE; - t = mailbox_transaction_begin(box, !silent ? 0 : + t = mailbox_transaction_begin(client->mailbox, !ctx.silent ? 0 : MAILBOX_TRANSACTION_FLAG_HIDE); - if (keywords_list == NULL && modify_type != MODIFY_REPLACE) - keywords = NULL; - else if (mailbox_keywords_create(box, keywords_list, &keywords) < 0) { - /* invalid keywords */ - mailbox_transaction_rollback(&t); - client_send_storage_error(cmd, mailbox_get_storage(box)); - return TRUE; - } search_ctx = mailbox_search_init(t, NULL, search_arg, NULL); + /* FIXME: UNCHANGEDSINCE should be atomic, but this requires support + from mail-storage API. So for now we fake it. */ mail = mail_alloc(t, MAIL_FETCH_FLAGS, NULL); while (mailbox_search_next(search_ctx, mail) > 0) { - if (modify_type == MODIFY_REPLACE || flags != 0) - mail_update_flags(mail, modify_type, flags); - if (modify_type == MODIFY_REPLACE || keywords != NULL) - mail_update_keywords(mail, modify_type, keywords); + if (ctx.max_modseq < (uint64_t)-1) { + if (mail_get_modseq(mail) > ctx.max_modseq) { + seq_range_array_add(&modified_set, 64, + cmd->uid ? mail->uid : mail->seq); + continue; + } + } + if (ctx.modify_type == MODIFY_REPLACE || ctx.flags != 0) + mail_update_flags(mail, ctx.modify_type, ctx.flags); + if (ctx.modify_type == MODIFY_REPLACE || ctx.keywords != NULL) { + mail_update_keywords(mail, ctx.modify_type, + ctx.keywords); + } } mail_free(&mail); - if (keywords != NULL) - mailbox_keywords_free(box, &keywords); + if (!array_is_created(&modified_set)) + tagged_reply = "OK Store completed."; + else { + str = str_new(cmd->pool, 256); + str_append(str, "OK [MODIFIED "); + imap_write_seq_range(str, &modified_set); + array_free(&modified_set); + str_append(str, "] Conditional store failed."); + tagged_reply = str_c(str); + } + + if (ctx.keywords != NULL) + mailbox_keywords_free(client->mailbox, &ctx.keywords); if (mailbox_search_deinit(&search_ctx) < 0) { failed = TRUE; @@ -123,13 +197,19 @@ flag changes that were caused by UID STORE and those that came externally, so we'll just send the UID for all flag changes that we see. */ + enum imap_sync_flags imap_sync_flags = 0; + + if (cmd->uid && + (!ctx.silent || (client->enabled_features & + MAILBOX_FEATURE_CONDSTORE) != 0)) + imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID; + return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES), - cmd->uid && !silent ? - IMAP_SYNC_FLAG_SEND_UID : 0, - "OK Store completed."); + imap_sync_flags, tagged_reply); } else { - client_send_storage_error(cmd, mailbox_get_storage(box)); + client_send_storage_error(cmd, + mailbox_get_storage(client->mailbox)); return TRUE; } }
--- a/src/imap/imap-fetch.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/imap-fetch.c Sat Mar 15 09:59:56 2008 +0200 @@ -20,7 +20,7 @@ #define ENVELOPE_NIL_REPLY \ "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)" -const struct imap_fetch_handler default_handlers[7]; +const struct imap_fetch_handler default_handlers[8]; static buffer_t *fetch_handlers = NULL; static int imap_fetch_handler_cmp(const void *p1, const void *p2) @@ -153,8 +153,7 @@ } } -void imap_fetch_begin(struct imap_fetch_context *ctx, - struct mail_search_arg *search_arg) +void imap_fetch_begin(struct imap_fetch_context *ctx) { const void *null = NULL; const void *data; @@ -188,7 +187,7 @@ ctx->mail = mail_alloc(ctx->trans, ctx->fetch_data, ctx->all_headers_ctx); ctx->search_ctx = - mailbox_search_init(ctx->trans, NULL, search_arg, NULL); + mailbox_search_init(ctx->trans, NULL, ctx->search_args, NULL); } static int imap_fetch_flush_buffer(struct imap_fetch_context *ctx) @@ -568,6 +567,24 @@ return TRUE; } +static int fetch_modseq(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + str_printfa(ctx->cur_str, "MODSEQ %llu ", + (unsigned long long)mail_get_modseq(mail)); + return 1; +} + +static bool +fetch_modseq_init(struct imap_fetch_context *ctx, const char *name, + const struct imap_arg **args ATTR_UNUSED) +{ + client_enable(ctx->client, MAILBOX_FEATURE_CONDSTORE); + imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL, + fetch_modseq, NULL); + return TRUE; +} + static int fetch_uid(struct imap_fetch_context *ctx, struct mail *mail, void *context ATTR_UNUSED) { @@ -583,12 +600,13 @@ return TRUE; } -const struct imap_fetch_handler default_handlers[7] = { +const struct imap_fetch_handler default_handlers[8] = { { "BODY", fetch_body_init }, { "BODYSTRUCTURE", fetch_bodystructure_init }, { "ENVELOPE", fetch_envelope_init }, { "FLAGS", fetch_flags_init }, { "INTERNALDATE", fetch_internaldate_init }, + { "MODSEQ", fetch_modseq_init }, { "RFC822", fetch_rfc822_init }, { "UID", fetch_uid_init } };
--- a/src/imap/imap-fetch.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/imap-fetch.h Sat Mar 15 09:59:56 2008 +0200 @@ -33,6 +33,7 @@ struct mailbox *box; struct mailbox_transaction_context *trans; + struct mail_search_arg *search_args; struct mail_search_context *search_ctx; struct mail *mail; @@ -95,8 +96,7 @@ bool imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *name, const struct imap_arg **args); -void imap_fetch_begin(struct imap_fetch_context *ctx, - struct mail_search_arg *search_arg); +void imap_fetch_begin(struct imap_fetch_context *ctx); int imap_fetch(struct imap_fetch_context *ctx); bool fetch_body_section_init(struct imap_fetch_context *ctx, const char *name,
--- a/src/imap/imap-status.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/imap-status.c Sat Mar 15 09:59:56 2008 +0200 @@ -33,6 +33,8 @@ items |= STATUS_UIDVALIDITY; else if (strcmp(item, "UNSEEN") == 0) items |= STATUS_UNSEEN; + else if (strcmp(item, "HIGHESTMODSEQ") == 0) + items |= STATUS_HIGHESTMODSEQ; else { client_send_tagline(cmd, t_strconcat( "BAD Invalid status item ", item, NULL)); @@ -65,6 +67,11 @@ if (box == NULL) return FALSE; + if ((items & STATUS_HIGHESTMODSEQ) != 0) + client_enable(client, MAILBOX_FEATURE_CONDSTORE); + if (client->enabled_features != 0) + mailbox_enable(box, client->enabled_features); + ret = mailbox_sync(box, 0, items, status_r); mailbox_close(&box); return ret == 0; @@ -91,6 +98,10 @@ str_printfa(str, "UIDVALIDITY %u ", status->uidvalidity); if (items & STATUS_UNSEEN) str_printfa(str, "UNSEEN %u ", status->unseen); + if (items & STATUS_HIGHESTMODSEQ) { + str_printfa(str, "HIGHESTMODSEQ %llu ", + (unsigned long long)status->highest_modseq); + } if (items != 0) str_truncate(str, str_len(str)-1);
--- a/src/imap/imap-sync.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/imap/imap-sync.c Sat Mar 15 09:59:56 2008 +0200 @@ -120,13 +120,30 @@ str_printfa(str, "* %u FETCH (", ctx->seq); if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID) str_printfa(str, "UID %u ", ctx->mail->uid); - + if ((mailbox_get_enabled_features(ctx->box) & + MAILBOX_FEATURE_CONDSTORE) != 0) { + str_printfa(str, "MODSEQ %llu ", + (unsigned long long)mail_get_modseq(ctx->mail)); + } str_append(str, "FLAGS ("); imap_write_flags(str, flags, keywords); str_append(str, "))"); return client_send_line(ctx->client, str_c(str)); } +static int imap_sync_send_modseq(struct imap_sync_context *ctx, string_t *str) +{ + mail_set_seq(ctx->mail, ctx->seq); + + str_truncate(str, 0); + str_printfa(str, "* %u FETCH (", ctx->seq); + if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID) + str_printfa(str, "UID %u ", ctx->mail->uid); + str_printfa(str, "MODSEQ %llu)", + (unsigned long long)mail_get_modseq(ctx->mail)); + return client_send_line(ctx->client, str_c(str)); +} + int imap_sync_more(struct imap_sync_context *ctx) { string_t *str; @@ -187,6 +204,22 @@ ctx->sync_rec.seq1 + 1; } break; + case MAILBOX_SYNC_TYPE_MODSEQ: + if ((ctx->client->enabled_features & + MAILBOX_FEATURE_CONDSTORE) == 0) + break; + + if (ctx->seq == 0) + ctx->seq = ctx->sync_rec.seq1; + + ret = 1; + for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) { + if (ret <= 0) + break; + + ret = imap_sync_send_modseq(ctx, str); + } + break; } if (ret <= 0) { /* failure / buffer full */
--- a/src/lib-imap/imap-util.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-imap/imap-util.c Sat Mar 15 09:59:56 2008 +0200 @@ -1,6 +1,7 @@ /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "array.h" #include "str.h" #include "mail-types.h" #include "imap-util.h" @@ -36,3 +37,18 @@ if (str_len(dest) != size) str_truncate(dest, str_len(dest)-1); } + +void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array) +{ + const struct seq_range *range; + unsigned int i, count; + + range = array_get(array, &count); + for (i = 0; i < count; i++) { + if (i > 0) + str_append_c(dest, ','); + str_printfa(dest, "%u", range[i].seq1); + if (range[i].seq1 != range[i].seq2) + str_printfa(dest, ":%u", range[i].seq2); + } +}
--- a/src/lib-imap/imap-util.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-imap/imap-util.h Sat Mar 15 09:59:56 2008 +0200 @@ -1,10 +1,15 @@ #ifndef IMAP_UTIL_H #define IMAP_UTIL_H +#include "seq-range-array.h" + enum mail_flags; /* Write flags as a space separated string. */ void imap_write_flags(string_t *dest, enum mail_flags flags, const char *const *keywords); +/* Write sequence range as IMAP sequence-set */ +void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array); + #endif
--- a/src/lib-index/Makefile.am Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/Makefile.am Sat Mar 15 09:59:56 2008 +0200 @@ -17,6 +17,7 @@ mail-index-fsck.c \ mail-index-lock.c \ mail-index-map.c \ + mail-index-modseq.c \ mail-index-transaction.c \ mail-index-transaction-view.c \ mail-index-sync.c \ @@ -37,6 +38,7 @@ mail-cache.h \ mail-cache-private.h \ mail-index.h \ + mail-index-modseq.h \ mail-index-private.h \ mail-index-sync-private.h \ mail-index-transaction-private.h \
--- a/src/lib-index/mail-index-map.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-map.c Sat Mar 15 09:59:56 2008 +0200 @@ -8,6 +8,7 @@ #include "read-full.h" #include "mail-index-private.h" #include "mail-index-sync-private.h" +#include "mail-index-modseq.h" #include "mail-transaction-log-private.h" static void mail_index_map_init_extbufs(struct mail_index_map *map, @@ -943,6 +944,8 @@ rec_map->mmap_base = NULL; } array_free(&rec_map->maps); + if (rec_map->modseq != NULL) + mail_index_map_modseq_free(rec_map->modseq); i_free(rec_map); }
--- a/src/lib-index/mail-index-private.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-private.h Sat Mar 15 09:59:56 2008 +0200 @@ -116,6 +116,7 @@ void *records; /* struct mail_index_record[] */ unsigned int records_count; + struct mail_index_map_modseq *modseq; uint32_t last_appended_uid; /* If this mapping is written to disk and write_atomic=FALSE, @@ -200,6 +201,7 @@ struct hash_table *keywords_hash; /* name -> idx */ uint32_t keywords_ext_id; + uint32_t modseq_ext_id; /* Module-specific contexts. */ ARRAY_DEFINE(module_contexts, union mail_index_module_context *); @@ -218,6 +220,7 @@ unsigned int mapping:1; unsigned int syncing:1; unsigned int need_recreate:1; + unsigned int modseqs_enabled:1; }; extern struct mail_index_module_register mail_index_module_register;
--- a/src/lib-index/mail-index-sync-ext.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-sync-ext.c Sat Mar 15 09:59:56 2008 +0200 @@ -348,6 +348,92 @@ return TRUE; } +static void +mail_index_sync_ext_init_new(struct mail_index_sync_map_ctx *ctx, + const char *name, + const struct mail_index_ext_header *ext_hdr, + uint32_t *ext_map_idx_r) +{ + struct mail_index_map *map = ctx->view->map; + const struct mail_index_ext *ext; + buffer_t *hdr_buf; + uint32_t ext_map_idx; + + /* be sure to get a unique mapping before we modify the extensions, + otherwise other map users will see the new extension but not the + data records that sync_ext_reorder() adds. */ + map = mail_index_sync_get_atomic_map(ctx); + + hdr_buf = map->hdr_copy_buf; + i_assert(hdr_buf->used == map->hdr.header_size); + + if (MAIL_INDEX_HEADER_SIZE_ALIGN(hdr_buf->used) != hdr_buf->used) { + /* we need to add padding between base header and extensions */ + buffer_append_zero(hdr_buf, + MAIL_INDEX_HEADER_SIZE_ALIGN(hdr_buf->used) - + hdr_buf->used); + } + + /* register record offset initially using zero, + sync_ext_reorder() will fix it. */ + ext_map_idx = mail_index_map_register_ext(map, name, hdr_buf->used, + ext_hdr); + ext = array_idx(&map->extensions, ext_map_idx); + + /* <ext_hdr> <name> [padding] [header data] */ + buffer_append(hdr_buf, ext_hdr, sizeof(*ext_hdr)); + buffer_append(hdr_buf, name, strlen(name)); + /* header must begin and end in correct alignment */ + buffer_append_zero(hdr_buf, + MAIL_INDEX_HEADER_SIZE_ALIGN(hdr_buf->used) - hdr_buf->used + + MAIL_INDEX_HEADER_SIZE_ALIGN(ext->hdr_size)); + i_assert(hdr_buf->used == + ext->hdr_offset + MAIL_INDEX_HEADER_SIZE_ALIGN(ext->hdr_size)); + i_assert((hdr_buf->used % sizeof(uint64_t)) == 0); + + map->hdr.header_size = hdr_buf->used; + map->hdr_base = hdr_buf->data; + + mail_index_sync_init_handlers(ctx); + sync_ext_reorder(map, ext_map_idx, 0); + + *ext_map_idx_r = ext_map_idx; +} + +void mail_index_sync_ext_init(struct mail_index_sync_map_ctx *ctx, + const char *name, bool fix_size, + uint32_t *ext_map_idx_r) +{ + struct mail_index_map *map = ctx->view->map; + const struct mail_index_registered_ext *rext; + struct mail_index_ext_header ext_hdr; + struct mail_transaction_ext_intro u; + uint32_t ext_id; + + if (!mail_index_ext_lookup(ctx->view->index, name, &ext_id)) + i_unreached(); + rext = array_idx(&ctx->view->index->extensions, ext_id); + + if (mail_index_map_lookup_ext(map, name, ext_map_idx_r)) { + if (!fix_size) + return; + + /* make sure it's the expected size */ + memset(&u, 0, sizeof(u)); + u.hdr_size = rext->hdr_size; + u.record_size = rext->record_size; + u.record_align = rext->record_align; + sync_ext_resize(&u, *ext_map_idx_r, ctx); + } else { + memset(&ext_hdr, 0, sizeof(ext_hdr)); + ext_hdr.hdr_size = rext->hdr_size; + ext_hdr.record_size = rext->record_size; + ext_hdr.record_align = rext->record_align; + mail_index_sync_ext_init_new(ctx, name, &ext_hdr, + ext_map_idx_r); + } +} + int mail_index_sync_ext_intro(struct mail_index_sync_map_ctx *ctx, const struct mail_transaction_ext_intro *u) { @@ -355,7 +441,6 @@ struct mail_index_ext_header ext_hdr; const struct mail_index_ext *ext; const char *name, *error; - buffer_t *hdr_buf; uint32_t ext_map_idx; /* default to ignoring the following extension updates in case this @@ -432,43 +517,7 @@ return 1; } - /* be sure to get a unique mapping before we modify the extensions, - otherwise other map users will see the new extension but not the - data records that sync_ext_reorder() adds. */ - map = mail_index_sync_get_atomic_map(ctx); - - hdr_buf = map->hdr_copy_buf; - i_assert(hdr_buf->used == map->hdr.header_size); - - if (MAIL_INDEX_HEADER_SIZE_ALIGN(hdr_buf->used) != hdr_buf->used) { - /* we need to add padding between base header and extensions */ - buffer_append_zero(hdr_buf, - MAIL_INDEX_HEADER_SIZE_ALIGN(hdr_buf->used) - - hdr_buf->used); - } - - /* register record offset initially using zero, - sync_ext_reorder() will fix it. */ - ext_map_idx = mail_index_map_register_ext(map, name, hdr_buf->used, - &ext_hdr); - ext = array_idx(&map->extensions, ext_map_idx); - - /* <ext_hdr> <name> [padding] [header data] */ - buffer_append(hdr_buf, &ext_hdr, sizeof(ext_hdr)); - buffer_append(hdr_buf, name, strlen(name)); - /* header must begin and end in correct alignment */ - buffer_append_zero(hdr_buf, - MAIL_INDEX_HEADER_SIZE_ALIGN(hdr_buf->used) - hdr_buf->used + - MAIL_INDEX_HEADER_SIZE_ALIGN(ext->hdr_size)); - i_assert(hdr_buf->used == - ext->hdr_offset + MAIL_INDEX_HEADER_SIZE_ALIGN(ext->hdr_size)); - i_assert((hdr_buf->used % sizeof(uint64_t)) == 0); - - map->hdr.header_size = hdr_buf->used; - map->hdr_base = hdr_buf->data; - - mail_index_sync_init_handlers(ctx); - sync_ext_reorder(map, ext_map_idx, 0); + mail_index_sync_ext_init_new(ctx, name, &ext_hdr, &ext_map_idx); ctx->cur_ext_ignore = FALSE; ctx->cur_ext_map_idx = ctx->internal_update ?
--- a/src/lib-index/mail-index-sync-keywords.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-sync-keywords.c Sat Mar 15 09:59:56 2008 +0200 @@ -3,6 +3,7 @@ #include "lib.h" #include "array.h" #include "buffer.h" +#include "mail-index-modseq.h" #include "mail-index-view-private.h" #include "mail-index-sync-private.h" #include "mail-transaction-log.h" @@ -209,6 +210,8 @@ return 1; mail_index_sync_write_seq_update(ctx, seq1, seq2); + mail_index_modseq_update_keyword(ctx->modseq_ctx, keyword_idx, + seq1, seq2); data_offset = keyword_idx / CHAR_BIT; data_mask = 1 << (keyword_idx % CHAR_BIT); @@ -329,6 +332,7 @@ continue; mail_index_sync_write_seq_update(ctx, seq1, seq2); + mail_index_modseq_reset_keywords(ctx->modseq_ctx, seq1, seq2); for (seq1--; seq1 < seq2; seq1++) { rec = MAIL_INDEX_MAP_IDX(map, seq1); memset(PTR_OFFSET(rec, ext->record_offset),
--- a/src/lib-index/mail-index-sync-private.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-sync-private.h Sat Mar 15 09:59:56 2008 +0200 @@ -24,6 +24,7 @@ struct mail_index_sync_map_ctx { struct mail_index_view *view; + struct mail_index_modseq_sync *modseq_ctx; uint32_t cur_ext_map_idx; uint32_t ext_intro_seq; @@ -67,6 +68,10 @@ void mail_index_sync_init_handlers(struct mail_index_sync_map_ctx *ctx); void mail_index_sync_deinit_handlers(struct mail_index_sync_map_ctx *ctx); +void mail_index_sync_ext_init(struct mail_index_sync_map_ctx *ctx, + const char *name, bool fix_size, + uint32_t *ext_map_idx_r); + int mail_index_sync_ext_intro(struct mail_index_sync_map_ctx *ctx, const struct mail_transaction_ext_intro *u); int mail_index_sync_ext_reset(struct mail_index_sync_map_ctx *ctx,
--- a/src/lib-index/mail-index-sync-update.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-sync-update.c Sat Mar 15 09:59:56 2008 +0200 @@ -4,6 +4,7 @@ #include "ioloop.h" #include "array.h" #include "mmap-util.h" +#include "mail-index-modseq.h" #include "mail-index-view-private.h" #include "mail-index-sync-private.h" #include "mail-transaction-log.h" @@ -261,6 +262,7 @@ seq_count = seq2 - seq1 + 1; map->rec_map->records_count -= seq_count; map->hdr.messages_count -= seq_count; + mail_index_modseq_expunge(ctx->modseq_ctx, seq1, seq2); } return 1; } @@ -325,6 +327,8 @@ mail_index_sync_write_seq_update(ctx, map->rec_map->records_count, map->rec_map->records_count); + mail_index_modseq_append(ctx->modseq_ctx, + map->rec_map->records_count); } map->hdr.messages_count++; @@ -351,6 +355,9 @@ return 1; mail_index_sync_write_seq_update(ctx, seq1, seq2); + mail_index_modseq_update_flags(ctx->modseq_ctx, + u->add_flags | u->remove_flags, + seq1, seq2); if ((u->add_flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) view->map->hdr.flags |= MAIL_INDEX_HDR_FLAG_HAVE_DIRTY; @@ -637,12 +644,15 @@ sync_map_ctx->view = view; sync_map_ctx->cur_ext_map_idx = (uint32_t)-1; sync_map_ctx->type = type; + sync_map_ctx->modseq_ctx = mail_index_modseq_sync_begin(sync_map_ctx); mail_index_sync_init_handlers(sync_map_ctx); } void mail_index_sync_map_deinit(struct mail_index_sync_map_ctx *sync_map_ctx) { + i_assert(sync_map_ctx->modseq_ctx == NULL); + if (sync_map_ctx->unknown_extensions != NULL) buffer_free(&sync_map_ctx->unknown_extensions); if (sync_map_ctx->expunge_handlers_used) @@ -827,6 +837,7 @@ if (had_dirty) mail_index_sync_update_hdr_dirty_flag(map); + mail_index_modseq_sync_end(&sync_map_ctx.modseq_ctx); mail_index_sync_update_log_offset(&sync_map_ctx, view->map, TRUE);
--- a/src/lib-index/mail-index-transaction.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-transaction.c Sat Mar 15 09:59:56 2008 +0200 @@ -1327,12 +1327,60 @@ *keywords = NULL; } +static bool +keyword_update_has_changes(struct mail_index_transaction *t, uint32_t seq, + enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_index_transaction_keyword_update *u; + ARRAY_TYPE(keyword_indexes) existing; + const unsigned int *existing_idx; + unsigned int i, j, existing_count; + bool found; + + t_array_init(&existing, 32); + mail_index_lookup_keywords(t->view, seq, &existing); + existing_idx = array_get(&existing, &existing_count); + + if (modify_type == MODIFY_REPLACE && existing_count != keywords->count) + return TRUE; + + for (i = 0; i < keywords->count; i++) { + u = array_idx_modifiable(&t->keyword_updates, + keywords->idx[i]); + if (array_is_created(&u->add_seq) || + array_is_created(&u->remove_seq)) + return TRUE; + + found = FALSE; + for (j = 0; j < existing_count; j++) { + if (existing_idx[j] == keywords->idx[i]) { + found = TRUE; + break; + } + } + switch (modify_type) { + case MODIFY_ADD: + case MODIFY_REPLACE: + if (!found) + return TRUE; + break; + case MODIFY_REMOVE: + if (found) + return TRUE; + break; + } + } + return FALSE; +} + void mail_index_update_keywords(struct mail_index_transaction *t, uint32_t seq, enum modify_type modify_type, struct mail_keywords *keywords) { struct mail_index_transaction_keyword_update *u; unsigned int i, ku_count; + bool changed; i_assert(seq > 0 && (seq <= mail_index_view_get_messages_count(t->view) || @@ -1346,6 +1394,16 @@ i_array_init(&t->keyword_updates, max_idx + 1); } + if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) { + T_BEGIN { + changed = keyword_update_has_changes(t, seq, + modify_type, + keywords); + } T_END; + if (!changed) + return; + } + /* Update add_seq and remove_seq arrays which describe the keyword changes. Don't bother updating remove_seq or keyword resets for newly added messages since they default to not having any @@ -1356,8 +1414,7 @@ u = array_idx_modifiable(&t->keyword_updates, keywords->idx[i]); seq_range_array_add(&u->add_seq, 16, seq); - if (seq < t->first_new_seq) - seq_range_array_remove(&u->remove_seq, seq); + seq_range_array_remove(&u->remove_seq, seq); } break; case MODIFY_REMOVE: @@ -1376,10 +1433,7 @@ &ku_count); for (i = 0; i < ku_count; i++) { seq_range_array_remove(&u[i].add_seq, seq); - if (seq < t->first_new_seq) { - seq_range_array_remove( - &u[i].remove_seq, seq); - } + seq_range_array_remove(&u[i].remove_seq, seq); } } /* Add the wanted keyword back */
--- a/src/lib-index/mail-index-view-sync.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index-view-sync.c Sat Mar 15 09:59:56 2008 +0200 @@ -5,6 +5,7 @@ #include "buffer.h" #include "mail-index-view-private.h" #include "mail-index-sync-private.h" +#include "mail-index-modseq.h" #include "mail-transaction-log.h" struct mail_index_view_sync_ctx { @@ -664,6 +665,7 @@ view->inconsistent = TRUE; ret = -1; } + mail_index_modseq_sync_end(&ctx->sync_map_ctx.modseq_ctx); if (view->sync_new_map != NULL) { mail_index_unmap(&view->map);
--- a/src/lib-index/mail-index.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index.c Sat Mar 15 09:59:56 2008 +0200 @@ -13,6 +13,7 @@ #include "mail-index-private.h" #include "mail-index-view-private.h" #include "mail-index-sync-private.h" +#include "mail-index-modseq.h" #include "mail-transaction-log.h" #include "mail-cache.h" @@ -50,6 +51,7 @@ hash_create(default_pool, index->keywords_pool, 0, strcase_hash, (hash_cmp_callback_t *)strcasecmp); index->log = mail_transaction_log_alloc(index); + mail_index_modseq_init(index); return index; } @@ -576,6 +578,11 @@ return 0; } +bool mail_index_is_in_memory(struct mail_index *index) +{ + return MAIL_INDEX_IS_IN_MEMORY(index); +} + void mail_index_mark_corrupted(struct mail_index *index) { index->indexid = 0;
--- a/src/lib-index/mail-index.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-index/mail-index.h Sat Mar 15 09:59:56 2008 +0200 @@ -187,6 +187,8 @@ /* Move the index into memory. Returns 0 if ok, -1 if error occurred. */ int mail_index_move_to_memory(struct mail_index *index); +/* Returns TRUE if index is currently in memory. */ +bool mail_index_is_in_memory(struct mail_index *index); struct mail_cache *mail_index_get_cache(struct mail_index *index);
--- a/src/lib-storage/index/cydir/cydir-mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/cydir/cydir-mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -122,6 +122,7 @@ index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, + index_mail_get_modseq, index_mail_get_parts, index_mail_get_date, cydir_mail_get_received_date,
--- a/src/lib-storage/index/cydir/cydir-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/cydir/cydir-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -415,6 +415,7 @@ { index_storage_is_readonly, index_storage_allow_new_keywords, + index_storage_mailbox_enable, index_storage_mailbox_close, index_storage_get_status, NULL,
--- a/src/lib-storage/index/dbox/dbox-mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/dbox/dbox-mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -223,6 +223,7 @@ index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, + index_mail_get_modseq, index_mail_get_parts, index_mail_get_date, dbox_mail_get_received_date,
--- a/src/lib-storage/index/dbox/dbox-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/dbox/dbox-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -664,6 +664,7 @@ { index_storage_is_readonly, index_storage_allow_new_keywords, + index_storage_mailbox_enable, dbox_storage_mailbox_close, index_storage_get_status, NULL,
--- a/src/lib-storage/index/index-mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -13,6 +13,7 @@ #include "imap-bodystructure.h" #include "imap-envelope.h" #include "mail-cache.h" +#include "mail-index-modseq.h" #include "index-storage.h" #include "index-mail.h" @@ -127,6 +128,20 @@ return data->flags; } +uint64_t index_mail_get_modseq(struct mail *_mail) +{ + struct index_mail *mail = (struct index_mail *)_mail; + + if (mail->data.modseq != 0) + return mail->data.modseq; + + mail_index_modseq_enable(mail->ibox->index); + mail->data.modseq = + mail_index_modseq_lookup_highest(mail->trans->trans_view, + _mail->seq); + return mail->data.modseq; +} + const char *const *index_mail_get_keywords(struct mail *_mail) { struct index_mail *mail = (struct index_mail *)_mail;
--- a/src/lib-storage/index/index-mail.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-mail.h Sat Mar 15 09:59:56 2008 +0200 @@ -81,6 +81,7 @@ uint32_t seq; uint32_t cache_flags; + uint64_t modseq; enum index_mail_access_part access_part; /* dont_cache_fields overrides cache_fields */ enum mail_fetch_field cache_fetch_fields, dont_cache_fetch_fields; @@ -169,6 +170,7 @@ struct istream **stream_r); enum mail_flags index_mail_get_flags(struct mail *_mail); +uint64_t index_mail_get_modseq(struct mail *_mail); const char *const *index_mail_get_keywords(struct mail *_mail); const ARRAY_TYPE(keyword_indexes) * index_mail_get_keyword_indexes(struct mail *_mail);
--- a/src/lib-storage/index/index-search.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-search.c Sat Mar 15 09:59:56 2008 +0200 @@ -10,6 +10,7 @@ #include "message-date.h" #include "message-search.h" #include "message-parser.h" +#include "mail-index-modseq.h" #include "index-storage.h" #include "index-mail.h" #include "index-sort.h" @@ -41,7 +42,7 @@ unsigned int failed:1; unsigned int sorted:1; unsigned int have_seqsets:1; - unsigned int have_flags_or_keywords:1; + unsigned int have_index_args:1; }; struct search_header_context { @@ -78,7 +79,10 @@ case SEARCH_UIDSET: case SEARCH_FLAGS: case SEARCH_KEYWORDS: - ctx->have_flags_or_keywords = TRUE; + case SEARCH_MODSEQ: + if (arg->type == SEARCH_MODSEQ) + mail_index_modseq_enable(ctx->ibox->index); + ctx->have_index_args = TRUE; break; case SEARCH_ALL: if (!arg->not) @@ -131,6 +135,7 @@ const struct mail_index_record *rec) { enum mail_flags flags; + uint64_t modseq; int ret; switch (arg->type) { @@ -147,7 +152,19 @@ ret = search_arg_match_keywords(ctx, arg); } T_END; return ret; - + case SEARCH_MODSEQ: { + if (arg->value.flags != 0) { + modseq = mail_index_modseq_lookup_flags(ctx->view, + arg->value.flags, ctx->mail_ctx.seq); + } else if (arg->value.keywords != NULL) { + modseq = mail_index_modseq_lookup_keywords(ctx->view, + arg->value.keywords, ctx->mail_ctx.seq); + } else { + modseq = mail_index_modseq_lookup_highest(ctx->view, + ctx->mail_ctx.seq); + } + return modseq >= arg->value.modseq->modseq; + } default: return -1; } @@ -1028,7 +1045,7 @@ _ctx->seq++; } - if (!ctx->have_seqsets && !ctx->have_flags_or_keywords) + if (!ctx->have_seqsets && !ctx->have_index_args) return _ctx->seq <= ctx->seq2 ? 1 : 0; ret = 0; @@ -1039,7 +1056,7 @@ if (ret != 0) { /* check if flags/keywords match before anything else is done. mail_set_seq() can be a bit slow. */ - if (!ctx->have_flags_or_keywords) + if (!ctx->have_index_args) break; ret = mail_search_args_foreach(ctx->mail_ctx.args, search_index_arg, ctx);
--- a/src/lib-storage/index/index-status.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-status.c Sat Mar 15 09:59:56 2008 +0200 @@ -2,6 +2,7 @@ #include "lib.h" #include "index-storage.h" +#include "mail-index-modseq.h" void index_storage_get_status(struct mailbox *box, enum mailbox_status_items items, @@ -25,6 +26,11 @@ status_r->unseen = hdr->messages_count - hdr->seen_messages_count; status_r->uidvalidity = hdr->uid_validity; status_r->uidnext = hdr->next_uid; + if ((items & STATUS_HIGHESTMODSEQ) != 0 && + !mail_index_is_in_memory(ibox->index)) { + status_r->highest_modseq = + mail_index_modseq_get_highest(ibox->view); + } if (items & STATUS_FIRST_UNSEEN_SEQ) { mail_index_lookup_first(ibox->view, 0, MAIL_SEEN,
--- a/src/lib-storage/index/index-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -7,6 +7,7 @@ #include "imap-parser.h" #include "mkdir-parents.h" #include "mail-index-private.h" +#include "mail-index-modseq.h" #include "index-storage.h" #include "index-mail.h" @@ -439,6 +440,18 @@ index_storage_mailbox_open(ibox); } +int index_storage_mailbox_enable(struct mailbox *box, + enum mailbox_feature feature) +{ + struct index_mailbox *ibox = (struct index_mailbox *)box; + + if ((feature & MAILBOX_FEATURE_CONDSTORE) != 0) { + box->enabled_features |= MAILBOX_FEATURE_CONDSTORE; + mail_index_modseq_enable(ibox->index); + } + return TRUE; +} + int index_storage_mailbox_close(struct mailbox *box) { struct index_mailbox *ibox = (struct index_mailbox *) box;
--- a/src/lib-storage/index/index-storage.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-storage.h Sat Mar 15 09:59:56 2008 +0200 @@ -103,6 +103,8 @@ enum mailbox_open_flags flags, bool move_to_memory); void index_storage_mailbox_open(struct index_mailbox *ibox); +int index_storage_mailbox_enable(struct mailbox *box, + enum mailbox_feature feature); int index_storage_mailbox_close(struct mailbox *box); bool index_storage_is_readonly(struct mailbox *box);
--- a/src/lib-storage/index/index-transaction.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/index-transaction.c Sat Mar 15 09:59:56 2008 +0200 @@ -64,11 +64,12 @@ struct index_mailbox *ibox = (struct index_mailbox *)box; struct mail_index_transaction *t; struct index_transaction_context *it; - enum mail_index_transaction_flags trans_flags = 0; + enum mail_index_transaction_flags trans_flags; if (!box->opened) index_storage_mailbox_open(ibox); + trans_flags = MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES; if ((flags & MAILBOX_TRANSACTION_FLAG_HIDE) != 0) trans_flags |= MAIL_INDEX_TRANSACTION_FLAG_HIDE; if ((flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0)
--- a/src/lib-storage/index/maildir/maildir-mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/maildir/maildir-mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -441,6 +441,7 @@ index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, + index_mail_get_modseq, index_mail_get_parts, index_mail_get_date, maildir_mail_get_received_date,
--- a/src/lib-storage/index/maildir/maildir-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -1022,6 +1022,7 @@ { index_storage_is_readonly, index_storage_allow_new_keywords, + index_storage_mailbox_enable, maildir_storage_mailbox_close, index_storage_get_status, maildir_list_index_has_changed,
--- a/src/lib-storage/index/mbox/mbox-mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/mbox/mbox-mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -283,6 +283,7 @@ index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, + index_mail_get_modseq, index_mail_get_parts, index_mail_get_date, mbox_mail_get_received_date,
--- a/src/lib-storage/index/mbox/mbox-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -972,6 +972,7 @@ { index_storage_is_readonly, index_storage_allow_new_keywords, + index_storage_mailbox_enable, mbox_storage_mailbox_close, index_storage_get_status, NULL,
--- a/src/lib-storage/index/raw/raw-mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/raw/raw-mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -112,6 +112,7 @@ index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, + index_mail_get_modseq, index_mail_get_parts, index_mail_get_date, raw_mail_get_received_date,
--- a/src/lib-storage/index/raw/raw-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/index/raw/raw-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -272,6 +272,7 @@ { index_storage_is_readonly, index_storage_allow_new_keywords, + index_storage_mailbox_enable, raw_mailbox_close, index_storage_get_status, NULL,
--- a/src/lib-storage/mail-search-build.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/mail-search-build.c Sat Mar 15 09:59:56 2008 +0200 @@ -148,6 +148,92 @@ return TRUE; } +static bool +arg_modseq_set_name(struct search_build_data *data, + struct mail_search_arg *sarg, const char *name) +{ + name = t_str_lcase(name); + if (strncmp(name, "/flags/", 7) != 0) { + data->error = "Invalid MODSEQ entry"; + return FALSE; + } + name += 7; + + if (*name == '\\') { + /* system flag */ + name++; + if (strcmp(name, "answered") == 0) + sarg->value.flags = MAIL_ANSWERED; + else if (strcmp(name, "flagged") == 0) + sarg->value.flags = MAIL_FLAGGED; + else if (strcmp(name, "deleted") == 0) + sarg->value.flags = MAIL_DELETED; + else if (strcmp(name, "seen") == 0) + sarg->value.flags = MAIL_SEEN; + else if (strcmp(name, "draft") == 0) + sarg->value.flags = MAIL_DRAFT; + else { + data->error = "Invalid MODSEQ system flag"; + return FALSE; + } + return TRUE; + } + sarg->value.str = p_strdup(data->pool, name); + return TRUE; +} + +static bool +arg_modseq_set_type(struct search_build_data *data, + struct mail_search_modseq *modseq, const char *name) +{ + if (strcasecmp(name, "all") == 0) + modseq->type = MAIL_SEARCH_MODSEQ_TYPE_ANY; + else if (strcasecmp(name, "priv") == 0) + modseq->type = MAIL_SEARCH_MODSEQ_TYPE_PRIVATE; + else if (strcasecmp(name, "shared") == 0) + modseq->type = MAIL_SEARCH_MODSEQ_TYPE_SHARED; + else { + data->error = "Invalid MODSEQ type"; + return FALSE; + } + return TRUE; +} + +#define ARG_NEW_MODSEQ() \ + arg_new_modseq(data, args, next_sarg) +static bool +arg_new_modseq(struct search_build_data *data, + const struct imap_arg **args, struct mail_search_arg **next_sarg) +{ + struct mail_search_arg *sarg; + const char *value; + + *next_sarg = sarg = search_arg_new(data->pool, SEARCH_MODSEQ); + if (!arg_get_next(data, args, &value)) + return FALSE; + + sarg->value.modseq = p_new(data->pool, struct mail_search_modseq, 1); + if ((*args)[-1].type == IMAP_ARG_STRING) { + /* <name> <type> */ + if (!arg_modseq_set_name(data, sarg, value)) + return FALSE; + + if (!arg_get_next(data, args, &value)) + return FALSE; + if (!arg_modseq_set_type(data, sarg->value.modseq, value)) + return FALSE; + + if (!arg_get_next(data, args, &value)) + return FALSE; + } + if (!is_numeric(value, '\0')) { + data->error = "Invalid MODSEQ value"; + return FALSE; + } + sarg->value.modseq->modseq = strtoull(value, NULL, 10); + return TRUE; +} + static bool search_arg_build(struct search_build_data *data, const struct imap_arg **args, struct mail_search_arg **next_sarg) @@ -272,6 +358,12 @@ return ARG_NEW_SIZE(SEARCH_LARGER); } break; + case 'M': + if (strcmp(str, "MODSEQ") == 0) { + /* [<name> <type>] <n> */ + return ARG_NEW_MODSEQ(); + } + break; case 'N': if (strcmp(str, "NOT") == 0) { if (!search_arg_build(data, args, next_sarg)) @@ -525,6 +617,10 @@ mailbox_uidseq_change(args, box); } T_END; break; + case SEARCH_MODSEQ: + if (args->value.str == NULL) + break; + /* modseq with keyword */ case SEARCH_KEYWORDS: keywords[0] = args->value.str; keywords[1] = NULL; @@ -533,6 +629,7 @@ args->value.keywords = mailbox_keywords_create_valid(box, keywords); break; + case SEARCH_SUB: case SEARCH_OR: mail_search_args_init(args->value.subargs, box, @@ -549,6 +646,7 @@ { for (; args != NULL; args = args->next) { switch (args->type) { + case SEARCH_MODSEQ: case SEARCH_KEYWORDS: if (args->value.keywords == NULL) break;
--- a/src/lib-storage/mail-search.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/mail-search.h Sat Mar 15 09:59:56 2008 +0200 @@ -38,7 +38,21 @@ SEARCH_BODY, SEARCH_TEXT, SEARCH_BODY_FAST, - SEARCH_TEXT_FAST + SEARCH_TEXT_FAST, + + /* extensions */ + SEARCH_MODSEQ +}; + +enum mail_search_modseq_type { + MAIL_SEARCH_MODSEQ_TYPE_ANY = 0, + MAIL_SEARCH_MODSEQ_TYPE_PRIVATE, + MAIL_SEARCH_MODSEQ_TYPE_SHARED +}; + +struct mail_search_modseq { + uint64_t modseq; + enum mail_search_modseq_type type; }; struct mail_search_arg { @@ -53,6 +67,7 @@ uoff_t size; enum mail_flags flags; struct mail_keywords *keywords; + struct mail_search_modseq *modseq; } value; void *context;
--- a/src/lib-storage/mail-storage-private.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/mail-storage-private.h Sat Mar 15 09:59:56 2008 +0200 @@ -75,6 +75,7 @@ bool (*is_readonly)(struct mailbox *box); bool (*allow_new_keywords)(struct mailbox *box); + int (*enable)(struct mailbox *box, enum mailbox_feature features); int (*close)(struct mailbox *box); void (*get_status)(struct mailbox *box, enum mailbox_status_items items, @@ -177,6 +178,7 @@ pool_t pool; unsigned int transaction_count; + enum mailbox_feature enabled_features; /* User's private flags if this is a shared mailbox */ enum mail_flags private_flags_mask; @@ -210,6 +212,7 @@ const char *const *(*get_keywords)(struct mail *mail); const ARRAY_TYPE(keyword_indexes) * (*get_keyword_indexes)(struct mail *mail); + uint64_t (*get_modseq)(struct mail *mail); int (*get_parts)(struct mail *mail, const struct message_part **parts_r);
--- a/src/lib-storage/mail-storage.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/mail-storage.c Sat Mar 15 09:59:56 2008 +0200 @@ -463,6 +463,16 @@ return box; } +int mailbox_enable(struct mailbox *box, enum mailbox_feature features) +{ + return box->v.enable(box, features); +} + +enum mailbox_feature mailbox_get_enabled_features(struct mailbox *box) +{ + return box->enabled_features; +} + int mailbox_close(struct mailbox **_box) { struct mailbox *box = *_box;
--- a/src/lib-storage/mail-storage.h Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/mail-storage.h Sat Mar 15 09:59:56 2008 +0200 @@ -54,7 +54,12 @@ /* Don't create index files for the mailbox */ MAILBOX_OPEN_NO_INDEX_FILES = 0x10, /* Keep mailbox exclusively locked all the time while it's open */ - MAILBOX_OPEN_KEEP_LOCKED = 0x20 + MAILBOX_OPEN_KEEP_LOCKED = 0x20, +}; + +enum mailbox_feature { + /* Enable tracking modsequences */ + MAILBOX_FEATURE_CONDSTORE = 0x01, }; enum mailbox_status_items { @@ -64,7 +69,8 @@ STATUS_UIDVALIDITY = 0x08, STATUS_UNSEEN = 0x10, STATUS_FIRST_UNSEEN_SEQ = 0x20, - STATUS_KEYWORDS = 0x40 + STATUS_KEYWORDS = 0x40, + STATUS_HIGHESTMODSEQ = 0x80 }; enum mail_sort_type { @@ -140,7 +146,8 @@ enum mailbox_sync_type { MAILBOX_SYNC_TYPE_EXPUNGE = 0x01, - MAILBOX_SYNC_TYPE_FLAGS = 0x02 + MAILBOX_SYNC_TYPE_FLAGS = 0x02, + MAILBOX_SYNC_TYPE_MODSEQ = 0x04 }; struct message_part; @@ -161,6 +168,7 @@ uint32_t uidnext; uint32_t first_unseen_seq; + uint64_t highest_modseq; const ARRAY_TYPE(keywords) *keywords; }; @@ -267,6 +275,10 @@ the mailbox was closed anyway. */ int mailbox_close(struct mailbox **box); +/* Enable the given feature for the mailbox. */ +int mailbox_enable(struct mailbox *box, enum mailbox_feature features); +enum mailbox_feature mailbox_get_enabled_features(struct mailbox *box); + /* Returns storage of given mailbox */ struct mail_storage *mailbox_get_storage(struct mailbox *box); @@ -416,6 +428,8 @@ const char *const *mail_get_keywords(struct mail *mail); /* Returns message's keywords */ const ARRAY_TYPE(keyword_indexes) *mail_get_keyword_indexes(struct mail *mail); +/* Returns message's modseq */ +uint64_t mail_get_modseq(struct mail *mail); /* Returns message's MIME parts */ int mail_get_parts(struct mail *mail, const struct message_part **parts_r);
--- a/src/lib-storage/mail.c Fri Mar 14 11:59:36 2008 +0200 +++ b/src/lib-storage/mail.c Sat Mar 15 09:59:56 2008 +0200 @@ -40,6 +40,13 @@ return p->v.get_flags(mail); } +uint64_t mail_get_modseq(struct mail *mail) +{ + struct mail_private *p = (struct mail_private *)mail; + + return p->v.get_modseq(mail); +} + const char *const *mail_get_keywords(struct mail *mail) { struct mail_private *p = (struct mail_private *)mail;