make expiration target side configurable
REFMAIL: 87k0fauw7q.fsf@wavexx.thregr.org
This commit is contained in:
		
							parent
							
								
									abb596709b
								
							
						
					
					
						commit
						8566283c59
					
				
					 8 changed files with 199 additions and 67 deletions
				
			
		
							
								
								
									
										2
									
								
								NEWS
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								NEWS
									
										
									
									
									
								
							|  | @ -12,6 +12,8 @@ they are flagged on the source side. | ||||||
| Renamed the ReNew/--renew/-N options to Upgrade/--upgrade/-u | Renamed the ReNew/--renew/-N options to Upgrade/--upgrade/-u | ||||||
| and Delete/--delete/-d to Gone/--gone/-g. | and Delete/--delete/-d to Gone/--gone/-g. | ||||||
| 
 | 
 | ||||||
|  | Made the Channel side to expire with MaxMessages configurable. | ||||||
|  | 
 | ||||||
| MaxMessages and MaxSize can be used together now. | MaxMessages and MaxSize can be used together now. | ||||||
| 
 | 
 | ||||||
| The unfiltered list of mailboxes in each Store can be printed now. | The unfiltered list of mailboxes in each Store can be printed now. | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								src/config.c
									
										
									
									
									
								
							
							
						
						
									
										13
									
								
								src/config.c
									
										
									
									
									
								
							|  | @ -261,6 +261,17 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf ) | ||||||
