aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--camel/ChangeLog21
-rw-r--r--camel/providers/imap/camel-imap-command.c311
-rw-r--r--camel/providers/imap/camel-imap-command.h41
-rw-r--r--camel/providers/imap/camel-imap-folder.c360
-rw-r--r--camel/providers/imap/camel-imap-store.c4
5 files changed, 504 insertions, 233 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog
index 8c01bb243c..c8c34fc609 100644
--- a/camel/ChangeLog
+++ b/camel/ChangeLog
@@ -1,3 +1,24 @@
+2001-07-26 Dan Winship <danw@ximian.com>
+
+ * providers/imap/camel-imap-command.c (camel_imap_command_start):
+ Send an IMAP command, but don't wait for responses.
+ (camel_imap_command_response): Read a single line of response from
+ the server.
+ (camel_imap_command, etc): Reimplement in terms of the new code.
+
+ * providers/imap/camel-imap-folder.c (imap_rescan): Use
+ camel_imap_command_start and camel_imap_command_response, and
+ call camel_operation_progress after each line read from the
+ server.
+ (imap_update_summary): Likewise, although with more fudging on the
+ precentages... Also, fix this so that if none of the new messages
+ are cached, it only does a single FETCH, and if some of them are
+ cached, it does two FETCHes (one to get the UIDs, FLAGS, and
+ SIZEs, and another to get the bodies of all of the messages that
+ aren't cached now that it knows the relevant UIDs). This should
+ speed up startup a bunch (especially if you have high bandwidth
+ but also high latency to the IMAP server).
+
2001-07-25 Dan Winship <danw@ximian.com>
* camel-mime-utils.c (mail_mlist_magic): Add another Sender
diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c
index db113e0fd7..428ecf565d 100644
--- a/camel/providers/imap/camel-imap-command.c
+++ b/camel/providers/imap/camel-imap-command.c
@@ -40,36 +40,34 @@
#include "camel-imap-private.h"
#include <camel/camel-exception.h>
+static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ const char *cmd, CamelException *ex);
+CamelImapResponse *imap_read_response (CamelImapStore *store,
+ CamelException *ex);
static char *imap_read_untagged (CamelImapStore *store, char *line,
CamelException *ex);
-static CamelImapResponse *imap_read_response (CamelImapStore *store,
- CamelException *ex);
static char *imap_command_strdup_vprintf (CamelImapStore *store,
const char *fmt, va_list ap);
+static char *imap_command_strdup_printf (CamelImapStore *store,
+ const char *fmt, ...);
/**
- * camel_imap_command: Send a command to a IMAP server and get a response
+ * camel_imap_command:
* @store: the IMAP store
* @folder: The folder to perform the operation in (or %NULL if not
* relevant).
* @ex: a CamelException
- * @fmt: an sort of printf-style format string, followed by arguments
+ * @fmt: a sort of printf-style format string, followed by arguments
*
- * This function makes sure that @folder (if non-%NULL) is the
- * currently-selected folder on @store and then sends the IMAP command
- * specified by @fmt and the following arguments. It then reads the
- * server's response(s) and parses the final result.
+ * This function calls camel_imap_command_start() to send the
+ * command, then reads the complete response to it using
+ * camel_imap_command_response() and returns a CamelImapResponse
+ * structure.
*
* As a special case, if @fmt is %NULL, it will just select @folder
* and return the response from doing so.
- *
- * @fmt can include the following %-escapes ONLY:
- * %s, %d, %%: as with printf
- * %S: an IMAP "string" (quoted string or literal)
*
- * %S strings will be passed as literals if the server supports LITERAL+
- * and quoted strings otherwise. (%S does not support strings that
- * contain newlines.)
+ * See camel_imap_command_start() for details on @fmt.
*
* On success, the store's command_lock will be locked. It will be freed
* when you call camel_imap_response_free. (The lock is recursive, so
@@ -84,73 +82,123 @@ CamelImapResponse *
camel_imap_command (CamelImapStore *store, CamelFolder *folder,
CamelException *ex, const char *fmt, ...)
{
- gchar *cmdbuf;
va_list ap;
- CamelException internal_ex;
+ char *cmd;
CAMEL_IMAP_STORE_LOCK (store, command_lock);
- /* Check for current folder */
- if (folder && (!fmt || folder != store->current_folder)) {
- CamelImapResponse *response;
-
+ if (fmt) {
+ va_start (ap, fmt);
+ cmd = imap_command_strdup_vprintf (store, fmt, ap);
+ va_end (ap);
+ } else {
if (store->current_folder) {
camel_object_unref (CAMEL_OBJECT (store->current_folder));
store->current_folder = NULL;
}
- response = camel_imap_command (store, NULL, ex, "SELECT %S",
- folder->full_name);
- if (!response) {
- CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return NULL;
- }
store->current_folder = folder;
camel_object_ref (CAMEL_OBJECT (folder));
+ cmd = imap_command_strdup_printf (store, "SELECT %S",
+ folder->full_name);
+ }
- camel_imap_folder_selected (folder, response, ex);
- if (!fmt) {
- /* This undoes the level of locking we did,
- * but not the level of locking associated with
- * "response".
- */
- CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return response;
- }
-
- /* Contrariwise, this undoes "response"s lock,
- * but not our own.
- */
- camel_imap_response_free (store, response);
+ if (!imap_command_start (store, folder, cmd, ex)) {
+ g_free (cmd);
+ CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
+ return NULL;
}
+ g_free (cmd);
+
+ return imap_read_response (store, ex);
+}
+
+/**
+ * camel_imap_command_start:
+ * @store: the IMAP store
+ * @folder: The folder to perform the operation in (or %NULL if not
+ * relevant).
+ * @ex: a CamelException
+ * @fmt: a sort of printf-style format string, followed by arguments
+ *
+ * This function makes sure that @folder (if non-%NULL) is the
+ * currently-selected folder on @store and then sends the IMAP command
+ * specified by @fmt and the following arguments.
+ *
+ * @fmt can include the following %-escapes ONLY:
+ * %s, %d, %%: as with printf
+ * %S: an IMAP "string" (quoted string or literal)
+ *
+ * %S strings will be passed as literals if the server supports LITERAL+
+ * and quoted strings otherwise. (%S does not support strings that
+ * contain newlines.)
+ *
+ * On success, the store's command_lock will be locked. It will be
+ * freed when %CAMEL_IMAP_RESPONSE_TAGGED or %CAMEL_IMAP_RESPONSE_ERROR
+ * is returned from camel_imap_command_response(). (The lock is
+ * recursive, so callers can grab and release it themselves if they
+ * need to run multiple commands atomically.)
+ *
+ * Return value: %TRUE if the command was sent successfully, %FALSE if
+ * an error occurred (in which case @ex will be set).
+ **/
+gboolean
+camel_imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ CamelException *ex, const char *fmt, ...)
+{
+ va_list ap;
+ char *cmd;
+ gboolean ok;
- /* Send the command */
va_start (ap, fmt);
- cmdbuf = imap_command_strdup_vprintf (store, fmt, ap);
+ cmd = imap_command_strdup_vprintf (store, fmt, ap);
va_end (ap);
- camel_exception_init (&internal_ex);
- camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), &internal_ex,
- "%c%.5d %s\r\n", store->tag_prefix,
- store->command++, cmdbuf);
- g_free (cmdbuf);
- if (camel_exception_is_set (&internal_ex)) {
- camel_exception_xfer (ex, &internal_ex);
+ CAMEL_IMAP_STORE_LOCK (store, command_lock);
+ ok = imap_command_start (store, folder, cmd, ex);
+ g_free (cmd);
+
+ if (!ok)
CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return NULL;
+ return ok;
+}
+
+static gboolean
+imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ const char *cmd, CamelException *ex)
+{
+ /* Check for current folder */
+ if (folder && folder != store->current_folder) {
+ CamelImapResponse *response;
+ CamelException internal_ex;
+
+ response = camel_imap_command (store, folder, ex, NULL);
+ if (!response)
+ return NULL;
+ camel_exception_init (&internal_ex);
+ camel_imap_folder_selected (folder, response, &internal_ex);
+ camel_imap_response_free (store, response);
+ if (camel_exception_is_set (&internal_ex)) {
+ camel_exception_xfer (ex, &internal_ex);
+ return FALSE;
+ }
}
- /* Read the response. */
- return imap_read_response (store, ex);
+ /* Send the command */
+ return camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex,
+ "%c%.5d %s\r\n",
+ store->tag_prefix,
+ store->command++, cmd) != -1;
}
/**
- * camel_imap_command_continuation: Send more command data to the IMAP server
+ * camel_imap_command_continuation:
* @store: the IMAP store
+ * @cmd: buffer containing the response/request data
* @ex: a CamelException
- * @cmdbuf: buffer containing the response/request data
*
* This method is for sending continuing responses to the IMAP server
- * after camel_imap_command returns a CAMEL_IMAP_PLUS response.
+ * after camel_imap_command() or camel_imap_command_response() returns
+ * a continuation response.
*
* This function assumes you have an exclusive lock on the remote stream.
*
@@ -158,11 +206,11 @@ camel_imap_command (CamelImapStore *store, CamelFolder *folder,
* command_lock will be released.
**/
CamelImapResponse *
-camel_imap_command_continuation (CamelImapStore *store, CamelException *ex,
- const char *cmdbuf)
+camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
+ CamelException *ex)
{
if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex,
- "%s\r\n", cmdbuf) < 0) {
+ "%s\r\n", cmd) < 0) {
CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
return NULL;
}
@@ -170,54 +218,90 @@ camel_imap_command_continuation (CamelImapStore *store, CamelException *ex,
return imap_read_response (store, ex);
}
-/* Read the response to an IMAP command. */
-static CamelImapResponse *
-imap_read_response (CamelImapStore *store, CamelException *ex)
+/**
+ * camel_imap_command_response:
+ * @store: the IMAP store
+ * @response: a pointer to pass back the response data in
+ * @ex: a CamelException
+ *
+ * This reads a single tagged, untagged, or continuation response from
+ * @store into *@response. The caller must free the string when it is
+ * done with it.
+ *
+ * Return value: One of %CAMEL_IMAP_RESPONSE_CONTINUATION,
+ * %CAMEL_IMAP_RESPONSE_UNTAGGED, %CAMEL_IMAP_RESPONSE_TAGGED, or
+ * %CAMEL_IMAP_RESPONSE_ERROR. If either of the last two, @store's
+ * command lock will be unlocked.
+ **/
+CamelImapResponseType
+camel_imap_command_response (CamelImapStore *store, char **response,
+ CamelException *ex)
{
- CamelImapResponse *response;
- CamelException internal_ex;
- char *respbuf, *retcode;
+ CamelImapResponseType type;
+ char *respbuf;
- /* Read first line */
if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store),
&respbuf, ex) < 0) {
CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return NULL;
+ return CAMEL_IMAP_RESPONSE_ERROR;
}
- response = g_new0 (CamelImapResponse, 1);
- if (camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
- response->folder = store->current_folder;
- if (response->folder)
- camel_object_ref (CAMEL_OBJECT (response->folder));
- }
- response->untagged = g_ptr_array_new ();
+ switch (*respbuf) {
+ case '*':
+ type = CAMEL_IMAP_RESPONSE_UNTAGGED;
- camel_exception_init (&internal_ex);
-
- /* Check for untagged data */
- while (!strncmp (respbuf, "* ", 2)) {
/* Read the rest of the response if it is multi-line. */
- respbuf = imap_read_untagged (store, respbuf, &internal_ex);
- if (camel_exception_is_set (&internal_ex))
- break;
-
- if (!g_strncasecmp (respbuf, "* BYE", 5)) {
+ respbuf = imap_read_untagged (store, respbuf, ex);
+ if (!respbuf)
+ type = CAMEL_IMAP_RESPONSE_ERROR;
+ else if (!g_strncasecmp (respbuf, "* BYE", 5)) {
/* Connection was lost, no more data to fetch */
store->connected = FALSE;
g_free (respbuf);
- respbuf = NULL;
- break;
+ type = CAMEL_IMAP_RESPONSE_ERROR;
}
+ break;
+ case '+':
+ type = CAMEL_IMAP_RESPONSE_CONTINUATION;
+ break;
+ default:
+ type = CAMEL_IMAP_RESPONSE_TAGGED;
+ break;
+ }
+ *response = respbuf;
- g_ptr_array_add (response->untagged, respbuf);
- if (camel_remote_store_recv_line (
- CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0)
- break;
+ if (type == CAMEL_IMAP_RESPONSE_ERROR ||
+ type == CAMEL_IMAP_RESPONSE_TAGGED)
+ CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
+ return type;
+}
+
+CamelImapResponse *
+imap_read_response (CamelImapStore *store, CamelException *ex)
+{
+ CamelImapResponse *response;
+ CamelImapResponseType type;
+ char *respbuf, *p;
+
+ /* Get another lock so that when we reach the tagged
+ * response and camel_imap_command_response unlocks,
+ * we're still locked. This lock is owned by response
+ * and gets unlocked when response is freed.
+ */
+ CAMEL_IMAP_STORE_LOCK (store, command_lock);
+
+ response = g_new0 (CamelImapResponse, 1);
+ if (store->current_folder && camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
+ response->folder = store->current_folder;
+ camel_object_ref (CAMEL_OBJECT (response->folder));
}
- if (!respbuf || camel_exception_is_set (&internal_ex)) {
- camel_exception_xfer (ex, &internal_ex);
+ response->untagged = g_ptr_array_new ();
+ while ((type = camel_imap_command_response (store, &respbuf, ex))
+ == CAMEL_IMAP_RESPONSE_UNTAGGED)
+ g_ptr_array_add (response->untagged, respbuf);
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR) {
camel_imap_response_free_without_processing (store, response);
return NULL;
}
@@ -227,14 +311,14 @@ imap_read_response (CamelImapStore *store, CamelException *ex)
/* Check for OK or continuation response. */
if (*respbuf == '+')
return response;
- retcode = imap_next_word (respbuf);
- if (!strncmp (retcode, "OK", 2))
+ p = strchr (respbuf, ' ');
+ if (p && !g_strncasecmp (p, " OK", 3))
return response;
/* We should never get BAD, or anything else but +, OK, or NO
* for that matter.
*/
- if (strncmp (retcode, "NO", 2) != 0) {
+ if (!p || g_strncasecmp (p, " NO", 3) != 0) {
g_warning ("Unexpected response from IMAP server: %s",
respbuf);
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
@@ -244,10 +328,12 @@ imap_read_response (CamelImapStore *store, CamelException *ex)
return NULL;
}
- retcode = imap_next_word (retcode);
+ p += 3;
+ if (!*p++)
+ p = NULL;
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
_("IMAP command failed: %s"),
- retcode ? retcode : _("Unknown error"));
+ p ? p : _("Unknown error"));
camel_imap_response_free_without_processing (store, response);
return NULL;
}
@@ -293,9 +379,9 @@ imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
str->str + 1, length);
if (nread == -1) {
if (errno == EINTR)
- camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
+ camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
else
- camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, strerror(errno));
+ camel_exception_set(ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, strerror(errno));
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
goto lose;
}
@@ -409,13 +495,6 @@ camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
sizeof (int));
}
g_array_append_val (expunged, number);
-
- /* camel_imap_folder_changed expects
- * "exists" to be the value after all
- * expunges.
- */
- if (exists)
- exists--;
}
}
g_free (resp);
@@ -427,12 +506,8 @@ camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
if (response->folder) {
if (exists > 0 || expunged) {
/* Update the summary */
- CamelException ex;
-
- camel_exception_init (&ex);
- camel_imap_folder_changed (response->folder, exists, expunged, &ex);
- camel_exception_clear (&ex);
-
+ camel_imap_folder_changed (response->folder,
+ exists, expunged, NULL);
if (expunged)
g_array_free (expunged, TRUE);
}
@@ -535,7 +610,6 @@ camel_imap_response_extract_continuation (CamelImapStore *store,
if (response->status && *response->status == '+') {
status = response->status;
response->status = NULL;
- CAMEL_IMAP_STORE_LOCK (store, command_lock);
camel_imap_response_free (store, response);
return status;
}
@@ -651,3 +725,16 @@ imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
return out;
}
+
+static char *
+imap_command_strdup_printf (CamelImapStore *store, const char *fmt, ...)
+{
+ va_list ap;
+ char *result;
+
+ va_start (ap, fmt);
+ result = imap_command_strdup_vprintf (store, fmt, ap);
+ va_end (ap);
+
+ return result;
+}
diff --git a/camel/providers/imap/camel-imap-command.h b/camel/providers/imap/camel-imap-command.h
index ba9ca7010a..fbcf82ee7d 100644
--- a/camel/providers/imap/camel-imap-command.h
+++ b/camel/providers/imap/camel-imap-command.h
@@ -36,6 +36,13 @@ extern "C" {
#include <glib.h>
#include "camel-imap-types.h"
+typedef enum {
+ CAMEL_IMAP_RESPONSE_ERROR,
+ CAMEL_IMAP_RESPONSE_CONTINUATION,
+ CAMEL_IMAP_RESPONSE_UNTAGGED,
+ CAMEL_IMAP_RESPONSE_TAGGED
+} CamelImapResponseType;
+
struct _CamelImapResponse {
CamelFolder *folder;
GPtrArray *untagged;
@@ -47,19 +54,27 @@ CamelImapResponse *camel_imap_command (CamelImapStore *store,
CamelException *ex,
const char *fmt, ...);
CamelImapResponse *camel_imap_command_continuation (CamelImapStore *store,
- CamelException *ex,
- const char *cmdbuf);
+ const char *cmd,
+ CamelException *ex);
+
+void camel_imap_response_free (CamelImapStore *store,
+ CamelImapResponse *response);
+void camel_imap_response_free_without_processing (CamelImapStore *store,
+ CamelImapResponse *response);
+char *camel_imap_response_extract (CamelImapStore *store,
+ CamelImapResponse *response,
+ const char *type,
+ CamelException *ex);
+char *camel_imap_response_extract_continuation (CamelImapStore *store,
+ CamelImapResponse *response,
+ CamelException *ex);
-void camel_imap_response_free (CamelImapStore *store,
- CamelImapResponse *response);
-void camel_imap_response_free_without_processing(CamelImapStore *store,
- CamelImapResponse *response);
-char *camel_imap_response_extract (CamelImapStore *store,
- CamelImapResponse *response,
- const char *type,
- CamelException *ex);
-char *camel_imap_response_extract_continuation (CamelImapStore *store,
- CamelImapResponse *response,
- CamelException *ex);
+gboolean camel_imap_command_start (CamelImapStore *store,
+ CamelFolder *folder,
+ CamelException *ex,
+ const char *fmt, ...);
+CamelImapResponseType camel_imap_command_response (CamelImapStore *store,
+ char **respbuf,
+ CamelException *ex);
#endif /* CAMEL_IMAP_COMMAND_H */
diff --git a/camel/providers/imap/camel-imap-folder.c b/camel/providers/imap/camel-imap-folder.c
index 88b8e5f1b5..3984db4bd9 100644
--- a/camel/providers/imap/camel-imap-folder.c
+++ b/camel/providers/imap/camel-imap-folder.c
@@ -411,59 +411,77 @@ imap_rescan (CamelFolder *folder, int exists, CamelException *ex)
{
CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
- CamelImapResponse *response;
struct {
char *uid;
guint32 flags;
- } *new = NULL;
+ } *new;
char *resp;
- int i, seq, summary_len;
+ CamelImapResponseType type;
+ int i, seq, summary_len, summary_got;
CamelMessageInfo *info;
CamelImapMessageInfo *iinfo;
GArray *removed;
- GData *fetch_data;
- gpointer data;
+ gboolean ok;
CAMEL_IMAP_STORE_ASSERT_LOCKED (store, command_lock);
imap_folder->need_rescan = FALSE;
- camel_operation_start (NULL, _("Scanning IMAP folder"));
-
summary_len = camel_folder_summary_count (folder->summary);
- if (summary_len) {
- /* Check UIDs and flags of all messages we already know of. */
- info = camel_folder_summary_index (folder->summary, summary_len - 1);
- response = camel_imap_command (store, folder, ex,
- "UID FETCH 1:%s (FLAGS)",
- camel_message_info_uid (info));
- camel_folder_summary_info_free (folder->summary, info);
- if (!response) {
- camel_operation_end (NULL);
- return;
- }
+ if (summary_len == 0) {
+ if (exists)
+ camel_imap_folder_changed (folder, exists, NULL, ex);
+ return;
+ }
- new = g_malloc0 (summary_len * sizeof (*new));
- for (i = 0; i < response->untagged->len; i++) {
- resp = response->untagged->pdata[i];
+ /* Check UIDs and flags of all messages we already know of. */
+ camel_operation_start (NULL, _("Scanning for changed messages"));
+ info = camel_folder_summary_index (folder->summary, summary_len - 1);
+ ok = camel_imap_command_start (store, folder, ex,
+ "UID FETCH 1:%s (FLAGS)",
+ camel_message_info_uid (info));
+ camel_folder_summary_info_free (folder->summary, info);
+ if (!ok) {
+ camel_operation_end (NULL);
+ return;
+ }
- seq = strtoul (resp + 2, &resp, 10);
- if (g_strncasecmp (resp, " FETCH (", 8) != 0)
- continue;
- if (seq >= summary_len)
- continue;
+ new = g_malloc0 (summary_len * sizeof (*new));
+ summary_got = 0;
+ while ((type = camel_imap_command_response (store, &resp, ex)) == CAMEL_IMAP_RESPONSE_UNTAGGED) {
+ GData *data;
+ char *uid;
+ guint32 flags;
- fetch_data = parse_fetch_response (imap_folder, resp + 7);
- data = g_datalist_get_data (&fetch_data, "UID");
- if (data && !new[seq - 1].uid)
- new[seq - 1].uid = g_strdup (data);
- data = g_datalist_get_data (&fetch_data, "FLAGS");
- if (data)
- new[seq - 1].flags = GPOINTER_TO_UINT (data);
- g_datalist_clear (&fetch_data);
+ data = parse_fetch_response (imap_folder, resp);
+ g_free (resp);
+ if (!data)
+ continue;
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ uid = g_datalist_get_data (&data, "UID");
+ flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS"));
+
+ if (!uid || !seq || seq >= summary_len) {
+ g_datalist_clear (&data);
+ continue;
}
- camel_imap_response_free_without_processing (store, response);
+
+ camel_operation_progress (NULL, ++summary_got * 100 / summary_len);
+ new[seq - 1].uid = g_strdup (uid);
+ new[seq - 1].flags = flags;
+ g_datalist_clear (&data);
}
+ camel_operation_end (NULL);
+ if (type == CAMEL_IMAP_RESPONSE_ERROR) {
+ for (i = 0; i < summary_len && new[i].uid; i++)
+ g_free (new[i].uid);
+ g_free (new);
+ return;
+ }
+ /* Free the final tagged response */
+ g_free (resp);
+
/* If we find a UID in the summary that doesn't correspond to
* the UID in the folder, then either: (a) it's a real UID,
* but the message was deleted on the server, or (b) it's a
@@ -521,8 +539,6 @@ imap_rescan (CamelFolder *folder, int exists, CamelException *ex)
/* And finally update the summary. */
camel_imap_folder_changed (folder, exists, removed, ex);
g_array_free (removed, TRUE);
-
- camel_operation_end (NULL);
}
/* Find all messages in @folder with flags matching @flags and @mask.
@@ -946,7 +962,7 @@ do_append (CamelFolder *folder, CamelMimeMessage *message,
/* send the rest of our data - the mime message */
g_byte_array_append (ba, "\0", 3);
- response = camel_imap_command_continuation (store, ex, ba->data);
+ response = camel_imap_command_continuation (store, ba->data, ex);
g_byte_array_free (ba, TRUE);
if (!response)
return response;
@@ -1536,110 +1552,226 @@ imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid,
camel_object_unref (CAMEL_OBJECT (stream));
}
+/* We pretend that a FLAGS or RFC822.SIZE response is always exactly
+ * 20 bytes long, and a BODY[HEADERS] response is always 2000 bytes
+ * long. Since we know how many of each kind of response we're
+ * expecting, we can find the total (pretend) amount of server traffic
+ * to expect and then count off the responses as we read them to update
+ * the progress bar.
+ */
+#define IMAP_PRETEND_SIZEOF_FLAGS 20
+#define IMAP_PRETEND_SIZEOF_SIZE 20
+#define IMAP_PRETEND_SIZEOF_HEADERS 2000
+
+static void
+add_message_from_data (CamelFolder *folder, GPtrArray *messages,
+ int first, GData *data)
+{
+ int seq;
+ CamelMimeMessage *msg;
+ CamelStream *stream;
+ CamelMessageInfo *mi;
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ if (seq < first)
+ return;
+ stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
+ if (!stream)
+ return;
+
+ if (seq - first >= messages->len)
+ g_ptr_array_set_size (messages, seq - first + 1);
+
+ msg = camel_mime_message_new ();
+ camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream);
+ mi = camel_folder_summary_info_new_from_message (folder->summary, msg);
+ camel_object_unref (CAMEL_OBJECT (msg));
+
+ messages->pdata[seq - first] = mi;
+}
+
static void
-imap_update_summary (CamelFolder *folder,
+imap_update_summary (CamelFolder *folder, int exists,
CamelFolderChangeInfo *changes,
GPtrArray *recents,
CamelException *ex)
{
CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
- CamelImapResponse *response;
- GPtrArray *lines, *messages;
- char *p, *uid;
- int i, seq, first, exists = 0;
- CamelMimeMessage *msg;
+ CamelImapResponseType type;
+ GPtrArray *fetch_data = NULL, *messages = NULL, *needheaders;
+ char *uid, *resp;
+ const char *header_spec;
+ int i, seq, first, size, got, uidval;
CamelMessageInfo *mi;
- GData *fetch_data;
CamelStream *stream;
+ guint32 flags;
+ GData *data;
CAMEL_IMAP_STORE_ASSERT_LOCKED (store, command_lock);
+ if (store->server_level >= IMAP_LEVEL_IMAP4REV1)
+ header_spec = "HEADER";
+ else
+ header_spec = "0";
- first = camel_folder_summary_count (folder->summary) + 1;
+ /* Figure out if any of the new messages are already cached (which
+ * may be the case if we're re-syncing after disconnected operation).
+ * If so, get their UIDs, FLAGS, and SIZEs. If not, get all that
+ * and ask for the headers too at the same time.
+ */
+ seq = camel_folder_summary_count (folder->summary);
+ first = seq + 1;
+ if (seq > 0) {
+ mi = camel_folder_summary_index (folder->summary, seq - 1);
+ uidval = atoi (camel_message_info_uid (mi));
+ camel_folder_summary_info_free (folder->summary, mi);
+ } else
+ uidval = 0;
- response = camel_imap_command (store, folder, ex, "FETCH %d:* (UID FLAGS RFC822.SIZE)", first);
- if (!response)
- return;
+ size = (exists - seq) * (IMAP_PRETEND_SIZEOF_FLAGS + IMAP_PRETEND_SIZEOF_SIZE);
+ got = 0;
+
+ if (uidval >= camel_imap_message_cache_max_uid (imap_folder->cache)) {
+ /* None of the new messages are cached */
+ size += (exists - seq) * IMAP_PRETEND_SIZEOF_HEADERS;
+ if (!camel_imap_command_start (store, folder, ex,
+ "UID FETCH %d:* (FLAGS RFC822.SIZE BODY.PEEK[%s])",
+ uidval + 1, header_spec))
+ return;
+ camel_operation_start (NULL, _("Fetching summary information for new messages"));
+ } else {
+ if (!camel_imap_command_start (store, folder, ex,
+ "UID FETCH %d:* (FLAGS RFC822.SIZE)",
+ uidval + 1))
+ return;
+ camel_operation_start (NULL, _("Scanning for new messages"));
+ }
- /* Walk through the responses, looking for UIDs, and make sure
- * we have those headers cached.
+ /* Parse the responses. We can't add a message to the summary
+ * until we've gotten its headers, and there's no guarantee
+ * the server will send the responses in a useful order...
*/
+ fetch_data = g_ptr_array_new ();
messages = g_ptr_array_new ();
- lines = response->untagged;
- for (i = 0; i < lines->len; i++) {
- p = lines->pdata[i];
- if (*p++ != '*' || *p++ != ' ') {
- g_ptr_array_remove_index_fast (lines, i--);
+ while ((type = camel_imap_command_response (store, &resp, ex)) ==
+ CAMEL_IMAP_RESPONSE_UNTAGGED) {
+ data = parse_fetch_response (imap_folder, resp);
+ g_free (resp);
+ if (!data)
continue;
- }
- seq = strtoul (p, &p, 10);
- if (!g_strcasecmp (p, " EXISTS")) {
- exists = seq;
- g_ptr_array_remove_index_fast (lines, i--);
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ if (seq < first) {
+ g_datalist_clear (&data);
continue;
}
- if (!seq || seq < first || g_strncasecmp (p, " FETCH (", 8) != 0) {
- g_ptr_array_remove_index_fast (lines, i--);
- continue;
+
+ if (g_datalist_get_data (&data, "FLAGS"))
+ got += IMAP_PRETEND_SIZEOF_FLAGS;
+ if (g_datalist_get_data (&data, "RFC822.SIZE"))
+ got += IMAP_PRETEND_SIZEOF_SIZE;
+ stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
+ if (stream) {
+ got += IMAP_PRETEND_SIZEOF_HEADERS;
+
+ /* Use the stream now so we don't tie up many
+ * many fds if we're fetching many many messages.
+ */
+ add_message_from_data (folder, messages, first, data);
+ g_datalist_set_data (&data, "BODY_PART_STREAM", NULL);
}
- if (seq - first >= messages->len)
- g_ptr_array_set_size (messages, seq - first + 1);
+ camel_operation_progress (NULL, got * 100 / size);
+ g_ptr_array_add (fetch_data, data);
+ }
+ camel_operation_end (NULL);
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR)
+ goto lose;
- fetch_data = parse_fetch_response (imap_folder, p + 7);
- uid = g_datalist_get_data (&fetch_data, "UID");
+ /* Figure out which headers we still need to fetch. */
+ needheaders = g_ptr_array_new ();
+ size = got = 0;
+ for (i = 0; i < fetch_data->len; i++) {
+ data = fetch_data->pdata[i];
+ if (g_datalist_get_data (&data, "BODY_PART_LEN"))
+ continue;
+
+ uid = g_datalist_get_data (&data, "UID");
if (uid) {
- stream = camel_imap_folder_fetch_data (
- imap_folder, uid,
- store->server_level >= IMAP_LEVEL_IMAP4REV1 ?
- "HEADER" : "0", FALSE, ex);
- if (!stream) {
- camel_imap_response_free_without_processing (store, response);
- /* XXX messages */
- return;
- }
+ g_ptr_array_add (needheaders, uid);
+ size += IMAP_PRETEND_SIZEOF_HEADERS;
+ }
+ }
- msg = camel_mime_message_new ();
- camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream);
- camel_object_unref (CAMEL_OBJECT (stream));
- mi = camel_folder_summary_info_new_from_message (folder->summary, msg);
- camel_object_unref (CAMEL_OBJECT (msg));
+ /* And fetch them */
+ if (needheaders->len) {
+ char *set;
- messages->pdata[seq - first] = mi;
+ /* FIXME: sort needheaders */
+ set = imap_uid_array_to_set (folder->summary, needheaders);
+ g_ptr_array_free (needheaders, TRUE);
+ if (!camel_imap_command_start (store, folder, ex,
+ "UID FETCH %s BODY.PEEK[%s]",
+ set, header_spec)) {
+ g_free (set);
+ goto lose;
}
- g_datalist_clear (&fetch_data);
+ g_free (set);
+
+ camel_operation_start (NULL, _("Fetching summary information for new messages"));
+ while ((type = camel_imap_command_response (store, &resp, ex))
+ == CAMEL_IMAP_RESPONSE_UNTAGGED) {
+ data = parse_fetch_response (imap_folder, resp);
+ g_free (resp);
+ if (!data)
+ continue;
+
+ stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
+ if (stream) {
+ add_message_from_data (folder, messages, first, data);
+ got += IMAP_PRETEND_SIZEOF_HEADERS;
+ camel_operation_progress (NULL, got * 100 / size);
+ }
+ g_datalist_clear (&data);
+ }
+ camel_operation_end (NULL);
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR)
+ goto lose;
}
- /* Now go back through and create summary items */
- lines = response->untagged;
- for (i = 0; i < lines->len; i++) {
- p = lines->pdata[i];
- seq = strtoul (p + 2, &p, 10);
- p = strchr (p, '(');
+ /* Now finish up summary entries (fix UIDs, set flags and size) */
+ for (i = 0; i < fetch_data->len; i++) {
+ data = fetch_data->pdata[i];
- mi = messages->pdata[seq - first];
- if (!mi) /* ? */
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ if (seq >= first + messages->len) {
+ g_datalist_clear (&data);
continue;
- fetch_data = parse_fetch_response (imap_folder, p);
-
- if (g_datalist_get_data (&fetch_data, "UID"))
- camel_message_info_set_uid (mi, g_strdup (g_datalist_get_data (&fetch_data, "UID")));
- if (g_datalist_get_data (&fetch_data, "FLAGS")) {
- guint32 flags = GPOINTER_TO_INT (g_datalist_get_data (&fetch_data, "FLAGS"));
+ }
+ mi = messages->pdata[seq - first];
+ uid = g_datalist_get_data (&data, "UID");
+ if (uid)
+ camel_message_info_set_uid (mi, g_strdup (uid));
+ flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS"));
+ if (flags) {
((CamelImapMessageInfo *)mi)->server_flags = flags;
/* "or" them in with the existing flags that may
* have been set by summary_info_new_from_message.
*/
mi->flags |= flags;
}
- if (g_datalist_get_data (&fetch_data, "RFC822.SIZE"))
- mi->size = GPOINTER_TO_INT (g_datalist_get_data (&fetch_data, "RFC822.SIZE"));
+ size = GPOINTER_TO_INT (g_datalist_get_data (&data, "RFC822.SIZE"));
+ if (size)
+ mi->size = size;
- g_datalist_clear (&fetch_data);
+ g_datalist_clear (&data);
}
- camel_imap_response_free_without_processing (store, response);
+ g_ptr_array_free (fetch_data, TRUE);
+ /* And add the entries to the summary, etc. */
for (i = 0; i < messages->len; i++) {
mi = messages->pdata[i];
if (!mi) {
@@ -1648,14 +1780,28 @@ imap_update_summary (CamelFolder *folder,
}
camel_folder_summary_add (folder->summary, mi);
camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
+
if (recents && (mi->flags & CAMEL_IMAP_MESSAGE_RECENT))
g_ptr_array_add (recents, (char *)camel_message_info_uid (mi));
}
g_ptr_array_free (messages, TRUE);
+ return;
- /* Did more mail arrive while we were doing this? */
- if (exists && exists > camel_folder_summary_count (folder->summary))
- imap_update_summary (folder, changes, recents, ex);
+ lose:
+ if (fetch_data) {
+ for (i = 0; i < fetch_data->len; i++) {
+ data = fetch_data->pdata[i];
+ g_datalist_clear (&data);
+ }
+ g_ptr_array_free (fetch_data, TRUE);
+ }
+ if (messages) {
+ for (i = 0; i < messages->len; i++) {
+ if (messages->pdata[i])
+ camel_folder_summary_info_free (folder->summary, messages->pdata[i]);
+ }
+ g_ptr_array_free (fetch_data, TRUE);
+ }
}
/* Called with the store's command_lock locked */
@@ -1697,7 +1843,7 @@ camel_imap_folder_changed (CamelFolder *folder, int exists,
if (exists > len) {
if (imap_folder->do_filtering)
recents = g_ptr_array_new ();
- imap_update_summary (folder, changes, recents, ex);
+ imap_update_summary (folder, exists, changes, recents, ex);
}
if (camel_folder_change_info_changed (changes)) {
@@ -1827,6 +1973,8 @@ parse_fetch_response (CamelImapFolder *imap_folder, char *response)
if (g_strncasecmp (response, " FETCH (", 8) != 0)
return NULL;
response += 7;
+
+ g_datalist_set_data (&data, "SEQUENCE", GINT_TO_POINTER (seq));
}
do {
diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c
index 7589aedcab..580a889078 100644
--- a/camel/providers/imap/camel-imap-store.c
+++ b/camel/providers/imap/camel-imap-store.c
@@ -409,7 +409,7 @@ try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
if (camel_exception_is_set (ex))
goto break_and_lose;
- response = camel_imap_command_continuation (store, ex, sasl_resp);
+ response = camel_imap_command_continuation (store, sasl_resp, ex);
g_free (sasl_resp);
if (!response)
goto lose;
@@ -430,7 +430,7 @@ try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
break_and_lose:
/* Get the server out of "waiting for continuation data" mode. */
- response = camel_imap_command_continuation (store, NULL, "*");
+ response = camel_imap_command_continuation (store, "*", NULL);
if (response)
camel_imap_response_free (store, response);