introduce FieldDelimiter and InfoDelimiter options

... for windows fs compatibility.

the maildir-specific InfoDelimiter inherits the global FieldDelimiter
(which affects SyncState), based on the assumption that if the sync
state is on a windows FS, the mailboxes certainly will be as well, while
the inverse is not necessarily true (when running on unix, anyway).

REFMAIL: <CA+m_8J1ynqAjHRJagvKt9sb31yz047Q7NH-ODRmHOKyfru8vtA@mail.gmail.com>
This commit is contained in:
Oswald Buddenhagen 2014-10-25 17:30:57 +02:00
parent 85fd5ceb54
commit f377e7b696
8 changed files with 79 additions and 23 deletions

2
NEWS
View File

@ -8,6 +8,8 @@ Notice: Tunnels are assumed to be secure and thus default to no SSL.
Support for SASL (flexible authentication) has been added. Support for SASL (flexible authentication) has been added.
Support for Windows file systems has been added.
[1.1.0] [1.1.0]
Support for hierarchical mailboxes in Patterns. Support for hierarchical mailboxes in Patterns.

3
README
View File

@ -59,9 +59,6 @@ isync executable still exists; it is a compatibility wrapper around mbsync.
At some point, ``isync'' has successfully run on: At some point, ``isync'' has successfully run on:
Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3. Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3.
Note that Cygwin cannot be reasonably supported due to restrictions
of the Windows file system.
* Requirements * Requirements
Berkley DB 4.2+ Berkley DB 4.2+

View File

@ -65,6 +65,7 @@
extern int DFlags; extern int DFlags;
extern int UseFSync; extern int UseFSync;
extern char FieldDelimiter;
extern int Pid; extern int Pid;
extern char Hostname[256]; extern char Hostname[256];

View File

