commit d9a983add6c627f50f3894f938e4e4892476ad22
Author: Oswald Buddenhagen <[email protected]>
Date:   Mon Dec 29 02:08:48 2014 +0100

    add support for propagating folder deletions

 NEWS              |    2 +
 TODO              |    2 -
 src/config.c      |    1 +
 src/driver.h      |   13 ++++++
 src/drv_imap.c    |   52 ++++++++++++++++++++++++
 src/drv_maildir.c |   70 +++++++++++++++++++++++++++++++++
 src/main.c        |   22 +++++++----
 src/mbsync.1      |   20 ++++++++-
 src/sync.c        |   95 +++++++++++++++++++++++++++++++++++++++++----
 src/sync.h        |   12 +++--
 10 files changed, 264 insertions(+), 25 deletions(-)

diff --git a/NEWS b/NEWS
index fe8c6f5..5861575 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,8 @@ Support for Windows file systems has been added.
 
 Support for compressed data transfer has been added.
 
+Folder deletions can be propagated now.
+
 [1.1.0]
 
 Support for hierarchical mailboxes in Patterns.
diff --git a/TODO b/TODO
index 02d80b4..ec1cf2e 100644
--- a/TODO
+++ b/TODO
@@ -56,8 +56,6 @@ create dummies describing MIME structure of messages bigger 
than MaxSize.
 flagging the dummy would fetch the real message. possibly remove --renew.
 note that all interaction needs to happen on the slave side probably.
 
-propagate folder deletions. for safety, the target must be empty.
-
 don't SELECT boxes unless really needed; in particular not for appending,
 and in write-only mode not before changes are made.
 problem: UIDVALIDITY change detection is delayed, significantly complicating
diff --git a/src/config.c b/src/config.c
index 25080ed..fb9dc5f 100644
--- a/src/config.c
+++ b/src/config.c
@@ -149,6 +149,7 @@ static const struct {
 } boxOps[] = {
        { OP_EXPUNGE, "Expunge" },
        { OP_CREATE, "Create" },
+       { OP_REMOVE, "Remove" },
 };
 
 static int
diff --git a/src/driver.h b/src/driver.h
index 217d624..9416686 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -174,6 +174,19 @@ struct driver {
        void (*open_box)( store_t *ctx,
                          void (*cb)( int sts, void *aux ), void *aux );
 
+       /* Confirm that the open mailbox is empty. */
+       int (*confirm_box_empty)( store_t *ctx );
+
+       /* Delete the open mailbox. The mailbox is expected to be empty.
+        * Subfolders of the mailbox are *not* deleted.
+        * Some artifacts of the mailbox may remain, but they won't be
+        * recognized as a mailbox any more. */
+       void (*delete_box)( store_t *ctx,
+                           void (*cb)( int sts, void *aux ), void *aux );
+
+       /* Remove the last artifacts of the open mailbox, as far as possible. */
+       int (*finish_delete_box)( store_t *ctx );
+
        /* Invoked before load_box(), this informs the driver which operations 
(OP_*)
         * will be performed on the mailbox. The driver may extend the set by 
implicitly
         * needed or available operations. */
diff --git a/src/drv_imap.c b/src/drv_imap.c
index 2db2670..b9505dd 100644
--- a/src/drv_imap.c
+++ b/src/drv_imap.c
@@ -2166,6 +2166,55 @@ imap_create_box( store_t *gctx,
        free( buf );
 }
 
+/******************* imap_delete_box *******************/
+
+static int
+imap_confirm_box_empty( store_t *gctx )
+{
+       return gctx->count ? DRV_BOX_BAD : DRV_OK;
+}
+
+static void imap_delete_box_p2( imap_store_t *, struct imap_cmd *, int );
+
+static void
+imap_delete_box( store_t *gctx,
+                 void (*cb)( int sts, void *aux ), void *aux )
+{
+       imap_store_t *ctx = (imap_store_t *)gctx;
+       struct imap_cmd_simple *cmd;
+
+       INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux)
+       imap_exec( ctx, &cmd->gen, imap_delete_box_p2, "CLOSE" );
+}
+
+static void
+imap_delete_box_p2( imap_store_t *ctx, struct imap_cmd *gcmd, int response )
+{
+       struct imap_cmd_simple *cmdp = (struct imap_cmd_simple *)gcmd;
+       struct imap_cmd_simple *cmd;
+       char *buf;
+
+       if (response != RESP_OK) {
+               imap_done_simple_box( ctx, &cmdp->gen, response );
+               return;
+       }
+
+       if (prepare_box( &buf, ctx ) < 0) {
+               imap_done_simple_box( ctx, &cmdp->gen, RESP_NO );
+               return;
+       }
+       INIT_IMAP_CMD(imap_cmd_simple, cmd, cmdp->callback, cmdp->callback_aux)
+       imap_exec( ctx, &cmd->gen, imap_done_simple_box,
+                  "DELETE \"%\\s\"", buf );
+       free( buf );
+}
+
+static int
+imap_finish_delete_box( store_t *gctx ATTR_UNUSED )
+{
+       return DRV_OK;
+}
+
 /******************* imap_load_box *******************/
 
 static void
