Przeglądaj źródła

employ alternative scheme to finding messages by TUID

instead of SEARCHing every single message (which is slow and happens to
be unreliabe with M$ Exchange 2010), just FETCH the new messages from
the mailbox - the ones we just appended will be amongst them.
Oswald Buddenhagen 14 lat temu
rodzic
commit
9c86ec3442
5 zmienionych plików z 183 dodań i 221 usunięć
  1. 50 70
      src/drv_imap.c
  2. 13 19
      src/drv_maildir.c
  3. 12 7
      src/isync.h
  4. 2 2
      src/run-tests.pl
  5. 106 123
      src/sync.c

+ 50 - 70
src/drv_imap.c

@@ -79,7 +79,6 @@ typedef struct imap_store {
 	store_t gen;
 	const char *prefix;
 	int ref_count;
-	int uidnext; /* from SELECT responses */
 	/* trash folder's existence is not confirmed yet */
 	enum { TrashUnknown, TrashChecking, TrashKnown } trashnc;
 	unsigned got_namespace:1;
@@ -602,7 +601,7 @@ static int
 parse_fetch( imap_store_t *ctx, list_t *list )
 {
 	list_t *tmp, *flags;
-	char *body = 0;
+	char *body = 0, *tuid = 0;
 	imap_message_t *cur;
 	msg_data_t *msgdata;
 	struct imap_cmd *cmdp;
@@ -663,6 +662,20 @@ parse_fetch( imap_store_t *ctx, list_t *list )
 					size = tmp->len;
 				} else
 					error( "IMAP error: unable to parse BODY[]\n" );
+			} else if (!strcmp( "BODY[HEADER.FIELDS", tmp->val )) {
+				tmp = tmp->next;
+				if (is_list( tmp )) {
+					tmp = tmp->next;
+					if (!is_atom( tmp ) || strcmp( tmp->val, "]" ))
+						goto bfail;
+					tmp = tmp->next;
+					if (!is_atom( tmp ) || memcmp( tmp->val, "X-TUID: ", 8 ))
+						goto bfail;
+					tuid = tmp->val + 8;
+				} else {
+				  bfail:
+					error( "IMAP error: unable to parse BODY[HEADER.FIELDS ...]\n" );
+				}
 			}
 		}
 	}
@@ -690,6 +703,13 @@ parse_fetch( imap_store_t *ctx, list_t *list )
 		cur->gen.flags = mask;
 		cur->gen.status = status;
 		cur->gen.size = size;
+		cur->gen.srec = 0;
+		if (tuid)
+			strncpy( cur->gen.tuid, tuid, TUIDL );
+		else
+			cur->gen.tuid[0] = 0;
+		if (ctx->gen.uidnext <= uid) /* in case the server sends no UIDNEXT */
+			ctx->gen.uidnext = uid + 1;
 	}
 
 	free_list( list );
