changeset 7670:c8b19aca45d9 HEAD

virtual mailboxes: Use saved search results to find out which messages were added/removed. Also several other fixes and optimizations.
author Timo Sirainen <tss@iki.fi>
date Mon, 09 Jun 2008 02:32:15 +0300
parents 242a4fae3587
children 882888286bf5
files src/plugins/virtual/virtual-mail.c src/plugins/virtual/virtual-storage.c src/plugins/virtual/virtual-storage.h src/plugins/virtual/virtual-sync.c
diffstat 4 files changed, 551 insertions(+), 188 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/virtual/virtual-mail.c	Mon Jun 09 00:15:36 2008 +0300
+++ b/src/plugins/virtual/virtual-mail.c	Mon Jun 09 02:32:15 2008 +0300
@@ -228,24 +228,6 @@
 	return mail_get_special(vmail->backend_mail, field, value_r);
 }
 
-static void
-virtual_mail_update_flags(struct mail *mail, enum modify_type modify_type,
-			  enum mail_flags flags)
-{
-	struct virtual_mail *vmail = (struct virtual_mail *)mail;
-
-	mail_update_flags(vmail->backend_mail, modify_type, flags);
-}
-
-static void
-virtual_mail_update_keywords(struct mail *mail, enum modify_type modify_type,
-			     struct mail_keywords *keywords)
-{
-	struct virtual_mail *vmail = (struct virtual_mail *)mail;
-
-	mail_update_keywords(vmail->backend_mail, modify_type, keywords);
-}
-
 static void virtual_mail_expunge(struct mail *mail)
 {
 	struct virtual_mail *vmail = (struct virtual_mail *)mail;
@@ -289,8 +271,8 @@
 	virtual_mail_get_header_stream,
 	virtual_mail_get_stream,
 	virtual_mail_get_special,
-	virtual_mail_update_flags,
-	virtual_mail_update_keywords,
+	index_mail_update_flags,
+	index_mail_update_keywords,
 	virtual_mail_expunge,
 	virtual_mail_set_cache_corrupted,
 	virtual_mail_get_index_mail
--- a/src/plugins/virtual/virtual-storage.c	Mon Jun 09 00:15:36 2008 +0300
+++ b/src/plugins/virtual/virtual-storage.c	Mon Jun 09 02:32:15 2008 +0300
@@ -160,6 +160,7 @@
 			break;
 		}
 		i_array_init(&bboxes[i]->uids, 64);
+		i_array_init(&bboxes[i]->sync_pending_removes, 64);
 	}
 	if (i == count)
 		return 0;
@@ -260,8 +261,10 @@
 
 	bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
 	for (i = 0; i < count; i++) {
+		mailbox_search_result_free(&bboxes[i]->search_result);
 		if (mailbox_close(&bboxes[i]->box) < 0)
 			ret = -1;
+		array_free(&bboxes[i]->sync_pending_removes);
 		array_free(&bboxes[i]->uids);
 	}
 	array_free(&mbox->backend_boxes);
--- a/src/plugins/virtual/virtual-storage.h	Mon Jun 09 00:15:36 2008 +0300
+++ b/src/plugins/virtual/virtual-storage.h	Mon Jun 09 02:32:15 2008 +0300
@@ -20,18 +20,29 @@
 	union mailbox_list_module_context list_module_ctx;
 };
 
+struct virtual_backend_uidmap {
+	uint32_t real_uid;
+	/* can be 0 temporarily while syncing before the UID is assigned */
+	uint32_t virtual_uid;
+};
+
 struct virtual_backend_box {
 	uint32_t mailbox_id;
 	const char *name;
+
 	struct mail_search_args *search_args;
+	struct mail_search_result *search_result;
 
 	struct mailbox *box;
-	/* Sorted list of UIDs currently included in the virtual mailbox */
-	ARRAY_TYPE(seq_range) uids;
+	/* Messages currently included in the virtual mailbox,
+	   sorted by real_uid */
+	ARRAY_DEFINE(uids, struct virtual_backend_uidmap);
 
+	/* temporary mail used while syncing */
 	struct mail *sync_mail;
-	unsigned int sync_iter_idx;
-	unsigned int sync_iter_prev_real_uid;
+	/* pending removed UIDs */
+	ARRAY_TYPE(seq_range) sync_pending_removes;
+	unsigned int sync_seen:1;
 };
 
 struct virtual_mailbox {
@@ -43,6 +54,8 @@
 
 	/* Mailboxes this virtual mailbox consists of, sorted by mailbox_id */
 	ARRAY_DEFINE(backend_boxes, struct virtual_backend_box *);
+
+	unsigned int uids_mapped:1;
 };
 
 extern struct mail_storage virtual_storage;
