balsa r7902 - in trunk: . libbalsa libbalsa/imap



Author: pawels
Date: Tue Apr  1 20:55:03 2008
New Revision: 7902
URL: http://svn.gnome.org/viewvc/balsa?rev=7902&view=rev

Log:
* libbalsa/mailbox_imap.c: use UIDPLUS information returned by server
  to improve caching.
* libbalsa/imap/imap-handle.[ch]: parse returned UIDPLUS data.
* libbalsa/imap/imap_private.h: add ImapMboxHandle::uidplus field.
* libbalsa/imap/imap-commands.[ch]: add API for accessing UIDPLUS data.


Modified:
   trunk/ChangeLog
   trunk/libbalsa/imap/imap-commands.c
   trunk/libbalsa/imap/imap-commands.h
   trunk/libbalsa/imap/imap-handle.c
   trunk/libbalsa/imap/imap-handle.h
   trunk/libbalsa/imap/imap_private.h
   trunk/libbalsa/imap/imap_tst.c
   trunk/libbalsa/mailbox_imap.c

Modified: trunk/libbalsa/imap/imap-commands.c
==============================================================================
--- trunk/libbalsa/imap/imap-commands.c	(original)
+++ trunk/libbalsa/imap/imap-commands.c	Tue Apr  1 20:55:03 2008
@@ -419,16 +419,57 @@
 {
   struct SingleAppendData sad = { dump_cb, arg, sz, flags };
   IMAP_REQUIRED_STATE2(handle,IMHS_AUTHENTICATED, IMHS_SELECTED, IMR_BAD);
-  return imap_mbox_append_multi(handle, mbox, single_append_cb, &sad);
+  return imap_mbox_append_multi(handle, mbox, single_append_cb, &sad, NULL);
 }
+
+static ImapResponse
+append_commit(ImapMboxHandle *handle, unsigned cmdno, ImapSequence *uid_seq)
+{
+  ImapResponse rc;
+
+  if(handle->uidplus.dst)
+    g_warning("Leaking memory in imap_append");
+
+  if(uid_seq) {
+    handle->uidplus.dst = uid_seq->ranges;
+    handle->uidplus.store_response = 1;
+  }
+  sio_write(handle->sio, "\r\n", 2);
+  sio_flush(handle->sio);
+  do {
+    rc = imap_cmd_step (handle, cmdno);
+  } while (rc == IMR_UNTAGGED);
+
+  if(uid_seq) {
+    if(uid_seq->uid_validity == 0) {
+      uid_seq->uid_validity = handle->uidplus.dst_uid_validity;
+      uid_seq->ranges = handle->uidplus.dst;
+    } else if(uid_seq->uid_validity != handle->uidplus.dst_uid_validity) {
+      printf("The IMAP server keeps changing UID validity, "
+	     "ignoring UIDPLUS response (%u -> %u)\n",
+	     uid_seq->uid_validity, handle->uidplus.dst_uid_validity);
+      uid_seq->uid_validity = handle->uidplus.dst_uid_validity;
+      g_list_free(uid_seq->ranges);
+      uid_seq->ranges = NULL;
+    } else {
+      uid_seq->ranges = handle->uidplus.dst;
+    }
+  }
+  handle->uidplus.dst = NULL;
+  handle->uidplus.store_response = 0;
+
+  return rc;
+}
+
 static ImapResponse
 imap_mbox_append_multi_real(ImapMboxHandle *handle,
 			    const char *mbox,
 			    ImapAppendMultiFunc dump_cb,
-			    void* cb_arg)
+			    void* cb_arg,
+			    ImapSequence *uid_sequence)
 {
   static const unsigned TRANSACTION_SIZE = 10*1024*1024;
-  int use_literal, use_multiappend;
+  int use_literal, use_multiappend, use_uidplus;
   unsigned cmdno;
   ImapResponse rc = IMR_OK;
   char *litstr;
@@ -442,6 +483,7 @@
 
   use_literal = imap_mbox_handle_can_do(handle, IMCAP_LITERAL);
   use_multiappend = imap_mbox_handle_can_do(handle, IMCAP_MULTIAPPEND);
+  use_uidplus = imap_mbox_handle_can_do(handle, IMCAP_UIDPLUS);
   litstr = use_literal ? "+" : "";
 
   imap_handle_idle_disable(handle);
@@ -518,26 +560,24 @@
        * make sure on a higher level that they are not sent.
        */
       /* sio_flush(handle->sio); */
-      sio_write(handle->sio, "\r\n", 2);
-      sio_flush(handle->sio);
-      do {
-	rc = imap_cmd_step (handle, cmdno);
-      } while (rc == IMR_UNTAGGED);
-      if(rc != IMR_OK)
-	return rc;
+      if( (rc=append_commit(handle, cmdno,
+			   use_uidplus ? uid_sequence : NULL)) != IMR_OK)
+      return rc;
     }
     /* And move to the next message... */
   }
 
   if(!new_append && use_multiappend) { /* We get the server response here... */
-    sio_write(handle->sio, "\r\n", 2);
-    sio_flush(handle->sio);
-    do {
-      rc = imap_cmd_step (handle, cmdno);
-    } while (rc == IMR_UNTAGGED);
+    rc = append_commit(handle, cmdno, use_uidplus ? uid_sequence : NULL);
   }
 
   imap_handle_idle_enable(handle, 30);
