changeset 7620:4b8c1c164d8f HEAD

Initial CONDSTORE support.
author Timo Sirainen <tss@iki.fi>
date Sat, 15 Mar 2008 09:59:56 +0200
parents 56f55bd35aa5
children bec7141872eb
files src/imap/client.c src/imap/client.h src/imap/cmd-append.c src/imap/cmd-copy.c src/imap/cmd-fetch.c src/imap/cmd-search.c src/imap/cmd-select.c src/imap/cmd-store.c src/imap/imap-fetch.c src/imap/imap-fetch.h src/imap/imap-status.c src/imap/imap-sync.c src/lib-imap/imap-util.c src/lib-imap/imap-util.h src/lib-index/Makefile.am src/lib-index/mail-index-map.c src/lib-index/mail-index-private.h src/lib-index/mail-index-sync-ext.c src/lib-index/mail-index-sync-keywords.c src/lib-index/mail-index-sync-private.h src/lib-index/mail-index-sync-update.c src/lib-index/mail-index-transaction.c src/lib-index/mail-index-view-sync.c src/lib-index/mail-index.c src/lib-index/mail-index.h src/lib-storage/index/cydir/cydir-mail.c src/lib-storage/index/cydir/cydir-storage.c src/lib-storage/index/dbox/dbox-mail.c src/lib-storage/index/dbox/dbox-storage.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-mail.h src/lib-storage/index/index-search.c src/lib-storage/index/index-status.c src/lib-storage/index/index-storage.c src/lib-storage/index/index-storage.h src/lib-storage/index/index-transaction.c src/lib-storage/index/maildir/maildir-mail.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/mbox/mbox-mail.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/raw/raw-mail.c src/lib-storage/index/raw/raw-storage.c src/lib-storage/mail-search-build.c src/lib-storage/mail-search.h src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/lib-storage/mail.c
diffstat 48 files changed, 792 insertions(+), 138 deletions(-) [+]
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;