commit f377e7b696f2345482e1b1ce1400be5761f5233b
parent 85fd5ceb54f756fe64e8fadd5b190df1352940de
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date: Sat, 25 Oct 2014 17:30:57 +0200
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>
Diffstat:
8 files changed, 79 insertions(+), 23 deletions(-)
diff --git a/NEWS b/NEWS
@@ -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.
diff --git a/README b/README
@@ -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+
diff --git a/src/common.h b/src/common.h
@@ -65,6 +65,7 @@
extern int DFlags;
extern int UseFSync;
+extern char FieldDelimiter;
extern int Pid;
extern char Hostname[256];
diff --git a/src/config.c b/src/config.c
@@ -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",
diff --git a/src/drv_maildir.c b/src/drv_maildir.c
@@ -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;
}
diff --git a/src/main.c b/src/main.c
@@ -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 */
diff --git a/src/mbsync.1 b/src/mbsync.1
@@ -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
diff --git a/src/sync.c b/src/sync.c
@@ -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 );