+
+  if(uid_sequence) {
+    printf("Handle returns uid seq of length %u\n",
+	   imap_sequence_length(uid_sequence));
+    uid_sequence->ranges = g_list_reverse(uid_sequence->ranges);
+  }
   return rc;
 }
 
@@ -561,11 +601,12 @@
 imap_mbox_append_multi(ImapMboxHandle *handle,
 		       const char *mbox,
 		       ImapAppendMultiFunc dump_cb,
-		       void* cb_arg)
+		       void* cb_arg,
+		       ImapSequence *uids)
 {
   ImapResponse rc;
   HANDLE_LOCK(handle);
-  rc = imap_mbox_append_multi_real(handle, mbox, dump_cb, cb_arg);
+  rc = imap_mbox_append_multi_real(handle, mbox, dump_cb, cb_arg, uids);
   HANDLE_UNLOCK(handle);
   return rc;
 }
@@ -632,8 +673,13 @@
 ImapResponse
 imap_mbox_expunge(ImapMboxHandle *handle)
 {
+  ImapResponse rc;
+
   IMAP_REQUIRED_STATE2(handle,IMHS_AUTHENTICATED, IMHS_SELECTED, IMR_BAD);
-  return imap_cmd_exec(handle, "EXPUNGE");
+  HANDLE_LOCK(handle);
+  rc = imap_cmd_exec(handle, "EXPUNGE");
+  HANDLE_UNLOCK(handle);
+  return rc;
 }
 
 ImapResponse
@@ -1233,17 +1279,36 @@
     selected in handle to given mailbox on same server. */
 ImapResponse
 imap_mbox_handle_copy(ImapMboxHandle* handle, unsigned cnt, unsigned *seqno,
-                      const gchar *dest)
+                      const gchar *dest,
+		      ImapSequence *ret_sequence)
 {
+  ImapResponse rc;
   IMAP_REQUIRED_STATE1(handle, IMHS_SELECTED, IMR_BAD);
+  HANDLE_LOCK(handle);
   {
-  gchar *mbx7 = imap_utf8_to_mailbox(dest);
-  char *seq = imap_coalesce_set(cnt, seqno);
-  gchar *cmd = g_strdup_printf("COPY %s \"%s\"", seq, mbx7);
-  ImapResponse rc = imap_cmd_exec(handle, cmd);
-  g_free(seq); g_free(mbx7); g_free(cmd);
-  return rc;
+    gchar *mbx7 = imap_utf8_to_mailbox(dest);
+    char *seq = imap_coalesce_set(cnt, seqno);
+    gchar *cmd = g_strdup_printf("COPY %s \"%s\"", seq, mbx7);
+    unsigned cmdno;
+    gboolean use_uidplus = imap_mbox_handle_can_do(handle, IMCAP_UIDPLUS);
+
+    handle->uidplus.store_response = ret_sequence ? 1 : 0;
+
+    rc = imap_cmd_exec_cmdno(handle, cmd, &cmdno);
+    g_free(seq); g_free(mbx7); g_free(cmd);
+    if(use_uidplus && ret_sequence) {
+      if(rc == IMR_OK /* && cmdno == handle->uidplus.cmdno */ ) {
+	ret_sequence->uid_validity = handle->uidplus.dst_uid_validity;
+	ret_sequence->ranges = g_list_reverse(handle->uidplus.dst);
+      } else {
+	g_list_free(handle->uidplus.dst);
+      }
+      handle->uidplus.dst = NULL;
+      handle->uidplus.store_response = 0;
+    }
   }
+  HANDLE_UNLOCK(handle);
+  return rc;
 }
 
 /* 6.4.8 UID Command */

Modified: trunk/libbalsa/imap/imap-commands.h
==============================================================================
--- trunk/libbalsa/imap/imap-commands.h	(original)
+++ trunk/libbalsa/imap/imap-commands.h	Tue Apr  1 20:55:03 2008
@@ -72,7 +72,8 @@
 ImapResponse imap_mbox_append_multi(ImapMboxHandle *handle,
 				    const char *mbox,
 				    ImapAppendMultiFunc dump_cb,
-				    void* arg);
+				    void* arg,
+				    ImapSequence *uid_range);
 #ifdef USE_IMAP_APPEND_STR /* not used currently */
 ImapResponse imap_mbox_append_str(ImapMboxHandle *handle, const char *mbox,
                               ImapMsgFlags flags, size_t sz, char *txt);
@@ -94,8 +95,11 @@
 unsigned imap_mbox_store_flag_a(ImapMboxHandle *r, unsigned cnt,
 				unsigned *seqno, ImapMsgFlag flg,
 				gboolean state);
