diff --git a/src/config.c b/src/config.c index ed0466f..658d7f8 100644 --- a/src/config.c +++ b/src/config.c @@ -430,7 +430,18 @@ parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err ) store->max_size = parse_size( cfg ); else if (!strcasecmp( "MapInbox", cfg->cmd )) store->map_inbox = nfstrdup( cfg->val ); - else { + else if (!strcasecmp( "Flatten", cfg->cmd )) { + int sl = strlen( cfg->val ); + if (sl != 1) { + error( "%s:%d: malformed flattened hierarchy delimiter\n", cfg->file, cfg->line ); + *err = 1; + } else if (cfg->val[0] == '/') { + error( "%s:%d: flattened hierarchy delimiter cannot be the canonical delimiter '/'\n", cfg->file, cfg->line ); + *err = 1; + } else { + store->flat_delim = cfg->val[0]; + } + } else { error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd ); *err = 1; } diff --git a/src/isync.h b/src/isync.h index 28b3b8e..efb365a 100644 --- a/src/isync.h +++ b/src/isync.h @@ -142,6 +142,7 @@ typedef struct store_conf { const char *trash; unsigned max_size; /* off_t is overkill */ unsigned trash_remote_new:1, trash_only_new:1; + char flat_delim; } store_conf_t; typedef struct string_list { @@ -216,7 +217,8 @@ typedef struct store { void *bad_callback_aux; /* currently open mailbox */ - const char *name; /* foreign! maybe preset? */ + const char *orig_name; /* foreign! maybe preset? */ + char *name; /* foreign! maybe preset? */ char *path; /* own */ message_t *msgs; /* own */ int uidvalidity; diff --git a/src/main.c b/src/main.c index d3d0302..1535a30 100644 --- a/src/main.c +++ b/src/main.c @@ -718,12 +718,21 @@ static void store_listed( int sts, void *aux ) { MVARS(aux) + string_list_t *box; switch (sts) { case DRV_CANCELED: return; case DRV_OK: mvars->ctx[t]->listed = 1; + if (mvars->ctx[t]->conf->flat_delim) { + for (box = mvars->ctx[t]->boxes; box; box = box->next) { + if (map_name( box->string, mvars->ctx[t]->conf->flat_delim, '/' ) < 0) { + error( "Error: flattened mailbox name '%s' contains canonical hierarchy delimiter\n", box->string ); + mvars->ret = mvars->skip = 1; + } + } + } if (mvars->ctx[t]->conf->map_inbox) add_string_list( &mvars->ctx[t]->boxes, mvars->ctx[t]->conf->map_inbox ); break; diff --git a/src/mbsync.1 b/src/mbsync.1 index 5d41e1a..7f653b6 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -150,6 +150,15 @@ Channels section. This virtual mailbox does not support subfolders. .. .TP +\fBFlatten\fR \fIdelim\fR +Flatten the hierarchy within this Store by substituting the canonical +hierarchy delimiter \fB/\fR with \fIdelim\fR. +This can be useful when the MUA used to access the Store provides +suboptimal handling of hierarchical mailboxes, as is the case with +\fBMutt\fR. +A common choice for the delimiter is \fB.\fR. +.. +.TP \fBTrash\fR \fImailbox\fR Specifies a mailbox (relative to \fBPath\fR) to copy deleted messages to prior to expunging. See \fBINHERENT PROBLEMS\fR below. @@ -318,6 +327,9 @@ This option is meaningless if a \fBPath\fR was specified. \fBPathDelimiter\fR \fIdelim\fR Specify the server's hierarchy delimiter character. (Default: taken from the server's first "personal" NAMESPACE) +.br +Do \fBNOT\fR abuse this to re-interpret the hierarchy. +Use \fBFlatten\fR instead. .. .SS Channels .TP diff --git a/src/sync.c b/src/sync.c index e7f0518..45fc2cc 100644 --- a/src/sync.c +++ b/src/sync.c @@ -467,6 +467,7 @@ stats( sync_vars_t *svars ) static void sync_bail( sync_vars_t *svars ); static void sync_bail1( sync_vars_t *svars ); static void sync_bail2( sync_vars_t *svars ); +static void sync_bail3( sync_vars_t *svars ); static void cancel_done( void *aux ); static void @@ -605,9 +606,16 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, svars->srecadd = &svars->srecs; for (t = 0; t < 2; t++) { - ctx[t]->name = + ctx[t]->orig_name = (!names[t] || (ctx[t]->conf->map_inbox && !strcmp( ctx[t]->conf->map_inbox, names[t] ))) ? "INBOX" : names[t]; + ctx[t]->name = nfstrdup( ctx[t]->orig_name ); + if (ctx[t]->conf->flat_delim && map_name( ctx[t]->name, '/', ctx[t]->conf->flat_delim ) < 0) { + error( "Error: canonical mailbox name '%s' contains flattened hierarchy delimiter\n", ctx[t]->name ); + svars->ret = SYNC_FAIL; + sync_bail3( svars ); + return; + } ctx[t]->uidvalidity = -1; set_bad_callback( ctx[t], store_bad, AUX ); svars->drv[t] = ctx[t]->conf->driver; @@ -615,7 +623,7 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, /* Both boxes must be fully set up at this point, so that error exit paths * don't run into uninitialized variables. */ for (t = 0; t < 2; t++) { - info( "Selecting %s %s...\n", str_ms[t], ctx[t]->name ); + info( "Selecting %s %s...\n", str_ms[t], ctx[t]->orig_name ); DRIVER_CALL(select( ctx[t], (chan->ops[t] & OP_CREATE) != 0, box_selected, AUX )); } } @@ -696,7 +704,7 @@ box_selected( int sts, void *aux ) } if (fcntl( svars->lfd, F_SETLK, &lck )) { error( "Error: channel :%s:%s-:%s:%s is locked\n", - chan->stores[M]->name, ctx[M]->name, chan->stores[S]->name, ctx[S]->name ); + chan->stores[M]->name, ctx[M]->orig_name, chan->stores[S]->name, ctx[S]->orig_name ); svars->ret = SYNC_FAIL; sync_bail1( svars ); return; @@ -1721,6 +1729,14 @@ sync_bail2( sync_vars_t *svars ) free( svars->jname ); free( svars->dname ); flushn(); + sync_bail3( svars ); +} + +static void +sync_bail3( sync_vars_t *svars ) +{ + free( svars->ctx[M]->name ); + free( svars->ctx[S]->name ); sync_deref( svars ); }