Browse Source

added sync support for the arrival date of messages

initial patch by Marc Hoersken <info@marc-hoersken.de>
Oswald Buddenhagen 12 years ago
parent
commit
eb1f10762f
7 changed files with 80 additions and 5 deletions
  1. 17 0
      configure.ac
  2. 2 0
      src/config.c
  3. 33 5
      src/drv_imap.c
  4. 15 0
      src/drv_maildir.c
  5. 2 0
      src/isync.h
  6. 10 0
      src/mbsync.1
  7. 1 0
      src/sync.c

+ 17 - 0
configure.ac

@@ -11,6 +11,23 @@ fi
 
 CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
 
+AC_CACHE_CHECK([whether strftime supports %z], ob_cv_strftime_z,
+    [AC_TRY_RUN(
+[#include <time.h>
+#include <string.h>
+
+int main(void)
+{
+    time_t t = 0;
+    char buf[32];
+    strftime(buf, sizeof(buf), "%z", gmtime(&t));
+    return !!strcmp(buf, "+0000");
+}
+], [ob_cv_strftime_z=yes], [ob_cv_strftime_z=no], [ob_cv_strftime_z="yes (assumed)"])])
+if test "x$ob_cv_strftime_z" = x"no"; then
+    AC_MSG_ERROR([libc lacks necessary feature])
+fi
+
 AC_CHECK_HEADERS(sys/poll.h sys/select.h)
 AC_CHECK_FUNCS(vasprintf memrchr)
 

+ 2 - 0
src/config.c

@@ -362,6 +362,8 @@ load_config( const char *where, int pseudo )
 					max_size = parse_size( &cfile );
 				else if (!strcasecmp( "MaxMessages", cfile.cmd ))
 					channel->max_messages = parse_int( &cfile );
+				else if (!strcasecmp( "CopyArrivalDate", cfile.cmd ))
+					channel->use_internal_date = parse_bool( &cfile );
 				else if (!strcasecmp( "Pattern", cfile.cmd ) ||
 				         !strcasecmp( "Patterns", cfile.cmd ))
 				{

+ 33 - 5
src/drv_imap.c

@@ -31,6 +31,7 @@
 #include <limits.h>
 #include <string.h>
 #include <ctype.h>
+#include <time.h>
 #include <sys/wait.h>
 
 typedef struct imap_server_conf {
@@ -710,6 +711,8 @@ parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED )
 	struct imap_cmd *cmdp;
 	int uid = 0, mask = 0, status = 0, size = 0;
 	unsigned i;
+	time_t date = 0;
+	struct tm datetime;
 
 	if (!is_list( list )) {
 		error( "IMAP error: bogus FETCH response\n" );
@@ -751,6 +754,15 @@ parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED )
 					status |= M_FLAGS;
 				} else
 					error( "IMAP error: unable to parse FLAGS\n" );
+			} else if (!strcmp( "INTERNALDATE", tmp->val )) {
+				tmp = tmp->next;
+				if (is_atom( tmp )) {
+					if (strptime( tmp->val, "%d-%b-%Y %H:%M:%S %z", &datetime ))
+						date = mktime( &datetime );
+					else
+						error( "IMAP error: unable to parse INTERNALDATE format\n" );
+				} else
+					error( "IMAP error: unable to parse INTERNALDATE\n" );
 			} else if (!strcmp( "RFC822.SIZE", tmp->val )) {
 				tmp = tmp->next;
 				if (is_atom( tmp ))
@@ -794,6 +806,7 @@ parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED )
 		msgdata = ((struct imap_cmd_fetch_msg *)cmdp)->msg_data;
 		msgdata->data = body;
 		msgdata->len = size;
+		msgdata->date = date;
 		if (status & M_FLAGS)
 			msgdata->flags = mask;
 	} else if (uid) { /* ignore async flag updates for now */
@@ -1738,8 +1751,9 @@ imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data,
 	cmd->gen.gen.param.uid = msg->uid;
 	cmd->msg_data = data;
 	imap_exec( (imap_store_t *)ctx, &cmd->gen.gen, imap_done_simple_msg,
-	           "UID FETCH %d (%sBODY.PEEK[])",
-	           msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " );
+	           "UID FETCH %d (%s%sBODY.PEEK[])", msg->uid,
+	           !(msg->status & M_FLAGS) ? "FLAGS " : "",
+	           (data->date== -1) ? "INTERNALDATE " : "" );
 }
 
 /******************* imap_set_flags *******************/