-ImapResponse imap_mbox_handle_copy(ImapMboxHandle* handle, unsigned cnt,
-                                   unsigned *seqno, const gchar *dest);
+
+ImapResponse imap_mbox_handle_copy(ImapMboxHandle* handle,
+				   unsigned cnt, unsigned *seqno,
+				   const gchar *dest,
+				   ImapSequence *ret_sequence);
 
 ImapResponse imap_mbox_find_unseen(ImapMboxHandle * h, unsigned *msgcnt,
 				   unsigned **msgs);

Modified: trunk/libbalsa/imap/imap-handle.c
==============================================================================
--- trunk/libbalsa/imap/imap-handle.c	(original)
+++ trunk/libbalsa/imap/imap-handle.c	Tue Apr  1 20:55:03 2008
@@ -878,6 +878,7 @@
   ImapMboxHandle* handle = IMAP_MBOX_HANDLE(gobject);
   g_return_if_fail(handle);
 
+  HANDLE_LOCK(handle);
   if(handle->state != IMHS_DISCONNECTED) {
     handle->doing_logout = TRUE;
     imap_cmd_exec(handle, "LOGOUT");
@@ -895,6 +896,8 @@
   imap_mbox_resize_cache(handle, 0);
   g_free(handle->msg_cache); handle->msg_cache = NULL;
   g_array_free(handle->flag_cache, TRUE);
+
+  HANDLE_UNLOCK(handle);
 #if defined(BALSA_USE_THREADS)
   pthread_mutex_destroy(&handle->mutex);
 #endif
@@ -1411,6 +1414,57 @@
 }
 
 
+unsigned
+imap_sequence_length(ImapSequence *i_seq)
+{
+  unsigned length = 0;
+  GList *l;
+  for(l = i_seq->ranges; l; l = l->next) {
+    ImapUidRange *r = (ImapUidRange*)l->data;
+    length += r->hi - r->lo +1;
+  }
+  return length;
+}
+
+unsigned
+imap_sequence_nth(ImapSequence *i_seq, unsigned nth)
+{
+  GList *l;
+
+  g_return_val_if_fail(i_seq->ranges, 0);
+
+  for(l = i_seq->ranges; l; l = l->next) {
+    ImapUidRange *r = (ImapUidRange*)l->data;
+    unsigned range_length = r->hi - r->lo +1;
+    if( nth < range_length)
+      return r->lo + nth;
+    nth -= range_length;
+  }
+  g_warning("imap_sequence_nth: too large parameter; returning bogus data\n");
+  return 0;
+}
+
+void
+imap_sequence_foreach(ImapSequence *i_seq,
+		      void(*cb)(unsigned uid, void *arg), void *cb_arg)
+{
+  GList *l;
+  for(l = i_seq->ranges; l; l = l->next) {
+    unsigned uid;
+    ImapUidRange *iur = (ImapUidRange*)l->data;
+    for(uid=iur->lo; uid<=iur->hi; uid++)
+      cb(uid, cb_arg);
+  }
+}
+
+
+void
+imap_sequence_release(ImapSequence *i_seq)
+{
+  g_list_foreach(i_seq->ranges, (GFunc)g_free, NULL);
+  i_seq->ranges = NULL;
+}
+
 void
 imap_body_append_part(ImapBody* body, ImapBody* sibling)
 {
@@ -1903,17 +1957,19 @@
     return lastcmd == cmdno ? rc : IMR_UNTAGGED;
 }
 
-/* imap_cmd_exec: 
- * execute a command, and wait for the response from the server.
+/**executes a command, and wait for the response from the server.
  * Also, handle untagged responses.
  * Returns ImapResponse.
  */
 ImapResponse
