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 Windows file systems has been added.
[1.1.0]
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:
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
Berkley DB 4.2+

View File

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

View File

@ -467,6 +467,19 @@ load_config( const char *where, int pseudo )
{
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 ))
{
error( "%s:%d: unknown section keyword '%s'\n",

View File

@ -57,6 +57,8 @@ typedef struct maildir_store_conf {
#ifdef USE_DB
int alt_map;
#endif /* USE_DB */
char info_delimiter;
char *info_prefix, *info_stop; /* precalculated from info_delimiter */
} maildir_store_conf_t;
typedef struct maildir_message {
@ -84,14 +86,14 @@ static int MaildirCount;
static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' };
static unsigned char
maildir_parse_flags( const char *base )
maildir_parse_flags( const char *info_prefix, const char *base )
{
const char *s;
unsigned i;
unsigned char flags;
flags = 0;
if ((s = strstr( base, ":2," )))
if ((s = strstr( base, info_prefix )))
for (s += 3, i = 0; i < as(Flags); i++)
if (strchr( s, Flags[i] ))
flags |= (1 << i);
@ -413,9 +415,9 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx )
#ifdef USE_DB
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->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;
}
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.size = sizeof(*uid);
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
maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
{
maildir_store_conf_t *conf = (maildir_store_conf_t *)ctx->gen.conf;
DIR *d;
FILE *f;
struct dirent *e;
@ -694,7 +697,7 @@ maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
ctx->gen.recent += i;
#ifdef USE_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 != DB_NOTFOUND) {
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=" )))
for (ru = u + 3; isdigit( (unsigned char)*ru ); ru++);
else
u = ru = strchr( entry->base, ':' );
u = ru = strchr( entry->base, conf->info_delimiter );
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", 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;
if (ctx->gen.opts & OPEN_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
msg->gen.flags = 0;
}
@ -1177,16 +1180,16 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data,
}
close( fd );
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 );
}
static int
maildir_make_flags( int flags, char *buf )
maildir_make_flags( char info_delimiter, int flags, char *buf )
{
unsigned i, d;
buf[0] = ':';
buf[0] = info_delimiter;
buf[1] = '2';
buf[2] = ',';
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;
}
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 );
if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
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,
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_message_t *msg = (maildir_message_t *)gmsg;
char *s, *p;
@ -1319,7 +1323,7 @@ maildir_set_flags( store_t *gctx, message_t *gmsg, int uid ATTR_UNUSED, int add,
oob();
memcpy( buf + 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;
fl = ol - (s - (nbuf + bl));
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;
} 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 ))
break;
@ -1362,7 +1366,7 @@ maildir_purge_msg( maildir_store_t *ctx, const char *name )
{
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 ))) {
ctx->db->err( ctx->db, ret, "Maildir error: db->del()" );
return DRV_BOX_BAD;
@ -1384,7 +1388,7 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg,
for (;;) {
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,
subdirs[gmsg->status & M_RECENT], (long)time( 0 ), Pid, ++MaildirCount, Hostname, s ? s : "" );
if (!rename( buf, nbuf ))
@ -1484,6 +1488,7 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
if (strcasecmp( "MaildirStore", cfg->cmd ))
return 0;
store = nfcalloc( sizeof(*store) );
store->info_delimiter = FieldDelimiter;
store->gen.driver = &maildir_driver;
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 ))
store->alt_map = parse_bool( cfg );
#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 );
if (!store->inbox)
store->inbox = expand_strdup( "~/Maildir" );
nfasprintf( &store->info_prefix, "%c2,", store->info_delimiter );
nfasprintf( &store->info_stop, "%c,", store->info_delimiter );
*storep = &store->gen;
return 1;
}

View File

@ -33,6 +33,11 @@
int DFlags;
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 */
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.
(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
.TP
\fBIMAPAccount\fR \fIname\fR
@ -513,7 +520,8 @@ mailbox name to make up a complete path.
.br
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
\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
(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.
(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
Make sure your IMAP server does not auto-expunge deleted messages - it is
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)
nfasprintf( &svars->dname, "%s%s", chan->sync_state, csname );
else {
char c = FieldDelimiter;
cmname = clean_strdup( svars->box_name[M] );
nfasprintf( &svars->dname, "%s:%s:%s_:%s:%s", global_conf.sync_state,
chan->stores[M]->name, cmname, chan->stores[S]->name, csname );
nfasprintf( &svars->dname, "%s%c%s%c%s_%c%s%c%s", global_conf.sync_state,
c, chan->stores[M]->name, c, cmname, c, chan->stores[S]->name, c, csname );
free( cmname );
}
free( csname );