--- a/src/plugins/virtual/virtual-sync.c	Mon Jun 09 00:15:36 2008 +0300
+++ b/src/plugins/virtual/virtual-sync.c	Mon Jun 09 02:32:15 2008 +0300
@@ -2,6 +2,7 @@
 
 #include "lib.h"
 #include "array.h"
+#include "bsearch-insert-pos.h"
 #include "ioloop.h"
 #include "str.h"
 #include "mail-search-build.h"
@@ -9,6 +10,11 @@
 
 #include <stdlib.h>
 
+struct virtual_add_record {
+	struct virtual_mail_index_record rec;
+	time_t received_date;
+};
+
 struct virtual_sync_mail {
 	uint32_t vseq;
 	struct virtual_mail_index_record vrec;
@@ -22,6 +28,10 @@
 	struct mail_index_transaction *trans;
 	const char *const *kw_all;
 
+	/* messages expunged within this sync */
+	ARRAY_TYPE(seq_range) sync_expunges;
+
+	ARRAY_DEFINE(all_adds, struct virtual_add_record);
 	enum mailbox_sync_flags flags;
 	uint32_t uid_validity;
 	unsigned int expunge_removed:1;
@@ -54,68 +64,9 @@
 
 	/* copy keywords */
 	kw_names = mail_get_keywords(bbox->sync_mail);
-	if (kw_names[0] != NULL) {
-		keywords = mail_index_keywords_create(ctx->index, kw_names);
-		mail_index_update_keywords(ctx->trans, vseq,
-					   MODIFY_REPLACE, keywords);
-		mail_index_keywords_free(&keywords);
-	}
-}
-
-static void virtual_sync_external_appends(struct virtual_sync_context *ctx,
-					  struct virtual_backend_box *bbox,
-					  uint32_t uid1, uint32_t uid2)
-{
-	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
-	struct virtual_mail_index_record vrec;
-	uint32_t uid, vseq;
-
-	vrec.mailbox_id = bbox->mailbox_id;
-	for (uid = uid1; uid <= uid2; uid++) {
-		mail_index_append(ctx->trans, 0, &vseq);
-		vrec.real_uid = uid;
-		mail_index_update_ext(ctx->trans, vseq, virtual_ext_id,
-				      &vrec, NULL);
-		virtual_sync_external_flags(ctx, bbox, vseq, uid);
-	}
-}
-
-static void
-virtual_sync_external_appends_finish_box(struct virtual_sync_context *ctx,
-					 struct virtual_backend_box *bbox)
-{
-	const struct seq_range *seqs;
-	unsigned int seqs_count;
-	uint32_t first_ruid, last_ruid;
-
-	seqs = array_get(&bbox->uids, &seqs_count);
-	while (bbox->sync_iter_idx < seqs_count) {
-		/* max(seq1,prev_uid+1)..seq2 contain newly seen UIDs */
-		first_ruid = I_MAX(seqs[bbox->sync_iter_idx].seq1,
-				   bbox->sync_iter_prev_real_uid + 1);
-		last_ruid = seqs[bbox->sync_iter_idx].seq2;
-
-		if (first_ruid <= last_ruid) {
-			virtual_sync_external_appends(ctx, bbox,
-						      first_ruid, last_ruid);
-		}
-		bbox->sync_iter_idx++;
-	}
-}
-
-static void
-virtual_sync_external_appends_finish(struct virtual_sync_context *ctx)
-{
-	struct virtual_backend_box *const *bboxes;
-	unsigned int i, count;
-	uint32_t next_uid;
-
-	next_uid = mail_index_get_header(ctx->sync_view)->next_uid;
-	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
-	for (i = 0; i < count; i++) {
-		virtual_sync_external_appends_finish_box(ctx, bboxes[i]);
-		mail_index_append_assign_uids(ctx->trans, next_uid, &next_uid);
-	}
+	keywords = mail_index_keywords_create(ctx->index, kw_names);
+	mail_index_update_keywords(ctx->trans, vseq, MODIFY_REPLACE, keywords);
+	mail_index_keywords_free(&keywords);
 }
 
 static int virtual_sync_mail_cmp(const void *p1, const void *p2)
@@ -135,79 +86,27 @@
 	return 0;
 }
 