-imap_cmd_exec(ImapMboxHandle* handle, const char* cmd)
+imap_cmd_exec_cmdno(ImapMboxHandle* handle, const char* cmd,
+		    unsigned *ret_cmdno)
 {
   unsigned cmdno;
   ImapResponse rc;
 
+  if(ret_cmdno) *ret_cmdno = 0;
+
   g_return_val_if_fail(handle, IMR_BAD);
   if (handle->state == IMHS_DISCONNECTED)
     return IMR_SEVERED;
@@ -1923,6 +1979,7 @@
   if (imap_cmd_start(handle, cmd, &cmdno)<0)
     return IMR_SEVERED;  /* irrecoverable connection error. */
 
+  if(ret_cmdno) *ret_cmdno = cmdno;
   sio_flush(handle->sio);
   do {
     rc = imap_cmd_step (handle, cmdno);
@@ -2090,7 +2147,7 @@
 
 #include "imap-handle.h"
 
-static void
+static int
 ignore_bad_charset(struct siobuf *sio, int c)
 {
   while(c==' ') {
@@ -2099,13 +2156,19 @@
   }
   if(c != ')')
     fprintf(stderr,"ignore_bad_charset: expected ')' got '%c'\n", c);
+  else c = sio_getc(sio);
+  return c;
 }
-static void
+
+static int
 ir_permanent_flags(ImapMboxHandle *h)
 {
-  while(sio_getc(h->sio) != ']')
+  int c;
+  while( (c=sio_getc(h->sio)) != EOF && c != ']')
     ;
+  return c;
 }
+
 static int
 ir_capability_data(ImapMboxHandle *handle)
 {
@@ -2117,7 +2180,7 @@
     "LOGINDISABLED", "MULTIAPPEND", "NAMESPACE", "SASL-IR",
     "SCAN", "STARTTLS",
     "SORT", "THREAD=ORDEREDSUBJECT", "THREAD=REFERENCES",
-    "UNSELECT"
+    "UIDPLUS", "UNSELECT"
   };
   unsigned x;
   int c;
@@ -2137,48 +2200,151 @@
   return c;
 }
 
+typedef void (*ImapUidRangeCb)(ImapUidRange *iur, void *arg);
+
+static ImapResponse
+imap_get_sequence(ImapMboxHandle *h, ImapUidRangeCb seq_cb, void *seq_arg)
+{
+  char value[30];
+  gchar *p;
+  int offset = 0;
+  int c = ' ';
+
+  do {
+    size_t value_len;
+    if(c != '\r' && c != '\n') /* Dont try to read beyond the line. */
+      c = imap_get_atom(h->sio, value + offset, sizeof(value)-offset);
+    value_len = strlen(value);
+
+    if(seq_cb) { /* makes sense to parse it ... */
+      static const unsigned LENGTH_OF_LARGEST_UNSIGNED = 10;
+      ImapUidRange seq;
+
+      for(p=value; *p &&
+	    (unsigned)(p-value) <=
+	    sizeof(value)-(2*LENGTH_OF_LARGEST_UNSIGNED+1); ) {
+	if(sscanf(p, "%u", &seq.lo) != 1)
+	  return IMR_PROTOCOL;
+	while(*p && isdigit(*p)) p++;
+	if( *p == ':') {
+	  p++;
+	  if(sscanf(p, "%u", &seq.hi) != 1)
+	    return c;
+	} else seq.hi = seq.lo;
+
+	seq_cb(&seq, seq_arg);
+
+	while(*p && isdigit(*p)) p++;
+	if(*p == ',') p++;
+      } /* End of for */
+	
+      /* Reuse what's left. */
+      if(*p) {
+	offset = value_len - (p-value);
+	memmove(value, p, offset+1);
+      } else offset = 0;
+
+    } /* End of if(search_cb) */
+
+  }  while (isdigit(c)|| offset);
+
+  if(c != EOF)
+    sio_ungetc(h->sio);
+  return IMR_OK;
+}
+
+static void
+append_uid_range(ImapUidRange *iur, GList **dst)
+{
+  ImapUidRange *iur_copy = g_new(ImapUidRange, 1);
+  /* printf("Prepending %u:%u\n", iur->lo, iur->hi); */
+  iur_copy->lo = iur->lo;
+  iur_copy->hi = iur->hi;
+  *dst = g_list_prepend(*dst, iur_copy);
+}
+
+static ImapResponse
+ir_get_append_copy_uids(ImapMboxHandle *h, gboolean append_only)
+{
+  int c;
+  char buf[12];
+  ImapResponse rc;
+
+  if( (c=imap_get_atom(h->sio, buf, sizeof(buf))) == EOF)
+    return IMR_PROTOCOL;
+  h->uidplus.dst_uid_validity = strtol(buf, NULL, 10);
+
+  if(c != ' ')
+    return IMR_PROTOCOL;
+
+  if(!append_only) {
+    if( (rc = imap_get_sequence(h, NULL, NULL)) != IMR_OK)
+      return rc;
+    if( (c=sio_getc(h->sio)) != ' ') {
+      printf("Expected ' ' found '%c'\n", c);
+      return IMR_PROTOCOL;
+    }
+  }
+  return imap_get_sequence(h, (ImapUidRangeCb)append_uid_range,
+			   &h->uidplus.dst);
+}
+
 static ImapResponse
 ir_resp_text_code(ImapMboxHandle *h)
 {
   static const char* resp_text_code[] = {
     "ALERT", "BADCHARSET", "CAPABILITY","PARSE", "PERMANENTFLAGS",
     "READ-ONLY", "READ-WRITE", "TRYCREATE", "UIDNEXT", "UIDVALIDITY",
-    "UNSEEN"
+    "UNSEEN", "APPENDUID", "COPYUID"
   };
   unsigned o;
   char buf[128];
   int c = imap_get_atom(h->sio, buf, sizeof(buf));
   ImapResponse rc = IMR_OK;
 
-
   for(o=0; o<ELEMENTS(resp_text_code); o++)
     if(g_ascii_strcasecmp(buf, resp_text_code[o]) == 0) break;
 
   switch(o) {
   case 0: rc = IMR_ALERT;        break;
-  case 1: ignore_bad_charset(h->sio, c); break;
-  case 2: ir_capability_data(h); break;
+  case 1: c = ignore_bad_charset(h->sio, c); break;
+  case 2: c = ir_capability_data(h); break;
   case 3: rc = IMR_PARSE;        break;
-  case 4: ir_permanent_flags(h); break;
+  case 4: c = ir_permanent_flags(h); break;
   case 5: h->readonly_mbox = TRUE;  /* read-only */; break;
   case 6: h->readonly_mbox = FALSE; /* read-write */; break;
   case 7: /* ignore try-create */; break;
   case 8:
-    imap_get_atom(h->sio, buf, sizeof(buf));
+    c = imap_get_atom(h->sio, buf, sizeof(buf));
     h->uidnext = strtol(buf, NULL, 10);
     break;
   case 9:
-    imap_get_atom(h->sio, buf, sizeof(buf));
+    c = imap_get_atom(h->sio, buf, sizeof(buf));
     h->uidval = strtol(buf, NULL, 10);
     break;
   case 10:
-    imap_get_atom(h->sio, buf, sizeof(buf));
+    c = imap_get_atom(h->sio, buf, sizeof(buf));
     h->unseen =strtol(buf, NULL, 10);
     break;
+  case 11: /* APPENDUID */
+    if( (rc=ir_get_append_copy_uids(h, TRUE)) != IMR_OK)
+      return rc;
+    /* printf("APPENDUID: uid_validity=\n"); */
+    c = sio_getc(h->sio);
+    break;
+  case 12: /* COPYUID */
+    /* printf("Copyuid\n"); */
+    if( (rc=ir_get_append_copy_uids(h, FALSE)) != IMR_OK)
+      return rc;
+    c = sio_getc(h->sio);
+    break;
   default: while( c != ']' && (c=sio_getc(h->sio)) != EOF) ; break;
   }
-  return c != EOF ? rc : IMR_SEVERED;
+  if(c != ']')
+    printf("ir_resp_text_code, on exit c=%c\n", c);
+  return c == ']' ? rc : IMR_PROTOCOL;
 }
+
 static ImapResponse
 ir_ok(ImapMboxHandle *h)
 {
@@ -2398,6 +2564,15 @@
   return ir_check_crlf(h, sio_getc(h->sio));
 }
 
+static void
+esearch_cb(ImapUidRange *iur, void *arg)
+{
+  ImapMboxHandle *h = (ImapMboxHandle*)arg;
+  unsigned i;
+  for(i=iur->lo; i<= iur->hi; i++)
+    h->search_cb(h, i, h->search_arg);
+}
+
 /** Process ESEARCH response. Consult RFC4466 and RFC4731 before
    modification.  */
 static ImapResponse
@@ -2436,56 +2611,19 @@
   }
   if(c == EOF) return IMR_SEVERED;
 
