Browse Source

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>
Oswald Buddenhagen 10 years ago
parent
commit
f377e7b696
8 changed files with 79 additions and 23 deletions
  1. 2 0
      NEWS
  2. 0 3
      README
  3. 1 0
      src/common.h
  4. 13 0
      src/config.c
  5. 36 17
      src/drv_maildir.c
  6. 5 0
      src/main.c
  7. 19 1
      src/mbsync.1
  8. 3 2
      src/sync.c

+ 2 - 0
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 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.

+ 0 - 3
README

@@ -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+

+ 1 - 0
src/common.h

@@ -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];

+ 13 - 0
src/config.c

@@ -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",

+ 36 - 17
src/drv_maildir.c

@@ -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;
 }
 }

+ 5 - 0
src/main.c

@@ -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 */

+ 19 - 1
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.
 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

+ 3 - 2
src/sync.c

@@ -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,
-			            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( cmname );
 		}
 		}
 		free( csname );
 		free( csname );