@ -467,6 +467,19 @@ load_config( const char *where, int pseudo )
{ {
UseFSync = parse_bool( &cfile ); UseFSync = parse_bool( &cfile );
} }
else if (!strcasecmp( "FieldDelimiter", cfile.cmd ))
{
if (strlen( cfile.val ) != 1) {
error( "%s:%d: Field delimiter must be exactly one character long\n", cfile.file, cfile.line );
cfile.err = 1;
} else {
FieldDelimiter = cfile.val[0];
if (!ispunct( FieldDelimiter )) {
error( "%s:%d: Field delimiter must be a punctuation character\n", cfile.file, cfile.line );
cfile.err = 1;
}
}
}
else if (!getopt_helper( &cfile, &gcops, &global_conf )) else if (!getopt_helper( &cfile, &gcops, &global_conf ))
{ {
error( "%s:%d: unknown section keyword '%s'\n", error( "%s:%d: unknown section keyword '%s'\n",

View File

@ -57,6 +57,8 @@ typedef struct maildir_store_conf {
#ifdef USE_DB #ifdef USE_DB
int alt_map; int alt_map;
#endif /* USE_DB */ #endif /* USE_DB */
char info_delimiter;
char *info_prefix, *info_stop; /* precalculated from info_delimiter */
} maildir_store_conf_t; } maildir_store_conf_t;
typedef struct maildir_message { typedef struct maildir_message {
@ -84,14 +86,14 @@ static int MaildirCount;
static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' };
static unsigned char static unsigned char
maildir_parse_flags( const char *base ) maildir_parse_flags( const char *info_prefix, const char *base )
{ {
const char *s; const char *s;
unsigned i; unsigned i;
unsigned char flags; unsigned char flags;
flags = 0; flags = 0;
if ((s = strstr( base, ":2," ))) if ((s = strstr( base, info_prefix )))
for (s += 3, i = 0; i < as(Flags); i++) for (s += 3, i = 0; i < as(Flags); i++)
if (strchr( s, Flags[i] )) if (strchr( s, Flags[i] ))
flags |= (1 << i); flags |= (1 << i);
@ -413,9 +415,9 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx )
#ifdef USE_DB #ifdef USE_DB
static void static void
make_key( DBT *tkey, char *name ) make_key( const char *info_stop, DBT *tkey, char *name )
{ {
char *u = strpbrk( name, ":," ); char *u = strpbrk( name, info_stop );
tkey->data = name; tkey->data = name;
tkey->size = u ? (size_t)(u - name) : strlen( name ); tkey->size = u ? (size_t)(u - name) : strlen( name );
} }
@ -439,7 +441,7 @@ maildir_set_uid( maildir_store_t *ctx, const char *name, int *uid )
return DRV_BOX_BAD; return DRV_BOX_BAD;
} }
if (uid) { if (uid) {
make_key( &key, (char *)name ); make_key( ((maildir_store_conf_t *)ctx->gen.conf)->info_stop, &key, (char *)name );
value.data = uid; value.data = uid;
value.size = sizeof(*uid); value.size = sizeof(*uid);
if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 ))) if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 )))
@ -615,6 +617,7 @@ maildir_compare( const void *l, const void *r )
static int static int
maildir_scan( maildir_store_t *ctx, msglist_t *msglist ) maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
{ {
maildir_store_conf_t *conf = (maildir_store_conf_t *)ctx->gen.conf;
DIR *d; DIR *d;
FILE *f; FILE *f;
struct dirent *e; struct dirent *e;
@ -694,7 +697,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
ctx->gen.recent += i; ctx->gen.recent += i;
#ifdef USE_DB #ifdef USE_DB
if (ctx->db) { if (ctx->db) {
make_key( &key, e->d_name ); make_key( conf->info_stop, &key, e->d_name );
if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) { if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) {
if (ret != DB_NOTFOUND) { if (ret != DB_NOTFOUND) {
ctx->db->err( ctx->db, ret, "Maildir error: db->get()" ); ctx->db->err( ctx->db, ret, "Maildir error: db->get()" );
@ -834,7 +837,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
if ((u = strstr( entry->base, ",U=" ))) if ((u = strstr( entry->base, ",U=" )))
for (ru = u + 3; isdigit( (unsigned char)*ru ); ru++); for (ru = u + 3; isdigit( (unsigned char)*ru ); ru++);
else else
u = ru = strchr( entry->base, ':' ); u = ru = strchr( entry->base, conf->info_delimiter );
fnl = (u ? fnl = (u ?
nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%.*s,U=%d%s", subdirs[entry->recent], (int)(u - entry->base), entry->base, uid, ru ) : nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%.*s,U=%d%s", subdirs[entry->recent], (int)(u - entry->base), entry->base, uid, ru ) :
nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s,U=%d", subdirs[entry->recent], entry->base, uid )) nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s,U=%d", subdirs[entry->recent], entry->base, uid ))
@ -906,7 +909,7 @@ maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry )
msg->gen.status |= M_RECENT; msg->gen.status |= M_RECENT;
if (ctx->gen.opts & OPEN_FLAGS) { if (ctx->gen.opts & OPEN_FLAGS) {
msg->gen.status |= M_FLAGS; msg->gen.status |= M_FLAGS;
msg->gen.flags = maildir_parse_flags( msg->base ); msg->gen.flags = maildir_parse_flags( ((maildir_store_conf_t *)ctx->gen.conf)->info_prefix, msg->base );
} else } else
msg->gen.flags = 0; msg->gen.flags = 0;
} }
@ -1177,16 +1180,16 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data,
} }
close( fd ); close( fd );
if (!(gmsg->status & M_FLAGS)) if (!(gmsg->status & M_FLAGS))
data->flags = maildir_parse_flags( msg->base ); data->flags = maildir_parse_flags( ((maildir_store_conf_t *)gctx->conf)->info_prefix, msg->base );
cb( DRV_OK, aux ); cb( DRV_OK, aux );
} }
static int static int
maildir_make_flags( int flags, char *buf ) maildir_make_flags( char info_delimiter, int flags, char *buf )
{ {
unsigned i, d; unsigned i, d;
buf[0] = ':'; buf[0] = info_delimiter;
buf[1] = '2'; buf[1] = '2';
buf[2] = ','; buf[2] = ',';
for (d = 3, i = 0; i < as(Flags); i++) for (d = 3, i = 0; i < as(Flags); i++)
@ -1231,7 +1234,7 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
box = ctx->trash; box = ctx->trash;
} }
maildir_make_flags( data->flags, fbuf ); maildir_make_flags( ((maildir_store_conf_t *)gctx->conf)->info_delimiter, data->flags, fbuf );
nfsnprintf( buf, sizeof(buf), "%s/tmp/%s%s", box, base, fbuf ); nfsnprintf( buf, sizeof(buf), "%s/tmp/%s%s", box, base, fbuf );
if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) { if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
if (errno != ENOENT || !to_trash) { if (errno != ENOENT || !to_trash) {
@ -1302,6 +1305,7 @@ static void
maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add, int del, maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add, int del,
void (*cb)( int sts, void *aux ), void *aux ) void (*cb)( int sts, void *aux ), void *aux )
{ {
maildir_store_conf_t *conf = (maildir_store_conf_t *)gctx->conf;
maildir_store_t *ctx = (maildir_store_t *)gctx; maildir_store_t *ctx = (maildir_store_t *)gctx;
maildir_message_t *msg = (maildir_message_t *)gmsg; maildir_message_t *msg = (maildir_message_t *)gmsg;
char *s, *p; char *s, *p;
@ -1319,7 +1323,7 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add,
oob(); oob();
memcpy( buf + bl, msg->base, ol + 1 ); memcpy( buf + bl, msg->base, ol + 1 );
memcpy( nbuf + bl, msg->base, ol + 1 ); memcpy( nbuf + bl, msg->base, ol + 1 );
if ((s = strstr( nbuf + bl, ":2," ))) { if ((s = strstr( nbuf + bl, conf->info_prefix ))) {
s += 3; s += 3;
fl = ol - (s - (nbuf + bl)); fl = ol - (s - (nbuf + bl));
for (i = 0; i < as(Flags); i++) { for (i = 0; i < as(Flags); i++) {
@ -1337,7 +1341,7 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add,
} }
tl = ol + 3 + fl; tl = ol + 3 + fl;
} else { } else {
tl = ol + maildir_make_flags( msg->gen.flags, nbuf + bl + ol ); tl = ol + maildir_make_flags( conf->info_delimiter, msg->gen.flags, nbuf + bl + ol );
} }
if (!rename( buf, nbuf )) if (!rename( buf, nbuf ))
break; break;
@ -1362,7 +1366,7 @@ maildir_purge_msg( maildir_store_t *ctx, const char *name )
{ {
int ret; int ret;
make_key( &key, (char *)name ); make_key( ((maildir_store_conf_t *)ctx->gen.conf)->info_stop, &key, (char *)name );
if ((ret = ctx->db->del( ctx->db, 0, &key, 0 ))) { if ((ret = ctx->db->del( ctx->db, 0, &key, 0 ))) {
ctx->db->err( ctx->db, ret, "Maildir error: db->del()" ); ctx->db->err( ctx->db, ret, "Maildir error: db->del()" );
return DRV_BOX_BAD; return DRV_BOX_BAD;
@ -1384,7 +1388,7 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg,
for (;;) { for (;;) {
nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base ); nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base );
s = strstr( msg->base, ":2," ); s = strstr( msg->base, ((maildir_store_conf_t *)gctx->conf)->info_prefix );
nfsnprintf( nbuf, sizeof(nbuf), "%s/%s/%ld.%d_%d.%s%s", ctx->trash, nfsnprintf( nbuf, sizeof(nbuf), "%s/%s/%ld.%d_%d.%s%s", ctx->trash,
subdirs[gmsg->status & M_RECENT], (long)time( 0 ), Pid, ++MaildirCount, Hostname, s ? s : "" ); subdirs[gmsg->status & M_RECENT], (long)time( 0 ), Pid, ++MaildirCount, Hostname, s ? s : "" );
if (!rename( buf, nbuf )) if (!rename( buf, nbuf ))
@ -1484,6 +1488,7 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
if (strcasecmp( "MaildirStore", cfg->cmd )) if (strcasecmp( "MaildirStore", cfg->cmd ))
return 0; return 0;
store = nfcalloc( sizeof(*store) ); store = nfcalloc( sizeof(*store) );
store->info_delimiter = FieldDelimiter;
store->gen.driver = &maildir_driver; store->gen.driver = &maildir_driver;
store->gen.name = nfstrdup( cfg->val ); store->gen.name = nfstrdup( cfg->val );
@ -1496,10 +1501,24 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
else if (!strcasecmp( "AltMap", cfg->cmd )) else if (!strcasecmp( "AltMap", cfg->cmd ))
store->alt_map = parse_bool( cfg ); store->alt_map = parse_bool( cfg );
#endif /* USE_DB */ #endif /* USE_DB */
else else if (!strcasecmp( "InfoDelimiter", cfg->cmd )) {
if (strlen( cfg->val ) != 1) {
error( "%s:%d: Info delimiter must be exactly one character long\n", cfg->file, cfg->line );
cfg->err = 1;
continue;
}
store->info_delimiter = cfg->val[0];
if (!ispunct( store->info_delimiter )) {
error( "%s:%d: Info delimiter must be a punctuation character\n", cfg->file, cfg->line );
cfg->err = 1;
continue;
}
} else
parse_generic_store( &store->gen, cfg ); parse_generic_store( &store->gen, cfg );
if (!store->inbox) if (!store->inbox)
store->inbox = expand_strdup( "~/Maildir" ); store->inbox = expand_strdup( "~/Maildir" );
nfasprintf( &store->info_prefix, "%c2,", store->info_delimiter );
nfasprintf( &store->info_stop, "%c,", store->info_delimiter );
*storep = &store->gen; *storep = &store->gen;
return 1; return 1;
} }

View File

@ -33,6 +33,11 @@
int DFlags; int DFlags;
int UseFSync = 1; int UseFSync = 1;
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__)
char FieldDelimiter = ';';
#else
char FieldDelimiter = ':';
#endif
int Pid; /* for maildir and imap */ int Pid; /* for maildir and imap */
char Hostname[256]; /* for maildir */ char Hostname[256]; /* for maildir */

View File

@ -229,6 +229,13 @@ The location of the \fBINBOX\fR. This is \fInot\fR relative to \fBPath\fR,
but it is allowed to place the \fBINBOX\fR inside the \fBPath\fR. but it is allowed to place the \fBINBOX\fR inside the \fBPath\fR.
(Default: \fI~/Maildir\fR) (Default: \fI~/Maildir\fR)
.. ..
.TP
\fBInfoDelimiter\fR \fIdelim\fR
The character used to delimit the info field from a message's basename.
The Maildir standard defines this to be the colon, but this is incompatible
with DOS/Windows file systems.
(Default: the value of \fBFieldDelimiter\fR)
..
.SS IMAP4 Accounts .SS IMAP4 Accounts
.TP .TP
\fBIMAPAccount\fR \fIname\fR \fBIMAPAccount\fR \fIname\fR
@ -513,7 +520,8 @@ mailbox name to make up a complete path.
.br .br
This option can be used outside any section for a global effect. In this case This option can be used outside any section for a global effect. In this case
the appended string is made up according to the pattern the appended string is made up according to the pattern
\fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR. \fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR
(see also \fBFieldDelimiter\fR below).
.br .br
(Global default: \fI~/.mbsync/\fR). (Global default: \fI~/.mbsync/\fR).
.. ..
@ -549,6 +557,16 @@ in particular modern systems like ext4, btrfs and xfs. The performance impact
on older file systems may be disproportionate. on older file systems may be disproportionate.
(Default: \fIyes\fR) (Default: \fIyes\fR)
.. ..
.TP
\fBFieldDelimiter\fR \fIdelim\fR
The character to use to delimit fields in the string appended to a global
\fBSyncState\fR.
\fBmbsync\fR prefers to use the colon, but this is incompatible with
DOS/Windows file systems.
This option is meaningless for \fBSyncState\fR if the latter is \fB*\fR,
obviously. However, it also determines the default of \fBInfoDelimiter\fR.
(Global default: \fI;\fR on Windows, \fI:\fR everywhere else)
..
.SH RECOMMENDATIONS .SH RECOMMENDATIONS
Make sure your IMAP server does not auto-expunge deleted messages - it is Make sure your IMAP server does not auto-expunge deleted messages - it is
slow, and semantically somewhat questionable. Specifically, Gmail needs to slow, and semantically somewhat questionable. Specifically, Gmail needs to

View File

@ -669,9 +669,10 @@ box_selected( int sts, void *aux )
if (chan->sync_state) if (chan->sync_state)
nfasprintf( &svars->dname, "%s%s", chan->sync_state, csname ); nfasprintf( &svars->dname, "%s%s", chan->sync_state, csname );
else { else {
char c = FieldDelimiter;
cmname = clean_strdup( svars->box_name[M] ); cmname = clean_strdup( svars->box_name[M] );
nfasprintf( &svars->dname, "%s:%s:%s_:%s:%s", global_conf.sync_state, nfasprintf( &svars->dname, "%s%c%s%c%s_%c%s%c%s", global_conf.sync_state,
chan->stores[M]->name, cmname, chan->stores[S]->name, csname ); c, chan->stores[M]->name, c, cmname, c, chan->stores[S]->name, c, csname );
free( cmname ); free( cmname );
} }
free( csname ); free( csname );