add --dry-run mode

REFMAIL: 20211130142121.xon5oygrpdfj5s2t@fastmail.com
This commit is contained in:
Oswald Buddenhagen 2022-05-05 18:33:59 +02:00
parent 5b9256f5dc
commit abb596709b
8 changed files with 142 additions and 10 deletions

2
NEWS
View File

@ -22,6 +22,8 @@ Added new sync operation 'Old'.
Added support for mirroring deletions more accurately. Added support for mirroring deletions more accurately.
Added --dry-run option.
[1.4.0] [1.4.0]
The 'isync' compatibility wrapper was removed. The 'isync' compatibility wrapper was removed.

View File

@ -113,6 +113,8 @@ BIT_ENUM(
PROGRESS, PROGRESS,
DRYRUN,
ZERODELAY, ZERODELAY,
KEEPJOURNAL, KEEPJOURNAL,
FORCEJOURNAL, FORCEJOURNAL,

View File

@ -23,6 +23,8 @@ typedef union proxy_store {
gen_cmd_t *pending_cmds, **pending_cmds_append; gen_cmd_t *pending_cmds, **pending_cmds_append;
gen_cmd_t *check_cmds, **check_cmds_append; gen_cmd_t *check_cmds, **check_cmds_append;
wakeup_t wakeup; wakeup_t wakeup;
uint fake_nextuid;
char is_fake; // Was "created" by dry-run
char force_async; char force_async;
void (*expunge_callback)( message_t *msg, void *aux ); void (*expunge_callback)( message_t *msg, void *aux );
@ -139,8 +141,11 @@ static @type@proxy_@name@( store_t *gctx )
{ {
proxy_store_t *ctx = (proxy_store_t *)gctx; proxy_store_t *ctx = (proxy_store_t *)gctx;
@type@rv = ctx->real_driver->@name@( ctx->real_store ); @type@rv;
debug( "%sCalled @name@, ret=@fmt@\n", ctx->label, rv ); @pre_invoke@
@indent_invoke@rv = ctx->real_driver->@name@( ctx->real_store );
@post_invoke@
debug( "%sCalled @name@@print_fmt_dry@, ret=@fmt@\n", ctx->label@print_pass_dry@, rv );
return rv; return rv;
} }
//# END //# END
@ -151,9 +156,12 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ )
proxy_store_t *ctx = (proxy_store_t *)gctx; proxy_store_t *ctx = (proxy_store_t *)gctx;
@pre_print_args@ @pre_print_args@
debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ ); debug( "%sEnter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label@print_pass_dry@@print_pass_args@ );
@print_args@ @print_args@
@type@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ ); @type@rv;
@pre_invoke@
@indent_invoke@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ );
@post_invoke@
debug( "%sLeave @name@, ret=@print_fmt_ret@\n", ctx->label, @print_pass_ret@ ); debug( "%sLeave @name@, ret=@print_fmt_ret@\n", ctx->label, @print_pass_ret@ );
return rv; return rv;
} }
@ -165,9 +173,11 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ )
proxy_store_t *ctx = (proxy_store_t *)gctx; proxy_store_t *ctx = (proxy_store_t *)gctx;
@pre_print_args@ @pre_print_args@
debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ ); debug( "%sEnter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label@print_pass_dry@@print_pass_args@ );
@print_args@ @print_args@
ctx->real_driver->@name@( ctx->real_store@pass_args@ ); @pre_invoke@
@indent_invoke@ctx->real_driver->@name@( ctx->real_store@pass_args@ );
@post_invoke@
debug( "%sLeave @name@\n", ctx->label ); debug( "%sLeave @name@\n", ctx->label );
@action@ @action@
} }
@ -226,9 +236,11 @@ proxy_do_@name@( gen_cmd_t *gcmd )
proxy_store_t *ctx = cmd->ctx; proxy_store_t *ctx = cmd->ctx;
@pre_print_args@ @pre_print_args@
debug( "%s[% 2d] Enter @name@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_args@ ); debug( "%s[% 2d] Enter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_dry@@print_pass_args@ );
@print_args@ @print_args@
ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd ); @pre_invoke@
@indent_invoke@ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd );
@post_invoke@
debug( "%s[% 2d] Leave @name@\n", ctx->label, cmd->tag ); debug( "%s[% 2d] Leave @name@\n", ctx->label, cmd->tag );
} }
@ -252,10 +264,51 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
debug( " %s\n", box->string ); debug( " %s\n", box->string );
//# END //# END
//# DEFINE select_box_pre_invoke
ctx->is_fake = 0;
//# END
//# DEFINE create_box_driable 1
//# DEFINE create_box_fake_invoke
ctx->is_fake = 1;
//# END
//# DEFINE create_box_counted 1 //# DEFINE create_box_counted 1
//# DEFINE open_box_fakeable 1
//# DEFINE open_box_fake_invoke
ctx->fake_nextuid = 1;
//# END
//# DEFINE open_box_fake_cb_args , 1
//# DEFINE get_uidnext_fakeable 1
//# DEFINE get_uidnext_fake_invoke
rv = ctx->fake_nextuid;
//# END
//# DEFINE get_uidnext_post_real_invoke
ctx->fake_nextuid = rv;
//# END
//# DEFINE get_supported_flags_fakeable 1
//# DEFINE get_supported_flags_fake_invoke
rv = 255;
//# END
//# DEFINE confirm_box_empty_fakeable 1
//# DEFINE confirm_box_empty_fake_invoke
rv = 1;
//# END
//# DEFINE delete_box_driable 1
//# DEFINE delete_box_fake_invoke
ctx->is_fake = 0;
//# END
//# DEFINE delete_box_counted 1 //# DEFINE delete_box_counted 1
//# DEFINE finish_delete_box_driable 1
//# DEFINE finish_delete_box_fake_invoke
rv = DRV_OK;
//# END
//# DEFINE prepare_load_box_print_fmt_args , opts=%s //# DEFINE prepare_load_box_print_fmt_args , opts=%s
//# DEFINE prepare_load_box_print_pass_args , fmt_opts( opts ).str //# DEFINE prepare_load_box_print_pass_args , fmt_opts( opts ).str
//# DEFINE prepare_load_box_print_fmt_ret %s //# DEFINE prepare_load_box_print_fmt_ret %s
@ -274,6 +327,8 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
debug( "\n" ); debug( "\n" );
} }
//# END //# END
//# DEFINE load_box_fakeable 1
//# DEFINE load_box_fake_cb_args , NULL, 0, 0
//# DEFINE load_box_print_fmt_cb_args , total=%d, recent=%d //# DEFINE load_box_print_fmt_cb_args , total=%d, recent=%d
//# DEFINE load_box_print_pass_cb_args , total_msgs, recent_msgs //# DEFINE load_box_print_pass_cb_args , total_msgs, recent_msgs
//# DEFINE load_box_print_cb_args //# DEFINE load_box_print_cb_args
@ -297,6 +352,11 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
//# DEFINE fetch_msg_print_fmt_args , uid=%u, want_flags=%s, want_date=%s //# DEFINE fetch_msg_print_fmt_args , uid=%u, want_flags=%s, want_date=%s
//# DEFINE fetch_msg_print_pass_args , cmd->msg->uid, !(cmd->msg->status & M_FLAGS) ? "yes" : "no", cmd->data->date ? "yes" : "no" //# DEFINE fetch_msg_print_pass_args , cmd->msg->uid, !(cmd->msg->status & M_FLAGS) ? "yes" : "no", cmd->data->date ? "yes" : "no"
//# DEFINE fetch_msg_driable 1
//# DEFINE fetch_msg_fake_invoke
cmd->data->data = strdup( "" );
cmd->data->len = 0;
//# END
//# DEFINE fetch_msg_print_fmt_cb_args , flags=%s, date=%lld, size=%u //# DEFINE fetch_msg_print_fmt_cb_args , flags=%s, date=%lld, size=%u
//# DEFINE fetch_msg_print_pass_cb_args , fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len //# DEFINE fetch_msg_print_pass_cb_args , fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len
//# DEFINE fetch_msg_print_cb_args //# DEFINE fetch_msg_print_cb_args
@ -318,17 +378,23 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
fflush( stdout ); fflush( stdout );
} }
//# END //# END
//# DEFINE store_msg_driable 1
//# DEFINE store_msg_fake_cb_args , cmd->to_trash ? 0 : ctx->fake_nextuid++
//# DEFINE store_msg_counted 1 //# DEFINE store_msg_counted 1
//# DEFINE set_msg_flags_checked 1 //# DEFINE set_msg_flags_checked 1
//# DEFINE set_msg_flags_print_fmt_args , uid=%u, add=%s, del=%s //# DEFINE set_msg_flags_print_fmt_args , uid=%u, add=%s, del=%s
//# DEFINE set_msg_flags_print_pass_args , cmd->uid, fmt_flags( cmd->add ).str, fmt_flags( cmd->del ).str //# DEFINE set_msg_flags_print_pass_args , cmd->uid, fmt_flags( cmd->add ).str, fmt_flags( cmd->del ).str
//# DEFINE set_msg_flags_driable 1
//# DEFINE set_msg_flags_counted 1 //# DEFINE set_msg_flags_counted 1
//# DEFINE trash_msg_print_fmt_args , uid=%u //# DEFINE trash_msg_print_fmt_args , uid=%u
//# DEFINE trash_msg_print_pass_args , cmd->msg->uid //# DEFINE trash_msg_print_pass_args , cmd->msg->uid
//# DEFINE trash_msg_driable 1
//# DEFINE trash_msg_counted 1 //# DEFINE trash_msg_counted 1
//# DEFINE close_box_driable 1
//# DEFINE close_box_fake_cb_args , 0
//# DEFINE close_box_counted 1 //# DEFINE close_box_counted 1
//# DEFINE commit_cmds_print_args //# DEFINE commit_cmds_print_args

View File

@ -175,15 +175,49 @@ for (@ptypes) {
$replace{'print_pass_args'} = $replace{'pass_args'} = $pass_args; $replace{'print_pass_args'} = $replace{'pass_args'} = $pass_args;
$replace{'print_fmt_args'} = make_format($cmd_args); $replace{'print_fmt_args'} = make_format($cmd_args);
} }
my ($fake_cond, $fake_invoke, $fake_cb_args, $post_invoke) = (undef, "", "", "");
for (keys %defines) { for (keys %defines) {
next if (!/^${cmd_name}_(.*)$/); next if (!/^${cmd_name}_(.*)$/);
my ($key, $val) = ($1, delete $defines{$_}); my ($key, $val) = ($1, delete $defines{$_});
if ($key eq 'counted') { if ($key eq 'counted') {
$replace{count_step} = "countStep();\n"; $replace{count_step} = "countStep();\n";
} elsif ($key eq 'fakeable') {
$fake_cond = "ctx->is_fake";
$replace{print_pass_dry} = ', '.$fake_cond.' ? " [FAKE]" : ""';
} elsif ($key eq 'driable') {
$fake_cond = "DFlags & DRYRUN";
$replace{print_pass_dry} = ', ('.$fake_cond.') ? " [DRY]" : ""';
} elsif ($key eq 'fake_invoke') {
$fake_invoke = $val;
} elsif ($key eq 'fake_cb_args') {
$fake_cb_args = $val;
} elsif ($key eq 'post_real_invoke') {
$post_invoke = $val;
} else { } else {
$replace{$key} = $val; $replace{$key} = $val;
} }
} }
if (defined($fake_cond)) {
$replace{print_fmt_dry} = '%s';
if ($inc_tpl eq 'CALLBACK_STS') {
$fake_invoke .= "proxy_${cmd_name}_cb( DRV_OK${fake_cb_args}, cmd );\n";
} elsif (length($fake_cb_args)) {
die("Unexpected fake callback arguments to $cmd_name\n");
}
my $num_fake = $fake_invoke =~ s/^(?=.)/\t/gsm;
my $num_real = $post_invoke =~ s/^(?=.)/\t/gsm;
my $pre_invoke = "if (".$fake_cond.")";
if ($num_fake > 1 || $num_real) {
$pre_invoke .= " {";
$fake_invoke .= "} else {\n";
$post_invoke .= "}\n";
} else {
$fake_invoke .= "else\n";
}
$replace{pre_invoke} = $pre_invoke."\n".$fake_invoke;
$replace{indent_invoke} = "\t";
$replace{post_invoke} = $post_invoke;
}
my %used; my %used;
my $text = $templates{$template}; my $text = $templates{$template};
if ($inc_tpl) { if ($inc_tpl) {

View File

@ -46,6 +46,7 @@ PACKAGE " " VERSION " - mailbox synchronizer\n"
" -x, --expunge-solo expunge deleted messages that are not paired\n" " -x, --expunge-solo expunge deleted messages that are not paired\n"
" -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n" " -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n"
" -D, --debug debugging modes (see manual)\n" " -D, --debug debugging modes (see manual)\n"
" -y, --dry-run do not actually modify anything\n"
" -V, --verbose display what is happening\n" " -V, --verbose display what is happening\n"
" -q, --quiet don't display progress counters\n" " -q, --quiet don't display progress counters\n"
" -v, --version display version\n" " -v, --version display version\n"
@ -217,6 +218,8 @@ main( int argc, char **argv )
else else
goto badopt; goto badopt;
DFlags |= op; DFlags |= op;
} else if (!strcmp( opt, "dry-run" )) {
DFlags |= DRYRUN;
} else if (!strcmp( opt, "pull" )) { } else if (!strcmp( opt, "pull" )) {
cops |= XOP_PULL, mvars->ops[F] |= XOP_HAVE_TYPE; cops |= XOP_PULL, mvars->ops[F] |= XOP_HAVE_TYPE;
} else if (!strcmp( opt, "push" )) { } else if (!strcmp( opt, "push" )) {
@ -454,6 +457,9 @@ main( int argc, char **argv )
op = DEBUG_ALL; op = DEBUG_ALL;
DFlags |= op; DFlags |= op;
break; break;
case 'y':
DFlags |= DRYRUN;
break;
case 'T': case 'T':
for (; *ochar; ) { for (; *ochar; ) {
switch (*ochar++) { switch (*ochar++) {

View File

@ -50,10 +50,14 @@ summary( void )
printf( "Processed %d box(es) in %d channel(s)", boxes_done, chans_done ); printf( "Processed %d box(es) in %d channel(s)", boxes_done, chans_done );
for (int t = 2; --t >= 0; ) { for (int t = 2; --t >= 0; ) {
if (ops_any[t]) if (ops_any[t])
printf( ",\n%sed %d new message(s) and %d flag update(s)", printf( (DFlags & DRYRUN) ?
",\nwould %s %d new message(s) and %d flag update(s)" :
",\n%sed %d new message(s) and %d flag update(s)",
str_hl[t], new_done[t], flags_done[t] ); str_hl[t], new_done[t], flags_done[t] );
if (trash_any[t]) if (trash_any[t])
printf( ",\nmoved %d %s message(s) to trash", printf( (DFlags & DRYRUN) ?
",\nwould move %d %s message(s) to trash" :
",\nmoved %d %s message(s) to trash",
trash_done[t], str_fn[t] ); trash_done[t], str_fn[t] );
} }
puts( "." ); puts( "." );

View File

@ -74,6 +74,11 @@ Display a summary of command line options.
\fB-v\fR, \fB--version\fR \fB-v\fR, \fB--version\fR
Display version information. Display version information.
.TP .TP
\fB-y\fR, \fB--dry-run\fR
Enter simulation mode: the Channel status is queried and all required
operations are determined, but no modifications are actually made
to either the mailboxes or the state files.
.TP
\fB-V\fR, \fB--verbose\fR \fB-V\fR, \fB--verbose\fR
Enable \fIverbose\fR mode, which displays what is currently happening. Enable \fIverbose\fR mode, which displays what is currently happening.
.TP .TP

View File

@ -77,6 +77,9 @@ lock_state( sync_vars_t *svars )
{ {
struct flock lck; struct flock lck;
if (DFlags & DRYRUN)
return 1;
if (svars->lfd >= 0) if (svars->lfd >= 0)
return 1; return 1;
memset( &lck, 0, sizeof(lck) ); memset( &lck, 0, sizeof(lck) );
@ -451,6 +454,8 @@ jFprintf( sync_vars_t *svars, const char *msg, ... )
va_list va; va_list va;
if (!svars->jfp) { if (!svars->jfp) {
if (DFlags & DRYRUN)
goto dryout;
create_state( svars ); create_state( svars );
if (!(svars->jfp = fopen( svars->jname, svars->replayed ? "a" : "w" ))) { if (!(svars->jfp = fopen( svars->jname, svars->replayed ? "a" : "w" ))) {
sys_error( "Error: cannot create journal %s", svars->jname ); sys_error( "Error: cannot create journal %s", svars->jname );
@ -463,6 +468,7 @@ jFprintf( sync_vars_t *svars, const char *msg, ... )
va_start( va, msg ); va_start( va, msg );
vFprintf( svars->jfp, msg, va ); vFprintf( svars->jfp, msg, va );
va_end( va ); va_end( va );
dryout:
countStep(); countStep();
JCount++; JCount++;
} }
@ -474,6 +480,10 @@ save_state( sync_vars_t *svars )
if (!svars->jfp && !svars->replayed) if (!svars->jfp && !svars->replayed)
return; return;
// jfp is NULL in this case anyway, but we might have replayed.
if (DFlags & DRYRUN)
return;
if (!svars->nfp) if (!svars->nfp)
create_state( svars ); create_state( svars );
Fprintf( svars->nfp, Fprintf( svars->nfp,
@ -506,6 +516,9 @@ save_state( sync_vars_t *svars )
void void
delete_state( sync_vars_t *svars ) delete_state( sync_vars_t *svars )
{ {
if (DFlags & DRYRUN)
return;
unlink( svars->nname ); unlink( svars->nname );
unlink( svars->jname ); unlink( svars->jname );
if (unlink( svars->dname ) || unlink( svars->lname )) { if (unlink( svars->dname ) || unlink( svars->lname )) {