@@ -2810,6 +2859,9 @@ struct driver imap_driver = {
        imap_select_box,
        imap_create_box,
        imap_open_box,
+       imap_confirm_box_empty,
+       imap_delete_box,
+       imap_finish_delete_box,
        imap_prepare_load_box,
        imap_load_box,
        imap_fetch_msg,
diff --git a/src/drv_maildir.c b/src/drv_maildir.c
index 0a4ea9f..613590d 100644
--- a/src/drv_maildir.c
+++ b/src/drv_maildir.c
@@ -1076,6 +1076,73 @@ maildir_create_box( store_t *gctx,
        cb( maildir_validate( gctx->path, 1, (maildir_store_t *)gctx ), aux );
 }
 
+static int
+maildir_confirm_box_empty( store_t *gctx )
+{
+       maildir_store_t *ctx = (maildir_store_t *)gctx;
+       msglist_t msglist;
+
+       ctx->nexcs = ctx->minuid = ctx->maxuid = ctx->newuid = 0;
+
+       if (maildir_scan( ctx, &msglist ) != DRV_OK)
+               return DRV_BOX_BAD;
+       maildir_free_scan( &msglist );
+       return gctx->count ? DRV_BOX_BAD : DRV_OK;
+}
+
+static void
+maildir_delete_box( store_t *gctx,
+                    void (*cb)( int sts, void *aux ), void *aux )
+{
+       int i, bl, ret = DRV_OK;
+       struct stat st;
+       char buf[_POSIX_PATH_MAX];
+
+       bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", gctx->path );
+       if (stat( buf, &st )) {
+               if (errno != ENOENT) {
+                       sys_error( "Maildir error: cannot access mailbox '%s'", 
gctx->path );
+                       ret = DRV_BOX_BAD;
+               }
+       } else if (!S_ISDIR(st.st_mode)) {
+               error( "Maildir error: '%s' is no valid mailbox\n", gctx->path 
);
+               ret = DRV_BOX_BAD;
+       } else if ((ret = maildir_clear_tmp( buf, sizeof(buf), bl )) == DRV_OK) 
{
+               nfsnprintf( buf + bl, sizeof(buf) - bl, ".uidvalidity" );
+               if (unlink( buf ) && errno != ENOENT)
+                       goto badrm;
+#ifdef USE_DB
+               nfsnprintf( buf + bl, sizeof(buf) - bl, ".isyncuidmap.db" );
+               if (unlink( buf ) && errno != ENOENT)
+                       goto badrm;
+#endif
+               /* We delete cur/ last, as it is the indicator for a present 
mailbox.
+                * That way an interrupted operation can be resumed. */
+               for (i = 3; --i >= 0; ) {
+                       memcpy( buf + bl, subdirs[i], 4 );
+                       if (rmdir( buf ) && errno != ENOENT) {
+                         badrm:
+                               sys_error( "Maildir error: cannot remove '%s'", 
buf );
+                               ret = DRV_BOX_BAD;
+                               break;
+                       }
+               }
+       }
+       cb( ret, aux );
+}
+
+static int
+maildir_finish_delete_box( store_t *gctx )
+{
+       /* Subfolders are not deleted; the deleted folder is only "stripped of 
its mailboxness".
+        * Consequently, the rmdir may legitimately fail. This behavior follows 
the IMAP spec. */
+       if (rmdir( gctx->path ) && errno != ENOENT && errno != ENOTEMPTY) {
+               sys_error( "Maildir warning: cannot remove '%s'", gctx->path );
+               return DRV_BOX_BAD;
+       }
+       return DRV_OK;
+}
+
 static void
 maildir_prepare_load_box( store_t *gctx, int opts )
 {
@@ -1565,6 +1632,9 @@ struct driver maildir_driver = {
        maildir_select_box,
        maildir_create_box,
        maildir_open_box,
+       maildir_confirm_box_empty,
+       maildir_delete_box,
+       maildir_finish_delete_box,
        maildir_prepare_load_box,
        maildir_load_box,
        maildir_fetch_msg,
diff --git a/src/main.c b/src/main.c
index b93b8c5..6c0b822 100644
--- a/src/main.c
+++ b/src/main.c
@@ -299,7 +299,11 @@ main( int argc, char **argv )
                                                mvars->ops[S] |= op;
                                        else
                                                goto badopt;
-                                       mvars->ops[M] |= op & 
(XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE);
+                                       mvars->ops[M] |= op & 
(XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE);
+                               } else if (starts_with( opt, -1, "remove", 6 )) 
{
+                                       opt += 6;
+                                       op = OP_REMOVE|XOP_HAVE_REMOVE;
+                                       goto lcop;
                                } else if (starts_with( opt, -1, "expunge", 7 
)) {
                                        opt += 7;
                                        op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
@@ -308,6 +312,8 @@ main( int argc, char **argv )
                                        mvars->ops[M] |= XOP_HAVE_EXPUNGE;
                                else if (!strcmp( opt, "no-create" ))
                                        mvars->ops[M] |= XOP_HAVE_CREATE;
+                               else if (!strcmp( opt, "no-remove" ))
+                                       mvars->ops[M] |= XOP_HAVE_REMOVE;
                                else if (!strcmp( opt, "full" ))
                                        mvars->ops[M] |= 
XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH;
                                else if (!strcmp( opt, "noop" ))
@@ -386,8 +392,11 @@ main( int argc, char **argv )
                                ochar++;
                        else
                                cops |= op;
-                       mvars->ops[M] |= op & 
(XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE);
+                       mvars->ops[M] |= op & 
(XOP_HAVE_CREATE|XOP_HAVE_REMOVE|XOP_HAVE_EXPUNGE);
                        break;
+               case 'R':
+                       op = OP_REMOVE|XOP_HAVE_REMOVE;
+                       goto cop;
                case 'X':
                        op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
                        goto cop;
@@ -589,6 +598,7 @@ sync_chans( main_vars_t *mvars, int ent )
                }
                merge_actions( mvars->chan, mvars->ops, XOP_HAVE_TYPE, 
OP_MASK_TYPE, OP_MASK_TYPE );
                merge_actions( mvars->chan, mvars->ops, XOP_HAVE_CREATE, 
OP_CREATE, 0 );
+               merge_actions( mvars->chan, mvars->ops, XOP_HAVE_REMOVE, 
OP_REMOVE, 0 );
                merge_actions( mvars->chan, mvars->ops, XOP_HAVE_EXPUNGE, 
OP_EXPUNGE, 0 );
 
                mvars->state[M] = mvars->state[S] = ST_FRESH;
@@ -652,12 +662,8 @@ sync_chans( main_vars_t *mvars, int ent )
                                        present[t] = BOX_PRESENT;
                                        present[1-t] = BOX_ABSENT;
                                        mvars->boxes[t] = mbox->next;
-                                       if ((mvars->chan->ops[1-t] & 
OP_MASK_TYPE) && (mvars->chan->ops[1-t] & OP_CREATE)) {
-                                               if (sync_listed_boxes( mvars, 
mbox, present ))
-                                                       goto syncw;
-                                       } else {
-                                               free( mbox );
-                                       }
+                                       if (sync_listed_boxes( mvars, mbox, 
present ))
+                                               goto syncw;
                                }
                } else {
                        if (!mvars->list) {
diff --git a/src/mbsync.1 b/src/mbsync.1
index 2f7e7ed..e5ad1fb 100644
--- a/src/mbsync.1
+++ b/src/mbsync.1
@@ -58,6 +58,9 @@ and exit.
 \fB-C\fR[\fBm\fR][\fBs\fR], \fB--create\fR[\fB-master\fR|\fB-slave\fR]
 Override any \fBCreate\fR options from the config file. See below.
 .TP
+\fB-R\fR[\fBm\fR][\fBs\fR], \fB--remove\fR[\fB-master\fR|\fB-slave\fR]
+Override any \fBRemove\fR options from the config file. See below.
+.TP
 \fB-X\fR[\fBm\fR][\fBs\fR], \fB--expunge\fR[\fB-master\fR|\fB-slave\fR]
 Override any \fBExpunge\fR options from the config file. See below.
 .TP
@@ -483,7 +486,20 @@ Note that it is not allowed to assert a cell in two ways, 
e.g.
 \fBCreate\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR}
 Automatically create missing mailboxes [on the Master/Slave].
 Otherwise print an error message and skip that mailbox pair if a mailbox
-does not exist.
+and the corresponding sync state does not exist.
+(Global default: \fINone\fR)
+..
+.TP
+\fBRemove\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR}
+Propagate mailbox deletions [to the Master/Slave].
+Otherwise print an error message and skip that mailbox pair if a mailbox
+does not exist but the corresponding sync state does.
+.br
+For MailDir mailboxes it is sufficient to delete the cur/ subdirectory to
+mark them as deleted. This ensures compatibility with \fBSyncState *\fR.
+.br
+Note that for safety, non-empty mailboxes are never deleted.
+.br
 (Global default: \fINone\fR)
 ..
 .TP
