Ver código fonte

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 anos atrás
pai
commit
f377e7b696
8 arquivos alterados com 79 adições e 23 exclusões
  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 Windows file systems has been added.
+
 [1.1.0]
 
 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:
     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+

+ 1 - 0
src/common.h

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

+ 13 - 0
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",

+ 36 - 17
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;
 }

+ 5 - 0
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 */

+ 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.
 (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

+ 3 - 2
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 );