commit 767a318eea666a3af4881e3f351dcdabdf2df216
Author: Oswald Buddenhagen <o...@users.sf.net>
Date:   Sat Jun 18 12:52:35 2022 +0200

    add new sync operation 'Old'
    
    this is essentially the same as 'New', but for previously seen messages,
    such as those that would have been instantly expunged (because they were
    marked as deleted), those that we failed to store for some reason, and
    already expired ones that are now flagged.
    
    REFMAIL: caogbznont0s0b_yps2vx81ru3cqp5m93xpz3sywbw-2cnox...@mail.gmail.com

 NEWS              |   2 +
 src/config.c      |  16 +++-
 src/driver.h      |   1 +
 src/drv_imap.c    |   8 +-
 src/drv_maildir.c |   3 +-
 src/main.c        |   5 +
 src/main_sync.c   |   2 +-
 src/mbsync.1      |  26 ++---
 src/run-tests.pl  | 240 +++++++++++++++++++++++++++++++++++++++++++++-
 src/sync.c        | 121 ++++++++++++++++-------
 src/sync.h        |   4 +-
 11 files changed, 372 insertions(+), 56 deletions(-)

diff --git a/NEWS b/NEWS
index 1a70e527..3b9ef74f 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,8 @@ The unfiltered list of mailboxes in each Store can be printed 
now.
 
 A proper summary is now printed prior to exiting.
 
+Added new sync operation 'Old'.
+
 [1.4.0]
 
 The 'isync' compatibility wrapper was removed.
diff --git a/src/config.c b/src/config.c
index 9cef416c..240c4cd9 100644
--- a/src/config.c
+++ b/src/config.c
@@ -198,6 +198,8 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t 
*conf )
                                *cops |= OP_UPGRADE;
                        } else if (!strcasecmp( "New", arg )) {
                                *cops |= OP_NEW;
+                       } else if (!strcasecmp( "Old", arg )) {
+                               *cops |= OP_OLD;
                        } else if (!strcasecmp( "Gone", arg )) {
                                *cops |= OP_GONE;
                        } else if (!strcasecmp( "Delete", arg )) {
@@ -214,6 +216,8 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t 
*conf )
                                conf->ops[N] |= OP_UPGRADE;
                        } else if (!strcasecmp( "PullNew", arg )) {
                                conf->ops[N] |= OP_NEW;
+                       } else if (!strcasecmp( "PullOld", arg )) {
+                               conf->ops[N] |= OP_OLD;
                        } else if (!strcasecmp( "PullGone", arg )) {
                                conf->ops[N] |= OP_GONE;
                        } else if (!strcasecmp( "PullDelete", arg )) {
@@ -230,6 +234,8 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t 
*conf )
                                conf->ops[F] |= OP_UPGRADE;
                        } else if (!strcasecmp( "PushNew", arg )) {
                                conf->ops[F] |= OP_NEW;
+                       } else if (!strcasecmp( "PushOld", arg )) {
+                               conf->ops[F] |= OP_OLD;
                        } else if (!strcasecmp( "PushGone", arg )) {
                                conf->ops[F] |= OP_GONE;
                        } else if (!strcasecmp( "PushDelete", arg )) {
@@ -366,15 +372,15 @@ merge_ops( int cops, int ops[], const char *chan_name )
                                               channel_str( chan_name ) );
                                        return 1;
                                }
-                               if (ops[N] & OP_MASK_TYPE)
+                               if (ops[N] & OP_DFLT_TYPE)
                                        goto ovl;