| 		conf->use_internal_date = parse_bool( cfile ); | 		conf->use_internal_date = parse_bool( cfile ); | ||||||
| 	} else if (!strcasecmp( "MaxMessages", cfile->cmd )) { | 	} else if (!strcasecmp( "MaxMessages", cfile->cmd )) { | ||||||
| 		conf->max_messages = parse_int( cfile ); | 		conf->max_messages = parse_int( cfile ); | ||||||
|  | 	} else if (!strcasecmp( "ExpireSide", cfile->cmd )) { | ||||||
|  | 		arg = cfile->val; | ||||||
|  | 		if (!strcasecmp( "Far", arg )) { | ||||||
|  | 			conf->expire_side = F; | ||||||
|  | 		} else if (!strcasecmp( "Near", arg )) { | ||||||
|  | 			conf->expire_side = N; | ||||||
|  | 		} else { | ||||||
|  | 			error( "%s:%d: invalid ExpireSide argument '%s'\n", | ||||||
|  | 			       cfile->file, cfile->line, arg ); | ||||||
|  | 			cfile->err = 1; | ||||||
|  | 		} | ||||||
| 	} else if (!strcasecmp( "ExpireUnread", cfile->cmd )) { | 	} else if (!strcasecmp( "ExpireUnread", cfile->cmd )) { | ||||||
| 		conf->expire_unread = parse_bool( cfile ); | 		conf->expire_unread = parse_bool( cfile ); | ||||||
| 	} else { | 	} else { | ||||||
|  | @ -481,6 +492,7 @@ load_config( const char *where ) | ||||||
| 
 | 
 | ||||||
| 	gcops = 0; | 	gcops = 0; | ||||||
| 	glob_ok = 1; | 	glob_ok = 1; | ||||||
|  | 	global_conf.expire_side = N; | ||||||
| 	global_conf.expire_unread = -1; | 	global_conf.expire_unread = -1; | ||||||
|   reloop: |   reloop: | ||||||
| 	while (getcline( &cfile )) { | 	while (getcline( &cfile )) { | ||||||
|  | @ -505,6 +517,7 @@ load_config( const char *where ) | ||||||
| 			channel = nfzalloc( sizeof(*channel) ); | 			channel = nfzalloc( sizeof(*channel) ); | ||||||
| 			channel->name = nfstrdup( cfile.val ); | 			channel->name = nfstrdup( cfile.val ); | ||||||
| 			channel->max_messages = global_conf.max_messages; | 			channel->max_messages = global_conf.max_messages; | ||||||
|  | 			channel->expire_side = global_conf.expire_side; | ||||||
| 			channel->expire_unread = global_conf.expire_unread; | 			channel->expire_unread = global_conf.expire_unread; | ||||||
| 			channel->use_internal_date = global_conf.use_internal_date; | 			channel->use_internal_date = global_conf.use_internal_date; | ||||||
| 			cops = 0; | 			cops = 0; | ||||||
|  |  | ||||||
|  | @ -579,6 +579,12 @@ case you need to enable this option. | ||||||
| (Global default: \fBno\fR). | (Global default: \fBno\fR). | ||||||
| . | . | ||||||
| .TP | .TP | ||||||
|  | \fBExpireSide\fR \fBFar\fR|\fBNear\fR | ||||||
|  | Selects on which side messages should be expired when \fBMaxMessages\fR is | ||||||
|  | configured. | ||||||
|  | (Global default: \fBNear\fR). | ||||||
|  | . | ||||||
|  | .TP | ||||||
| \fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBOld\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 | ||||||
|  | @ -693,7 +699,8 @@ date\fR) is actually the arrival time, but it is usually close enough. | ||||||
| . | . | ||||||
| .P | .P | ||||||
| \fBSync\fR, \fBCreate\fR, \fBRemove\fR, \fBExpunge\fR, \fBExpungeSolo\fR, | \fBSync\fR, \fBCreate\fR, \fBRemove\fR, \fBExpunge\fR, \fBExpungeSolo\fR, | ||||||
| \fBMaxMessages\fR, \fBExpireUnread\fR, and \fBCopyArrivalDate\fR | \fBMaxMessages\fR, \fBExpireUnread\fR, \fBExpireSide\fR, | ||||||
|  | and \fBCopyArrivalDate\fR | ||||||
| can be used before any section for a global effect. | can be used before any section for a global effect. | ||||||
| The global settings are overridden by Channel-specific options, | The global settings are overridden by Channel-specific options, | ||||||
| which in turn are overridden by command line switches. | which in turn are overridden by command line switches. | ||||||
|  |  | ||||||
|  | @ -266,12 +266,28 @@ sub parse_chan($;$) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	$$ss{max_pulled} = resolv_msg($$ics[0], $cs, "far"); | 	$$ss{max_pulled} = resolv_msg($$ics[0], $cs, "far"); | ||||||
| 	$$ss{max_expired} = resolv_msg($$ics[1], $cs, "far"); | 	$$ss{max_expired_far} = resolv_msg($$ics[1], $cs, "far"); | ||||||
| 	$$ss{max_pushed} = resolv_msg($$ics[2], $cs, "near"); | 	$$ss{max_pushed} = resolv_msg($$ics[2], $cs, "near"); | ||||||
|  | 	$$ss{max_expired_near} = 0; | ||||||
| 
 | 
 | ||||||
| 	return $cs; | 	return $cs; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | sub flip_chan($) | ||||||
|  | { | ||||||
|  | 	my ($cs) = @_; | ||||||
|  | 
 | ||||||
|  | 	($$cs{far}, $$cs{near}) = ($$cs{near}, $$cs{far}); | ||||||
|  | 	($$cs{far_trash}, $$cs{near_trash}) = ($$cs{near_trash}, $$cs{far_trash}); | ||||||
|  | 	my $ss = $$cs{state}; | ||||||
|  | 	($$ss{max_pulled}, $$ss{max_pushed}) = ($$ss{max_pushed}, $$ss{max_pulled}); | ||||||
|  | 	($$ss{max_expired_far}, $$ss{max_expired_near}) = ($$ss{max_expired_near}, $$ss{max_expired_far}); | ||||||
|  | 	for my $ent (@{$$ss{entries}}) { | ||||||
|  | 		($$ent[0], $$ent[1]) = ($$ent[1], $$ent[0]); | ||||||
|  | 		$$ent[2] =~ tr/<>/></; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| sub qm($) | sub qm($) | ||||||
| { | { | ||||||
|  | @ -442,8 +458,9 @@ sub readstate(;$) | ||||||
| 	my @ents; | 	my @ents; | ||||||
| 	my %ss = ( | 	my %ss = ( | ||||||
| 		max_pulled => 0, | 		max_pulled => 0, | ||||||
| 		max_expired => 0, | 		max_expired_far => 0, | ||||||
| 		max_pushed => 0, | 		max_pushed => 0, | ||||||
|  | 		max_expired_near => 0, | ||||||
| 		entries => \@ents | 		entries => \@ents | ||||||
| 	); | 	); | ||||||
| 	my ($far_val, $near_val) = (0, 0); | 	my ($far_val, $near_val) = (0, 0); | ||||||
|  | @ -452,7 +469,8 @@ sub readstate(;$) | ||||||
| 		'NearUidValidity' => \$near_val, | 		'NearUidValidity' => \$near_val, | ||||||
| 		'MaxPulledUid' => \$ss{max_pulled}, | 		'MaxPulledUid' => \$ss{max_pulled}, | ||||||
| 		'MaxPushedUid' => \$ss{max_pushed}, | 		'MaxPushedUid' => \$ss{max_pushed}, | ||||||
| 		'MaxExpiredFarUid' => \$ss{max_expired} | 		'MaxExpiredFarUid' => \$ss{max_expired_far}, | ||||||
|  | 		'MaxExpiredNearUid' => \$ss{max_expired_near} | ||||||
| 	); | 	); | ||||||
| 	OUTER: while (1) { | 	OUTER: while (1) { | ||||||
| 		while (@$ls) { | 		while (@$ls) { | ||||||
|  | @ -473,6 +491,7 @@ sub readstate(;$) | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	delete $hdr{'MaxExpiredFarUid'};  # optional field | 	delete $hdr{'MaxExpiredFarUid'};  # optional field | ||||||
|  | 	delete $hdr{'MaxExpiredNearUid'};  # ditto | ||||||
| 	my @ky = keys %hdr; | 	my @ky = keys %hdr; | ||||||
| 	if (@ky) { | 	if (@ky) { | ||||||
| 		print STDERR "Keys missing from sync state header: @ky\n"; | 		print STDERR "Keys missing from sync state header: @ky\n"; | ||||||
|  | @ -537,8 +556,12 @@ sub mkstate($) | ||||||
| 	open(FILE, ">", "near/.mbsyncstate") or | 	open(FILE, ">", "near/.mbsyncstate") or | ||||||
| 		die "Cannot create sync state.\n"; | 		die "Cannot create sync state.\n"; | ||||||
| 	print FILE "FarUidValidity 1\nMaxPulledUid ".$$ss{max_pulled}."\n". | 	print FILE "FarUidValidity 1\nMaxPulledUid ".$$ss{max_pulled}."\n". | ||||||
| 	           "NearUidValidity 1\nMaxExpiredFarUid ".$$ss{max_expired}. | 	           "NearUidValidity 1\nMaxPushedUid ".$$ss{max_pushed}."\n"; | ||||||
| 	           "\nMaxPushedUid ".$$ss{max_pushed}."\n\n"; | 	print FILE "MaxExpiredFarUid ".$$ss{max_expired_far}."\n" | ||||||
|  | 		if ($$ss{max_expired_far}); | ||||||
|  | 	print FILE "MaxExpiredNearUid ".$$ss{max_expired_near}."\n" | ||||||
|  | 		if ($$ss{max_expired_near}); | ||||||
|  | 	print FILE "\n"; | ||||||
| 	for my $ent (@{$$ss{entries}}) { | 	for my $ent (@{$$ss{entries}}) { | ||||||
| 		print FILE $$ent[0]." ".$$ent[1]." ".$$ent[2]."\n"; | 		print FILE $$ent[0]." ".$$ent[1]." ".$$ent[2]."\n"; | ||||||
| 	} | 	} | ||||||
|  | @ -646,8 +669,9 @@ sub cmpstate($$) | ||||||
| 	return 0 if ($ss == $ref_ss); | 	return 0 if ($ss == $ref_ss); | ||||||
| 	my $ret = 0; | 	my $ret = 0; | ||||||
| 	for my $h (['MaxPulledUid', 'max_pulled'], | 	for my $h (['MaxPulledUid', 'max_pulled'], | ||||||
| 	           ['MaxExpiredFarUid', 'max_expired'], | 	           ['MaxExpiredFarUid', 'max_expired_far'], | ||||||
| 	           ['MaxPushedUid', 'max_pushed']) { | 	           ['MaxPushedUid', 'max_pushed'], | ||||||
|  | 	           ['MaxExpiredNearUid', 'max_expired_near']) { | ||||||
| 		my ($hn, $sn) = @$h; | 		my ($hn, $sn) = @$h; | ||||||
| 		my ($got, $want) = ($$ss{$sn}, $$ref_ss{$sn}); | 		my ($got, $want) = ($$ss{$sn}, $$ref_ss{$sn}); | ||||||
| 		if ($got != $want) { | 		if ($got != $want) { | ||||||
|  | @ -735,7 +759,8 @@ sub printstate($) | ||||||
| 	my ($ss) = @_; | 	my ($ss) = @_; | ||||||
| 
 | 
 | ||||||
| 	return if (!$ss); | 	return if (!$ss); | ||||||
| 	print " [ ".$$ss{max_pulled}.", ".$$ss{max_expired}.", ".$$ss{max_pushed}.",\n   "; | 	print " [ ".$$ss{max_pulled}.", ".$$ss{max_expired_far}.", ". | ||||||
|  | 			$$ss{max_pushed}.", ".$$ss{max_expired_near}.",\n   "; | ||||||
| 	my $frst = 1; | 	my $frst = 1; | ||||||
| 	for my $ent (@{$$ss{entries}}) { | 	for my $ent (@{$$ss{entries}}) { | ||||||
| 		if ($frst) { | 		if ($frst) { | ||||||
|  | @ -884,10 +909,10 @@ sub test_impl($$$$) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| # $title, \@source_state, \@target_state, \@channel_configs | # $title, \@source_state, \@target_state, \@channel_configs, $flip_sides | ||||||
| sub test($$$$) | sub test($$$$;$) | ||||||
| { | { | ||||||
| 	my ($ttl, $isx, $itx, $sfx) = @_; | 	my ($ttl, $isx, $itx, $sfx, $flip) = @_; | ||||||
| 
 | 
 | ||||||
| 	if (@match) { | 	if (@match) { | ||||||
| 		if ($start) { | 		if ($start) { | ||||||
|  | @ -899,10 +924,16 @@ sub test($$$$) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	print "Testing: ".$ttl." ...\n"; | 	print "Testing: ".$ttl." ...\n"; | ||||||
|  | 	# We don't flip the Store configs, as inverting the Channel config | ||||||
|  | 	# would be unreasonably complex. | ||||||
| 	writecfg($sfx); | 	writecfg($sfx); | ||||||
| 
 | 
 | ||||||
| 	my $sx = parse_chan($isx); | 	my $sx = parse_chan($isx); | ||||||
| 	my $tx = parse_chan($itx, $sx); | 	my $tx = parse_chan($itx, $sx); | ||||||
|  | 	if ($flip) { | ||||||
|  | 		flip_chan($sx); | ||||||
|  | 		flip_chan($tx); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	test_impl(0, $sx, $tx, $sfx); | 	test_impl(0, $sx, $tx, $sfx); | ||||||
| 	test_impl(1, $sx, $tx, $sfx); | 	test_impl(1, $sx, $tx, $sfx); | ||||||
|  | @ -1263,6 +1294,9 @@ my @X33 = ( | ||||||
| ); | ); | ||||||
| test("max messages + expire - full", \@x33, \@X33, \@O31); | test("max messages + expire - full", \@x33, \@X33, \@O31); | ||||||
| 
 | 
 | ||||||
|  | my @O31a = ("", "", "MaxMessages 3\nExpireSide Far\n"); | ||||||
|  | test("max messages + expire far - full", \@x33, \@X33, \@O31a, 1); | ||||||
|  | 
 | ||||||
| my @O34 = ("", "", "Sync New\nMaxMessages 3\n"); | my @O34 = ("", "", "Sync New\nMaxMessages 3\n"); | ||||||
| my @X34 = ( | my @X34 = ( | ||||||
|   I, F, I, |   I, F, I, | ||||||
|  | @ -1465,6 +1499,9 @@ my @X61 = ( | ||||||
| ); | ); | ||||||
| test("maxuid topping", \@x60, \@X61, \@O61); | test("maxuid topping", \@x60, \@X61, \@O61); | ||||||
| 
 | 
 | ||||||
|  | my @O62 = ("", "", "Sync Flags\nExpireSide Far"); | ||||||
|  | test("maxuid topping (expire far)", \@x60, \@X61, \@O62, 1); | ||||||
|  | 
 | ||||||
| # Tests for refreshing previously skipped/failed/expired messages. | # Tests for refreshing previously skipped/failed/expired messages. | ||||||
| # We don't know the flags at the time of the (hypothetical) previous | # 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. | # sync, so we can't know why a particular message is missing. | ||||||
|  | @ -1688,6 +1725,24 @@ my @X82a = ( | ||||||
| ); | ); | ||||||
| test("weird old + expire + expunge near", \@x80a, \@X82a, \@O72a); | test("weird old + expire + expunge near", \@x80a, \@X82a, \@O72a); | ||||||
| 
 | 
 | ||||||
|  | my @O82b = ("", "", "Sync New Old\nMaxMessages 3\nExpireUnread yes\nExpireSide Far\nExpunge Far\n"); | ||||||
|  | my @X82b = ( | ||||||
|  |   U, L, D, | ||||||
|  |   E, "", "/", "/", | ||||||
|  |   F, "", ">", "", | ||||||
|  |   G, "", "", "/", | ||||||
|  |   L, "", "/", "", | ||||||
|  |   N, "", "/", "/", | ||||||
|  |   O, "", ">", "", | ||||||
|  |   P, "", "", "/", | ||||||
|  |   T, "", "", "/", | ||||||
|  |   D, "", "*F", "*F", | ||||||
|  |   H, "*", "*", "", | ||||||
|  |   Q, "*", "*", "", | ||||||
|  |   U, "*", "*", "", | ||||||
|  | ); | ||||||
|  | test("weird old + expire far + expunge far", \@x80a, \@X82b, \@O82b, 1); | ||||||
|  | 
 | ||||||
| my @X83 = ( | my @X83 = ( | ||||||
|   S, L, Q, |   S, L, Q, | ||||||
|   E, "", "<", "", |   E, "", "<", "", | ||||||
|  | @ -1816,6 +1871,24 @@ my @X13 = ( | ||||||
| ); | ); | ||||||
| test("trash new remotely", \@x10, \@X13, \@O13); | test("trash new remotely", \@x10, \@X13, \@O13); | ||||||
| 
 | 
 | ||||||
|  | my @O14 = ("Trash far_trash\n", "", | ||||||
|  |            "Sync Flags Gone\nMaxMessages 20\nExpireUnread yes\nExpireSide Far\nMaxSize 1k\nExpunge Both\n"); | ||||||
|  | my @X14 = ( | ||||||
|  |   K, A, K, | ||||||
|  |   A, "", "/", "/", | ||||||
|  |   B, "/", "/", "", | ||||||
|  |   C, "/", "/", "#/", | ||||||
|  |   D, "", "/", "#/", | ||||||
|  |   E, "/", "/", "", | ||||||
|  |   F, "/", "/", "/", | ||||||
|  |   G, "/", "/", "#/", | ||||||
|  |   H, "/", "/", "/", | ||||||
|  |   I, "/", "/", "#/", | ||||||
|  |   L, "/", "", "", | ||||||
|  |   M, "", "", "#/", | ||||||
|  | ); | ||||||
|  | test("trash near (expire far)", \@x10, \@X14, \@O14, 1); | ||||||
|  | 
 | ||||||
| # Test "mirroring" expunges. | # Test "mirroring" expunges. | ||||||
| 
 | 
 | ||||||
| my @xa0 = ( | my @xa0 = ( | ||||||
|  |  | ||||||
							
								
								
									
										118
									
								
								src/sync.c
									
										
									
									
									
								
							
							
						
						
									
										118
									
								
								src/sync.c
									
										
									
									
									
								
							|  | @ -625,14 +625,15 @@ 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_OLD | OP_NEW | OP_UPGRADE | OP_FLAGS)) && chan->max_messages) | 	int xt = chan->expire_side; | ||||||
|  | 	if ((chan->ops[xt] & (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[xt] |= OPEN_PAIRED | OPEN_FLAGS; | ||||||
| 		if (any_dummies[N]) | 		if (any_dummies[xt]) | ||||||
| 			opts[F] |= OPEN_PAIRED | OPEN_FLAGS; | 			opts[xt^1] |= OPEN_PAIRED | OPEN_FLAGS; | ||||||
| 		else if (chan->ops[N] & (OP_OLD | OP_NEW | OP_UPGRADE)) | 		else if (chan->ops[xt] & (OP_OLD | OP_NEW | OP_UPGRADE)) | ||||||
| 			opts[F] |= OPEN_FLAGS; | 			opts[xt^1] |= OPEN_FLAGS; | ||||||
| 	} | 	} | ||||||
| 	for (t = 0; t < 2; t++) { | 	for (t = 0; t < 2; t++) { | ||||||
| 		svars->opts[t] = svars->drv[t]->prepare_load_box( ctx[t], opts[t] ); | 		svars->opts[t] = svars->drv[t]->prepare_load_box( ctx[t], opts[t] ); | ||||||
|  | @ -651,36 +652,37 @@ box_opened2( sync_vars_t *svars, int t ) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ARRAY_INIT( &mexcs ); | 	ARRAY_INIT( &mexcs ); | ||||||
| 	if ((svars->opts[F] & OPEN_PAIRED) && !(svars->opts[F] & OPEN_OLD) && chan->max_messages) { | 	if ((svars->opts[xt^1] & OPEN_PAIRED) && !(svars->opts[xt^1] & OPEN_OLD) && chan->max_messages) { | ||||||
| 		/* When messages have been expired on the near side, the far side fetch is split into
 | 		/* When messages have been expired on one side, the other side's fetch is split into
 | ||||||
| 		 * two ranges: The bulk fetch which corresponds with the most recent messages, and an | 		 * two ranges: The bulk fetch which corresponds with the most recent messages, and an | ||||||
| 		 * exception list of messages which would have been expired if they weren't important. */ | 		 * exception list of messages which would have been expired if they weren't important. */ | ||||||
| 		debug( "preparing far side selection - max expired far uid is %u\n", svars->maxxfuid ); | 		debug( "preparing %s selection - max expired %s uid is %u\n", | ||||||
|  | 		       str_fn[xt^1], str_fn[xt^1], svars->maxxfuid ); | ||||||
| 		/* First, find out the lower bound for the bulk fetch. */ | 		/* First, find out the lower bound for the bulk fetch. */ | ||||||
| 		minwuid = svars->maxxfuid + 1; | 		minwuid = svars->maxxfuid + 1; | ||||||
| 		/* Next, calculate the exception fetch. */ | 		/* Next, calculate the exception fetch. */ | ||||||
| 		for (srec = svars->srecs; srec; srec = srec->next) { | 		for (srec = svars->srecs; srec; srec = srec->next) { | ||||||
| 			if (srec->status & S_DEAD) | 			if (srec->status & S_DEAD) | ||||||
| 				continue; | 				continue; | ||||||
| 			if (!srec->uid[F]) | 			if (!srec->uid[xt^1]) | ||||||
| 				continue;  // No message; other state is irrelevant
 | 				continue;  // No message; other state is irrelevant
 | ||||||
| 			if (srec->uid[F] >= minwuid) | 			if (srec->uid[xt^1] >= minwuid) | ||||||
| 				continue;  // Message is in non-expired range
 | 				continue;  // Message is in non-expired range
 | ||||||
| 			if ((svars->opts[F] & OPEN_NEW) && srec->uid[F] > svars->maxuid[F]) | 			if ((svars->opts[xt^1] & OPEN_NEW) && srec->uid[xt^1] > svars->maxuid[xt^1]) | ||||||
| 				continue;  // Message is in expired range, but new range overlaps that
 | 				continue;  // Message is in expired range, but new range overlaps that
 | ||||||
| 			if (!srec->uid[N] && !(srec->status & S_PENDING)) | 			if (!srec->uid[xt] && !(srec->status & S_PENDING)) | ||||||
| 				continue;  // Only actually paired up messages matter
 | 				continue;  // Only actually paired up messages matter
 | ||||||
| 			// The pair is alive, but outside the bulk range
 | 			// The pair is alive, but outside the bulk range
 | ||||||
| 			*uint_array_append( &mexcs ) = srec->uid[F]; | 			*uint_array_append( &mexcs ) = srec->uid[xt^1]; | ||||||
| 		} | 		} | ||||||
| 		sort_uint_array( mexcs.array ); | 		sort_uint_array( mexcs.array ); | ||||||
| 	} else { | 	} else { | ||||||
| 		minwuid = 1; | 		minwuid = 1; | ||||||
| 	} | 	} | ||||||
| 	sync_ref( svars ); | 	sync_ref( svars ); | ||||||
| 	load_box( svars, F, minwuid, mexcs.array ); | 	load_box( svars, xt^1, minwuid, mexcs.array ); | ||||||
| 	if (!check_cancel( svars )) | 	if (!check_cancel( svars )) | ||||||
| 		load_box( svars, N, 1, (uint_array_t){ NULL, 0 } ); | 		load_box( svars, xt, 1, (uint_array_t){ NULL, 0 } ); | ||||||
| 	sync_deref( svars ); | 	sync_deref( svars ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -747,6 +749,16 @@ cmp_srec_far( const void *a, const void *b ) | ||||||
| 	return au > bu ? 1 : -1;  // Can't subtract, the result might not fit into signed int.
 | 	return au > bu ? 1 : -1;  // Can't subtract, the result might not fit into signed int.
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int | ||||||
|  | cmp_srec_near( const void *a, const void *b ) | ||||||
|  | { | ||||||
|  | 	uint au = (*(const alive_srec_t *)a).srec->uid[N]; | ||||||
|  | 	uint bu = (*(const alive_srec_t *)b).srec->uid[N]; | ||||||
|  | 	assert( au && bu ); | ||||||
|  | 	assert( au != bu ); | ||||||
|  | 	return au > bu ? 1 : -1;  // Can't subtract, the result might not fit into signed int.
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| 	void *aux; | 	void *aux; | ||||||
| 	sync_rec_t *srec; | 	sync_rec_t *srec; | ||||||
|  | @ -877,6 +889,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 	svars->oldmaxuid[N] = svars->newmaxuid[N]; | 	svars->oldmaxuid[N] = svars->newmaxuid[N]; | ||||||
| 
 | 
 | ||||||
| 	info( "Synchronizing...\n" ); | 	info( "Synchronizing...\n" ); | ||||||
|  | 	int xt = svars->chan->expire_side; | ||||||
| 	for (t = 0; t < 2; t++) | 	for (t = 0; t < 2; t++) | ||||||
| 		svars->good_flags[t] = (uchar)svars->drv[t]->get_supported_flags( svars->ctx[t] ); | 		svars->good_flags[t] = (uchar)svars->drv[t]->get_supported_flags( svars->ctx[t] ); | ||||||
| 
 | 
 | ||||||
|  | @ -953,15 +966,16 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 				} else if (del[t^1]) { | 				} else if (del[t^1]) { | ||||||
| 					// The source was newly expunged, so possibly propagate the deletion.
 | 					// The source was newly expunged, so possibly propagate the deletion.
 | ||||||
| 					// The target may be in an unknown state (not fetched).
 | 					// The target may be in an unknown state (not fetched).
 | ||||||
| 					if ((t == F) && (srec->status & (S_EXPIRE|S_EXPIRED))) { | 					if ((t != xt) && (srec->status & (S_EXPIRE | S_EXPIRED))) { | ||||||
| 						/* Don't propagate deletion resulting from expiration. */ | 						/* Don't propagate deletion resulting from expiration. */ | ||||||
| 						if (~srec->status & (S_EXPIRE | S_EXPIRED)) { | 						if (~srec->status & (S_EXPIRE | S_EXPIRED)) { | ||||||
| 							// An expiration was interrupted, but the message was expunged since.
 | 							// An expiration was interrupted, but the message was expunged since.
 | ||||||
| 							srec->status |= S_EXPIRE | S_EXPIRED;  // Override failed unexpiration attempts.
 | 							srec->status |= S_EXPIRE | S_EXPIRED;  // Override failed unexpiration attempts.
 | ||||||
| 							JLOG( "~ %u %u %u", (srec->uid[F], srec->uid[N], srec->status), "forced expiration commit" ); | 							JLOG( "~ %u %u %u", (srec->uid[F], srec->uid[N], srec->status), "forced expiration commit" ); | ||||||
| 						} | 						} | ||||||
| 						JLOG( "> %u %u 0", (srec->uid[F], srec->uid[N]), "near side expired, orphaning far side" ); | 						JLOG( "%c %u %u 0", ("<>"[xt], srec->uid[F], srec->uid[N]), | ||||||
| 						srec->uid[N] = 0; | 						      "%s expired, orphaning %s", (str_fn[xt], str_fn[xt^1]) ); | ||||||
|  | 						srec->uid[xt] = 0; | ||||||
| 					} else { | 					} else { | ||||||
| 						if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) && | 						if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) && | ||||||
| 						    // Ignore deleted flag, as that's what we'll change ourselves ...
 | 						    // Ignore deleted flag, as that's what we'll change ourselves ...
 | ||||||
|  | @ -988,7 +1002,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 				  doflags: | 				  doflags: | ||||||
| 					if (svars->chan->ops[t] & OP_FLAGS) { | 					if (svars->chan->ops[t] & OP_FLAGS) { | ||||||
| 						sflags = sanitize_flags( sflags, svars, t ); | 						sflags = sanitize_flags( sflags, svars, t ); | ||||||
| 						if ((t == F) && (srec->status & (S_EXPIRE|S_EXPIRED))) { | 						if ((t != xt) && (srec->status & (S_EXPIRE | S_EXPIRED))) { | ||||||
| 							/* Don't propagate deletion resulting from expiration. */ | 							/* Don't propagate deletion resulting from expiration. */ | ||||||
| 							debug( "  near side expiring\n" ); | 							debug( "  near side expiring\n" ); | ||||||
| 							sflags &= ~F_DELETED; | 							sflags &= ~F_DELETED; | ||||||
|  | @ -1054,7 +1068,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 							// debug( "not re-propagating orphaned message %u\n", tmsg->uid );
 | 							// debug( "not re-propagating orphaned message %u\n", tmsg->uid );
 | ||||||
| 							continue; | 							continue; | ||||||
| 						} | 						} | ||||||
| 						if (t == F || !(srec->status & S_EXPIRED)) { | 						if (t != xt || !(srec->status & S_EXPIRED)) { | ||||||
| 							// Orphans are essentially deletion propagation transactions which
 | 							// Orphans are essentially deletion propagation transactions which
 | ||||||
| 							// were interrupted midway through by not expunging the target. We
 | 							// were interrupted midway through by not expunging the target. We
 | ||||||
| 							// don't re-propagate these, as it would be illogical, and also
 | 							// don't re-propagate these, as it would be illogical, and also
 | ||||||
|  | @ -1100,7 +1114,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 				// The 1st unknown message which should be known marks the end
 | 				// The 1st unknown message which should be known marks the end
 | ||||||
| 				// of the synced range; more known messages may follow (from an
 | 				// of the synced range; more known messages may follow (from an
 | ||||||
| 				// unidirectional sync in the opposite direction).
 | 				// unidirectional sync in the opposite direction).
 | ||||||
| 				if (t == F || tmsg->uid > svars->maxxfuid) | 				if (t != xt || tmsg->uid > svars->maxxfuid) | ||||||
| 					topping = 0; | 					topping = 0; | ||||||
| 
 | 
 | ||||||
| 				const char *what; | 				const char *what; | ||||||
|  | @ -1163,50 +1177,50 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (svars->any_expiring) { | 	if (svars->any_expiring) { | ||||||
| 		// Note: When this branch is entered, we have loaded all near side messages.
 |  | ||||||
| 		/* Expire excess messages. Important (flagged, unread, or unpropagated) messages
 | 		/* Expire excess messages. Important (flagged, unread, or unpropagated) messages
 | ||||||
| 		 * older than the first not expired message are not counted towards the total. */ | 		 * older than the first not expired message are not counted towards the total. */ | ||||||
|  | 		// Note: When this branch is entered, we have loaded all expire-side messages.
 | ||||||
| 		debug( "preparing message expiration\n" ); | 		debug( "preparing message expiration\n" ); | ||||||
| 		alive_srec_t *arecs = nfmalloc( sizeof(*arecs) * svars->nsrecs ); | 		alive_srec_t *arecs = nfmalloc( sizeof(*arecs) * svars->nsrecs ); | ||||||
| 		int alive = 0; | 		int alive = 0; | ||||||
| 		for (srec = svars->srecs; srec; srec = srec->next) { | 		for (srec = svars->srecs; srec; srec = srec->next) { | ||||||
| 			if (srec->status & S_DEAD) | 			if (srec->status & S_DEAD) | ||||||
| 				continue; | 				continue; | ||||||
| 			// We completely ignore unpaired near-side messages, as we cannot expire
 | 			// We completely ignore unpaired expire-side messages, as we cannot expire
 | ||||||
| 			// them without data loss; consequently, we also don't count them.
 | 			// them without data loss; consequently, we also don't count them.
 | ||||||
| 			// Note that we also ignore near-side messages we're currently propagating,
 | 			// Note that we also ignore expire-side messages we're currently propagating,
 | ||||||
| 			// which delays expiration of some messages by one cycle. Otherwise, we'd
 | 			// which delays expiration of some messages by one cycle. Otherwise, we'd
 | ||||||
| 			// have to sequence flag updating after message propagation to avoid a race
 | 			// have to sequence flag updating after message propagation to avoid a race
 | ||||||
| 			// with external expunging, and that seems unreasonably expensive.
 | 			// with external expunging, and that seems unreasonably expensive.
 | ||||||
| 			if (!srec->uid[F]) | 			if (!srec->uid[xt^1]) | ||||||
| 				continue; | 				continue; | ||||||
| 			if (!(srec->status & S_PENDING)) { | 			if (!(srec->status & S_PENDING)) { | ||||||
| 				// We ignore unpaired far-side messages, as there is obviously nothing
 | 				// We ignore unpaired far-side messages, as there is obviously nothing
 | ||||||
| 				// to expire in the first place.
 | 				// to expire in the first place.
 | ||||||
| 				if (!srec->msg[N]) | 				if (!srec->msg[xt]) | ||||||
| 					continue; | 					continue; | ||||||
| 				nflags = srec->msg[N]->flags; | 				nflags = srec->msg[xt]->flags; | ||||||
| 				if (srec->status & S_DUMMY(N)) { | 				if (srec->status & S_DUMMY(xt)) { | ||||||
| 					if (!srec->msg[F]) | 					if (!srec->msg[xt^1]) | ||||||
| 						continue; | 						continue; | ||||||
| 					// We need to pull in the real Flagged and Seen even if flag
 | 					// We need to pull in the real Flagged and Seen even if flag
 | ||||||
| 					// propagation was not requested, as the placeholder's ones are
 | 					// propagation was not requested, as the placeholder's ones are
 | ||||||
| 					// useless (except for un-seeing).
 | 					// useless (except for un-seeing).
 | ||||||
| 					// This results in the somewhat weird situation that messages
 | 					// This results in the somewhat weird situation that messages
 | ||||||
| 					// which are not visibly flagged remain unexpired.
 | 					// which are not visibly flagged remain unexpired.
 | ||||||
| 					sflags = srec->msg[F]->flags; | 					sflags = srec->msg[xt^1]->flags; | ||||||
| 					aflags = (sflags & ~srec->flags) & (F_SEEN | F_FLAGGED); | 					aflags = (sflags & ~srec->flags) & (F_SEEN | F_FLAGGED); | ||||||
| 					dflags = (~sflags & srec->flags) & F_SEEN; | 					dflags = (~sflags & srec->flags) & F_SEEN; | ||||||
| 					nflags = (nflags & (~(F_SEEN | F_FLAGGED) | (srec->flags & F_SEEN)) & ~dflags) | aflags; | 					nflags = (nflags & (~(F_SEEN | F_FLAGGED) | (srec->flags & F_SEEN)) & ~dflags) | aflags; | ||||||
| 				} | 				} | ||||||
| 				nflags = (nflags | srec->aflags[N]) & ~srec->dflags[N]; | 				nflags = (nflags | srec->aflags[xt]) & ~srec->dflags[xt]; | ||||||
| 			} else { | 			} else { | ||||||
| 				if (srec->status & S_UPGRADE) { | 				if (srec->status & S_UPGRADE) { | ||||||
| 					// The dummy's F & S flags are mostly masked out anyway,
 | 					// The dummy's F & S flags are mostly masked out anyway,
 | ||||||
| 					// but we may be pulling in the real ones.
 | 					// but we may be pulling in the real ones.
 | ||||||
| 					nflags = (srec->pflags | srec->aflags[N]) & ~srec->dflags[N]; | 					nflags = (srec->pflags | srec->aflags[xt]) & ~srec->dflags[xt]; | ||||||
| 				} else { | 				} else { | ||||||
| 					nflags = srec->msg[F]->flags; | 					nflags = srec->msg[xt^1]->flags; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if (!(nflags & F_DELETED) || (srec->status & (S_EXPIRE | S_EXPIRED))) { | 			if (!(nflags & F_DELETED) || (srec->status & (S_EXPIRE | S_EXPIRED))) { | ||||||
|  | @ -1216,7 +1230,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 		} | 		} | ||||||
| 		// Sort such that the messages which have been in the
 | 		// Sort such that the messages which have been in the
 | ||||||
| 		// complete store longest expire first.
 | 		// complete store longest expire first.
 | ||||||
| 		qsort( arecs, alive, sizeof(*arecs), cmp_srec_far ); | 		qsort( arecs, alive, sizeof(*arecs), (xt == F) ? cmp_srec_near : cmp_srec_far ); | ||||||
| 		int todel = alive - svars->chan->max_messages; | 		int todel = alive - svars->chan->max_messages; | ||||||
| 		debug( "%d alive messages, %d excess - expiring\n", alive, todel ); | 		debug( "%d alive messages, %d excess - expiring\n", alive, todel ); | ||||||
| 		int unseen = 0; | 		int unseen = 0; | ||||||
|  | @ -1230,7 +1244,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 				todel--; | 				todel--; | ||||||
| 			} else if (todel > 0 || | 			} else if (todel > 0 || | ||||||
| 			           ((srec->status & (S_EXPIRE | S_EXPIRED)) == (S_EXPIRE | S_EXPIRED)) || | 			           ((srec->status & (S_EXPIRE | S_EXPIRED)) == (S_EXPIRE | S_EXPIRED)) || | ||||||
| 			           ((srec->status & (S_EXPIRE | S_EXPIRED)) && (srec->msg[N]->flags & F_DELETED))) { | 			           ((srec->status & (S_EXPIRE | S_EXPIRED)) && (srec->msg[xt]->flags & F_DELETED))) { | ||||||
| 				/* The message is excess or was already (being) expired. */ | 				/* The message is excess or was already (being) expired. */ | ||||||
| 				srec->status |= S_NEXPIRE; | 				srec->status |= S_NEXPIRE; | ||||||
| 				debug( "  expiring pair(%u,%u)\n", srec->uid[F], srec->uid[N] ); | 				debug( "  expiring pair(%u,%u)\n", srec->uid[F], srec->uid[N] ); | ||||||
|  | @ -1241,7 +1255,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 		if (svars->chan->expire_unread < 0 && unseen * 2 > svars->chan->max_messages) { | 		if (svars->chan->expire_unread < 0 && unseen * 2 > svars->chan->max_messages) { | ||||||
| 			error( "%s: %d unread messages in excess of MaxMessages (%d).\n" | 			error( "%s: %d unread messages in excess of MaxMessages (%d).\n" | ||||||
| 			       "Please set ExpireUnread to decide outcome. Skipping mailbox.\n", | 			       "Please set ExpireUnread to decide outcome. Skipping mailbox.\n", | ||||||
| 			       svars->orig_name[N], unseen, svars->chan->max_messages ); | 			       svars->orig_name[xt], unseen, svars->chan->max_messages ); | ||||||
| 			svars->ret |= SYNC_FAIL; | 			svars->ret |= SYNC_FAIL; | ||||||
| 			cancel_sync( svars ); | 			cancel_sync( svars ); | ||||||
| 			return; | 			return; | ||||||
|  | @ -1272,9 +1286,9 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 					// If we have so many new messages that some of them are instantly expired,
 | 					// If we have so many new messages that some of them are instantly expired,
 | ||||||
| 					// but some are still propagated because they are important, we need to
 | 					// but some are still propagated because they are important, we need to
 | ||||||
| 					// ensure explicitly that the bulk fetch limit is upped.
 | 					// ensure explicitly that the bulk fetch limit is upped.
 | ||||||
| 					if (svars->maxxfuid < srec->uid[F]) | 					if (svars->maxxfuid < srec->uid[xt^1]) | ||||||
| 						svars->maxxfuid = srec->uid[F]; | 						svars->maxxfuid = srec->uid[xt^1]; | ||||||
| 					srec->msg[F]->srec = NULL; | 					srec->msg[xt^1]->srec = NULL; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -1307,7 +1321,7 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				/* The trigger is an expiration transaction being ongoing ... */ | 				/* The trigger is an expiration transaction being ongoing ... */ | ||||||
| 				if ((t == N) && ((shifted_bit(srec->status, S_EXPIRE, S_EXPIRED) ^ srec->status) & S_EXPIRED)) { | 				if ((t == xt) && ((shifted_bit(srec->status, S_EXPIRE, S_EXPIRED) ^ srec->status) & S_EXPIRED)) { | ||||||
| 					// ... but the actual action derives from the wanted state -
 | 					// ... but the actual action derives from the wanted state -
 | ||||||
| 					// so that canceled transactions are rolled back as well.
 | 					// so that canceled transactions are rolled back as well.
 | ||||||
| 					if (srec->status & S_NEXPIRE) | 					if (srec->status & S_NEXPIRE) | ||||||
|  | @ -1529,14 +1543,14 @@ flags_set_p2( sync_vars_t *svars, sync_rec_t *srec, int t ) | ||||||
| 			      (str_hl[t], fmt_lone_flags( nflags ).str, fmt_lone_flags( srec->flags ).str) ); | 			      (str_hl[t], fmt_lone_flags( nflags ).str, fmt_lone_flags( srec->flags ).str) ); | ||||||
| 			srec->flags = nflags; | 			srec->flags = nflags; | ||||||
| 		} | 		} | ||||||
| 		if (t == N) { | 		if (t == svars->chan->expire_side) { | ||||||
| 			uchar ex = (srec->status / S_EXPIRE) & 1; | 			uchar ex = (srec->status / S_EXPIRE) & 1; | ||||||
| 			uchar exd = (srec->status / S_EXPIRED) & 1; | 			uchar exd = (srec->status / S_EXPIRED) & 1; | ||||||
| 			if (ex != exd) { | 			if (ex != exd) { | ||||||
| 				uchar nex = (srec->status / S_NEXPIRE) & 1; | 				uchar nex = (srec->status / S_NEXPIRE) & 1; | ||||||
| 				if (nex == ex) { | 				if (nex == ex) { | ||||||
| 					if (nex && svars->maxxfuid < srec->uid[F]) | 					if (nex && svars->maxxfuid < srec->uid[t^1]) | ||||||
| 						svars->maxxfuid = srec->uid[F]; | 						svars->maxxfuid = srec->uid[t^1]; | ||||||
| 					srec->status = (srec->status & ~S_EXPIRED) | (nex * S_EXPIRED); | 					srec->status = (srec->status & ~S_EXPIRED) | (nex * S_EXPIRED); | ||||||
| 					JLOG( "~ %u %u %d", (srec->uid[F], srec->uid[N], srec->status & S_LOGGED), | 					JLOG( "~ %u %u %d", (srec->uid[F], srec->uid[N], srec->status & S_LOGGED), | ||||||
| 					      "expired %d - commit", nex ); | 					      "expired %d - commit", nex ); | ||||||
|  | @ -1582,6 +1596,7 @@ msgs_flags_set( sync_vars_t *svars, int t ) | ||||||
| 		only_solo = 0; | 		only_solo = 0; | ||||||
| 	else | 	else | ||||||
| 		goto skip; | 		goto skip; | ||||||
|  | 	int xt = svars->chan->expire_side; | ||||||
| 	int expunge_other = (svars->chan->ops[t^1] & OP_EXPUNGE); | 	int expunge_other = (svars->chan->ops[t^1] & OP_EXPUNGE); | ||||||
| 	// Driver-wise, this makes sense only if (svars->opts[t] & OPEN_UID_EXPUNGE),
 | 	// Driver-wise, this makes sense only if (svars->opts[t] & OPEN_UID_EXPUNGE),
 | ||||||
| 	// but the trashing loop uses the result as well.
 | 	// but the trashing loop uses the result as well.
 | ||||||
|  | @ -1603,7 +1618,7 @@ msgs_flags_set( sync_vars_t *svars, int t ) | ||||||
| 					debugn( "(orphaned) " ); | 					debugn( "(orphaned) " ); | ||||||
| 				} else if (expunge_other && (srec->status & S_DEL(t^1))) { | 				} else if (expunge_other && (srec->status & S_DEL(t^1))) { | ||||||
| 					debugn( "(orphaning) " ); | 					debugn( "(orphaning) " ); | ||||||
| 				} else if (t == N && (srec->status & (S_EXPIRE | S_EXPIRED))) { | 				} else if (t == xt && (srec->status & (S_EXPIRE | S_EXPIRED))) { | ||||||
| 					// Expiration overrides mirroring, as otherwise the combination
 | 					// Expiration overrides mirroring, as otherwise the combination
 | ||||||
| 					// makes no sense at all.
 | 					// makes no sense at all.
 | ||||||
| 					debugn( "(expire) " ); | 					debugn( "(expire) " ); | ||||||
|  | @ -1644,7 +1659,7 @@ msgs_flags_set( sync_vars_t *svars, int t ) | ||||||
| 		} | 		} | ||||||
| 		debugn( "  message %u ", tmsg->uid ); | 		debugn( "  message %u ", tmsg->uid ); | ||||||
| 		if ((srec = tmsg->srec)) { | 		if ((srec = tmsg->srec)) { | ||||||
| 			if (t == N && (srec->status & (S_EXPIRE | S_EXPIRED))) { | 			if (t == xt && (srec->status & (S_EXPIRE | S_EXPIRED))) { | ||||||
| 				// Don't trash messages that are deleted only due to expiring.
 | 				// Don't trash messages that are deleted only due to expiring.
 | ||||||
| 				// However, this is an unlikely configuration to start with ...
 | 				// However, this is an unlikely configuration to start with ...
 | ||||||
| 				debug( "is expired\n" ); | 				debug( "is expired\n" ); | ||||||
|  | @ -1814,12 +1829,17 @@ box_closed_p2( sync_vars_t *svars, int t ) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	debug( "purging obsolete entries\n" ); | 	debug( "purging obsolete entries\n" ); | ||||||
|  | 	int xt = svars->chan->expire_side; | ||||||
| 	for (srec = svars->srecs; srec; srec = srec->next) { | 	for (srec = svars->srecs; srec; srec = srec->next) { | ||||||
| 		if (srec->status & S_DEAD) | 		if (srec->status & S_DEAD) | ||||||
| 			continue; | 			continue; | ||||||
| 		if (!srec->uid[N] || (srec->status & S_GONE(N))) { | 		if ((srec->status & S_EXPIRED) && | ||||||
| 			if (!srec->uid[F] || (srec->status & S_GONE(F)) || | 		    (!srec->uid[xt] || (srec->status & S_GONE(xt))) && | ||||||
| 				((srec->status & S_EXPIRED) && svars->maxuid[F] >= srec->uid[F] && svars->maxxfuid >= srec->uid[F])) { | 		    svars->maxuid[xt^1] >= srec->uid[xt^1] && svars->maxxfuid >= srec->uid[xt^1]) { | ||||||
|  | 		    PC_JLOG( "- %u %u", (srec->uid[F], srec->uid[N]), "killing expired" ); | ||||||
|  | 			srec->status = S_DEAD; | ||||||
|  | 		} else if (!srec->uid[N] || (srec->status & S_GONE(N))) { | ||||||
|  | 			if (!srec->uid[F] || (srec->status & S_GONE(F))) { | ||||||
| 				PC_JLOG( "- %u %u", (srec->uid[F], srec->uid[N]), "killing" ); | 				PC_JLOG( "- %u %u", (srec->uid[F], srec->uid[N]), "killing" ); | ||||||
| 				srec->status = S_DEAD; | 				srec->status = S_DEAD; | ||||||
| 			} else if (srec->uid[N] && (srec->status & S_DEL(F))) { | 			} else if (srec->uid[N] && (srec->status & S_DEL(F))) { | ||||||
|  |  | ||||||
|  | @ -57,6 +57,7 @@ typedef struct channel_conf { | ||||||
| 	string_list_t *patterns; | 	string_list_t *patterns; | ||||||
| 	int ops[2]; | 	int ops[2]; | ||||||
| 	int max_messages;  // For near side only.
 | 	int max_messages;  // For near side only.
 | ||||||
|  | 	int expire_side; | ||||||
| 	signed char expire_unread; | 	signed char expire_unread; | ||||||
| 	char use_internal_date; | 	char use_internal_date; | ||||||
| } channel_conf_t; | } channel_conf_t; | ||||||
|  |  | ||||||
|  | @ -11,8 +11,8 @@ | ||||||
| 
 | 
 | ||||||
| BIT_ENUM( | BIT_ENUM( | ||||||
| 	S_DEAD,         // ephemeral: the entry was killed and should be ignored
 | 	S_DEAD,         // ephemeral: the entry was killed and should be ignored
 | ||||||
| 	S_EXPIRE,       // the entry is being expired (near side message removal scheduled)
 | 	S_EXPIRE,       // the entry is being expired (expire-side message removal scheduled)
 | ||||||
| 	S_EXPIRED,      // the entry is expired (near side message removal confirmed)
 | 	S_EXPIRED,      // the entry is expired (expire-side message removal confirmed)
 | ||||||
| 	S_NEXPIRE,      // temporary: new expiration state
 | 	S_NEXPIRE,      // temporary: new expiration state
 | ||||||
| 	S_PENDING,      // the entry is new and awaits propagation (possibly a retry)
 | 	S_PENDING,      // the entry is new and awaits propagation (possibly a retry)
 | ||||||
| 	S_DUMMY(2),     // f/n message is only a placeholder
 | 	S_DUMMY(2),     // f/n message is only a placeholder
 | ||||||
|  | @ -62,7 +62,7 @@ typedef struct { | ||||||
| 	uint uidval[2];     // UID validity value
 | 	uint uidval[2];     // UID validity value
 | ||||||
| 	uint newuidval[2];  // UID validity obtained from driver
 | 	uint newuidval[2];  // UID validity obtained from driver
 | ||||||
| 	uint finduid[2];    // TUID lookup makes sense only for UIDs >= this
 | 	uint finduid[2];    // TUID lookup makes sense only for UIDs >= this
 | ||||||
| 	uint maxxfuid;      // highest expired UID on far side
 | 	uint maxxfuid;      // highest expired UID on full side
 | ||||||
| 	uchar good_flags[2], bad_flags[2], can_crlf[2]; | 	uchar good_flags[2], bad_flags[2], can_crlf[2]; | ||||||
| } sync_vars_t; | } sync_vars_t; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -126,6 +126,7 @@ load_state( sync_vars_t *svars ) | ||||||
| 	char fbuf[16];  // enlarge when support for keywords is added
 | 	char fbuf[16];  // enlarge when support for keywords is added
 | ||||||
| 	char buf[128], buf1[64], buf2[64]; | 	char buf[128], buf1[64], buf2[64]; | ||||||
| 
 | 
 | ||||||
|  | 	int xt = svars->chan->expire_side; | ||||||
| 	if ((jfp = fopen( svars->dname, "r" ))) { | 	if ((jfp = fopen( svars->dname, "r" ))) { | ||||||
| 		if (!lock_state( svars )) | 		if (!lock_state( svars )) | ||||||
| 			goto jbail; | 			goto jbail; | ||||||
|  | @ -148,6 +149,8 @@ load_state( sync_vars_t *svars ) | ||||||
| 					error( "Error: invalid sync state header in %s\n", svars->dname ); | 					error( "Error: invalid sync state header in %s\n", svars->dname ); | ||||||
| 					goto jbail; | 					goto jbail; | ||||||
| 				} | 				} | ||||||
|  | 				if (maxxnuid && xt != N) | ||||||
|  | 					goto sidefail; | ||||||
| 				goto gothdr; | 				goto gothdr; | ||||||
| 			} | 			} | ||||||
| 			uint uid; | 			uint uid; | ||||||
|  | @ -164,8 +167,19 @@ load_state( sync_vars_t *svars ) | ||||||
| 			} else if (!strcmp( buf1, "MaxPushedUid" )) { | 			} else if (!strcmp( buf1, "MaxPushedUid" )) { | ||||||
| 				svars->maxuid[N] = uid; | 				svars->maxuid[N] = uid; | ||||||
| 			} else if (!strcmp( buf1, "MaxExpiredFarUid" ) || !strcmp( buf1, "MaxExpiredMasterUid" ) /* Pre-1.4 legacy */) { | 			} else if (!strcmp( buf1, "MaxExpiredFarUid" ) || !strcmp( buf1, "MaxExpiredMasterUid" ) /* Pre-1.4 legacy */) { | ||||||
|  | 				if (xt != N) { | ||||||
|  | 				  sidefail: | ||||||
|  | 					error( "Error: state file %s does not match ExpireSide setting\n", svars->dname ); | ||||||
|  | 					goto jbail; | ||||||
|  | 				} | ||||||
|  | 				svars->maxxfuid = uid; | ||||||
|  | 			} else if (!strcmp( buf1, "MaxExpiredNearUid" )) { | ||||||
|  | 				if (xt != F) | ||||||
|  | 					goto sidefail; | ||||||
| 				svars->maxxfuid = uid; | 				svars->maxxfuid = uid; | ||||||
| 			} else if (!strcmp( buf1, "MaxExpiredSlaveUid" )) {  // Pre-1.3 legacy
 | 			} else if (!strcmp( buf1, "MaxExpiredSlaveUid" )) {  // Pre-1.3 legacy
 | ||||||
|  | 				if (xt != N) | ||||||
|  | 					goto sidefail; | ||||||
| 				maxxnuid = uid; | 				maxxnuid = uid; | ||||||
| 			} else { | 			} else { | ||||||
| 				error( "Error: unrecognized sync state header entry at %s:%d\n", svars->dname, line ); | 				error( "Error: unrecognized sync state header entry at %s:%d\n", svars->dname, line ); | ||||||
|  | @ -397,8 +411,8 @@ load_state( sync_vars_t *svars ) | ||||||
| 						break; | 						break; | ||||||
| 					case '~': | 					case '~': | ||||||
| 						srec->status = (srec->status & ~S_LOGGED) | t3; | 						srec->status = (srec->status & ~S_LOGGED) | t3; | ||||||
| 						if ((srec->status & S_EXPIRED) && svars->maxxfuid < srec->uid[F]) | 						if ((srec->status & S_EXPIRED) && svars->maxxfuid < srec->uid[xt^1]) | ||||||
| 							svars->maxxfuid = srec->uid[F]; | 							svars->maxxfuid = srec->uid[xt^1]; | ||||||
| 						debug( "status now %s\n", fmt_sts( srec->status ).str ); | 						debug( "status now %s\n", fmt_sts( srec->status ).str ); | ||||||
| 						break; | 						break; | ||||||
| 					case '_': | 					case '_': | ||||||
|  | @ -490,7 +504,9 @@ save_state( sync_vars_t *svars ) | ||||||
| 	         "FarUidValidity %u\nNearUidValidity %u\nMaxPulledUid %u\nMaxPushedUid %u\n", | 	         "FarUidValidity %u\nNearUidValidity %u\nMaxPulledUid %u\nMaxPushedUid %u\n", | ||||||
| 	         svars->uidval[F], svars->uidval[N], svars->maxuid[F], svars->maxuid[N] ); | 	         svars->uidval[F], svars->uidval[N], svars->maxuid[F], svars->maxuid[N] ); | ||||||
| 	if (svars->maxxfuid) | 	if (svars->maxxfuid) | ||||||
| 		Fprintf( svars->nfp, "MaxExpiredFarUid %u\n", svars->maxxfuid ); | 		Fprintf( svars->nfp, | ||||||
|  | 		         svars->chan->expire_side == N ? "MaxExpiredFarUid %u\n" : "MaxExpiredNearUid %u\n", | ||||||
|  | 		         svars->maxxfuid ); | ||||||
| 	Fprintf( svars->nfp, "\n" ); | 	Fprintf( svars->nfp, "\n" ); | ||||||
| 	for (sync_rec_t *srec = svars->srecs; srec; srec = srec->next) { | 	for (sync_rec_t *srec = svars->srecs; srec; srec = srec->next) { | ||||||
| 		if (srec->status & S_DEAD) | 		if (srec->status & S_DEAD) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue