commit abb596709b6fd4f1d020370f5f546db0d932f324
Author: Oswald Buddenhagen <o...@users.sf.net>
Date:   Thu May 5 18:33:59 2022 +0200

    add --dry-run mode
    
    REFMAIL: 20211130142121.xon5oygrpdfj5...@fastmail.com

 NEWS                 |  2 ++
 src/common.h         |  2 ++
 src/drv_proxy.c      | 82 +++++++++++++++++++++++++++++++++++++++-----
 src/drv_proxy_gen.pl | 34 ++++++++++++++++++
 src/main.c           |  6 ++++
 src/main_sync.c      |  8 +++--
 src/mbsync.1         |  5 +++
 src/sync_state.c     | 13 +++++++
 8 files changed, 142 insertions(+), 10 deletions(-)

diff --git a/NEWS b/NEWS
index 9faa4825..bbee6112 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,8 @@ Added new sync operation 'Old'.
 
 Added support for mirroring deletions more accurately.
 
+Added --dry-run option.
+
 [1.4.0]
 
 The 'isync' compatibility wrapper was removed.
diff --git a/src/common.h b/src/common.h
index c7ad08c5..92d1e89e 100644
--- a/src/common.h
+++ b/src/common.h
@@ -113,6 +113,8 @@ BIT_ENUM(
 
        PROGRESS,
 
+       DRYRUN,
+
        ZERODELAY,
        KEEPJOURNAL,
        FORCEJOURNAL,
diff --git a/src/drv_proxy.c b/src/drv_proxy.c
index 0cca3a52..37dbc34f 100644
--- a/src/drv_proxy.c
+++ b/src/drv_proxy.c
@@ -23,6 +23,8 @@ typedef union proxy_store {
                gen_cmd_t *pending_cmds, **pending_cmds_append;
                gen_cmd_t *check_cmds, **check_cmds_append;
                wakeup_t wakeup;
+               uint fake_nextuid;
+               char is_fake;  // Was "created" by dry-run
                char force_async;
 
                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;
 
-       @type@rv = ctx->real_driver->@name@( ctx->real_store );
-       debug( "%sCalled @name@, ret=@fmt@\n", ctx->label, rv );
+       @type@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;
 }
 //# END
@@ -151,9 +156,12 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ )
        proxy_store_t *ctx = (proxy_store_t *)gctx;
 
        @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@
-       @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@ );
        return rv;
 }
@@ -165,9 +173,11 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ )
        proxy_store_t *ctx = (proxy_store_t *)gctx;
 
        @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@
-       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 );
        @action@
 }
@@ -226,9 +236,11 @@ proxy_do_@name@( gen_cmd_t *gcmd )
        proxy_store_t *ctx = cmd->ctx;
 
        @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@
-       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 );
 }
 
@@ -252,10 +264,51 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void 
(*cb)( @decl_cb_args@v
                debug( "  %s\n", box->string );
 //# 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 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 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_pass_args , fmt_opts( opts ).str
 //# 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" );
        }
 //# 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_pass_cb_args , total_msgs, recent_msgs
 //# 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_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_pass_cb_args , fmt_flags( cmd->data->flags ).str, 
(long long)cmd->data->date, cmd->data->len
 //# 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 );
        }
 //# 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 set_msg_flags_checked 1
 //# 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_driable 1
 //# DEFINE set_msg_flags_counted 1
 
 //# DEFINE trash_msg_print_fmt_args , uid=%u
 //# DEFINE trash_msg_print_pass_args , cmd->msg->uid
+//# DEFINE trash_msg_driable 1
 //# DEFINE trash_msg_counted 1
 
+//# DEFINE close_box_driable 1
+//# DEFINE close_box_fake_cb_args , 0
 //# DEFINE close_box_counted 1
 
 //# DEFINE commit_cmds_print_args
diff --git a/src/drv_proxy_gen.pl b/src/drv_proxy_gen.pl
index 3fda58cb..6b9c8685 100755
--- a/src/drv_proxy_gen.pl
+++ b/src/drv_proxy_gen.pl
@@ -175,15 +175,49 @@ for (@ptypes) {
                $replace{'print_pass_args'} = $replace{'pass_args'} = 
$pass_args;
                $replace{'print_fmt_args'} = make_format($cmd_args);
        }