-                               ops[N] |= OP_MASK_TYPE;
+                               ops[N] |= OP_DFLT_TYPE;
                        } else if (cops & XOP_PUSH) {
                                if (cops & OP_MASK_TYPE)
                                        goto ivl;
-                               if (ops[F] & OP_MASK_TYPE)
+                               if (ops[F] & OP_DFLT_TYPE)
                                        goto ovl;
-                               ops[F] |= OP_MASK_TYPE;
+                               ops[F] |= OP_DFLT_TYPE;
                        } else {
                                ops[F] |= cops & OP_MASK_TYPE;
                                ops[N] |= cops & OP_MASK_TYPE;
@@ -383,7 +389,7 @@ merge_ops( int cops, int ops[], const char *chan_name )
                        if (ops[F] & XOP_TYPE_NOOP)
                                goto cfl;
                        if (!(cops & OP_MASK_TYPE))
-                               cops |= OP_MASK_TYPE;
+                               cops |= OP_DFLT_TYPE;
                        else if (!(cops & XOP_MASK_DIR))
                                cops |= XOP_PULL|XOP_PUSH;
                        if (cops & XOP_PULL)
diff --git a/src/driver.h b/src/driver.h
index 62fe15b9..de1d6ec1 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -91,6 +91,7 @@ BIT_ENUM(
        OPEN_NEW,         // Messages (possibly) not yet propagated *from* this 
store.
        OPEN_FIND,
        OPEN_FLAGS,       // Note that fetch_msg() gets the flags regardless.
+       OPEN_OLD_SIZE,
        OPEN_NEW_SIZE,
        OPEN_PAIRED_IDS,
        OPEN_APPEND,
diff --git a/src/drv_imap.c b/src/drv_imap.c
index 45160239..e70654df 100644
--- a/src/drv_imap.c
+++ b/src/drv_imap.c
@@ -2895,8 +2895,14 @@ imap_load_box( store_t *gctx, uint minuid, uint maxuid, 
uint finduid, uint pairu
                        ranges[0].last = maxuid;
                        ranges[0].flags = 0;
                        uint nranges = 1;
-                       if (ctx->opts & OPEN_NEW_SIZE)
+                       if (ctx->opts & OPEN_OLD_SIZE) {
+                               if (ctx->opts & OPEN_NEW_SIZE)
+                                       ranges[0].flags = WantSize;
+                               else
+                                       imap_set_range( ranges, &nranges, 
WantSize, 0, newuid );
+                       } else if (ctx->opts & OPEN_NEW_SIZE) {
                                imap_set_range( ranges, &nranges, 0, WantSize, 
newuid );
+                       }
                        if (ctx->opts & OPEN_FIND)
                                imap_set_range( ranges, &nranges, 0, WantTuids, 
finduid - 1 );
                        if (ctx->opts & OPEN_PAIRED_IDS)
diff --git a/src/drv_maildir.c b/src/drv_maildir.c
index 3ea03483..eb32ee42 100644
--- a/src/drv_maildir.c
+++ b/src/drv_maildir.c
@@ -1111,7 +1111,8 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t 
*msglist )
                                free( entry->base );
                                entry->base = nfstrndup( buf + bl + 4, 
(size_t)fnl );
                        }
-                       int want_size = ((ctx->opts & OPEN_NEW_SIZE) && uid > 
ctx->newuid);
+                       int want_size = ((ctx->opts & OPEN_OLD_SIZE) && uid <= 
ctx->newuid) ||
+                                       ((ctx->opts & OPEN_NEW_SIZE) && uid > 
ctx->newuid);
                        int want_tuid = ((ctx->opts & OPEN_FIND) && uid >= 
ctx->finduid);
                        int want_msgid = ((ctx->opts & OPEN_PAIRED_IDS) && uid 
<= ctx->pairuid);
                        if (!want_size && !want_tuid && !want_msgid)
diff --git a/src/main.c b/src/main.c
index 55eed80d..bd6ffe5e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -272,6 +272,8 @@ main( int argc, char **argv )
                                  rlcac:
                                        if (!strcmp( opt, "new" )) {
                                                op |= OP_NEW;
+                                       } else if (!strcmp( opt, "old" )) {
+                                               op |= OP_OLD;
                                        } else if (!strcmp( opt, "upgrade" )) {
                                                op |= OP_UPGRADE;
                                        } else if (!strcmp( opt, "renew" )) {
@@ -354,6 +356,7 @@ main( int argc, char **argv )
                        mvars->ops[F] |= XOP_TYPE_NOOP | XOP_HAVE_TYPE;
                        break;
                case 'n':
+               case 'o':
                case 'd':
                case 'g':
                case 'f':
@@ -365,6 +368,8 @@ main( int argc, char **argv )
                        for (;; ochar++) {
                                if (*ochar == 'n')
                                        op |= OP_NEW;
+                               else if (*ochar == 'o')
+                                       op |= OP_OLD;
                                else if (*ochar == 'g')
                                        op |= OP_GONE;
                                else if (*ochar == 'd')
diff --git a/src/main_sync.c b/src/main_sync.c
index 2dd859e6..14c04d7b 100644
--- a/src/main_sync.c
+++ b/src/main_sync.c
@@ -182,7 +182,7 @@ add_channel( chan_ent_t ***chanapp, channel_conf_t *chan, 
int ops[] )
        chan_ent_t *ce = nfzalloc( sizeof(*ce) );
        ce->conf = chan;
 
-       merge_actions( chan, ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE );
+       merge_actions( chan, ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_DFLT_TYPE );
        merge_actions( chan, ops, XOP_HAVE_CREATE, OP_CREATE, 0 );
        merge_actions( chan, ops, XOP_HAVE_REMOVE, OP_REMOVE, 0 );
        merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
diff --git a/src/mbsync.1 b/src/mbsync.1
index c13496be..d362b2bf 100644
--- a/src/mbsync.1
+++ b/src/mbsync.1
@@ -61,11 +61,11 @@ Override any \fBRemove\fR options from the config file. See 
below.
 \fB-X\fR[\fBf\fR][\fBn\fR], \fB--expunge\fR[\fB-far\fR|\fB-near\fR]
 Override any \fBExpunge\fR options from the config file. See below.
 .TP
-{\fB-n\fR|\fB-u\fR|\fB-g\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\
- 
{\fB--new\fR|\fB--upgrade\fR|\fB--gone\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
+{\fB-n\fR|\fB-o\fR|\fB-u\fR|\fB-g\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\
+ 
{\fB--new\fR|\fB--old\fR|\fB--upgrade\fR|\fB--gone\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
 .TP
-\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBu\fR][\fBg\fR][\fBf\fR],\
- 
{\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-upgrade\fR|\fB-gone\fR|\fB-flags\fR]
+\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBo\fR][\fBu\fR][\fBg\fR][\fBf\fR],\
+ 
{\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-old\fR|\fB-upgrade\fR|\fB-gone\fR|\fB-flags\fR]
 Override any \fBSync\fR options from the config file. See below.
 .TP
 \fB-h\fR, \fB--help\fR
@@ -574,7 +574,7 @@ case you need to enable this option.
 (Global default: \fBno\fR).
 .
 .TP
-\fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBUpgrade\fR] 
[\fBGone\fR] [\fBFlags\fR] [\fBFull\fR]}
+\fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBOld\fR] 
[\fBUpgrade\fR] [\fBGone\fR] [\fBFlags\fR] [\fBFull\fR]}
 Select the synchronization operation(s) to perform:
 .br
 \fBPull\fR - propagate changes from far to near side.
@@ -583,6 +583,10 @@ Select the synchronization operation(s) to perform:
 .br
 \fBNew\fR - propagate newly appeared messages.
 .br
+\fBOld\fR - propagate previously skipped, failed, and expired messages.
+This has a (relatively) high cost and may repeatedly produce error messages,
+so it always must be specified explicitly.
+.br
 \fBUpgrade\fR - upgrade placeholders to full messages. Useful only with
 a configured \fBMaxSize\fR.
 .br
@@ -601,10 +605,10 @@ This is the global default.
 \fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything.
 Useful if you want to expunge only.
 .IP
-\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBUpgrade\fR,
-\fBGone\fR and \fBFlags\fR are type flags. The two flag classes make up a
-two-dimensional matrix (a table). Its cells are the individual actions to
-perform. There are two styles of asserting the cells:
+\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBOld\fR,
+\fBUpgrade\fR, \fBGone\fR, and \fBFlags\fR are type flags.
+The two flag classes make up a two-dimensional matrix (a table). Its cells are
+the individual actions to perform. There are two styles of asserting the cells:
 .br
 In the first style, the flags select entire rows/colums in the matrix. Only
 the cells which are selected both horizontally and vertically are asserted.
@@ -620,10 +624,10 @@ In the second style, direction flags are concatenated 
with type flags; every
 compound flag immediately asserts a cell in the matrix. In addition to at least
 one compound flag, the individual flags can be used as well, but as opposed to
 the first style, they immediately assert all cells in their respective
-row/column. For example,
+row/column (with the exception of \fBOld\fR). For example,
 "\fBSync\fR\ \fBPullNew\fR\ \fBPullGone\fR\ \fBPush\fR" will propagate
 message arrivals and deletions from the far side to the near side and any
-changes from the near side to the far side.
+changes (except old messages) from the near side to the far side.
 .br
 Note that it is not allowed to assert a cell in two ways, e.g.
 "\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and
diff --git a/src/run-tests.pl b/src/run-tests.pl
index 01525949..7fc89d49 100755
--- a/src/run-tests.pl
+++ b/src/run-tests.pl
@@ -1117,7 +1117,7 @@ my @x20 = (
   C, "", "", "*FS*",
 );
 
-my @O21 = ("MaxSize 1k\n", "MaxSize 1k\n", "Expunge Near");
+my @O21 = ("MaxSize 1k\n", "MaxSize 1k\n", "Sync Full Old\nExpunge Near\n");
 my @X21 = (
   C, 0, B,
   C, "*S?", "*<S", "",
@@ -1462,6 +1462,244 @@ my @X61 = (
 );
 test("maxuid topping", \@x60, \@X61, \@O61);
 
+# Tests for refreshing previously skipped/failed/expired messages.
+# We don't know the flags at the time of the (hypothetical) previous
+# sync, so we can't know why a particular message is missing.
+# We test with MaxMessages to cover different behaviors wrt maxxfuid.
+
+my @x70 = (
+  L, E, P,
+  A, "*", "", "",
+  B, "*F", "", "",
+  C, "", "", "*",
+  D, "*F", "*F", "*F",
+  E, "*", "", "_",      # MaxExpiredFarUid
+  F, "*", "*", "*",
+  G, "_", "*T", "*T",
+  H, "*T", "*T", "_",
+  I, "", "", "*T",
+  J, "", "", "*",
+  K, "*T", "", "",
+  L, "*", "", "",       # MaxPulledUid
+  M, "_", "*T", "*T",
+  N, "*T", "*T", "_",
+  O, "", "", "*T",
+  P, "", "", "*",       # MaxPushedUid
+  Q, "*T", "", "",
+  R, "*", "", "",
+  S, "", "", "*",
+);
+
+my @O71 = ("", "", "Sync New\nMaxMessages 20\nExpireUnread yes\n");
+my @X71 = (
+  S, E, R,
+  N, "", ">", "",
+  S, "*", "*", "",
+  Q, "", "*T", "*T",
+  R, "", "*", "*",
+);
+test("not old", \@x70, \@X71, \@O71);
+
+my @O71a = ("", "", "Sync New\nMaxMessages 3\nExpireUnread yes\n");
+test("not old + expire", \@x70, \@X71, \@O71a);
+
+my @O72 = ("", "", "Sync New Old\nMaxMessages 20\nExpireUnread yes\nExpunge 
Near\n");
+my @X72 = (
+  S, E, R,
+  G, "", "/", "/",
+  H, "", ">", "",
+  I, "", "", "/",
+  M, "", "/", "/",
+  N, "", ">", "",
+  O, "", "", "/",
+  C, "*", "*", "",
+  J, "*", "*", "",
+  P, "*", "*", "",
+  S, "*", "*", "",
+  B, "", "*F", "*F",
+  L, "", "*", "*",
+  R, "", "*", "*",
+);
+test("old + expunge near", \@x70, \@X72, \@O72);
+
+# This omits the entries that cause idempotence problems
+# due to not counting/expiring newly pushed messages.
+my @x70a = (
+  L, E, O,
+  A, "*", "", "",
+  B, "*F", "", "",
+  D, "*F", "*F", "*F",
+  E, "*", "", "_",      # MaxExpiredFarUid
+  G, "_", "*T", "*T",
+  H, "*T", "*T", "_",
+  I, "", "", "*T",
+  K, "*T", "", "",
+  L, "*", "", "",       # MaxPulledUid
+  M, "_", "*T", "*T",
+  N, "*T", "*T", "_",
+  O, "", "", "*T",      # MaxPushedUid
+  Q, "*T", "", "",
+  R, "*", "", "",
+  S, "", "", "*",
+);
+
+my @O72a = ("", "", "Sync New Old\nMaxMessages 3\nExpireUnread yes\nExpunge 
Near\n");
+my @X72a = (
+  S, E, R,
+  G, "", "/", "/",
+  H, "", ">", "",
+  I, "", "", "/",
+  M, "", "/", "/",
+  N, "", ">", "",
+  O, "", "", "/",
+  S, "*", "*", "",
+  B, "", "*F", "*F",
+  L, "", "*", "*",
+  R, "", "*", "*",
+);
+test("old + expire + expunge near", \@x70a, \@X72a, \@O72a);
+
+my @O73 = ("", "", "Sync Pull New Old\nMaxMessages 20\nExpireUnread yes\n");
+my @X73 = (
+  R, E, P,
+  B, "", "*F", "*F",
+  G, "", "<", "",
+  H, "", ">", "",
+  K, "", "*T", "*T",
+  L, "", "*", "*",
+  M, "", "<", "",
+  N, "", ">", "",
+  Q, "", "*T", "*T",
+  R, "", "*", "*",
+);
+test("pull old", \@x70, \@X73, \@O73);
+
+# This is "weird", because expiration overtook pulling.
+my @x80 = (
+  D, L, Q,
+  A, "", "", "*",
+  B, "*", "", "",
+  C, "*T", "", "",
+  D, "*F", "", "",      # MaxPulledUid
+  E, "_", "*T", "*T",
+  F, "*T", "*T", "_",
+  G, "", "", "*T",
+  H, "", "", "*",
+  I, "*T", "", "",
+  J, "*", "", "",
+  K, "*F", "*F", "*F",
+  L, "*", "*~", "_",    # MaxExpiredFarUid
+  M, "*", "*", "*",
+  N, "_", "*T", "*T",
+  O, "*T", "*T", "_",
+  P, "", "", "*T",
+  Q, "", "", "*",       # MaxPushedUid
+  R, "*T", "", "",
+  S, "*", "", "",
+  T, "", "", "*T",
+  U, "", "", "*",
+);
+
+my @X81 = (
+  U, L, S,
+  F, "", ">", "",
+  L, "", "/", "",
+  O, "", ">", "",
+  T, "*T", "*T", "",
+  U, "*", "*", "",
+  I, "", "*T", "*T",
+  J, "", "*", "*",
+  R, "", "*T", "*T",
+  S, "", "*", "*",
+);
+test("weird not old", \@x80, \@X81, \@O71);
+
+my @X81a = (
+  U, L, S,
+  F, "", ">", "",
+  L, "", "/", "",
+  O, "", ">", "",
+  T, "*T", "*T", "",
+  U, "*", "*", "",
+  I, "", "*T", "*T",
+  R, "", "*T", "*T",
+  S, "", "*", "*",
+);
+test("weird not old + expire", \@x80, \@X81a, \@O71a);
+
+my @X82 = (
+  U, L, S,
+  E, "", "/", "/",
+  F, "", ">", "",
+  G, "", "", "/",
+  L, "", "/", "",
+  N, "", "/", "/",
+  O, "", ">", "",
+  P, "", "", "/",
+  T, "", "", "/",
+  A, "*", "*", "",
+  H, "*", "*", "",
+  Q, "*", "*", "",
+  U, "*", "*", "",
+  D, "", "*F", "*F",
+  J, "", "*", "*",
+  S, "", "*", "*",
+);
+test("weird old + expunge near", \@x80, \@X82, \@O72);
+
+my @x80a = (
+  D, L, Q,
+  B, "*", "", "",
+  C, "*T", "", "",
+  D, "*F", "", "",      # MaxPulledUid
+  E, "_", "*T", "*T",
+  F, "*T", "*T", "_",
+  G, "", "", "*T",
+  H, "", "", "*",
+  I, "*T", "", "",
+  K, "*F", "*F", "*F",
+  L, "*", "*~", "_",    # MaxExpiredFarUid
+  N, "_", "*T", "*T",
+  O, "*T", "*T", "_",
+  P, "", "", "*T",
+  Q, "", "", "*",       # MaxPushedUid
+  R, "*T", "", "",
+  T, "", "", "*T",
+  U, "", "", "*",
+);
+
+my @X82a = (
+  U, L, D,
+  E, "", "/", "/",
+  F, "", ">", "",
+  G, "", "", "/",
+  L, "", "/", "",
+  N, "", "/", "/",
+  O, "", ">", "",
+  P, "", "", "/",
+  T, "", "", "/",
+  H, "*", "*", "",
+  Q, "*", "*", "",
+  U, "*", "*", "",
+  D, "", "*F", "*F",
+);
+test("weird old + expire + expunge near", \@x80a, \@X82a, \@O72a);
+
+my @X83 = (
+  S, L, Q,
+  E, "", "<", "",
+  F, "", ">", "",
+  L, "", "/", "",
+  N, "", "<", "",
+  O, "", ">", "",
+  D, "", "*F", "*F",
+  I, "", "*T", "*T",
+  J, "", "*", "*",
+  R, "", "*T", "*T",
+  S, "", "*", "*",
+);
+test("weird pull old", \@x80, \@X83, \@O73);
+
 # Messages that would be instantly expunged on the target side.
 
 my @x90 = (
diff --git a/src/sync.c b/src/sync.c
index 42b7dc1e..a671fbe2 100644
--- a/src/sync.c
+++ b/src/sync.c
@@ -709,6 +709,7 @@ box_opened2( sync_vars_t *svars, int t )
        int any_dummies[2] = { 0, 0 };
        int any_purges[2] = { 0, 0 };
        int any_upgrades[2] = { 0, 0 };
+       int any_old[2] = { 0, 0 };
        int any_new[2] = { 0, 0 };
        int any_tuids[2] = { 0, 0 };
        if (svars->replayed || ((chan->ops[F] | chan->ops[N]) & OP_UPGRADE)) {
@@ -731,6 +732,8 @@ box_opened2( sync_vars_t *svars, int t )
                                t = !srec->uid[F] ? F : N;
                                if (srec->status & S_UPGRADE)
                                        any_upgrades[t]++;
+                               else if (srec->uid[t^1] <= svars->maxuid[t^1])
+                                       any_old[t]++;
                                else
                                        any_new[t]++;
                                if (srec->tuid[0])
@@ -762,8 +765,14 @@ box_opened2( sync_vars_t *svars, int t )
                        chan->ops[t] &= ~OP_UPGRADE;
                        debug( "no %s dummies; masking Upgrade\n", str_fn[t] );
                }
-               if ((chan->ops[t] & (OP_NEW | OP_UPGRADE)) || any_new[t] || 
any_upgrades[t]) {
+               if ((chan->ops[t] & (OP_OLD | OP_NEW | OP_UPGRADE)) || 
any_old[t] || any_new[t] || any_upgrades[t]) {
                        opts[t] |= OPEN_APPEND;
+                       if ((chan->ops[t] & OP_OLD) || any_old[t]) {
+                               debug( "resuming %s of %d old message(s)\n", 
str_hl[t], any_old[t] );
+                               opts[t^1] |= OPEN_OLD;
+                               if (chan->stores[t]->max_size != UINT_MAX)
+                                       opts[t^1] |= OPEN_OLD_SIZE;
+                       }
                        if ((chan->ops[t] & OP_NEW) || any_new[t]) {
                                debug( "resuming %s of %d new message(s)\n", 
str_hl[t], any_new[t] );
                                opts[t^1] |= OPEN_NEW;
@@ -795,13 +804,13 @@ box_opened2( sync_vars_t *svars, int t )
        // The latter would also apply when the expired box is the source,
        // but it's more natural to treat it as read-only in that case.
        // OP_UPGRADE makes sense only for legacy S_SKIPPED entries.
-       if ((chan->ops[N] & (OP_NEW | OP_UPGRADE | OP_FLAGS)) && 
chan->max_messages)
+       if ((chan->ops[N] & (OP_OLD | OP_NEW | OP_UPGRADE | OP_FLAGS)) && 
chan->max_messages)
                svars->any_expiring = 1;
        if (svars->any_expiring) {
                opts[N] |= OPEN_PAIRED | OPEN_FLAGS;
                if (any_dummies[N])
                        opts[F] |= OPEN_PAIRED | OPEN_FLAGS;
-               else if (chan->ops[N] & (OP_NEW | OP_UPGRADE))
+               else if (chan->ops[N] & (OP_OLD | OP_NEW | OP_UPGRADE))
                        opts[F] |= OPEN_FLAGS;
        }
        svars->opts[F] = svars->drv[F]->prepare_load_box( ctx[F], opts[F] );
@@ -1197,37 +1206,60 @@ box_loaded( int sts, message_t *msgs, int total_msgs, 
int recent_msgs, void *aux
 
                                if (srec->status & S_SKIPPED) {
                                        // Pre-1.4 legacy only: The message was 
skipped due to being too big.
-                                       if (!(svars->chan->ops[t] & OP_UPGRADE))
+                                       if (!(svars->chan->ops[t] & 
OP_UPGRADE))  // OP_OLD would be somewhat logical, too.
                                                continue;
                                        // The message size was not queried, so 
this won't be dummified below.
                                        srec->status = S_PENDING | S_DUMMY(t);
                                        JLOG( "_ %u %u", (srec->uid[F], 
srec->uid[N]), "placeholder only - was previously skipped" );
                                } else {
-                                       if (!(svars->chan->ops[t] & OP_NEW) && 
!(srec->status & S_UPGRADE))
-                                               continue;
-                                       if (!(srec->status & S_PENDING))
-                                               continue;  // Nothing to do - 
the message is paired or expired
-                                       // Propagation was scheduled, but we 
got interrupted
-                                       debug( "unpropagated old message %u\n", 
tmsg->uid );
-
-                                       if (srec->status & S_UPGRADE) {
-                                               if (((svars->chan->ops[t] & 
OP_EXPUNGE) &&
-                                                    ((srec->pflags | 
srec->aflags[t]) & ~srec->dflags[t] & F_DELETED)) ||
-                                                   ((svars->chan->ops[t^1] & 
OP_EXPUNGE) &&
-                                                    ((srec->msg[t^1]->flags | 
srec->aflags[t^1]) & ~srec->dflags[t^1] & F_DELETED))) {
-                                                       // We can't just kill 
the entry, as we may be propagating flags
-                                                       // (in particular, 
F_DELETED) towards the real message.
-                                                       // No dummy is actually 
present, but pretend there is, so the
-                                                       // real message is 
considered new when trashing.
-                                                       srec->status = 
(srec->status & ~(S_PENDING | S_UPGRADE)) | S_DUMMY(t);
-                                                       JLOG( "~ %u %u %d", 
(srec->uid[F], srec->uid[N], srec->status & S_LOGGED),
-                                                             "canceling 
placeholder upgrade - would be expunged anyway" );
+                                       if (!(srec->status & S_PENDING)) {
+                                               if (srec->uid[t])
+                                                       continue;  // Nothing 
to do - the message is paired
+                                               if (!(svars->chan->ops[t] & 
OP_OLD)) {
+                                                       // This was reported as 
'no <opposite>' already.
+                                                       // debug( "not 
re-propagating orphaned message %u\n", tmsg->uid );
+                                                       continue;
+                                               }
+                                               if (t == F || !(srec->status & 
S_EXPIRED)) {
+                                                       // Orphans are 
essentially deletion propagation transactions which
+                                                       // were interrupted 
midway through by not expunging the target. We
+                                                       // don't re-propagate 
these, as it would be illogical, and also
+                                                       // make a mess of 
placeholder upgrades.
+                                                       debug( "ignoring 
orphaned message %u\n", tmsg->uid );
+                                                       continue;
+                                               }
+                                               if ((!(tmsg->flags & F_FLAGGED) 
&&
+                                                    ((tmsg->flags & F_SEEN) || 
svars->chan->expire_unread > 0))) {
+                                                       debug( "not 
re-propagating tracked expired message %u\n", tmsg->uid );
+                                                       continue;
+                                               }
+                                               assert( !(srec->status & 
S_LOGGED) );
+                                               srec->status |= S_PENDING;
+                                               JLOG( "~ %u %u " 
stringify(S_PENDING), (srec->uid[F], srec->uid[N]),
+                                                     "re-propagate tracked 
expired message" );
+                                       } else {
+                                               // Propagation was scheduled, 
but we got interrupted
+                                               debug( "unpropagated old 
message %u\n", tmsg->uid );
+
+                                               if (srec->status & S_UPGRADE) {
+                                                       if 
(((svars->chan->ops[t] & OP_EXPUNGE) &&
+                                                            ((srec->pflags | 
srec->aflags[t]) & ~srec->dflags[t] & F_DELETED)) ||
+                                                           
((svars->chan->ops[t^1] & OP_EXPUNGE) &&
+                                                            
((srec->msg[t^1]->flags | srec->aflags[t^1]) & ~srec->dflags[t^1] & 
F_DELETED))) {
+                                                               // We can't 
just kill the entry, as we may be propagating flags
+                                                               // (in 
particular, F_DELETED) towards the real message.
+                                                               // No dummy is 
actually present, but pretend there is, so the
+                                                               // real message 
is considered new when trashing.
+                                                               srec->status = 
(srec->status & ~(S_PENDING | S_UPGRADE)) | S_DUMMY(t);
+                                                               JLOG( "~ %u %u 
%d", (srec->uid[F], srec->uid[N], srec->status & S_LOGGED),
+                                                                     
"canceling placeholder upgrade - would be expunged anyway" );
+                                                               continue;
+                                                       }
+                                                       // Prevent the driver 
from "completing" the flags, as we'll ignore them anyway.
+                                                       tmsg->status |= M_FLAGS;
+                                                       any_new[t] = 1;
                                                        continue;
                                                }
-                                               // Prevent the driver from 
"completing" the flags, as we'll ignore them anyway.
-                                               tmsg->status |= M_FLAGS;
-                                               any_new[t] = 1;
-                                               continue;
                                        }
                                }
                        } else {
@@ -1237,16 +1269,35 @@ box_loaded( int sts, message_t *msgs, int total_msgs, 
int recent_msgs, void *aux
                                if (t == F || tmsg->uid > svars->maxxfuid)
                                        topping = 0;
 
-                               if (!(svars->chan->ops[t] & OP_NEW))
-                                       continue;
+                               const char *what;
                                if (tmsg->uid <= svars->maxuid[t^1]) {
                                        // The message should be already 
paired. It's not, so it was:
-                                       // - previously paired, but the entry 
was expired and pruned => ignore
-                                       // - attempted, but failed => ignore 
(the wisdom of this is debatable)
-                                       // - ignored, as it would have been 
expunged anyway => ignore (even if undeleted)
-                                       continue;
+                                       // - attempted, but failed
+                                       // - ignored, as it would have been 
expunged anyway
+                                       // - paired, but subsequently expired 
and pruned
+                                       if (!(svars->chan->ops[t] & OP_OLD)) {
+                                               debug( "not propagating old 
message %u\n", tmsg->uid );
+                                               continue;
+                                       }
+                                       if (topping) {
+                                               // The message is below the 
boundary of the bulk range.
+                                               // We'll sync it only if it has 
become important meanwhile.
+                                               if (!(tmsg->flags & F_FLAGGED) 
&&
+                                                   ((tmsg->flags & F_SEEN) || 
svars->chan->expire_unread > 0)) {
+                                                       debug( "not 
re-propagating untracked expired message %u\n", tmsg->uid );
+                                                       continue;
+                                               }
+                                               what = "untracked expired 
message";
+                                       } else {
+                                               what = "old message";
+                                       }
+                               } else {
+                                       if (!(svars->chan->ops[t] & OP_NEW)) {
+                                               debug( "not propagating new 
message %u\n", tmsg->uid );
+                                               continue;
+                                       }
+                                       what = "new message";
                                }
-                               debug( "new message %u\n", tmsg->uid );
 
                                srec = nfzalloc( sizeof(*srec) );
                                *svars->srecadd = srec;
@@ -1258,7 +1309,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int 
recent_msgs, void *aux
                                tmsg->srec = srec;
                                if (svars->newmaxuid[t^1] < tmsg->uid)
                                        svars->newmaxuid[t^1] = tmsg->uid;
-                               JLOG( "+ %u %u", (srec->uid[F], srec->uid[N]), 
"fresh" );
+                               JLOG( "+ %u %u", (srec->uid[F], srec->uid[N]), 
"%s", what );
                        }
                        if (((svars->chan->ops[t] | svars->chan->ops[t^1]) & 
OP_EXPUNGE) && (tmsg->flags & F_DELETED)) {
                                // Yes, we may nuke fresh entries, created only 
for newmaxuid tracking.
diff --git a/src/sync.h b/src/sync.h
index 61bc8135..27a719f8 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -16,6 +16,7 @@
 
 BIT_ENUM(
        OP_NEW,
+       OP_OLD,
        OP_UPGRADE,
        OP_GONE,
        OP_FLAGS,
@@ -38,7 +39,8 @@ BIT_ENUM(
        XOP_REMOVE_NOOP,
 )
 
-#define OP_MASK_TYPE (OP_NEW | OP_UPGRADE | OP_GONE | OP_FLAGS)  // Asserted 
in the target side ops
+#define OP_DFLT_TYPE (OP_NEW | OP_UPGRADE | OP_GONE | OP_FLAGS)
+#define OP_MASK_TYPE (OP_DFLT_TYPE | OP_OLD)  // Asserted in the target side 
ops
 #define XOP_MASK_DIR (XOP_PUSH | XOP_PULL)
 
 DECL_BIT_FORMATTER_FUNCTION(ops, OP)


_______________________________________________
isync-devel mailing list
isync-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/isync-devel

Reply via email to