-static void virtual_sync_external(struct virtual_sync_context *ctx)
+static void
+virtual_backend_box_sync_mail_set(struct virtual_backend_box *bbox)
 {
-	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
-	struct virtual_backend_box *bbox;
-	struct virtual_sync_mail *vmails;
-	const struct virtual_mail_index_record *vrec;
-	const void *data;
-	const struct seq_range *seqs;
-	unsigned int i, seqs_count;
-	uint32_t vseq, first_ruid, last_ruid, messages;
-	bool expunged;
-
-	messages = mail_index_view_get_messages_count(ctx->sync_view);
+	struct mailbox_transaction_context *trans;
 
-	/* sort the messages by their backend mailbox and real UID */
-	vmails = messages == 0 ? NULL :
-		i_new(struct virtual_sync_mail, messages);
-	for (vseq = 1; vseq <= messages; vseq++) {
-		mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id,
-				      &data, &expunged);
-		vrec = data;
-		vmails[vseq-1].vseq = vseq;
-		vmails[vseq-1].vrec = *vrec;
+	if (bbox->sync_mail == NULL) {
+		trans = mailbox_transaction_begin(bbox->box, 0);
+		bbox->sync_mail = mail_alloc(trans, 0, NULL);
 	}
-	qsort(vmails, messages, sizeof(*vmails), virtual_sync_mail_cmp);
-
-	bbox = NULL;
-	for (i = 0; i < messages; i++) {
-		vseq = vmails[i].vseq;
-		vrec = &vmails[i].vrec;
+}
 
-		if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
-			bbox = virtual_backend_box_lookup(ctx->mbox,
-							  vrec->mailbox_id);
-			if (bbox == NULL) {
-				/* the entire mailbox is lost */
-				mail_index_expunge(ctx->trans, vseq);
-				continue;
-			}
-		}
+static void
+virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox)
+{
+	struct mailbox_transaction_context *trans;
 
-		seqs = array_get(&bbox->uids, &seqs_count);
-		while (bbox->sync_iter_idx < seqs_count) {
-			/* max(seq1,prev_uid+1)..min(seq2,uid-1) contain
-			   newly seen UIDs */
-			first_ruid = I_MAX(seqs[bbox->sync_iter_idx].seq1,
-					   bbox->sync_iter_prev_real_uid + 1);
-			last_ruid = I_MIN(seqs[bbox->sync_iter_idx].seq2,
-					  vrec->real_uid - 1);
-			if (first_ruid <= last_ruid) {
-				virtual_sync_external_appends(ctx, bbox,
-							      first_ruid,
-							      last_ruid);
-			}
-			if (vrec->real_uid <= seqs[bbox->sync_iter_idx].seq2)
-				break;
-			bbox->sync_iter_idx++;
-		}
-		if (bbox->sync_iter_idx >= seqs_count ||
-		    vrec->real_uid < seqs[bbox->sync_iter_idx].seq1) {
-			if (ctx->expunge_removed) {
-				mail_index_expunge(ctx->trans, vseq);
-				continue;
-			}
-		}
-
-		/* uid is within seq1..seq2 */
-		bbox->sync_iter_prev_real_uid = vrec->real_uid;
-		virtual_sync_external_flags(ctx, bbox, vseq, vrec->real_uid);
+	if (bbox->sync_mail != NULL) {
+		trans = bbox->sync_mail->transaction;
+		mail_free(&bbox->sync_mail);
+		(void)mailbox_transaction_commit(&trans);
 	}
-	i_free(vmails);
-
-	virtual_sync_external_appends_finish(ctx);
 }
 
 static void virtual_sync_index_rec(struct virtual_sync_context *ctx,
@@ -251,6 +150,7 @@
 		if (bbox == NULL)
 			continue;
 
+		virtual_backend_box_sync_mail_set(bbox);
 		if (!mail_set_uid(bbox->sync_mail, vrec->real_uid))
 			i_panic("UID lost unexpectedly");
 
@@ -298,12 +198,22 @@
 	}
 }
 
+static void virtual_sync_index_changes(struct virtual_sync_context *ctx)
+{
+	const ARRAY_TYPE(keywords) *keywords;
+	struct mail_index_sync_rec sync_rec;
+
+	keywords = mail_index_get_keywords(ctx->index);
+	ctx->kw_all = array_count(keywords) == 0 ? NULL :
+		array_idx(keywords, 0);
+	while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec))
+		virtual_sync_index_rec(ctx, &sync_rec);
+}
+
 static void virtual_sync_index(struct virtual_sync_context *ctx)
 {
 	struct mailbox *box = &ctx->mbox->ibox.box;
-	const ARRAY_TYPE(keywords) *keywords;
 	const struct mail_index_header *hdr;
-	struct mail_index_sync_rec sync_rec;
 	uint32_t seq1, seq2;
 
 	hdr = mail_index_get_header(ctx->sync_view);
@@ -319,40 +229,41 @@
 					     seq1, seq2);
 	}
 
-	keywords = mail_index_get_keywords(ctx->index);
-	ctx->kw_all = array_count(keywords) == 0 ? NULL :
-		array_idx(keywords, 0);
-	while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec))
-		virtual_sync_index_rec(ctx, &sync_rec);
-
 	if (box->v.sync_notify != NULL)
 		box->v.sync_notify(box, 0, 0);
 }
 
-static int virtual_sync_backend_box(struct virtual_sync_context *ctx,
-				    struct virtual_backend_box *bbox)
+static int virtual_sync_backend_box_init(struct virtual_backend_box *bbox)
 {
 	struct mailbox_transaction_context *trans;
 	struct mail_search_context *search_ctx;
 	struct mail *mail;
-	enum mailbox_sync_flags sync_flags;
+	struct virtual_backend_uidmap uidmap;
+	enum mailbox_search_result_flags result_flags;
 	int ret;
 
-	sync_flags = ctx->flags & (MAILBOX_SYNC_FLAG_FULL_READ |
-				   MAILBOX_SYNC_FLAG_FULL_WRITE |
-				   MAILBOX_SYNC_FLAG_FAST);
-	if (mailbox_sync(bbox->box, sync_flags, 0, NULL) < 0)
-		return -1;
-
+	/* FIXME: build the initial search result from the saved view and
+	   sync beginning from the saved modseq */
 	trans = mailbox_transaction_begin(bbox->box, 0);
 	mail = mail_alloc(trans, 0, NULL);
 
 	mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL);
 	search_ctx = mailbox_search_init(trans, bbox->search_args, NULL);
 
+	/* save the result and keep it updated */
+	result_flags = MAILBOX_SEARCH_RESULT_FLAG_UPDATE |
+		MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC;
+	bbox->search_result =
+		mailbox_search_result_save(search_ctx, result_flags);
+
+	/* add the found UIDs to uidmap. virtual_uid gets assigned later. */
+	memset(&uidmap, 0, sizeof(uidmap));
 	array_clear(&bbox->uids);
-	while (mailbox_search_next(search_ctx, mail) > 0)
-		seq_range_array_add(&bbox->uids, 0, mail->uid);
+	while (mailbox_search_next(search_ctx, mail) > 0) {
+		uidmap.real_uid = mail->uid;
+		array_append(&bbox->uids, &uidmap, 1);
+	}
+
 	ret = mailbox_search_deinit(&search_ctx);
 	mail_free(&mail);
 
@@ -361,41 +272,495 @@
 	return ret;
 }
 