-  while(c == ' ') {
-    static const unsigned LENGTH_OF_LARGEST_UNSIGNED = 10;
-    char value[30];
-    gchar *p;
-    int offset = 0;
+  while(c == ' ') { /* search-return-data in rfc4466 speak */
+    ImapResponse rc;
     /* atom contains search-modifier-name, time to fetch
-       search-return-value. In ESEARCH, it is always an atom, so we
-       cut the corners here. We get values in chunks.  The chunk size
-       is pretty arbitrary as long as it can fit two largest possible
-       32-bit unsigned numbers and a colon. */
-    do {
-      size_t value_len;
-      if(c != '\r' && c != '\n') /* Dont try to read beyond the line. */
-        c = imap_get_atom(h->sio, value + offset, sizeof(value)-offset);
-      value_len = strlen(value);
-
-      if(h->search_cb) {
-        unsigned seq, seq1;
-
-        for(p=value; *p &&
-              (unsigned)(p-value) <=
-              sizeof(value)-(2*LENGTH_OF_LARGEST_UNSIGNED+1); ) {
-          if(sscanf(p, "%u", &seq) != 1)
-            return IMR_PROTOCOL;
-          while(*p && isdigit(*p)) p++;
-          if( *p == ':') {
-            p++;
-            if(sscanf(p, "%u", &seq1) != 1)
-              return IMR_PROTOCOL;
-          } else seq1 = seq;
-
-          for(; seq<=seq1; seq++) {
-            h->search_cb(h, seq, h->search_arg);
-          }
-          while(*p && isdigit(*p)) p++;
-          if(*p == ',') p++;
-        } /* End of for */
-	
-	  /* Reuse what's left. */
-        if(*p) {
-          offset = value_len - (p-value);
-          memmove(value, p, offset+1);
-        } else offset = 0;
-
-      } /* End of if(h->search_cb) */
-
-    }  while (isdigit(c)|| offset);
+       search-return-value. In ESEARCH, it is always an
+       tagged-ext-simple=sequence-set/number, which are an atoms, so
+       we cut the corners here. We get values in chunks.  The chunk
+       size is pretty arbitrary as long as it can fit two largest
+       possible 32-bit unsigned numbers and a colon. */
+    if ( (rc=imap_get_sequence(h, esearch_cb, h)) != IMR_OK)
+      return rc;
 
-    if(c == ' ')
-      c = imap_get_atom(h->sio, value, sizeof(value));
+    if( (c=sio_getc(h->sio)) == ' ')
+      c = imap_get_atom(h->sio, atom, sizeof(atom));
   }
 
   return ir_check_crlf(h, c);

Modified: trunk/libbalsa/imap/imap-handle.h
==============================================================================
--- trunk/libbalsa/imap/imap-handle.h	(original)
+++ trunk/libbalsa/imap/imap-handle.h	Tue Apr  1 20:55:03 2008
@@ -78,6 +78,7 @@
   IMCAP_THREAD_ORDEREDSUBJECT,
   IMCAP_THREAD_REFERENCES,
   /* http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-13.txt */
+  IMCAP_UIDPLUS,                /* RFC 4315 */
   IMCAP_UNSELECT,               /* RFC 3691 */
   IMCAP_FETCHBODY,              /* basic imap implemented correctly by
                                  * most imap servers but not all. We
@@ -181,6 +182,24 @@
 
 /* ================ END OF BODY STRUCTURE FUNCTIONS ==================== */
 
+/** Stores data returned when UIDPLUS extension is provided. */
+typedef struct ImapSequence_ {
+  GList *ranges; /**< list of ImapUidRange items */
+  unsigned uid_validity;
+}  ImapSequence;
+
+/** defines range [lo, hi] */
+typedef struct ImapUidRange_ {
+  unsigned lo, hi; 
+} ImapUidRange;
+
+#define imap_sequence_empty(i_seq) ( (i_seq)->ranges == NULL)
+unsigned imap_sequence_length(ImapSequence *i_seq);
+unsigned imap_sequence_nth(ImapSequence *i_seq, unsigned nth);
+void imap_sequence_foreach(ImapSequence *i_seq,
+			   void(*cb)(unsigned uid, void *arg), void *cb_arg);
+void imap_sequence_release(ImapSequence *i_seq);
+
 /* ================ BEGIN OF MBOX_VIEW FUNCTIONS ======================= */
 typedef struct _MboxView MboxView;
 void mbox_view_init(MboxView *mv);

Modified: trunk/libbalsa/imap/imap_private.h
==============================================================================
--- trunk/libbalsa/imap/imap_private.h	(original)
+++ trunk/libbalsa/imap/imap_private.h	Tue Apr  1 20:55:03 2008
@@ -76,6 +76,13 @@
                           * processing of current line is finished. */
   GNode *thread_root; /* deprecated! */
 
+  struct {
+    GList* src; /**< returned by COPY */
+    GList* dst; /**< returned by APPEND and COPY */
+    unsigned dst_uid_validity;
+    unsigned store_response:1;
+  } uidplus;
+
   /* BYE handling depends on the state */
   gboolean doing_logout;
   ImapInfoCb info_cb;
@@ -144,7 +151,10 @@
 
 void imap_mbox_resize_cache(ImapMboxHandle *h, unsigned new_size);
 
-ImapResponse imap_cmd_exec(ImapMboxHandle* handle, const char* cmd);
+ImapResponse imap_cmd_exec_cmdno(ImapMboxHandle* handle, const char* cmd,
+				 unsigned *cmdno);
+#define imap_cmd_exec(h, c) imap_cmd_exec_cmdno((h),(c),NULL)
+
 ImapResponse imap_cmd_issue(ImapMboxHandle* handle, const char* cmd);
 char* imap_mbox_gets(ImapMboxHandle *h, char* buf, size_t sz);
 

Modified: trunk/libbalsa/imap/imap_tst.c
==============================================================================
--- trunk/libbalsa/imap/imap_tst.c	(original)
+++ trunk/libbalsa/imap/imap_tst.c	Tue Apr  1 20:55:03 2008
@@ -488,7 +488,7 @@
     mi.src_dir = src_dir;
     mi.dir = dir;
     mi.fh = NULL;
-    res = imap_mbox_append_multi(h, mailbox, msg_iterator, &mi);
+    res = imap_mbox_append_multi(h, mailbox, msg_iterator, &mi, NULL);
 
   } else {
     for(res = IMR_OK; res == IMR_OK && (file = readdir(dir)) != NULL;) {

Modified: trunk/libbalsa/mailbox_imap.c
==============================================================================
--- trunk/libbalsa/mailbox_imap.c	(original)
+++ trunk/libbalsa/mailbox_imap.c	Tue Apr  1 20:55:03 2008
@@ -427,10 +427,10 @@
     gchar *fname;
 
     res[0] = get_cache_dir(is_persistent);
-    fname = g_strdup_printf("%s %s-%s-%s-%u-%u",
+    fname = g_strdup_printf("%s %s-%s-%u-%u-%s",
                             s->user, s->host,
                             (mailbox->path ? mailbox->path : "INBOX"),
-                            type, uid_validity, uid);
+                            uid_validity, uid, type);
     res[1] = libbalsa_urlencode(fname);
     g_free(fname);
     res[2] = NULL;
@@ -2641,7 +2641,7 @@
     LibBalsaAddMessageIterator msg_iterator;
     void *iterator_data;
     GMimeStream *outstream;
-    gchar *outfile;
+    GList *outfiles;
     GError **err;
     guint copied;
 };
@@ -2652,11 +2652,20 @@
     if(macd->outstream) {
 	g_object_unref(macd->outstream);
 	macd->outstream = NULL;
-	unlink(macd->outfile);
-	g_free(macd->outfile);
     }
 }
 
+static void
+macd_destroy(struct MultiAppendCbData *macd)
+{
+    GList *outmsgs;
+    for(outmsgs = macd->outfiles; outmsgs; outmsgs = outmsgs->next) {
+	unlink(outmsgs->data);
+	g_free(outmsgs->data);
+    }
+    g_list_free(macd->outfiles);
+}
+
 static size_t
 multi_append_cb(char * buf, size_t buflen,
 		ImapAppendMultiStage stage,
@@ -2674,6 +2683,7 @@
 	gssize len;
 	LibBalsaMessageFlag flags;
 	GError**err = macd->err;
+	gchar *outf = NULL;
 
 	macd_clear(macd);
 
@@ -2701,7 +2711,7 @@
 	g_mime_stream_filter_add(GMIME_STREAM_FILTER(tmpstream), crlffilter);
 	g_object_unref(crlffilter);
 
-	outfd = g_file_open_tmp("balsa-tmp-file-XXXXXX", &macd->outfile, err);
+	outfd = g_file_open_tmp("balsa-tmp-file-XXXXXX", &outf, err);
 	if (outfd < 0) {
 	    g_warning("Could not create temporary file: %s", (*err)->message);
 	    g_object_unref(tmpstream);
@@ -2710,6 +2720,7 @@
 	}
 
 	macd->outstream = g_mime_stream_fs_new(outfd);
+	macd->outfiles = g_list_append(macd->outfiles, outf);
 	libbalsa_mime_stream_shared_lock(stream);
 	g_mime_stream_write_to_stream(tmpstream, macd->outstream);
 	libbalsa_mime_stream_shared_unlock(stream);
@@ -2735,6 +2746,59 @@
     return 0;
 }
 
+struct append_to_cache_data {
+    const gchar *user, *host, *path, *cache_dir;
+    GList *curr_name;
+    unsigned uid_validity;
+};
+
+static void
+append_to_cache(unsigned uid, void *arg)
+{
+    struct append_to_cache_data *atcd = (struct append_to_cache_data*)arg;
+    gchar *name = g_strdup_printf("%s %s-%s-%u-%u-%s",
+				  atcd->user, atcd->host, atcd->path,
+				  atcd->uid_validity,
+				  uid, "body");
+    gchar *fname = libbalsa_urlencode(name);
+    gchar *cache_name = g_build_filename(atcd->cache_dir, fname, NULL);
+    gchar *msg = atcd->curr_name->data;
+
+    g_free(name);
+    g_free(fname);
+
+    atcd->curr_name = g_list_next(atcd->curr_name);
+
+    g_return_if_fail(msg);
+
+    printf("Copy tmp file %s  to cache %s\n", msg, cache_name);
+
+    if(link(msg, cache_name) == 0) {
+	printf("Linking msg for message UID %u succeeded.\n", uid);
+    } else {
+	FILE *in  = fopen(msg, "r");
+
+	if(in) {
+	    FILE *out = fopen(cache_name, "w");
+	    char buf[65536];
+	    size_t sz;
+	    if(out) {
+		gboolean err;
+		while( (sz=fread(buf, 1, sizeof(buf), in)) > 0)
+		    if(fwrite(buf, 1, sz, out) != sz)
+			break;
+		err = ferror(in) || ferror(out);
+		fclose(out);
+		if(err)
+		    unlink(cache_name);
+	    }
+	    fclose(in);
+	}
+	printf("Copying msg for message UID %u succeeded.\n", uid);
+    }
+    g_free(cache_name);
+}
+
 static guint
 libbalsa_mailbox_imap_add_messages(LibBalsaMailbox * mailbox,
 				   LibBalsaAddMessageIterator msg_iterator,
@@ -2744,6 +2808,7 @@
     ImapMboxHandle *handle = libbalsa_mailbox_imap_get_handle(mimap, err);
     struct MultiAppendCbData macd;
     ImapResponse rc;
+    ImapSequence uid_sequence;
 
     if (!handle) {
 	/* Perhaps the mailbox was closed and the authentication
@@ -2755,13 +2820,40 @@
     macd.msg_iterator = msg_iterator;
     macd.iterator_data = arg;
     macd.outstream = NULL;
-    macd.outfile = NULL;
+    macd.outfiles = NULL;
     macd.err = err;
     macd.copied = 0;
+    uid_sequence.uid_validity = 0;
+    uid_sequence.ranges = NULL;
     rc = imap_mbox_append_multi(handle,	mimap->path,
-				multi_append_cb, &macd);
+				multi_append_cb, &macd, &uid_sequence);
     libbalsa_mailbox_imap_release_handle(mimap);
     macd_clear(&macd);
+    printf("Counts: tmp files : %u uids: %u\n",
+	   g_list_length(macd.outfiles), imap_sequence_length(&uid_sequence));
+    if(!imap_sequence_empty(&uid_sequence) &&
+       g_list_length(macd.outfiles) == imap_sequence_length(&uid_sequence)) {
+	/* Hurray, server returned UID data on appended messages! */
+	LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
+	LibBalsaServer *s      = LIBBALSA_MAILBOX_REMOTE(mailbox)->server;
+	LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s);
+	gboolean is_persistent = libbalsa_imap_server_has_persistent_cache(is);
+	struct append_to_cache_data atcd;
+	gchar *cache_dir;
+
+	atcd.user = s->user;
+	atcd.host = s->host;
+	atcd.path = mimap->path ? mimap->path : "INBOX";
+	atcd.cache_dir = cache_dir = get_cache_dir(is_persistent);
+	atcd.curr_name = macd.outfiles;
+	atcd.uid_validity = uid_sequence.uid_validity;
+
+	imap_sequence_foreach(&uid_sequence, append_to_cache, &atcd);
+	imap_sequence_release(&uid_sequence);
+	g_free(cache_dir);
+    }
+
+    macd_destroy(&macd);
     return rc == IMR_OK ? macd.copied : 0;
 }
 #endif
