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_ow@mail.gmail.com
This commit is contained in:
parent
a8e145e589
commit
767a318eea
2
NEWS
2
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.
|
A proper summary is now printed prior to exiting.
|
||||||
|
|
||||||
|
Added new sync operation 'Old'.
|
||||||
|
|
||||||
[1.4.0]
|
[1.4.0]
|
||||||
|
|
||||||
The 'isync' compatibility wrapper was removed.
|
The 'isync' compatibility wrapper was removed.
|
||||||
|
|
16
src/config.c
16
src/config.c
|
@ -198,6 +198,8 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
|
||||||
*cops |= OP_UPGRADE;
|
*cops |= OP_UPGRADE;
|
||||||
} else if (!strcasecmp( "New", arg )) {
|
} else if (!strcasecmp( "New", arg )) {
|
||||||
*cops |= OP_NEW;
|
*cops |= OP_NEW;
|
||||||
|
} else if (!strcasecmp( "Old", arg )) {
|
||||||
|
*cops |= OP_OLD;
|
||||||
} else if (!strcasecmp( "Gone", arg )) {
|
} else if (!strcasecmp( "Gone", arg )) {
|
||||||
*cops |= OP_GONE;
|
*cops |= OP_GONE;
|
||||||
} else if (!strcasecmp( "Delete", arg )) {
|
} 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;
|
conf->ops[N] |= OP_UPGRADE;
|
||||||
} else if (!strcasecmp( "PullNew", arg )) {
|
} else if (!strcasecmp( "PullNew", arg )) {
|
||||||
conf->ops[N] |= OP_NEW;
|
conf->ops[N] |= OP_NEW;
|
||||||
|
} else if (!strcasecmp( "PullOld", arg )) {
|
||||||
|
conf->ops[N] |= OP_OLD;
|
||||||
} else if (!strcasecmp( "PullGone", arg )) {
|
} else if (!strcasecmp( "PullGone", arg )) {
|
||||||
conf->ops[N] |= OP_GONE;
|
conf->ops[N] |= OP_GONE;
|
||||||
} else if (!strcasecmp( "PullDelete", arg )) {
|
} 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;
|
conf->ops[F] |= OP_UPGRADE;
|
||||||
} else if (!strcasecmp( "PushNew", arg )) {
|
} else if (!strcasecmp( "PushNew", arg )) {
|
||||||
conf->ops[F] |= OP_NEW;
|
conf->ops[F] |= OP_NEW;
|
||||||
|
} else if (!strcasecmp( "PushOld", arg )) {
|
||||||
|
conf->ops[F] |= OP_OLD;
|
||||||
} else if (!strcasecmp( "PushGone", arg )) {
|
} else if (!strcasecmp( "PushGone", arg )) {
|
||||||
conf->ops[F] |= OP_GONE;
|
conf->ops[F] |= OP_GONE;
|
||||||
} else if (!strcasecmp( "PushDelete", arg )) {
|
} else if (!strcasecmp( "PushDelete", arg )) {
|
||||||
|
@ -366,15 +372,15 @@ merge_ops( int cops, int ops[], const char *chan_name )
|
||||||
channel_str( chan_name ) );
|
channel_str( chan_name ) );
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (ops[N] & OP_MASK_TYPE)
|
if (ops[N] & OP_DFLT_TYPE)
|
||||||
goto ovl;
|
goto ovl;
|
||||||
ops[N] |= OP_MASK_TYPE;
|
ops[N] |= OP_DFLT_TYPE;
|
||||||
} else if (cops & XOP_PUSH) {
|
} else if (cops & XOP_PUSH) {
|
||||||
if (cops & OP_MASK_TYPE)
|
if (cops & OP_MASK_TYPE)
|
||||||
goto ivl;
|
goto ivl;
|
||||||
if (ops[F] & OP_MASK_TYPE)
|
if (ops[F] & OP_DFLT_TYPE)
|
||||||
goto ovl;
|
goto ovl;
|
||||||
ops[F] |= OP_MASK_TYPE;
|
ops[F] |= OP_DFLT_TYPE;
|
||||||
} else {
|
} else {
|
||||||
ops[F] |= cops & OP_MASK_TYPE;
|
ops[F] |= cops & OP_MASK_TYPE;
|
||||||
ops[N] |= 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)
|
if (ops[F] & XOP_TYPE_NOOP)
|
||||||
goto cfl;
|
goto cfl;
|
||||||
if (!(cops & OP_MASK_TYPE))
|
if (!(cops & OP_MASK_TYPE))
|
||||||
cops |= OP_MASK_TYPE;
|
cops |= OP_DFLT_TYPE;
|
||||||
else if (!(cops & XOP_MASK_DIR))
|
else if (!(cops & XOP_MASK_DIR))
|
||||||
cops |= XOP_PULL|XOP_PUSH;
|
cops |= XOP_PULL|XOP_PUSH;
|
||||||
if (cops & XOP_PULL)
|
if (cops & XOP_PULL)
|
||||||
|
|
|
@ -91,6 +91,7 @@ BIT_ENUM(
|
||||||
OPEN_NEW, // Messages (possibly) not yet propagated *from* this store.
|
OPEN_NEW, // Messages (possibly) not yet propagated *from* this store.
|
||||||
OPEN_FIND,
|
OPEN_FIND,
|
||||||
OPEN_FLAGS, // Note that fetch_msg() gets the flags regardless.
|
OPEN_FLAGS, // Note that fetch_msg() gets the flags regardless.
|
||||||
|
OPEN_OLD_SIZE,
|
||||||
OPEN_NEW_SIZE,
|
OPEN_NEW_SIZE,
|
||||||
OPEN_PAIRED_IDS,
|
OPEN_PAIRED_IDS,
|
||||||
OPEN_APPEND,
|
OPEN_APPEND,
|
||||||
|
|
|
@ -2895,8 +2895,14 @@ imap_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pairu
|
||||||
ranges[0].last = maxuid;
|
ranges[0].last = maxuid;
|
||||||
ranges[0].flags = 0;
|
ranges[0].flags = 0;
|
||||||
uint nranges = 1;
|
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 );
|
imap_set_range( ranges, &nranges, 0, WantSize, newuid );
|
||||||
|
}
|
||||||
if (ctx->opts & OPEN_FIND)
|
if (ctx->opts & OPEN_FIND)
|
||||||
imap_set_range( ranges, &nranges, 0, WantTuids, finduid - 1 );
|
imap_set_range( ranges, &nranges, 0, WantTuids, finduid - 1 );
|
||||||
if (ctx->opts & OPEN_PAIRED_IDS)
|
if (ctx->opts & OPEN_PAIRED_IDS)
|
||||||
|
|
|
@ -1111,7 +1111,8 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
|
||||||
free( entry->base );
|
free( entry->base );
|
||||||
entry->base = nfstrndup( buf + bl + 4, (size_t)fnl );
|
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_tuid = ((ctx->opts & OPEN_FIND) && uid >= ctx->finduid);
|
||||||
int want_msgid = ((ctx->opts & OPEN_PAIRED_IDS) && uid <= ctx->pairuid);
|
int want_msgid = ((ctx->opts & OPEN_PAIRED_IDS) && uid <= ctx->pairuid);
|
||||||
if (!want_size && !want_tuid && !want_msgid)
|
if (!want_size && !want_tuid && !want_msgid)
|
||||||
|
|
|
@ -272,6 +272,8 @@ main( int argc, char **argv )
|
||||||
rlcac:
|
rlcac:
|
||||||
if (!strcmp( opt, "new" )) {
|
if (!strcmp( opt, "new" )) {
|
||||||
op |= OP_NEW;
|
op |= OP_NEW;
|
||||||
|
} else if (!strcmp( opt, "old" )) {
|
||||||
|
op |= OP_OLD;
|
||||||
} else if (!strcmp( opt, "upgrade" )) {
|
} else if (!strcmp( opt, "upgrade" )) {
|
||||||
op |= OP_UPGRADE;
|
op |= OP_UPGRADE;
|
||||||
} else if (!strcmp( opt, "renew" )) {
|
} else if (!strcmp( opt, "renew" )) {
|
||||||
|
@ -354,6 +356,7 @@ main( int argc, char **argv )
|
||||||
mvars->ops[F] |= XOP_TYPE_NOOP | XOP_HAVE_TYPE;
|
mvars->ops[F] |= XOP_TYPE_NOOP | XOP_HAVE_TYPE;
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
|
case 'o':
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'g':
|
case 'g':
|
||||||
case 'f':
|
case 'f':
|
||||||
|
@ -365,6 +368,8 @@ main( int argc, char **argv )
|
||||||
for (;; ochar++) {
|
for (;; ochar++) {
|
||||||
if (*ochar == 'n')
|
if (*ochar == 'n')
|
||||||
op |= OP_NEW;
|
op |= OP_NEW;
|
||||||
|
else if (*ochar == 'o')
|
||||||
|
op |= OP_OLD;
|
||||||
else if (*ochar == 'g')
|
else if (*ochar == 'g')
|
||||||
op |= OP_GONE;
|
op |= OP_GONE;
|
||||||
else if (*ochar == 'd')
|
else if (*ochar == 'd')
|
||||||
|
|
|
@ -182,7 +182,7 @@ add_channel( chan_ent_t ***chanapp, channel_conf_t *chan, int ops[] )
|
||||||
chan_ent_t *ce = nfzalloc( sizeof(*ce) );
|
chan_ent_t *ce = nfzalloc( sizeof(*ce) );
|
||||||
ce->conf = chan;
|
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_CREATE, OP_CREATE, 0 );
|
||||||
merge_actions( chan, ops, XOP_HAVE_REMOVE, OP_REMOVE, 0 );
|
merge_actions( chan, ops, XOP_HAVE_REMOVE, OP_REMOVE, 0 );
|
||||||
merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
|
merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
|
||||||
|
|
26
src/mbsync.1
26
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]
|
\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.
|
Override any \fBExpunge\fR options from the config file. See below.
|
||||||
.TP
|
.TP
|
||||||
{\fB-n\fR|\fB-u\fR|\fB-g\fR|\fB-f\fR|\fB-0\fR|\fB-F\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--upgrade\fR|\fB--gone\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
|
{\fB--new\fR|\fB--old\fR|\fB--upgrade\fR|\fB--gone\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
|
||||||
.TP
|
.TP
|
||||||
\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBu\fR][\fBg\fR][\fBf\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-upgrade\fR|\fB-gone\fR|\fB-flags\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.
|
Override any \fBSync\fR options from the config file. See below.
|
||||||
.TP
|
.TP
|
||||||
\fB-h\fR, \fB--help\fR
|
\fB-h\fR, \fB--help\fR
|
||||||
|
@ -574,7 +574,7 @@ case you need to enable this option.
|
||||||
(Global default: \fBno\fR).
|
(Global default: \fBno\fR).
|
||||||
.
|
.
|
||||||
.TP
|
.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:
|
Select the synchronization operation(s) to perform:
|
||||||
.br
|
.br
|
||||||
\fBPull\fR - propagate changes from far to near side.
|
\fBPull\fR - propagate changes from far to near side.
|
||||||
|
@ -583,6 +583,10 @@ Select the synchronization operation(s) to perform:
|
||||||
.br
|
.br
|
||||||
\fBNew\fR - propagate newly appeared messages.
|
\fBNew\fR - propagate newly appeared messages.
|
||||||
.br
|
.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
|
\fBUpgrade\fR - upgrade placeholders to full messages. Useful only with
|
||||||
a configured \fBMaxSize\fR.
|
a configured \fBMaxSize\fR.
|
||||||
.br
|
.br
|
||||||
|
@ -601,10 +605,10 @@ This is the global default.
|
||||||
\fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything.
|
\fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything.
|
||||||
Useful if you want to expunge only.
|
Useful if you want to expunge only.
|
||||||
.IP
|
.IP
|
||||||
\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBUpgrade\fR,
|
\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBOld\fR,
|
||||||
\fBGone\fR and \fBFlags\fR are type flags. The two flag classes make up a
|
\fBUpgrade\fR, \fBGone\fR, and \fBFlags\fR are type flags.
|
||||||
two-dimensional matrix (a table). Its cells are the individual actions to
|
The two flag classes make up a two-dimensional matrix (a table). Its cells are
|
||||||
perform. There are two styles of asserting the cells:
|
the individual actions to perform. There are two styles of asserting the cells:
|
||||||
.br
|
.br
|
||||||
In the first style, the flags select entire rows/colums in the matrix. Only
|
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.
|
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
|
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
|
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
|
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
|
"\fBSync\fR\ \fBPullNew\fR\ \fBPullGone\fR\ \fBPush\fR" will propagate
|
||||||
message arrivals and deletions from the far side to the near side and any
|
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
|
.br
|
||||||
Note that it is not allowed to assert a cell in two ways, e.g.
|
Note that it is not allowed to assert a cell in two ways, e.g.
|
||||||
"\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and
|
"\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and
|
||||||
|
|
240
src/run-tests.pl
240
src/run-tests.pl
|
@ -1117,7 +1117,7 @@ my @x20 = (
|
||||||
C, "", "", "*FS*",
|
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 = (
|
my @X21 = (
|
||||||
C, 0, B,
|
C, 0, B,
|
||||||
C, "*S?", "*<S", "",
|
C, "*S?", "*<S", "",
|
||||||
|
@ -1462,6 +1462,244 @@ my @X61 = (
|
||||||
);
|
);
|
||||||
test("maxuid topping", \@x60, \@X61, \@O61);
|
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.
|
# Messages that would be instantly expunged on the target side.
|
||||||
|
|
||||||
my @x90 = (
|
my @x90 = (
|
||||||
|
|
121
src/sync.c
121
src/sync.c
|
@ -709,6 +709,7 @@ box_opened2( sync_vars_t *svars, int t )
|
||||||
int any_dummies[2] = { 0, 0 };
|
int any_dummies[2] = { 0, 0 };
|
||||||
int any_purges[2] = { 0, 0 };
|
int any_purges[2] = { 0, 0 };
|
||||||
int any_upgrades[2] = { 0, 0 };
|
int any_upgrades[2] = { 0, 0 };
|
||||||
|
int any_old[2] = { 0, 0 };
|
||||||
int any_new[2] = { 0, 0 };
|
int any_new[2] = { 0, 0 };
|
||||||
int any_tuids[2] = { 0, 0 };
|
int any_tuids[2] = { 0, 0 };
|
||||||
if (svars->replayed || ((chan->ops[F] | chan->ops[N]) & OP_UPGRADE)) {
|
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;
|
t = !srec->uid[F] ? F : N;
|
||||||
if (srec->status & S_UPGRADE)
|
if (srec->status & S_UPGRADE)
|
||||||
any_upgrades[t]++;
|
any_upgrades[t]++;
|
||||||
|
else if (srec->uid[t^1] <= svars->maxuid[t^1])
|
||||||
|
any_old[t]++;
|
||||||
else
|
else
|
||||||
any_new[t]++;
|
any_new[t]++;
|
||||||
if (srec->tuid[0])
|
if (srec->tuid[0])
|
||||||
|
@ -762,8 +765,14 @@ box_opened2( sync_vars_t *svars, int t )
|
||||||
chan->ops[t] &= ~OP_UPGRADE;
|
chan->ops[t] &= ~OP_UPGRADE;
|
||||||
debug( "no %s dummies; masking Upgrade\n", str_fn[t] );
|
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;
|
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]) {
|
if ((chan->ops[t] & OP_NEW) || any_new[t]) {
|
||||||
debug( "resuming %s of %d new message(s)\n", str_hl[t], any_new[t] );
|
debug( "resuming %s of %d new message(s)\n", str_hl[t], any_new[t] );
|
||||||
opts[t^1] |= OPEN_NEW;
|
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,
|
// 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.
|
// but it's more natural to treat it as read-only in that case.
|
||||||
// OP_UPGRADE makes sense only for legacy S_SKIPPED entries.
|
// 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;
|
svars->any_expiring = 1;
|
||||||
if (svars->any_expiring) {
|
if (svars->any_expiring) {
|
||||||
opts[N] |= OPEN_PAIRED | OPEN_FLAGS;
|
opts[N] |= OPEN_PAIRED | OPEN_FLAGS;
|
||||||
if (any_dummies[N])
|
if (any_dummies[N])
|
||||||
opts[F] |= OPEN_PAIRED | OPEN_FLAGS;
|
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;
|
opts[F] |= OPEN_FLAGS;
|
||||||
}
|
}
|
||||||
svars->opts[F] = svars->drv[F]->prepare_load_box( ctx[F], opts[F] );
|
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) {
|
if (srec->status & S_SKIPPED) {
|
||||||
// Pre-1.4 legacy only: The message was skipped due to being too big.
|
// 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;
|
continue;
|
||||||
// The message size was not queried, so this won't be dummified below.
|
// The message size was not queried, so this won't be dummified below.
|
||||||
srec->status = S_PENDING | S_DUMMY(t);
|
srec->status = S_PENDING | S_DUMMY(t);
|
||||||
JLOG( "_ %u %u", (srec->uid[F], srec->uid[N]), "placeholder only - was previously skipped" );
|
JLOG( "_ %u %u", (srec->uid[F], srec->uid[N]), "placeholder only - was previously skipped" );
|
||||||
} else {
|
} else {
|
||||||
if (!(svars->chan->ops[t] & OP_NEW) && !(srec->status & S_UPGRADE))
|
if (!(srec->status & S_PENDING)) {
|
||||||
continue;
|
if (srec->uid[t])
|
||||||
if (!(srec->status & S_PENDING))
|
continue; // Nothing to do - the message is paired
|
||||||
continue; // Nothing to do - the message is paired or expired
|
if (!(svars->chan->ops[t] & OP_OLD)) {
|
||||||
// Propagation was scheduled, but we got interrupted
|
// This was reported as 'no <opposite>' already.
|
||||||
debug( "unpropagated old message %u\n", tmsg->uid );
|
// debug( "not re-propagating orphaned message %u\n", tmsg->uid );
|
||||||
|
continue;
|
||||||
if (srec->status & S_UPGRADE) {
|
}
|
||||||
if (((svars->chan->ops[t] & OP_EXPUNGE) &&
|
if (t == F || !(srec->status & S_EXPIRED)) {
|
||||||
((srec->pflags | srec->aflags[t]) & ~srec->dflags[t] & F_DELETED)) ||
|
// Orphans are essentially deletion propagation transactions which
|
||||||
((svars->chan->ops[t^1] & OP_EXPUNGE) &&
|
// were interrupted midway through by not expunging the target. We
|
||||||
((srec->msg[t^1]->flags | srec->aflags[t^1]) & ~srec->dflags[t^1] & F_DELETED))) {
|
// don't re-propagate these, as it would be illogical, and also
|
||||||
// We can't just kill the entry, as we may be propagating flags
|
// make a mess of placeholder upgrades.
|
||||||
// (in particular, F_DELETED) towards the real message.
|
debug( "ignoring orphaned message %u\n", tmsg->uid );
|
||||||
// No dummy is actually present, but pretend there is, so the
|
continue;
|
||||||
// real message is considered new when trashing.
|
}
|
||||||
srec->status = (srec->status & ~(S_PENDING | S_UPGRADE)) | S_DUMMY(t);
|
if ((!(tmsg->flags & F_FLAGGED) &&
|
||||||
JLOG( "~ %u %u %d", (srec->uid[F], srec->uid[N], srec->status & S_LOGGED),
|
((tmsg->flags & F_SEEN) || svars->chan->expire_unread > 0))) {
|
||||||
"canceling placeholder upgrade - would be expunged anyway" );
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
// Prevent the driver from "completing" the flags, as we'll ignore them anyway.
|
|
||||||
tmsg->status |= M_FLAGS;
|
|
||||||
any_new[t] = 1;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
if (t == F || tmsg->uid > svars->maxxfuid)
|
||||||
topping = 0;
|
topping = 0;
|
||||||
|
|
||||||
if (!(svars->chan->ops[t] & OP_NEW))
|
const char *what;
|
||||||
continue;
|
|
||||||
if (tmsg->uid <= svars->maxuid[t^1]) {
|
if (tmsg->uid <= svars->maxuid[t^1]) {
|
||||||
// The message should be already paired. It's not, so it was:
|
// 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
|
||||||
// - attempted, but failed => ignore (the wisdom of this is debatable)
|
// - ignored, as it would have been expunged anyway
|
||||||
// - ignored, as it would have been expunged anyway => ignore (even if undeleted)
|
// - paired, but subsequently expired and pruned
|
||||||
continue;
|
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) );
|
srec = nfzalloc( sizeof(*srec) );
|
||||||
*svars->srecadd = 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;
|
tmsg->srec = srec;
|
||||||
if (svars->newmaxuid[t^1] < tmsg->uid)
|
if (svars->newmaxuid[t^1] < tmsg->uid)
|
||||||
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)) {
|
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.
|
// Yes, we may nuke fresh entries, created only for newmaxuid tracking.
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
BIT_ENUM(
|
BIT_ENUM(
|
||||||
OP_NEW,
|
OP_NEW,
|
||||||
|
OP_OLD,
|
||||||
OP_UPGRADE,
|
OP_UPGRADE,
|
||||||
OP_GONE,
|
OP_GONE,
|
||||||
OP_FLAGS,
|
OP_FLAGS,
|
||||||
|
@ -38,7 +39,8 @@ BIT_ENUM(
|
||||||
XOP_REMOVE_NOOP,
|
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)
|
#define XOP_MASK_DIR (XOP_PUSH | XOP_PULL)
|
||||||
|
|
||||||
DECL_BIT_FORMATTER_FUNCTION(ops, OP)
|
DECL_BIT_FORMATTER_FUNCTION(ops, OP)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user