@@ -731,7 +751,7 @@ parse_response_code( imap_store_t *ctx, struct imap_cmd *cmd, char *s )
 			return RESP_CANCEL;
 		}
 	} else if (!strcmp( "UIDNEXT", arg )) {
-		if (!(arg = next_arg( &s )) || (ctx->uidnext = strtol( arg, &p, 10 ), *p)) {
+		if (!(arg = next_arg( &s )) || (ctx->gen.uidnext = strtol( arg, &p, 10 ), *p)) {
 			error( "IMAP error: malformed NEXTUID status\n" );
 			return RESP_CANCEL;
 		}
@@ -756,35 +776,6 @@ parse_response_code( imap_store_t *ctx, struct imap_cmd *cmd, char *s )
 	return RESP_OK;
 }
 
-static void
-parse_search( imap_store_t *ctx, char *cmd )
-{
-	char *arg;
-	struct imap_cmd *cmdp;
-	int uid;
-
-	if (!(arg = next_arg( &cmd )))
-		uid = -1;
-	else if (!(uid = atoi( arg ))) {
-		error( "IMAP error: malformed SEARCH response\n" );
-		return;
-	} else if (next_arg( &cmd )) {
-		warn( "IMAP warning: SEARCH returns multiple matches\n" );
-		uid = -1; /* to avoid havoc */
-	}
-
-	/* Find the first command that expects a UID - this is guaranteed
-	 * to come in-order, as there are no other means to identify which
-	 * SEARCH response belongs to which request.
-	 */
-	for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next)
-		if (cmdp->param.uid == -1) {
-			((struct imap_cmd_out_uid *)cmdp)->out_uid = uid;
-			return;
-		}
-	error( "IMAP error: unexpected SEARCH response (UID %u)\n", uid );
-}
-
 static void
 parse_list_rsp( imap_store_t *ctx, char *cmd )
 {
@@ -861,8 +852,6 @@ imap_socket_read( void *aux )
 				parse_capability( ctx, cmd );
 			else if (!strcmp( "LIST", arg ))
 				parse_list_rsp( ctx, cmd );
-			else if (!strcmp( "SEARCH", arg ))
-				parse_search( ctx, cmd );
 			else if ((arg1 = next_arg( &cmd ))) {
 				if (!strcmp( "EXISTS", arg1 ))
 					ctx->gen.count = atoi( arg );
@@ -980,7 +969,7 @@ get_cmd_result_p2( imap_store_t *ctx, struct imap_cmd *cmd, int response )
 	if (response != RESP_OK) {
 		done_imap_cmd( ctx, ocmd, response );
 	} else {
-		ctx->uidnext = 0;
+		ctx->gen.uidnext = 0;
 		if (ocmd->param.to_trash)
 			ctx->trashnc = TrashKnown;
 		ocmd->param.create = 0;
@@ -1447,7 +1436,7 @@ imap_select( store_t *gctx, int create,
 		prefix = ctx->prefix;
 	}
 
-	ctx->uidnext = -1;
+	ctx->gen.uidnext = -1;
 
 	INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux)
 	cmd->gen.param.create = create;
@@ -1458,11 +1447,11 @@ imap_select( store_t *gctx, int create,
 
 /******************* imap_load *******************/
 
-static int imap_submit_load( imap_store_t *, const char *, struct imap_cmd_refcounted_state * );
+static int imap_submit_load( imap_store_t *, const char *, int, struct imap_cmd_refcounted_state * );
 static void imap_load_p2( imap_store_t *, struct imap_cmd *, int );
 
 static void
-imap_load( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
+imap_load( store_t *gctx, int minuid, int maxuid, int newuid, int *excs, int nexcs,
            void (*cb)( int sts, void *aux ), void *aux )
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
@@ -1487,14 +1476,21 @@ imap_load( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
 				if (i != j)
 					bl += sprintf( buf + bl, ":%d", excs[i] );
 			}
-			if (imap_submit_load( ctx, buf, sts ) < 0)
+			if (imap_submit_load( ctx, buf, 0, sts ) < 0)
 				goto done;
 		}
 		if (maxuid == INT_MAX)
-			maxuid = ctx->uidnext >= 0 ? ctx->uidnext - 1 : 1000000000;
+			maxuid = ctx->gen.uidnext >= 0 ? ctx->gen.uidnext - 1 : 1000000000;
 		if (maxuid >= minuid) {
-			sprintf( buf, "%d:%d", minuid, maxuid );
-			imap_submit_load( ctx, buf, sts );
+			if ((ctx->gen.opts & OPEN_FIND) && minuid < newuid) {
+				sprintf( buf, "%d:%d", minuid, newuid - 1 );
+				if (imap_submit_load( ctx, buf, 0, sts ) < 0)
+					goto done;
+				sprintf( buf, "%d:%d", newuid, maxuid );
+			} else {
+				sprintf( buf, "%d:%d", minuid, maxuid );
+			}
+			imap_submit_load( ctx, buf, (ctx->gen.opts & OPEN_FIND), sts );
 		}
 	  done:
 		free( excs );
@@ -1503,12 +1499,13 @@ imap_load( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
 }
 
 static int
-imap_submit_load( imap_store_t *ctx, const char *buf, struct imap_cmd_refcounted_state *sts )
+imap_submit_load( imap_store_t *ctx, const char *buf, int tuids, struct imap_cmd_refcounted_state *sts )
 {
 	return imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_load_p2,
-	                  "UID FETCH %s (UID%s%s)", buf,
+	                  "UID FETCH %s (UID%s%s%s)", buf,
 	                  (ctx->gen.opts & OPEN_FLAGS) ? " FLAGS" : "",
-	                  (ctx->gen.opts & OPEN_SIZE) ? " RFC822.SIZE" : "" );
+	                  (ctx->gen.opts & OPEN_SIZE) ? " RFC822.SIZE" : "",
+	                  tuids ? " BODY.PEEK[HEADER.FIELDS (X-TUID)]" : "");
 }
 
 static void
@@ -1693,35 +1690,18 @@ imap_store_msg_p2( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, int resp
 	cmdp->callback( response, cmdp->out_uid, cmdp->callback_aux );
 }
 
-/******************* imap_find_msg *******************/
-
-static void imap_find_msg_p2( imap_store_t *, struct imap_cmd *, int );
+/******************* imap_find_new_msgs *******************/
 
 static void
-imap_find_msg( store_t *gctx, const char *tuid,
-               void (*cb)( int sts, int uid, void *aux ), void *aux )
+imap_find_new_msgs( store_t *gctx,
+                    void (*cb)( int sts, void *aux ), void *aux )
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
-	struct imap_cmd_out_uid *cmd;
-
-	INIT_IMAP_CMD(imap_cmd_out_uid, cmd, cb, aux)
-	cmd->gen.param.uid = -1; /* we're looking for a UID */
-	cmd->out_uid = -1; /* in case we get no SEARCH response at all */
-	imap_exec( ctx, &cmd->gen, imap_find_msg_p2,
-	           "UID SEARCH HEADER X-TUID %." stringify(TUIDL) "s", tuid );
-}
-
-static void
-imap_find_msg_p2( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, int response )
-{
-	struct imap_cmd_out_uid *cmdp = (struct imap_cmd_out_uid *)cmd;
+	struct imap_cmd_simple *cmd;
 
-	transform_msg_response( &response );
-	if (response != DRV_OK)
-		cmdp->callback( response, -1, cmdp->callback_aux );
-	else
-		cmdp->callback( cmdp->out_uid <= 0 ? DRV_MSG_BAD : DRV_OK,
-		                cmdp->out_uid, cmdp->callback_aux );
+	INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux)
+	imap_exec( (imap_store_t *)ctx, &cmd->gen, imap_done_simple_box,
+	           "UID FETCH %d:1000000000 (UID BODY.PEEK[HEADER.FIELDS (X-TUID)])", ctx->gen.uidnext );
 }
 
 /******************* imap_list *******************/
@@ -1917,7 +1897,7 @@ struct driver imap_driver = {
 	imap_load,
 	imap_fetch_msg,
 	imap_store_msg,
-	imap_find_msg,
+	imap_find_new_msgs,
 	imap_set_flags,
 	imap_trash_msg,
 	imap_close,

+ 13 - 19
src/drv_maildir.c

@@ -24,6 +24,7 @@
 
 #include "isync.h"
 
+#include <assert.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
@@ -57,13 +58,12 @@ typedef struct maildir_store_conf {
 typedef struct maildir_message {
 	message_t gen;
 	char *base;
-	char tuid[TUIDL];
 } maildir_message_t;
 
 typedef struct maildir_store {
 	store_t gen;
 	int uvfd, uvok, nuid;
-	int minuid, maxuid, nexcs, *excs;
+	int minuid, maxuid, newuid, nexcs, *excs;
 #ifdef USE_DB
 	DB *db;
 #endif /* USE_DB */
@@ -642,7 +642,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
 					goto again;
 				}
 				uid = entry->uid;
-				if (ctx->gen.opts & (OPEN_SIZE|OPEN_FIND))
+				if ((ctx->gen.opts & OPEN_SIZE) || ((ctx->gen.opts & OPEN_FIND) && uid >= ctx->newuid))
 					nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s", subdirs[entry->recent], entry->base );
 #ifdef USE_DB
 			} else if (ctx->db) {
@@ -651,7 +651,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
 					return ret;
 				}
 				entry->uid = uid;
-				if (ctx->gen.opts & (OPEN_SIZE|OPEN_FIND))
+				if ((ctx->gen.opts & OPEN_SIZE) || ((ctx->gen.opts & OPEN_FIND) && uid >= ctx->newuid))
 					nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s", subdirs[entry->recent], entry->base );
 #endif /* USE_DB */
 			} else {
@@ -696,7 +696,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
 				}
 				entry->size = st.st_size;
 			}