@@ -1888,7 +1902,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
 	imap_store_t *ctx = (imap_store_t *)gctx;
 	struct imap_cmd_out_uid *cmd;
 	int d;
-	char flagstr[128], buf[1024];
+	char flagstr[128], datestr[64], buf[1024];
 
 	d = 0;
 	if (data->flags) {
@@ -1915,8 +1929,22 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
 			return;
 		}
 	}
-	imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
-	           "APPEND \"%s\" %s", buf, flagstr );
+	if (data->date) {
+#ifdef __GNUC__
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wformat"
+		/* configure ensures that %z actually works. */
+#endif
+		strftime( datestr, sizeof(datestr), "%d-%b-%Y %H:%M:%S %z", localtime( &data->date ) );
+#ifdef __GNUC__
+#  pragma GCC diagnostic pop
+#endif
+		imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
+		           "APPEND \"%s\" %s\"%s\" ", buf, flagstr, datestr );
+	} else {
+		imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
+		           "APPEND \"%s\" %s", buf, flagstr );
+	}
 }
 
 static void

+ 15 - 0
src/drv_maildir.c

@@ -36,6 +36,7 @@
 #include <sys/file.h>
 #include <errno.h>
 #include <time.h>
+#include <utime.h>
 
 #define USE_DB 1
 #ifdef __linux__
@@ -1124,6 +1125,8 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data,
 	}
 	fstat( fd, &st );
 	data->len = st.st_size;
+	if (data->date == -1)
+		data->date = st.st_mtime;
 	data->data = nfmalloc( data->len );
 	if (read( fd, data->data, data->len ) != data->len) {
 		sys_error( "Maildir error: cannot read %s", buf );
@@ -1225,6 +1228,18 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
 		cb( DRV_BOX_BAD, 0, aux );
 		return;
 	}
+
+	if (data->date) {
+		/* Set atime and mtime according to INTERNALDATE or mtime of source message */
+		struct utimbuf utimebuf;
+		utimebuf.actime = utimebuf.modtime = data->date;
+		if (utime( buf, &utimebuf ) < 0) {
+			sys_error( "Maildir error: cannot set times for %s", buf );
+			cb( DRV_BOX_BAD, 0, aux );
+			return;
+		}
+	}
+
 	/* Moving seen messages to cur/ is strictly speaking incorrect, but makes mutt happy. */
 	nfsnprintf( nbuf, sizeof(nbuf), "%s/%s/%s%s", box, subdirs[!(data->flags & F_SEEN)], base, fbuf );
 	if (rename( buf, nbuf )) {

+ 2 - 0
src/isync.h

@@ -166,6 +166,7 @@ typedef struct channel_conf {
 	string_list_t *patterns;
 	int ops[2];
 	unsigned max_messages; /* for slave only */
+	unsigned use_internal_date:1;
 } channel_conf_t;
 
 typedef struct group_conf {
@@ -245,6 +246,7 @@ set_bad_callback( store_t *ctx, void (*cb)( void *aux ), void *aux )
 typedef struct {
 	char *data;
 	int len;
+	time_t date;
 	unsigned char flags;
 } msg_data_t;
 

+ 10 - 0
src/mbsync.1

@@ -477,6 +477,16 @@ a global effect. The global settings are overridden by Channel-specific options,
 which in turn are overridden by command line switches.
 ..
 .TP
+\fBCopyArrivalDate\fR {\fIyes\fR|\fIno\fR}
+Selects whether their arrival time should be propagated together with
+the messages.
+Enabling this makes sense in order to keep the time stamp based message
+sorting intact.
+Note that IMAP does not guarantee that the time stamp (termed \fBinternal
+date\fR) is actually the arrival time, but it is usually close enough.
+(Default: \fIno\fR)
+..
+.TP
 \fBSyncState\fR {\fB*\fR|\fIpath\fR}
 Set the location of this Channel's synchronization state files. \fB*\fR means
 that the state should be saved in a file named .mbsyncstate in the

+ 1 - 0
src/sync.c

@@ -281,6 +281,7 @@ copy_msg( copy_vars_t *vars )
 
 	t ^= 1;
 	vars->data.flags = vars->msg->flags;
+	vars->data.date = svars->chan->use_internal_date ? -1 : 0;
 	DRIVER_CALL_RET(fetch_msg( svars->ctx[t], vars->msg, &vars->data, msg_fetched, vars ));
 }