+static int virtual_backend_uidmap_cmp(const void *key, const void *data)
+{
+	const uint32_t *uidp = key;
+	const struct virtual_backend_uidmap *uidmap = data;
+
+	return *uidp < uidmap->real_uid ? -1 :
+		(*uidp > uidmap->real_uid ? 1 : 0);
+}
+
+static void
+virtual_sync_mailbox_box_remove(struct virtual_sync_context *ctx,
+				struct virtual_backend_box *bbox,
+				const ARRAY_TYPE(seq_range) *removed_uids)
+{
+	const struct seq_range *uids;
+	struct virtual_backend_uidmap *uidmap;
+	unsigned int i, src, dest, uid_count, rec_count, left;
+	uint32_t uid, vseq;
+
+	uids = array_get(removed_uids, &uid_count);
+	if (uid_count == 0)
+		return;
+
+	/* everything in removed_uids should exist in bbox->uids */
+	uidmap = array_get_modifiable(&bbox->uids, &rec_count);
+	i_assert(rec_count >= uid_count);
+
+	/* find the first uidmap record to be removed */
+	if (!bsearch_insert_pos(&uids[0].seq1, uidmap, rec_count,
+				sizeof(*uidmap),
+				virtual_backend_uidmap_cmp, &src))
+		i_unreached();
+
+	/* remove the unwanted messages */
+	for (i = dest = 0; i < uid_count; i++) {
+		uid = uids[i].seq1;
+		while (uidmap[src].real_uid != uid) {
+			uidmap[dest++] = uidmap[src++];
+			i_assert(src < rec_count);
+		}
+
+		for (; uid <= uids[i].seq2; uid++, src++) {
+			if (!mail_index_lookup_seq(ctx->sync_view,
+						   uidmap[src].virtual_uid,
+						   &vseq))
+				i_unreached();
+			mail_index_expunge(ctx->trans, vseq);
+		}
+	}
+	left = rec_count - src;
+	memmove(uidmap + dest, uidmap + src, left);
+	array_delete(&bbox->uids, dest + left, src - dest);
+}
+
+static void
+virtual_sync_mailbox_box_add(struct virtual_sync_context *ctx,
+			     struct virtual_backend_box *bbox,
+			     const ARRAY_TYPE(seq_range) *added_uids)
+{
+	const struct seq_range *uids;
+	struct virtual_backend_uidmap *uidmap;
+	struct virtual_add_record rec;
+	unsigned int i, src, dest, uid_count, add_count, rec_count;
+	uint32_t uid;
+
+	uids = array_get(added_uids, &uid_count);
+	if (uid_count == 0)
+		return;
+	add_count = seq_range_count(added_uids);
+
+	/* none of added_uids should exist in bbox->uids. find the position
+	   of the first inserted index. */
+	uidmap = array_get_modifiable(&bbox->uids, &rec_count);
+	if (uids[0].seq1 > uidmap[rec_count-1].real_uid) {
+		/* fast path: usually messages are appended */
+		dest = rec_count;
+	} else if (bsearch_insert_pos(&uids[0].seq1, uidmap, rec_count,
+				      sizeof(*uidmap),
+				      virtual_backend_uidmap_cmp, &dest))
+		i_unreached();
+
+	/* make space for all added UIDs. */
+	array_copy(&bbox->uids.arr, dest + add_count,
+		   &bbox->uids.arr, dest, rec_count - dest);
+	uidmap = array_get_modifiable(&bbox->uids, &rec_count);
+	src = dest + add_count;
+
+	/* add/move the UIDs to their correct positions */
+	memset(&rec, 0, sizeof(rec));
+	rec.rec.mailbox_id = bbox->mailbox_id;
+	for (i = 0; i < uid_count; i++) {
+		uid = uids[i].seq1;
+		while (src < rec_count && uidmap[src].real_uid < uid) {
+			uidmap[dest++] = uidmap[src++];
+			i_assert(src < rec_count);
+		}
+
+		for (; uid <= uids[i].seq2; uid++, dest++) {
+			uidmap[dest].real_uid = uid;
+			uidmap[dest].virtual_uid = 0;
+
+			rec.rec.real_uid = uid;
+			array_append(&ctx->all_adds, &rec, 1);
+		}
+	}
+}
+
+static void virtual_sync_mailbox_box_update(struct virtual_sync_context *ctx,
+					    struct virtual_backend_box *bbox)
+{
+	ARRAY_TYPE(seq_range) removed_uids, added_uids, temp_uids;
+	unsigned int count1, count2;
+
+	t_array_init(&removed_uids, 128);
+	t_array_init(&added_uids, 128);
+
+	mailbox_search_result_sync(bbox->search_result,
+				   &removed_uids, &added_uids);
+
+	/* if any of the pending removes came back, we don't want to expunge
+	   them anymore. also since they already exist, remove them from
+	   added_uids. */
+	count1 = array_count(&bbox->sync_pending_removes);
+	count2 = array_count(&added_uids);
+	if (count1 > 0 && count2 > 0) {
+		t_array_init(&temp_uids, count1);
+		array_append_array(&temp_uids, &bbox->sync_pending_removes);
+		if (seq_range_array_remove_seq_range(
+				&bbox->sync_pending_removes, &added_uids) > 0) {
+			seq_range_array_remove_seq_range(&added_uids,
+							 &temp_uids);
+		}
+	}
+
+	if (!ctx->expunge_removed) {
+		/* delay removing messages that don't match the search
+		   criteria, but don't delay removing expunged messages */
+		if (array_count(&ctx->sync_expunges) > 0) {
+			seq_range_array_remove_seq_range(&removed_uids,
+							 &ctx->sync_expunges);
+			virtual_sync_mailbox_box_remove(ctx, bbox,
+							&ctx->sync_expunges);
+		}
+		seq_range_array_merge(&bbox->sync_pending_removes,
+				      &removed_uids);
+	} else if (array_count(&bbox->sync_pending_removes) > 0) {
+		/* remove all current and old */
+		seq_range_array_merge(&bbox->sync_pending_removes,
+				      &removed_uids);
+		virtual_sync_mailbox_box_remove(ctx, bbox,
+						&bbox->sync_pending_removes);
+		array_clear(&bbox->sync_pending_removes);
+	} else {
+		virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids);
+	}
+	virtual_sync_mailbox_box_add(ctx, bbox, &added_uids);
+}
+
+static bool virtual_sync_find_seqs(struct virtual_backend_box *bbox,
+				   const struct mailbox_sync_rec *sync_rec,
+				   unsigned int *idx1_r,
+				   unsigned int *idx2_r)
+{
+	struct index_mailbox *ibox = (struct index_mailbox *)bbox->box;
+	const struct virtual_backend_uidmap *uidmap;
+	unsigned int idx, count;
+	uint32_t uid1, uid2;
+
+	mail_index_lookup_uid(ibox->view, sync_rec->seq1, &uid1);
+	mail_index_lookup_uid(ibox->view, sync_rec->seq2, &uid2);
+	uidmap = array_get_modifiable(&bbox->uids, &count);
+	(void)bsearch_insert_pos(&uid1, uidmap, count, sizeof(*uidmap),
+				 virtual_backend_uidmap_cmp, &idx);
+	if (idx == count || uidmap[idx].real_uid > uid2)
+		return FALSE;
+
+	*idx1_r = idx;
+	while (idx < count && uidmap[idx].real_uid <= uid2) idx++;
+	*idx2_r = idx - 1;
+	return TRUE;
+}
+
+static int virtual_sync_backend_box_sync(struct virtual_sync_context *ctx,
+					 struct virtual_backend_box *bbox,
+					 enum mailbox_sync_flags sync_flags)
+{
+	struct mailbox_sync_context *sync_ctx;
+	const struct virtual_backend_uidmap *uidmap;
+	struct mailbox_sync_rec sync_rec;
+	unsigned int idx1, idx2;
+	uint32_t vseq, vuid;
+
+	sync_ctx = mailbox_sync_init(bbox->box, sync_flags);
+	virtual_backend_box_sync_mail_set(bbox);
+	while (mailbox_sync_next(sync_ctx, &sync_rec)) {
+		switch (sync_rec.type) {
+		case MAILBOX_SYNC_TYPE_EXPUNGE:
+			if (ctx->expunge_removed) {
+				/* no need to keep track of expunges */
+				break;
+			}
+			seq_range_array_add_range(&ctx->sync_expunges,
+						  sync_rec.seq1, sync_rec.seq2);
+			break;
+		case MAILBOX_SYNC_TYPE_FLAGS:
+			if (!virtual_sync_find_seqs(bbox, &sync_rec,
+						    &idx1, &idx2))
+				break;
+			uidmap = array_idx(&bbox->uids, 0);
+			for (; idx1 <= idx2; idx1++) {
+				vuid = uidmap[idx1].virtual_uid;
+				if (!mail_index_lookup_seq(ctx->sync_view,
+							   vuid, &vseq))
+					i_unreached();
+				virtual_sync_external_flags(ctx, bbox, vseq,
+							uidmap[idx1].real_uid);
+			}
+			break;
+		case MAILBOX_SYNC_TYPE_MODSEQ:
+			break;
+		}
+	}
+	return mailbox_sync_deinit(&sync_ctx, 0, NULL);
+}
+
+static int virtual_sync_backend_box(struct virtual_sync_context *ctx,
+				    struct virtual_backend_box *bbox)
+{
+	enum mailbox_sync_flags sync_flags;
+	int ret;
+
+	/* if we already did some changes to index, commit them before
+	   syncing starts. */
+	virtual_backend_box_sync_mail_unset(bbox);
+
+	sync_flags = ctx->flags & (MAILBOX_SYNC_FLAG_FULL_READ |
+				   MAILBOX_SYNC_FLAG_FULL_WRITE |
+				   MAILBOX_SYNC_FLAG_FAST);
+
+	if (bbox->search_result == NULL) {
+		/* initial sync, do a full search */
+		if (mailbox_sync(bbox->box, sync_flags, 0, NULL) < 0)
+			return -1;
+
+		virtual_backend_box_sync_mail_set(bbox);
+		ret = virtual_sync_backend_box_init(bbox);
+	} else {
+		/* sync using the existing search result */
+		i_array_init(&ctx->sync_expunges, 32);
+		ret = virtual_sync_backend_box_sync(ctx, bbox, sync_flags);
+		if (ret == 0) T_BEGIN {
+			virtual_sync_mailbox_box_update(ctx, bbox);
+		} T_END;
+		array_free(&ctx->sync_expunges);
+	}
+	return ret;
+}
+
+static void virtual_sync_backend_map_uids(struct virtual_sync_context *ctx)
+{
+	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+	struct virtual_sync_mail *vmails;
+	struct virtual_backend_box *bbox, *const *bboxes;
+	struct virtual_backend_uidmap *uidmap = NULL;
+	struct virtual_add_record add_rec;
+	const struct virtual_mail_index_record *vrec;
+	const void *data;
+	bool expunged;
+	uint32_t i, vseq, vuid, messages, count;
+	unsigned int j = 0, uidmap_count = 0;
+
+	messages = mail_index_view_get_messages_count(ctx->sync_view);
+
+	/* sort the messages in current view by their backend mailbox and
+	   real UID */
+	vmails = messages == 0 ? NULL :
+		i_new(struct virtual_sync_mail, messages);
+	for (vseq = 1; vseq <= messages; vseq++) {
+		mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id,
+				      &data, &expunged);
+		vrec = data;
+		vmails[vseq-1].vseq = vseq;
+		vmails[vseq-1].vrec = *vrec;
+	}
+	qsort(vmails, messages, sizeof(*vmails), virtual_sync_mail_cmp);
+
+	/* create real mailbox uid -> virtual uid mapping and expunge
+	   messages no longer matching the search rule */
+	memset(&add_rec, 0, sizeof(add_rec));
+	bbox = NULL;
+	for (i = 0; i < messages; i++) {
+		vseq = vmails[i].vseq;
+		vrec = &vmails[i].vrec;
+
+		if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+			/* add the rest of the newly seen messages */
+			for (; j < uidmap_count; j++) {
+				add_rec.rec.real_uid = uidmap[j].real_uid;
+				array_append(&ctx->all_adds, &add_rec, 1);
+			}
+			bbox = virtual_backend_box_lookup(ctx->mbox,
+							  vrec->mailbox_id);
+			if (bbox == NULL) {
+				/* the entire mailbox is lost */
+				mail_index_expunge(ctx->trans, vseq);
+				continue;
+			}
+			uidmap = array_get_modifiable(&bbox->uids,
+						      &uidmap_count);
+			j = 0;
+			add_rec.rec.mailbox_id = bbox->mailbox_id;
+			bbox->sync_seen = TRUE;
+		}
+		mail_index_lookup_uid(ctx->sync_view, vseq, &vuid);
+
+		/* if virtual record doesn't exist in uidmap, it's expunged */
+		for (; j < uidmap_count; j++) {
+			if (uidmap[j].real_uid >= vrec->real_uid)
+				break;
+
+			/* newly seen message */
+			add_rec.rec.real_uid = uidmap[j].real_uid;
+			array_append(&ctx->all_adds, &add_rec, 1);
+		}
+		if (uidmap[j].real_uid != vrec->real_uid)
+			mail_index_expunge(ctx->trans, vseq);
+		else {
+			/* exists - update uidmap and flags */
+			uidmap[j++].virtual_uid = vuid;
+			virtual_sync_external_flags(ctx, bbox, vseq,
+						    vrec->real_uid);
+		}
+	}
+	i_free(vmails);
+
+	/* if there are any mailboxes we didn't yet sync, add new messages in
+	   them */
+	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
+	for (i = 0; i < count; i++) {
+		if (bboxes[i]->sync_seen)
+			continue;
+
+		add_rec.rec.mailbox_id = bboxes[i]->mailbox_id;
+		uidmap = array_get_modifiable(&bbox->uids, &uidmap_count);
+		for (j = 0; j < uidmap_count; j++) {
+			add_rec.rec.real_uid = uidmap[j].real_uid;
+			array_append(&ctx->all_adds, &add_rec, 1);
+		}
+	}
+}
+
+static int virtual_add_record_cmp(const void *p1, const void *p2)
+{
+	const struct virtual_add_record *add1 = p1, *add2 = p2;
+
+	if (add1->received_date < add2->received_date)
+		return -1;
+	if (add1->received_date > add2->received_date)
+		return 1;
+
+	/* if they're in same mailbox, we can order them correctly by the UID.
+	   if they're in different mailboxes, ordering by UID doesn't really
+	   help but it doesn't really harm either. */
+	if (add1->rec.real_uid < add2->rec.real_uid)
+		return -1;
+	if (add1->rec.real_uid > add2->rec.real_uid)
+		return 1;
+
+	/* two messages in different mailboxes have the same received date
+	   and UID. */
+	return 0;
+}
+
+static void virtual_sync_backend_sort_new(struct virtual_sync_context *ctx)
+{
+	struct virtual_backend_box *bbox;
+	struct virtual_add_record *adds;
+	const struct virtual_mail_index_record *vrec;
+	unsigned int i, count;
+
+	/* get all messages' received dates */
+	adds = array_get_modifiable(&ctx->all_adds, &count);
+	for (bbox = NULL, i = 0; i < count; i++) {
+		vrec = &adds[i].rec;
+
+		if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+			bbox = virtual_backend_box_lookup(ctx->mbox,
+							  vrec->mailbox_id);
+		}
+		if (!mail_set_uid(bbox->sync_mail, vrec->real_uid))
+			i_unreached();
+		if (mail_get_received_date(bbox->sync_mail,
+					   &adds[i].received_date) < 0) {
+			/* probably expunged already, just add it somewhere */
+			adds[i].received_date = 0;
+		}
+	}
+
+	qsort(adds, count, sizeof(*adds), virtual_add_record_cmp);
+}
+
+static void virtual_sync_backend_add_new(struct virtual_sync_context *ctx)
+{
+	uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id;
+	struct virtual_add_record *adds;
+	struct virtual_backend_box *bbox;
+	struct virtual_backend_uidmap *uidmap;
+	const struct virtual_mail_index_record *vrec;
+	unsigned int i, count, idx, uid_count;
+	uint32_t vseq, first_uid, next_uid;
+
+	adds = array_get_modifiable(&ctx->all_adds, &count);
+	if (count == 0)
+		return;
+
+	if (adds[0].rec.mailbox_id == adds[count-1].rec.mailbox_id) {
+		/* all messages are from a single mailbox. add them in
+		   the same order. */
+	} else {
+		/* sort new messages by received date to get the add order */
+		virtual_sync_backend_sort_new(ctx);
+	}
+
+	for (bbox = NULL, i = 0; i < count; i++) {
+		vrec = &adds[i].rec;
+		if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+			bbox = virtual_backend_box_lookup(ctx->mbox,
+							  vrec->mailbox_id);
+		}
+
+		mail_index_append(ctx->trans, 0, &vseq);
+		mail_index_update_ext(ctx->trans, vseq, virtual_ext_id,
+				      vrec, NULL);
+		virtual_sync_external_flags(ctx, bbox, vseq, vrec->real_uid);
+	}
+
+	/* assign UIDs to new messages */
+	first_uid = mail_index_get_header(ctx->sync_view)->next_uid;
+	mail_index_append_assign_uids(ctx->trans, first_uid, &next_uid);
+
+	/* update virtual UIDs in uidmap */
+	for (bbox = NULL, i = 0; i < count; i++) {
+		vrec = &adds[i].rec;
+		if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) {
+			bbox = virtual_backend_box_lookup(ctx->mbox,
+							  vrec->mailbox_id);
+		}
+
+		uidmap = array_get_modifiable(&bbox->uids, &uid_count);
+		if (!bsearch_insert_pos(&vrec->real_uid, uidmap, uid_count,
+					sizeof(*uidmap),
+					virtual_backend_uidmap_cmp, &idx))
+			i_unreached();
+		i_assert(uidmap[idx].virtual_uid == 0);
+		uidmap[idx].virtual_uid = first_uid + i;
+	}
+}
+
 static int virtual_sync_backend_boxes(struct virtual_sync_context *ctx)
 {
 	struct virtual_backend_box *const *bboxes;
-	struct mailbox_transaction_context *trans;
 	unsigned int i, count;
 
+	i_array_init(&ctx->all_adds, 128);
 	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
 	for (i = 0; i < count; i++) {
 		if (virtual_sync_backend_box(ctx, bboxes[i]) < 0)
 			return -1;
-
-		bboxes[i]->sync_iter_idx = 0;
-		bboxes[i]->sync_iter_prev_real_uid = 0;
+	}
 
-		i_assert(bboxes[i]->sync_mail == NULL);
-		trans = mailbox_transaction_begin(bboxes[i]->box, 0);
-		bboxes[i]->sync_mail = mail_alloc(trans, 0, NULL);
+	if (!ctx->mbox->uids_mapped) {
+		/* initial sync: assign virtual UIDs to existing messages and
+		   sync all flags */
+		ctx->mbox->uids_mapped = TRUE;
+		virtual_sync_backend_map_uids(ctx);
 	}
+	virtual_sync_backend_add_new(ctx);
+	array_free(&ctx->all_adds);
 	return 0;
 }
 
 static void virtual_sync_backend_boxes_finish(struct virtual_sync_context *ctx)
 {
 	struct virtual_backend_box *const *bboxes;
-	struct mailbox_transaction_context *trans;
 	unsigned int i, count;
 
 	bboxes = array_get(&ctx->mbox->backend_boxes, &count);
-	for (i = 0; i < count; i++) {
-		if (bboxes[i]->sync_mail != NULL) {
-			trans = bboxes[i]->sync_mail->transaction;
-			mail_free(&bboxes[i]->sync_mail);
-			(void)mailbox_transaction_commit(&trans);
-		}
-	}
+	for (i = 0; i < count; i++)
+		virtual_backend_box_sync_mail_unset(bboxes[i]);
 }
 
 static int virtual_sync_finish(struct virtual_sync_context *ctx, bool success)
@@ -449,11 +814,11 @@
 	}
 
 	/* update list of UIDs in mailboxes */
+	virtual_sync_index_changes(ctx);
 	if (virtual_sync_backend_boxes(ctx) < 0)
 		return virtual_sync_finish(ctx, FALSE);
 
 	virtual_sync_index(ctx);
-	virtual_sync_external(ctx);
 	return virtual_sync_finish(ctx, TRUE);
 }