-			if (ctx->gen.opts & OPEN_FIND) {
+			if ((ctx->gen.opts & OPEN_FIND) && uid >= ctx->newuid) {
 				if (!(f = fopen( buf, "r" ))) {
 					if (errno != ENOENT) {
 						sys_error( "Maildir error: cannot open %s", buf );
@@ -730,7 +730,8 @@ maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry )
 	msg->base = entry->base;
 	entry->base = 0; /* prevent deletion */
 	msg->gen.size = entry->size;
-	strncpy( msg->tuid, entry->tuid, TUIDL );
+	msg->gen.srec = 0;
+	strncpy( msg->gen.tuid, entry->tuid, TUIDL );
 	if (entry->recent)
 		msg->gen.status |= M_RECENT;
 	if (ctx->gen.opts & OPEN_FLAGS) {
@@ -861,7 +862,7 @@ maildir_prepare_opts( store_t *gctx, int opts )
 }
 
 static void
-maildir_load( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
+maildir_load( store_t *gctx, int minuid, int maxuid, int newuid, int *excs, int nexcs,
               void (*cb)( int sts, void *aux ), void *aux )
 {
 	maildir_store_t *ctx = (maildir_store_t *)gctx;
@@ -871,6 +872,7 @@ maildir_load( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
 
 	ctx->minuid = minuid;
 	ctx->maxuid = maxuid;
+	ctx->newuid = newuid;
 	ctx->excs = nfrealloc( excs, nexcs * sizeof(int) );
 	ctx->nexcs = nexcs;
 
@@ -1086,18 +1088,10 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
 }
 
 static void
-maildir_find_msg( store_t *gctx, const char *tuid,
-                  void (*cb)( int sts, int uid, void *aux ), void *aux )
+maildir_find_new_msgs( store_t *gctx ATTR_UNUSED,
+                       void (*cb)( int sts, void *aux ) ATTR_UNUSED, void *aux ATTR_UNUSED )
 {
-	message_t *msg;
-
-	/* using a hash table might turn out to be more appropriate ... */
-	for (msg = gctx->msgs; msg; msg = msg->next)
-		if (!(msg->status & M_DEAD) && !memcmp( ((maildir_message_t *)msg)->tuid, tuid, TUIDL )) {
-			cb( DRV_OK, msg->uid, aux );
-			return;
-		}
-	cb( DRV_MSG_BAD, -1, aux );
+	assert( !"maildir_find_new_msgs is not supposed to be called" );
 }
 
 static void
@@ -1324,7 +1318,7 @@ struct driver maildir_driver = {
 	maildir_load,
 	maildir_fetch_msg,
 	maildir_store_msg,
-	maildir_find_msg,
+	maildir_find_new_msgs,
 	maildir_set_flags,
 	maildir_trash_msg,
 	maildir_close,

+ 12 - 7
src/isync.h

@@ -184,6 +184,8 @@ typedef struct group_conf {
 #define M_DEAD         (1<<1) /* expunged */
 #define M_FLAGS        (1<<2) /* flags fetched */
 
+#define TUIDL 12
+
 typedef struct message {
 	struct message *next;
 	struct sync_rec *srec;
@@ -191,6 +193,7 @@ typedef struct message {
 	size_t size; /* zero implies "not fetched" */
 	int uid;
 	unsigned char flags, status;
+	char tuid[TUIDL];
 } message_t;
 
 /* For opts, both in store and driver_t->select() */
@@ -217,6 +220,7 @@ typedef struct store {
 	char *path; /* own */
 	message_t *msgs; /* own */
 	int uidvalidity;
+	int uidnext; /* from SELECT responses */
 	unsigned opts; /* maybe preset? */
 	/* note that the following do _not_ reflect stats from msgs, but mailbox totals */
 	int count; /* # of messages */
@@ -255,8 +259,6 @@ typedef struct {
 */
 #define DRV_CRLF        1
 
-#define TUIDL 12
-
 struct driver {
 	int flags;
 
@@ -298,8 +300,9 @@ struct driver {
 	/* Load the message attributes needed to perform the requested operations.
 	 * Consider only messages with UIDs between minuid and maxuid (inclusive)
 	 * and those named in the excs array (smaller than minuid).
-	 * The driver takes ownership of the excs array. */
-	void (*load)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs,
+	 * The driver takes ownership of the excs array. Messages below newuid do not need
+	 * to have the TUID populated even if OPEN_FIND is set. */
+	void (*load)( store_t *ctx, int minuid, int maxuid, int newuid, int *excs, int nexcs,
 	              void (*cb)( int sts, void *aux ), void *aux );
 
 	/* Fetch the contents and flags of the given message from the current mailbox. */
@@ -311,9 +314,11 @@ struct driver {
 	void (*store_msg)( store_t *ctx, msg_data_t *data, int to_trash,
 	                   void (*cb)( int sts, int uid, void *aux ), void *aux );
 
-	/* Find a message by its temporary UID header to determine its real UID. */
-	void (*find_msg)( store_t *ctx, const char *tuid,
-	                  void (*cb)( int sts, int uid, void *aux ), void *aux );
+	/* Index the messages which have newly appeared in the mailbox, including their
+	 * temporary UID headers. This is needed if store_msg() does not guarantee returning
+	 * a UID; otherwise the driver needs to implement only the OPEN_FIND flag. */
+	void (*find_new_msgs)( store_t *ctx,
+	                       void (*cb)( int sts, void *aux ), void *aux );
 
 	/* Add/remove the named flags to/from the given message. The message may be either
 	 * a pre-fetched one (in which case the in-memory representation is updated),

+ 2 - 2
src/run-tests.pl

@@ -366,7 +366,7 @@ sub showstate($)
 		close FILE;
 		return;
 	}
-	if (!/^1:(\d+) 1:(\d+):(\d+)\n$/) {
+	if (!/^1:(\d+):0 1:(\d+):(\d+):0\n$/) {
 		print STDERR " Malformed sync state header '$_'.\n";
 		close FILE;
 		return;
@@ -507,7 +507,7 @@ sub ckstate($@)
 		return 1;
 	}
 	chomp($l);
-	my $xl = "1:".shift(@T)." 1:".shift(@T).":".shift(@T);
+	my $xl = "1:".shift(@T).":0 1:".shift(@T).":".shift(@T).":0";
 	if ($l ne $xl) {
 		print STDERR "Sync state header mismatch: '$l' instead of '$xl'.\n";
 		return 1;

+ 106 - 123
src/sync.c

@@ -96,6 +96,7 @@ make_flags( int flags, char *buf )
 #define S_EXPIRE       (1<<5)
 #define S_NEXPIRE      (1<<6)
 #define S_EXP_S        (1<<7)
+#define S_FIND         (1<<8)
 
 #define mvBit(in,ib,ob) ((unsigned char)(((unsigned)in) * (ob) / (ib)))
 
@@ -146,15 +147,13 @@ typedef struct {
 	store_t *ctx[2];
 	driver_t *drv[2];
 	int state[2], ref_count, ret, lfd;
-	int find_old_total[2], find_old_done[2];
 	int new_total[2], new_done[2];
-	int find_new_total[2], find_new_done[2];
 	int flags_total[2], flags_done[2];
 	int trash_total[2], trash_done[2];
 	int maxuid[2]; /* highest UID that was already propagated */
 	int uidval[2]; /* UID validity value */
+	int uidnext[2]; /* next expected UID; TUID lookup makes sense only for lower UIDs */
 	int smaxxuid; /* highest expired UID on slave */
-	unsigned find:1;
 } sync_vars_t;
 
 static void sync_ref( sync_vars_t *svars ) { ++svars->ref_count; }
@@ -190,10 +189,8 @@ static int check_cancel( sync_vars_t *svars );
 
 /* operation dependencies:
    select(S): -
-   find_old(S): select(S)
-   select(M): find_old(S) | -
-   find_old(M): select(M)
-   new(M), new(S), flags(M): find_old(M) & find_old(S)
+   select(M): select(S) | -
+   new(M), new(S), flags(M): select(M) & select(S)
    flags(S): count(new(S))
    find_new(x): new(x)
    trash(x): flags(x)
@@ -201,9 +198,9 @@ static int check_cancel( sync_vars_t *svars );
    cleanup: close(M) & close(S)
 */
 
-#define ST_SENT_FIND_OLD   (1<<0)
+#define ST_LOADED          (1<<0)
 #define ST_SENT_NEW        (1<<1)
-#define ST_SENT_FIND_NEW   (1<<2)
+#define ST_FOUND_NEW       (1<<2)
 #define ST_SENT_FLAGS      (1<<3)
 #define ST_SENT_TRASH      (1<<4)
 #define ST_CLOSED          (1<<5)
@@ -214,6 +211,51 @@ static int check_cancel( sync_vars_t *svars );
 #define ST_DID_EXPUNGE     (1<<16)
 
 
+static void
+match_tuids( sync_vars_t *svars, int t )
+{
+	sync_rec_t *srec;
+	message_t *tmsg, *ntmsg = 0;
+	const char *diag;
+
+	for (srec = svars->srecs; srec; srec = srec->next) {
+		if (srec->status & S_DEAD)
+			continue;
+		if (srec->uid[t] == -2 && srec->tuid[0]) {
+			debug( "  pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid );
+			for (tmsg = ntmsg; tmsg; tmsg = tmsg->next) {
+				if (tmsg->status & M_DEAD)
+					continue;
+				if (tmsg->tuid[0] && !memcmp( tmsg->tuid, srec->tuid, TUIDL )) {
+					diag = (tmsg == ntmsg) ? "adjacently" : "after gap";
+					goto mfound;
+				}
+			}
+			for (tmsg = svars->ctx[t]->msgs; tmsg != ntmsg; tmsg = tmsg->next) {
+				if (tmsg->status & M_DEAD)
+					continue;
+				if (tmsg->tuid[0] && !memcmp( tmsg->tuid, srec->tuid, TUIDL )) {
+					diag = "after reset";
+					goto mfound;
+				}
+			}
+			debug( "  -> TUID lost\n" );
+			Fprintf( svars->jfp, "& %d %d\n", srec->uid[M], srec->uid[S] );
+			srec->flags = 0;
+			srec->tuid[0] = 0;
+			continue;
+		  mfound:
+			debug( "  -> new UID %d %s\n", tmsg->uid, diag );
+			Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], srec->uid[M], srec->uid[S], tmsg->uid );
+			tmsg->srec = srec;
+			ntmsg = tmsg->next;
+			srec->uid[t] = tmsg->uid;
+			srec->tuid[0] = 0;
+		}
+	}
+}
+
+
 typedef struct copy_vars {
 	void (*cb)( int sts, int uid, struct copy_vars *vars );
 	void *aux;
@@ -410,9 +452,7 @@ stats( sync_vars_t *svars )
 		cols = 36;
 	if (!(DFlags & QUIET)) {
 		for (t = 0; t < 2; t++) {
-			l = sprintf( buf[t], "?%d/%d +%d/%d *%d/%d #%d/%d",
-			             svars->find_old_done[t] + svars->find_new_done[t],
-			             svars->find_old_total[t] + svars->find_new_total[t],
+			l = sprintf( buf[t], "+%d/%d *%d/%d #%d/%d",
 			             svars->new_done[t], svars->new_total[t],
 			             svars->flags_done[t], svars->flags_total[t],
 			             svars->trash_done[t], svars->trash_total[t] );
@@ -591,7 +631,7 @@ box_selected( int sts, void *aux )
 	struct stat st;
 	struct flock lck;
 	char fbuf[16]; /* enlarge when support for keywords is added */
-	char buf[64];
+	char buf[128], buf1[64], buf2[64];
 
 	if (check_ret( sts, aux ))
 		return;
@@ -668,7 +708,9 @@ box_selected( int sts, void *aux )
 			sync_bail( svars );
 			return;
 		}
-		if (sscanf( buf, "%d:%d %d:%d:%d", &svars->uidval[M], &svars->maxuid[M], &svars->uidval[S], &svars->smaxxuid, &svars->maxuid[S]) != 5) {
+		if (sscanf( buf, "%63s %63s", buf1, buf2 ) != 2 ||
+		    sscanf( buf1, "%d:%d:%d", &svars->uidval[M], &svars->maxuid[M], &svars->uidnext[M] ) < 2 ||
+		    sscanf( buf2, "%d:%d:%d:%d", &svars->uidval[S], &svars->smaxxuid, &svars->maxuid[S], &svars->uidnext[S] ) < 3) {
 			error( "Error: invalid sync state header in %s\n", svars->dname );
 			goto jbail;
 		}
@@ -731,7 +773,7 @@ box_selected( int sts, void *aux )
 				}
 				if (buf[0] == '#' ?
 				      (t3 = 0, (sscanf( buf + 2, "%d %d %n", &t1, &t2, &t3 ) < 2) || !t3 || (t - t3 != TUIDL + 3)) :
-				      buf[0] == '(' || buf[0] == ')' ?
+				      buf[0] == '(' || buf[0] == ')' || buf[0] == '{' || buf[0] == '}' ?
 				        (sscanf( buf + 2, "%d", &t1 ) != 1) :
 				        buf[0] == '+' || buf[0] == '&' || buf[0] == '-' || buf[0] == '|' || buf[0] == '/' || buf[0] == '\\' ?
 				          (sscanf( buf + 2, "%d %d", &t1, &t2 ) != 2) :
@@ -744,6 +786,10 @@ box_selected( int sts, void *aux )
 					svars->maxuid[M] = t1;
 				else if (buf[0] == ')')
 					svars->maxuid[S] = t1;
+				else if (buf[0] == '{')
+					svars->uidnext[M] = t1;
+				else if (buf[0] == '}')
+					svars->uidnext[S] = t1;
 				else if (buf[0] == '|') {
 					svars->uidval[M] = t1;
 					svars->uidval[S] = t2;
@@ -900,15 +946,14 @@ box_selected( int sts, void *aux )
 				opts[S] |= OPEN_OLD|OPEN_FLAGS;
 			if (srec->tuid[0]) {
 				if (srec->uid[M] == -2)
-					opts[M] |= OPEN_OLD|OPEN_FIND;
+					opts[M] |= OPEN_NEW|OPEN_FIND, svars->state[M] |= S_FIND;
 				else if (srec->uid[S] == -2)
-					opts[S] |= OPEN_OLD|OPEN_FIND;
+					opts[S] |= OPEN_NEW|OPEN_FIND, svars->state[S] |= S_FIND;
 			}
 		}
 	svars->drv[M]->prepare_opts( ctx[M], opts[M] );
 	svars->drv[S]->prepare_opts( ctx[S], opts[S] );
 
-	svars->find = line != 0;
 	if (!svars->smaxxuid && load_box( svars, M, (ctx[M]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 ))
 		return;
 	load_box( svars, S, (ctx[S]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 );
@@ -935,75 +980,7 @@ load_box( sync_vars_t *svars, int t, int minwuid, int *mexcs, int nmexcs )
 		maxwuid = 0;
 	info( "Loading %s...\n", str_ms[t] );
 	debug( maxwuid == INT_MAX ? "loading %s [%d,inf]\n" : "loading %s [%d,%d]\n", str_ms[t], minwuid, maxwuid );
-	DRIVER_CALL_RET(load( svars->ctx[t], minwuid, maxwuid, mexcs, nmexcs, box_loaded, AUX ));
-}
-
-typedef struct {
-	void *aux;
-	sync_rec_t *srec;
-} find_vars_t;
-
-static void msg_found_sel( int sts, int uid, void *aux );
-static void msgs_found_sel( sync_vars_t *svars, int t );
-
-static void
-box_loaded( int sts, void *aux )
-{
-	find_vars_t *fv;
-	sync_rec_t *srec;
-
-	SVARS_CHECK_RET;
-	info( "%s: %d messages, %d recent\n", str_ms[t], svars->ctx[t]->count, svars->ctx[t]->recent );
-
-	if (svars->find) {
-		/*
-		 * Alternatively, the TUIDs could be fetched into the messages and
-		 * looked up here. This would make the search faster (probably) and
-		 * save roundtrips. On the downside, quite some additional data would
-		 * have to be fetched for every message and the IMAP driver would be
-		 * more complicated. This is a corner case anyway, so why bother.
-		 */
-		debug( "finding previously copied messages\n" );
-		for (srec = svars->srecs; srec; srec = srec->next) {
-			if (srec->status & S_DEAD)
-				continue;
-			if (srec->uid[t] == -2 && srec->tuid[0]) {
-				debug( "  pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid );
-				svars->find_old_total[t]++;
-				stats( svars );
-				fv = nfmalloc( sizeof(*fv) );
-				fv->aux = AUX;
-				fv->srec = srec;
-				DRIVER_CALL(find_msg( svars->ctx[t], srec->tuid, msg_found_sel, fv ));
-			}
-		}
-	}
-	svars->state[t] |= ST_SENT_FIND_OLD;
-	msgs_found_sel( svars, t );
-}
-
-static void
-msg_found_sel( int sts, int uid, void *aux )
-{
-	SVARS_CHECK_RET_VARS(find_vars_t);
-	switch (sts) {
-	case DRV_OK:
-		debug( "  -> new UID %d\n", uid );
-		Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], vars->srec->uid[M], vars->srec->uid[S], uid );
-		vars->srec->uid[t] = uid;
-		vars->srec->tuid[0] = 0;
-		break;
-	default:
-		debug( "  -> TUID lost\n" );
-		Fprintf( svars->jfp, "& %d %d\n", vars->srec->uid[M], vars->srec->uid[S] );
-		vars->srec->flags = 0;
-		vars->srec->tuid[0] = 0;
-		break;
-	}
-	free( vars );
-	svars->find_old_done[t]++;
-	stats( svars );
-	msgs_found_sel( svars, t );
+	DRIVER_CALL_RET(load( svars->ctx[t], minwuid, maxwuid, svars->uidnext[t], mexcs, nmexcs, box_loaded, AUX ));
 }
 
 typedef struct {
@@ -1021,8 +998,9 @@ static void msg_copied_p2( sync_vars_t *svars, sync_rec_t *srec, int t, message_
 static void msgs_copied( sync_vars_t *svars, int t );
 
 static void
-msgs_found_sel( sync_vars_t *svars, int t )
+box_loaded( int sts, void *aux )
 {
+	DECL_SVARS;
 	sync_rec_t *srec, *nsrec = 0;
 	message_t *tmsg;
 	copy_vars_t *cv;
@@ -1032,8 +1010,18 @@ msgs_found_sel( sync_vars_t *svars, int t )
 	int sflags, nflags, aflags, dflags, nex;
 	char fbuf[16]; /* enlarge when support for keywords is added */
 
-	if (!(svars->state[t] & ST_SENT_FIND_OLD) || svars->find_old_done[t] < svars->find_old_total[t])
+	if (check_ret( sts, aux ))
 		return;
+	INIT_SVARS(aux);
+	svars->state[t] |= ST_LOADED;
+	info( "%s: %d messages, %d recent\n", str_ms[t], svars->ctx[t]->count, svars->ctx[t]->recent );
+
+	if (svars->state[t] & S_FIND) {
+		svars->state[t] &= ~S_FIND;
+		debug( "matching previously copied messages on %s\n", str_ms[t] );
+		match_tuids( svars, t );
+	}
+	Fprintf( svars->jfp, "%c %d\n", "{}"[t], svars->ctx[t]->uidnext );
 
 	/*
 	 * Mapping tmsg -> srec (this variant) is dog slow for new messages.
@@ -1043,6 +1031,8 @@ msgs_found_sel( sync_vars_t *svars, int t )
 	 */
 	debug( "matching messages on %s against sync records\n", str_ms[t] );
 	for (tmsg = svars->ctx[t]->msgs; tmsg; tmsg = tmsg->next) {
+		if (tmsg->srec) /* found by TUID */
+			continue;
 		uid = tmsg->uid;
 		if (DFlags & DEBUG) {
 			make_flags( tmsg->flags, fbuf );
@@ -1133,7 +1123,7 @@ msgs_found_sel( sync_vars_t *svars, int t )
 		return;
 	}
 
-	if (!(svars->state[1-t] & ST_SENT_FIND_OLD) || svars->find_old_done[1-t] < svars->find_old_total[1-t])
+	if (!(svars->state[1-t] & ST_LOADED))
 		return;
 
 	if (svars->uidval[M] < 0 || svars->uidval[S] < 0) {
@@ -1369,6 +1359,8 @@ msg_copied( int sts, int uid, copy_vars_t *vars )
 	SVARS_CHECK_CANCEL_RET;
 	switch (sts) {
 	case SYNC_OK:
+		if (uid < 0)
+			svars->state[t] |= S_FIND;
 		msg_copied_p2( svars, vars->srec, t, vars->msg, uid );
 		break;
 	case SYNC_NOGOOD:
@@ -1405,55 +1397,45 @@ msg_copied_p2( sync_vars_t *svars, sync_rec_t *srec, int t, message_t *tmsg, int
 	}
 }
 
-static void msg_found_new( int sts, int uid, void *aux );
+static void msgs_found_new( int sts, void *aux );
+static void msgs_new_done( sync_vars_t *svars, int t );
 static void sync_close( sync_vars_t *svars, int t );
 
 static void
 msgs_copied( sync_vars_t *svars, int t )
 {
-	sync_rec_t *srec;
-	find_vars_t *fv;
-
 	if (!(svars->state[t] & ST_SENT_NEW) || svars->new_done[t] < svars->new_total[t])
 		return;
 
-	debug( "finding just copied messages on %s\n", str_ms[t] );
-	for (srec = svars->srecs; srec; srec = srec->next) {
-		if (srec->status & S_DEAD)
-			continue;
-		if (srec->tuid[0] && srec->uid[t] == -2) {
-			debug( "  pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid );
-			svars->find_new_total[t]++;
-			stats( svars );
-			fv = nfmalloc( sizeof(*fv) );
-			fv->aux = AUX;
-			fv->srec = srec;
-			DRIVER_CALL(find_msg( svars->ctx[t], srec->tuid, msg_found_new, fv ));
-		}
+	if (svars->state[t] & S_FIND) {
+		debug( "finding just copied messages on %s\n", str_ms[t] );
+		svars->drv[t]->find_new_msgs( svars->ctx[t], msgs_found_new, AUX );
+	} else {
+		msgs_new_done( svars, t );
 	}
-	svars->state[t] |= ST_SENT_FIND_NEW;
-	sync_close( svars, t );
 }
 
 static void
-msg_found_new( int sts, int uid, void *aux )
+msgs_found_new( int sts, void *aux )
 {
-	SVARS_CHECK_RET_VARS(find_vars_t);
+	SVARS_CHECK_RET;
 	switch (sts) {
 	case DRV_OK:
-		debug( "  -> new UID %d\n", uid );
+		debug( "matching just copied messages on %s\n", str_ms[t] );
 		break;
 	default:
-		warn( "Warning: cannot find newly stored message %." stringify(TUIDL) "s on %s.\n", vars->srec->tuid, str_ms[t] );
-		uid = 0;
+		warn( "Warning: cannot find newly stored messages on %s.\n", str_ms[t] );
 		break;
 	}
-	Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], vars->srec->uid[M], vars->srec->uid[S], uid );
-	vars->srec->uid[t] = uid;
-	vars->srec->tuid[0] = 0;
-	free( vars );
-	svars->find_new_done[t]++;
-	stats( svars );
+	match_tuids( svars, t );
+	msgs_new_done( svars, t );
+}
+
+static void
+msgs_new_done( sync_vars_t *svars, int t )
+{
+	Fprintf( svars->jfp, "%c %d\n", "{}"[t], svars->ctx[t]->uidnext );
+	svars->state[t] |= ST_FOUND_NEW;
 	sync_close( svars, t );
 }
 
@@ -1614,8 +1596,7 @@ static void box_closed_p2( sync_vars_t *svars, int t );
 static void
 sync_close( sync_vars_t *svars, int t )
 {
-	if ((~svars->state[t] & (ST_SENT_FIND_NEW|ST_SENT_TRASH)) ||
-	    svars->find_new_done[t] < svars->find_new_total[t] ||
+	if ((~svars->state[t] & (ST_FOUND_NEW|ST_SENT_TRASH)) ||
 	    svars->trash_done[t] < svars->trash_total[t])
 		return;
 
@@ -1686,7 +1667,9 @@ box_closed_p2( sync_vars_t *svars, int t )
 		}
 	}
 
-	Fprintf( svars->nfp, "%d:%d %d:%d:%d\n", svars->uidval[M], svars->maxuid[M], svars->uidval[S], svars->smaxxuid, svars->maxuid[S] );
+	Fprintf( svars->nfp, "%d:%d:%d %d:%d:%d:%d\n",
+	         svars->uidval[M], svars->maxuid[M], svars->ctx[M]->uidnext,
+	         svars->uidval[S], svars->smaxxuid, svars->maxuid[S], svars->ctx[S]->uidnext );
 	for (srec = svars->srecs; srec; srec = srec->next) {
 		if (srec->status & S_DEAD)
 			continue;