@@ -503,7 +519,7 @@ date\fR) is actually the arrival time, but it is usually 
close enough.
 (Default: \fIno\fR)
 ..
 .P
-\fBSync\fR, \fBCreate\fR, \fBExpunge\fR,
+\fBSync\fR, \fBCreate\fR, \fBRemove\fR, \fBExpunge\fR,
 \fBMaxMessages\fR, and \fBCopyArrivalDate\fR
 can be used before any section for a global effect.
 The global settings are overridden by Channel-specific options,
diff --git a/src/sync.c b/src/sync.c
index 82d7f40..736f9ca 100644
--- a/src/sync.c
+++ b/src/sync.c
@@ -205,6 +205,8 @@ static int check_cancel( sync_vars_t *svars );
 #define ST_SELECTED        (1<<10)
 #define ST_DID_EXPUNGE     (1<<11)
 #define ST_CLOSING         (1<<12)
+#define ST_CONFIRMED       (1<<13)
+#define ST_PRESENT         (1<<14)
 
 
 static void
@@ -923,7 +925,20 @@ load_state( sync_vars_t *svars )
        return 1;
 }
 
+static void
+delete_state( sync_vars_t *svars )
+{
+       unlink( svars->nname );
+       unlink( svars->jname );
+       if (unlink( svars->dname ) || unlink( svars->lname )) {
+               sys_error( "Error: channel %s: sync state cannot be deleted", 
svars->chan->name );
+               svars->ret = SYNC_FAIL;
+       }
+}
+
 static void box_confirmed( int sts, void *aux );