+       my ($fake_cond, $fake_invoke, $fake_cb_args, $post_invoke) = (undef, 
"", "", "");
        for (keys %defines) {
                next if (!/^${cmd_name}_(.*)$/);
                my ($key, $val) = ($1, delete $defines{$_});
                if ($key eq 'counted') {
                        $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 {
                        $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 $text = $templates{$template};
        if ($inc_tpl) {
diff --git a/src/main.c b/src/main.c
index 37304fab..ba467ac9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -46,6 +46,7 @@ PACKAGE " " VERSION " - mailbox synchronizer\n"
 "  -x, --expunge-solo  expunge deleted messages that are not paired\n"
 "  -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n"
 "  -D, --debug         debugging modes (see manual)\n"
+"  -y, --dry-run               do not actually modify anything\n"
 "  -V, --verbose               display what is happening\n"
 "  -q, --quiet         don't display progress counters\n"
 "  -v, --version               display version\n"
@@ -217,6 +218,8 @@ main( int argc, char **argv )
                                        else
                                                goto badopt;
                                        DFlags |= op;
+                               } else if (!strcmp( opt, "dry-run" )) {
+                                       DFlags |= DRYRUN;
                                } else if (!strcmp( opt, "pull" )) {
                                        cops |= XOP_PULL, mvars->ops[F] |= 
XOP_HAVE_TYPE;
                                } else if (!strcmp( opt, "push" )) {
@@ -454,6 +457,9 @@ main( int argc, char **argv )
                                op = DEBUG_ALL;
                        DFlags |= op;
                        break;
+               case 'y':
+                       DFlags |= DRYRUN;
+                       break;
                case 'T':
                        for (; *ochar; ) {
                                switch (*ochar++) {
diff --git a/src/main_sync.c b/src/main_sync.c
index 0c7f6dbf..3d3d90f6 100644
--- a/src/main_sync.c
+++ b/src/main_sync.c
@@ -50,10 +50,14 @@ summary( void )
        printf( "Processed %d box(es) in %d channel(s)", boxes_done, chans_done 
);
        for (int t = 2; --t >= 0; ) {
                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] );
                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] );
        }
        puts( "." );
diff --git a/src/mbsync.1 b/src/mbsync.1
index dd0ad981..79a6afba 100644
--- a/src/mbsync.1
+++ b/src/mbsync.1
@@ -74,6 +74,11 @@ Display a summary of command line options.
 \fB-v\fR, \fB--version\fR
 Display version information.
 .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
 Enable \fIverbose\fR mode, which displays what is currently happening.
 .TP
diff --git a/src/sync_state.c b/src/sync_state.c
index 3329efd0..385d089f 100644
--- a/src/sync_state.c
+++ b/src/sync_state.c
@@ -77,6 +77,9 @@ lock_state( sync_vars_t *svars )
 {
        struct flock lck;
 
+       if (DFlags & DRYRUN)
+               return 1;
+
        if (svars->lfd >= 0)
                return 1;
        memset( &lck, 0, sizeof(lck) );
@@ -451,6 +454,8 @@ jFprintf( sync_vars_t *svars, const char *msg, ... )
        va_list va;
 
        if (!svars->jfp) {
+               if (DFlags & DRYRUN)
+                       goto dryout;
                create_state( svars );
                if (!(svars->jfp = fopen( svars->jname, svars->replayed ? "a" : 
"w" ))) {
                        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 );
        vFprintf( svars->jfp, msg, va );
        va_end( va );
+  dryout:
        countStep();
        JCount++;
 }
@@ -474,6 +480,10 @@ save_state( sync_vars_t *svars )
        if (!svars->jfp && !svars->replayed)
                return;
 
+       // jfp is NULL in this case anyway, but we might have replayed.
+       if (DFlags & DRYRUN)
+               return;
+
        if (!svars->nfp)
                create_state( svars );
        Fprintf( svars->nfp,
@@ -506,6 +516,9 @@ save_state( sync_vars_t *svars )
 void
 delete_state( sync_vars_t *svars )
 {
+       if (DFlags & DRYRUN)
+               return;
+
        unlink( svars->nname );
        unlink( svars->jname );
        if (unlink( svars->dname ) || unlink( svars->lname )) {


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

Reply via email to