@@ -3057,14 +3149,25 @@
 	LIBBALSA_MAILBOX_REMOTE(dest)->server ==
 	LIBBALSA_MAILBOX_REMOTE(mailbox)->server) {
         gboolean ret;
+	LibBalsaMailboxImap *mimap = LIBBALSA_MAILBOX_IMAP(mailbox);
 	ImapMboxHandle *handle = LIBBALSA_MAILBOX_IMAP(mailbox)->handle;
+	ImapSequence uid_sequence;
+	unsigned *seqno = (unsigned*)msgnos->data, *uids;
+	unsigned im;
 	g_return_val_if_fail(handle, FALSE);
-
+	
 	/* User server-side copy. */
 	g_array_sort(msgnos, cmp_msgno);
+	uids = g_new(unsigned, msgnos->len);
+	for(im=0; im<msgnos->len; im++) {
+	    ImapMessage * imsg = imap_mbox_handle_get_msg(handle, seqno[im]);
+	    uids[im] = imsg ? imsg->uid : 0;
+	}
+	    
 	ret = imap_mbox_handle_copy(handle, msgnos->len,
                                     (guint *) msgnos->data,
-                                    LIBBALSA_MAILBOX_IMAP(dest)->path)
+                                    LIBBALSA_MAILBOX_IMAP(dest)->path,
+				    &uid_sequence)
 	    == IMR_OK;
         if(!ret) {
             gchar *msg = imap_mbox_handle_get_last_msg(handle);
@@ -3072,7 +3175,69 @@
                         LIBBALSA_MAILBOX_COPY_ERROR,
                         "%s", msg);
             g_free(msg);
-        }
+        } else {
+	    /* Copy cache files. */
+	    GDir *dir;
+	    LibBalsaServer *s      = LIBBALSA_MAILBOX_REMOTE(mailbox)->server;
+	    LibBalsaImapServer *is = LIBBALSA_IMAP_SERVER(s);
+	    LibBalsaMailboxImap *dst_imap = LIBBALSA_MAILBOX_IMAP(dest);
+	    gboolean is_persistent =
+		libbalsa_imap_server_has_persistent_cache(is);
+	    gchar *dir_name = get_cache_dir(is_persistent);
+	    gchar *src_prefix = g_strdup_printf("%s %s-%s-%u-",
+						s->user, s->host,
+						(mimap->path 
+						 ? mimap->path : "INBOX"),
+						mimap->uid_validity);
+	    gchar *encoded_path = libbalsa_urlencode(src_prefix);
+	    g_free(src_prefix);
+	    dir = g_dir_open(dir_name, 0, NULL);
+	    if(dir) {
+		const gchar *filename;
+		size_t prefix_length = strlen(encoded_path);
+		unsigned nth;
+		printf("UIDVAL=%u Look for files that match %s \n",
+		       uid_sequence.uid_validity, encoded_path);
+		while ((filename = g_dir_read_name(dir)) != NULL) {
+		    unsigned msg_uid;
+		    gchar *tail;
+		    if(strncmp(encoded_path, filename, prefix_length))
+			continue;
+		    msg_uid = strtol(filename + prefix_length, &tail, 10);
+		    for(im = 0; im<msgnos->len; im++) {
+			if(uids[im]>msg_uid) break;
+			else if(uids[im]==msg_uid &&
+				(nth = imap_sequence_nth(&uid_sequence, im))
+				 ) {
+			    gchar *dst;
+			    /* Copy by hardlinks! */
+			    gchar *src =
+				g_build_filename(dir_name, filename, NULL);
+			    gchar *dst_prefix =
+				g_strdup_printf("%s %s-%s-%u-%u%s",
+						s->user, s->host,
+						(dst_imap->path 
+						 ? dst_imap->path : "INBOX"),
+						uid_sequence.uid_validity,
+						nth, tail);
+			    gchar *encoded = libbalsa_urlencode(dst_prefix);
+
+			    g_free(dst_prefix);
+			    dst = g_build_filename(dir_name, encoded, NULL);
+			    printf("Linking %s to %s...\n", src, dst);
+			    if(link(src, dst) != 0)
+				printf("...failed.\n");
+			    g_free(dst);
+			    break;
+			}
+		    }
+		}
+		g_dir_close(dir);
+	    }
+	    g_free(dir_name);
+	}
+	g_free(uids);
+	imap_sequence_release(&uid_sequence);
         return ret;
     }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]