+static void box_confirmed2( sync_vars_t *svars, int t );
+static void box_deleted( int sts, void *aux );
 static void box_created( int sts, void *aux );
 static void box_opened( int sts, void *aux );
 static void box_opened2( sync_vars_t *svars, int t );
@@ -988,7 +1003,7 @@ sync_boxes( store_t *ctx[], const char *names[], int 
present[], channel_conf_t *
        for (t = 0; ; t++) {
                info( "Opening %s box %s...\n", str_ms[t], svars->orig_name[t] 
);
                if (present[t] == BOX_ABSENT)
-                       box_confirmed( DRV_BOX_BAD, AUX );
+                       box_confirmed2( svars, t );
                else
                        svars->drv[t]->open_box( ctx[t], box_confirmed, AUX );
                if (t || check_cancel( svars ))
@@ -1008,16 +1023,80 @@ box_confirmed( int sts, void *aux )
        if (check_cancel( svars ))
                return;
 
-       if (sts == DRV_BOX_BAD) {
-               if (!(svars->chan->ops[t] & OP_CREATE)) {
-                       box_opened( sts, aux );
+       if (sts == DRV_OK)
+               svars->state[t] |= ST_PRESENT;
+       box_confirmed2( svars, t );
+}
+
+static void
+box_confirmed2( sync_vars_t *svars, int t )
+{
+       svars->state[t] |= ST_CONFIRMED;
+       if (!(svars->state[1-t] & ST_CONFIRMED))
+               return;
+
+       sync_ref( svars );
+       for (t = 0; ; t++) {
+               if (!(svars->state[t] & ST_PRESENT)) {
+                       if (!(svars->state[1-t] & ST_PRESENT)) {
+                               if (!svars->existing) {
+                                       error( "Error: channel %s: both master 
%s and slave %s cannot be opened.\n",
+                                              svars->chan->name, 
svars->orig_name[M], svars->orig_name[S] );
+                                 bail:
+                                       svars->ret = SYNC_FAIL;
+                               } else {
+                                       /* This can legitimately happen if a 
deletion propagation was interrupted.
+                                        * We have no place to record this 
transaction, so we just assume it.
+                                        * Of course this bears the danger of 
clearing the state if both mailboxes
+                                        * temorarily cannot be opened for some 
weird reason (while the stores can). */
+                                       delete_state( svars );
+                               }
+                         done:
+                               sync_bail( svars );
+                               break;
+                       }
+                       if (svars->existing) {
+                               if (!(svars->chan->ops[1-t] & OP_REMOVE)) {
+                                       error( "Error: channel %s: %s %s cannot 
be opened.\n",
+                                              svars->chan->name, str_ms[t], 
svars->orig_name[t] );
+                                       goto bail;
+                               }
+                               if (svars->drv[1-t]->confirm_box_empty( 
svars->ctx[1-t] ) != DRV_OK) {
+                                       warn( "Warning: channel %s: %s %s 
cannot be opened and %s %s not empty.\n",
+                                             svars->chan->name, str_ms[t], 
svars->orig_name[t], str_ms[1-t], svars->orig_name[1-t] );
+                                       goto done;
+                               }
+                               info( "Deleting %s %s...\n", str_ms[1-t], 
svars->orig_name[1-t] );
+                               svars->drv[1-t]->delete_box( svars->ctx[1-t], 
box_deleted, INV_AUX );
+                       } else {
+                               if (!(svars->chan->ops[t] & OP_CREATE)) {
+                                       box_opened( DRV_BOX_BAD, AUX );
+                               } else {
+                                       info( "Creating %s %s...\n", str_ms[t], 
svars->orig_name[t] );
+                                       svars->drv[t]->create_box( 
svars->ctx[t], box_created, AUX );
+                               }
+                       }
                } else {
-                       info( "Creating %s %s...\n", str_ms[t], 
svars->orig_name[t] );
-                       svars->drv[t]->create_box( svars->ctx[t], box_created, 
AUX );
+                       box_opened2( svars, t );
                }
-       } else {
-               box_opened2( svars, t );
+               if (t || check_cancel( svars ))
+                       break;
        }
+       sync_deref( svars );
+}
+
+static void
+box_deleted( int sts, void *aux )
+{
+       DECL_SVARS;
+
+       if (check_ret( sts, aux ))
+               return;
+       INIT_SVARS(aux);
+
+       delete_state( svars );
+       svars->drv[t]->finish_delete_box( svars->ctx[t] );
+       sync_bail( svars );
 }
 
 static void
diff --git a/src/sync.h b/src/sync.h
index f3b8039..65a23ce 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -35,12 +35,14 @@
 #define  OP_MASK_TYPE      (OP_NEW|OP_RENEW|OP_DELETE|OP_FLAGS) /* asserted in 
the target ops */
 #define OP_EXPUNGE         (1<<4)
 #define OP_CREATE          (1<<5)
-#define XOP_PUSH           (1<<6)
-#define XOP_PULL           (1<<7)
+#define OP_REMOVE          (1<<6)
+#define XOP_PUSH           (1<<8)
+#define XOP_PULL           (1<<9)
 #define  XOP_MASK_DIR      (XOP_PUSH|XOP_PULL)
-#define XOP_HAVE_TYPE      (1<<8)
-#define XOP_HAVE_EXPUNGE   (1<<9)
-#define XOP_HAVE_CREATE    (1<<10)
+#define XOP_HAVE_TYPE      (1<<10)
+#define XOP_HAVE_EXPUNGE   (1<<11)
+#define XOP_HAVE_CREATE    (1<<12)
+#define XOP_HAVE_REMOVE    (1<<13)
 
 typedef struct channel_conf {
        struct channel_conf *next;

------------------------------------------------------------------------------
New Year. New Location. New Benefits. New Data Center in Ashburn, VA.
GigeNET is offering a free month of service with a new server in Ashburn.
Choose from 2 high performing configs, both with 100TB of bandwidth.
Higher redundancy.Lower latency.Increased capacity.Completely compliant.
http://p.sf.net/sfu/gigenet
_______________________________________________
isync-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/isync-devel

Reply via email to