From 7dc4b13d477ea40930d0edf717b04fae4e558232 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Thu, 15 Aug 2002 06:17:09 +0000 Subject: added a flags field, defined some flags, currently only used by imap. This 2002-08-15 Not Zed * camel-store.h (CamelFolderInfo): added a flags field, defined some flags, currently only used by imap. This and below is first step to getting incremental folder tree loading. * providers/imap/camel-imap-store.c (get_subscribed_folders): If the subscribed list is empty, always add inbox. (imap_connect_offline): If the 'storeinfo' namespace isn't the same as our own (if its set), then ignore it, assume we changed settings. Otherwise you can't change the namespace ... (get_folders): allow the env variable CAMEL_IMAP_MAX_DEPTH to override the default maximum recursion depth if 10 levels. (camel_imap_store_readline): Dont depend the log debug on d(x) being defined. (get_folder_info_online): changed to use new functions/names. 2002-08-14 Not Zed * providers/imap/camel-imap-store.c (get_subscribed_folders): Removed the by_hand sillyness. Return an array instead. (get_folders): New method to get folders recursively without having to use '*'. Uses '%', and stops if it gets too deep (current max of 10). (get_folder_counts): New method to fill out unread counts on folderinfo tree. (get_subscribed_folders): Fix some failure logic. * providers/imap/camel-imap-utils.[ch]: Changed IMAP_LIST_FLAGS to use the new CamelFolderInfoFlags directly. svn path=/trunk/; revision=17777 --- camel/providers/imap/camel-imap-store.c | 448 +++++++++++++++++++------------- camel/providers/imap/camel-imap-store.h | 5 + camel/providers/imap/camel-imap-utils.c | 12 +- camel/providers/imap/camel-imap-utils.h | 5 - 4 files changed, 277 insertions(+), 193 deletions(-) (limited to 'camel/providers/imap') diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c index 7373df146b..d8f62d627a 100644 --- a/camel/providers/imap/camel-imap-store.c +++ b/camel/providers/imap/camel-imap-store.c @@ -60,7 +60,7 @@ #include "camel-imap-private.h" #include "camel-private.h" -#define d(x) x +#define d(x) /* Specified in RFC 2060 */ #define IMAP_PORT 143 @@ -1247,9 +1247,9 @@ imap_connect_online (CamelService *service, CamelException *ex) result = response->untagged->pdata[i]; if (!imap_parse_list_response (store, result, &flags, NULL, &name)) continue; - if (flags & (IMAP_LIST_FLAG_MARKED | IMAP_LIST_FLAG_UNMARKED)) + if (flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED)) store->capabilities |= IMAP_CAPABILITY_useful_lsub; - if (flags & IMAP_LIST_FLAG_NOSELECT) { + if (flags & CAMEL_FOLDER_NOSELECT) { g_free (name); continue; } @@ -1284,7 +1284,7 @@ imap_connect_offline (CamelService *service, CamelException *ex) char *buf, *name, *path; FILE *storeinfo; guint32 tmp; - + path = g_strdup_printf ("%s/journal", store->storage_path); disco_store->diary = camel_disco_diary_new (disco_store, path, ex); g_free (path); @@ -1307,25 +1307,31 @@ imap_connect_offline (CamelService *service, CamelException *ex) return camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex); } + store->subscribed_folders = g_hash_table_new (g_str_hash, g_str_equal); + camel_file_util_decode_uint32 (storeinfo, &store->capabilities); imap_set_server_level (store); - camel_file_util_decode_string (storeinfo, &store->namespace); - camel_file_util_decode_uint32 (storeinfo, &tmp); - store->dir_sep = tmp; - ((CamelStore *)store)->dir_sep = tmp; - - /* Get subscribed folders */ - store->subscribed_folders = g_hash_table_new (g_str_hash, g_str_equal); - while (camel_file_util_decode_string (storeinfo, &buf) == 0) { - if (!imap_parse_list_response (store, buf, NULL, NULL, &name)) { + camel_file_util_decode_string (storeinfo, &name); + /* if the namespace has changed, the subscribed folder list in this file is bogus */ + if (store->namespace == NULL || (name != NULL && strcmp(name, store->namespace) == 0)) { + g_free(store->namespace); + store->namespace = name; + camel_file_util_decode_uint32 (storeinfo, &tmp); + store->dir_sep = tmp; + ((CamelStore *)store)->dir_sep = tmp; + while (camel_file_util_decode_string (storeinfo, &buf) == 0) { + if (!imap_parse_list_response (store, buf, NULL, NULL, &name)) { + g_free (buf); + continue; + } + g_hash_table_insert (store->subscribed_folders, name, + GINT_TO_POINTER (1)); g_free (buf); - continue; } - g_hash_table_insert (store->subscribed_folders, name, - GINT_TO_POINTER (1)); - g_free (buf); + } else { + g_free(name); } - + fclose (storeinfo); imap_store_refresh_folders (store, ex); @@ -1769,7 +1775,7 @@ create_folder (CamelStore *store, const char *parent_name, continue; if (strcmp (thisone, parent_name) == 0) { - if (flags & IMAP_LIST_FLAG_NOINFERIORS) + if (flags & CAMEL_FOLDER_NOINFERIORS) need_convert = TRUE; break; } @@ -1905,6 +1911,7 @@ parse_list_response_as_folder_info (CamelImapStore *imap_store, } fi = g_new0 (CamelFolderInfo, 1); + fi->flags = flags; fi->full_name = dir; if (sep && name) fi->name = g_strdup (name); @@ -1914,12 +1921,13 @@ parse_list_response_as_folder_info (CamelImapStore *imap_store, url = camel_url_new (imap_store->base_url, NULL); g_free (url->path); url->path = g_strdup_printf ("/%s", dir); - if (flags & IMAP_LIST_FLAG_NOSELECT || fi->name[0] == 0) + if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0) camel_url_set_param (url, "noselect", "yes"); fi->url = camel_url_to_string (url, 0); camel_url_free (url); - - if (flags & IMAP_LIST_FLAG_UNMARKED) + + /* FIXME: redundant */ + if (flags & CAMEL_IMAP_FOLDER_UNMARKED) fi->unread_message_count = -1; return fi; @@ -1931,28 +1939,31 @@ copy_folder_name (gpointer name, gpointer key, gpointer array) g_ptr_array_add (array, name); } -static void -get_subscribed_folders_by_hand (CamelImapStore *imap_store, const char *top, - GPtrArray *folders, CamelException *ex) +/* this is used when lsub doesn't provide very useful information */ +static GPtrArray * +get_subscribed_folders(CamelImapStore *imap_store, const char *top, CamelException *ex) { - GPtrArray *names; + GPtrArray *names, *folders; CamelImapResponse *response; CamelFolderInfo *fi; char *result; int i, toplen = strlen (top); - + + folders = g_ptr_array_new(); names = g_ptr_array_new (); g_hash_table_foreach (imap_store->subscribed_folders, copy_folder_name, names); - + + if (names->len == 0) + g_ptr_array_add(names, "INBOX"); + for (i = 0; i < names->len; i++) { response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %F", names->pdata[i]); - if (!response) { - g_ptr_array_free (names, TRUE); - return; - } + if (!response) + break; + result = camel_imap_response_extract (imap_store, response, "LIST", NULL); if (!result) { g_hash_table_remove (imap_store->subscribed_folders, @@ -1973,7 +1984,10 @@ get_subscribed_folders_by_hand (CamelImapStore *imap_store, const char *top, g_ptr_array_add (folders, fi); } + g_ptr_array_free (names, TRUE); + + return folders; } static void @@ -2000,170 +2014,238 @@ get_folders_online (CamelImapStore *imap_store, const char *pattern, camel_imap_response_free (imap_store, response); } -static CamelFolderInfo * -get_folder_info_online (CamelStore *store, const char *top, - guint32 flags, CamelException *ex) +#if 0 +static void +dumpfi(CamelFolderInfo *fi) +{ + int depth; + CamelFolderInfo *n = fi; + + if (fi == NULL) + return; + + depth = 0; + while (n->parent) { + depth++; + n = n->parent; + } + + while (fi) { + printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->name), fi->name); + if (fi->child) + dumpfi(fi->child); + fi = fi->sibling; + } +} +#endif + +static void +get_folder_counts(CamelImapStore *imap_store, CamelFolderInfo *fi, CamelException *ex) +{ + GSList *q; + + /* non-recursive breath first search */ + + q = g_slist_append(NULL, fi); + + while (q) { + fi = q->data; + q = g_slist_remove_link(q, q); + + while (fi) { + /* ignore noselect folders, and check only inbox if we only check inbox */ + if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0 + && ( (imap_store->parameters & IMAP_PARAM_CHECK_ALL) + || strcasecmp(fi->full_name, "inbox") == 0) ) { + + CAMEL_IMAP_STORE_LOCK (imap_store, command_lock); + /* For the current folder, poke it to check for new + * messages and then report that number, rather than + * doing a STATUS command. + */ + if (imap_store->current_folder && strcmp(imap_store->current_folder->full_name, fi->full_name) == 0) { + /* we bypass the folder locking otherwise we can deadlock. we use the command lock for + any operations anyway so this is 'safe'. See comment above imap_store_refresh_folders() for info */ + CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex); + fi->unread_message_count = camel_folder_get_unread_message_count (imap_store->current_folder); + } else + fi->unread_message_count = get_folder_status (imap_store, fi->full_name, "UNSEEN"); + + CAMEL_IMAP_STORE_UNLOCK (imap_store, command_lock); + } else { + fi->unread_message_count = -1; + } + + if (fi->child) + q = g_slist_append(q, fi->child); + fi = fi->sibling; + } + } +} + +/* imap needs to treat inbox case insensitive */ +/* we'll assume the names are normalised already */ +static guint folder_hash(const void *ap) +{ + const char *a = ap; + + if (strcasecmp(a, "INBOX") == 0) + a = "INBOX"; + + return g_str_hash(a); +} + +static int folder_eq(const void *ap, const void *bp) +{ + const char *a = ap; + const char *b = bp; + + if (strcasecmp(a, "INBOX") == 0) + a = "INBOX"; + if (strcasecmp(b, "INBOX") == 0) + b = "INBOX"; + + return g_str_equal(a, b); +} + +static GPtrArray * +get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *ex) { CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); - gboolean need_inbox = FALSE; - GPtrArray *folders; - const char *name; - char *pattern; - CamelFolderInfo *fi, *tree; + GSList *p = NULL; + GHashTable *infos; int i; - + GPtrArray *folders, *folders_out; + CamelFolderInfo *fi; + char *name; + int depth = 0; + int haveinbox = 0; + static int imap_max_depth = 0; + if (!camel_imap_store_connected (imap_store, ex)) return NULL; - - name = top; - if (!name || name[0] == '\0') { - need_inbox = TRUE; - name = imap_store->namespace ? imap_store->namespace : ""; - } - - folders = g_ptr_array_new (); - - /* Get top-level */ - get_folders_online (imap_store, name, folders, FALSE, ex); - if (camel_exception_is_set (ex)) - goto lose; - - if (folders->len) { - const char *noselect; - CamelURL *url; - - fi = folders->pdata[0]; - - url = camel_url_new (fi->url, NULL); - noselect = url ? camel_url_get_param (url, "noselect") : NULL; - if (noselect && !g_strcasecmp (noselect, "yes") && name[0] == '\0') { - camel_folder_info_free (fi); - g_ptr_array_remove_index (folders, 0); - } - camel_url_free (url); - } - - /* If we want to look at only subscribed folders AND check if - * any of them have new mail, AND the server doesn't return - * Marked/UnMarked with LSUB, then use - * get_subscribed_folders_by_hand. In all other cases, use a - * single LIST or LSUB command. - */ - if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) && - !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub) && - (imap_store->parameters & IMAP_PARAM_CHECK_ALL)) { - get_subscribed_folders_by_hand (imap_store, name, folders, ex); - } else { - pattern = imap_concat (imap_store, name, (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) ? "*" : "%"); - get_folders_online (imap_store, pattern, folders, (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED), ex); - g_free (pattern); + + /* allow megalomaniacs to override the max of 10 */ + if (imap_max_depth == 0) { + name = getenv("CAMEL_IMAP_MAX_DEPTH"); + if (name) { + imap_max_depth = atoi(name); + imap_max_depth = MIN(imap_max_depth, 2); + } else + imap_max_depth = 10; } - - if (camel_exception_is_set (ex)) { - lose: - for (i = 0; i < folders->len; i++) - camel_folder_info_free (folders->pdata[i]); - g_ptr_array_free (folders, TRUE); - return NULL; + + infos = g_hash_table_new(folder_hash, folder_eq); + + /* get starting point & strip trailing '/' */ + if (top[0] == 0 && imap_store->namespace) + top = imap_store->namespace; + i = strlen(top)-1; + name = alloca(i+2); + strcpy(name, top); + while (i>0 && name[i] == store->dir_sep) + name[i--] = 0; + + d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE")); + + folders_out = g_ptr_array_new(); + folders = g_ptr_array_new(); + + /* first get working list of names */ + get_folders_online (imap_store, name[0]?name:"%", folders, FALSE, ex); + for (i=0; ilen && !haveinbox; i++) { + fi = folders->pdata[i]; + haveinbox = (strcasecmp(fi->full_name, "INBOX")) == 0; } - - /* Add INBOX, if necessary */ - if (need_inbox) { - for (i = 0; i < folders->len; i++) { - fi = folders->pdata[i]; - if (!g_strcasecmp (fi->full_name, "INBOX")) { - need_inbox = FALSE; - break; + + if (!haveinbox && top[0] == 0) + get_folders_online(imap_store, "INBOX", folders, FALSE, ex); + + for (i=0; ilen; i++) + p = g_slist_prepend(p, folders->pdata[i]); + + g_ptr_array_set_size(folders, 0); + + /* p is a reversed list of pending folders for the next level, q is the list of folders for this */ + while (p) { + GSList *q = g_slist_reverse(p); + + p = NULL; + while (q) { + fi = q->data; + + q = g_slist_remove_link(q, q); + g_ptr_array_add(folders_out, fi); + + d(printf("Checking folder '%s'\n", fi->full_name)); + + /* First if we're not recursive mode on the top level, and we know it has or doesn't + or can't have children, no need to go further - a bit ugly */ + if ( top == imap_store->namespace + && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0 + && (fi->flags & (CAMEL_FOLDER_CHILDREN|CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) != 0) { + /* do nothing */ + } + /* Otherwise, if this has (or might have) children, scan it */ + else if ( (fi->flags & (CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0 + || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) { + char *n; + + n = imap_concat(imap_store, fi->full_name, "%"); + get_folders_online(imap_store, n, folders, FALSE, ex); + g_free(n); + + if (folders->len > 0) + fi->flags |= CAMEL_FOLDER_CHILDREN; + + for (i=0;ilen;i++) { + fi = folders->pdata[i]; + if (g_hash_table_lookup(infos, fi->full_name) == NULL) { + g_hash_table_insert(infos, fi->full_name, fi); + if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) && depthbase_url, NULL); - g_free (url->path); - url->path = g_strdup ("/INBOX"); - uri = camel_url_to_string (url, 0); - camel_url_free (url); - - fi = g_new0 (CamelFolderInfo, 1); - fi->full_name = g_strdup ("INBOX"); - fi->name = g_strdup ("INBOX"); - fi->url = uri; - fi->unread_message_count = 0; - - g_ptr_array_add (folders, fi); - } + depth++; } - - /* Assemble. */ - /* if building the folder tree, preserve the namespace at the top. Note the - * ==, not strcmp. This makes it so that the subscribe dialog and the folder - * tree have the same layout and prevents the subscribe dialog from building - * infinitely large trees. - */ + g_ptr_array_free(folders, TRUE); + g_hash_table_destroy(infos); + + return folders_out; +} + +static CamelFolderInfo * +get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex) +{ + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + GPtrArray *folders; + CamelFolderInfo *tree; + + if (top == NULL) + top = ""; - if (name == imap_store->namespace) - tree = camel_folder_info_build (folders, "", imap_store->dir_sep, TRUE); + if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) + && !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub) + && (imap_store->parameters & IMAP_PARAM_CHECK_ALL)) + folders = get_subscribed_folders(imap_store, top, ex); else - tree = camel_folder_info_build (folders, name, imap_store->dir_sep, TRUE); + folders = get_folders(store, top, flags, ex); + + /* note the weird top stuff, it is so a namespace based list "" is properly tree-ised */ + tree = camel_folder_info_build(folders, top[0] == 0 && imap_store->namespace?"":top, imap_store->dir_sep, TRUE); + g_ptr_array_free(folders, TRUE); + + if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST)) + get_folder_counts(imap_store, tree, ex); - if (flags & CAMEL_STORE_FOLDER_INFO_FAST) { - g_ptr_array_free (folders, TRUE); - return tree; - } - - /* Get unread counts. Sync flag changes to the server first so - * it has the same ideas about read/unread as we do. - */ - camel_store_sync (store, NULL); - for (i = 0; i < folders->len; i++) { - const char *noselect; - CamelURL *url; - - fi = folders->pdata[i]; - - /* Don't check if it doesn't contain messages or if it - * was \UnMarked. - */ - url = camel_url_new (fi->url, NULL); - noselect = url ? camel_url_get_param (url, "noselect") : NULL; - if (fi->unread_message_count == -1 || (noselect && !g_strcasecmp (noselect, "yes"))) { - camel_url_free (url); - continue; - } - camel_url_free (url); - - /* Don't check if it's not INBOX and we're only - * checking INBOX. - */ - if ((!(imap_store->parameters & IMAP_PARAM_CHECK_ALL)) - && (g_strcasecmp (fi->name, "INBOX") != 0)) { - fi->unread_message_count = -1; - continue; - } - - CAMEL_IMAP_STORE_LOCK (imap_store, command_lock); - /* For the current folder, poke it to check for new - * messages and then report that number, rather than - * doing a STATUS command. - */ - if (imap_store->current_folder && - !strcmp (imap_store->current_folder->full_name, fi->full_name)) { - /* we bypass the folder locking otherwise we can deadlock. we use the command lock for - any operations anyway so this is 'safe'. See comment above imap_store_refresh_folders() for info */ - CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex); - fi->unread_message_count = camel_folder_get_unread_message_count (imap_store->current_folder); - } else - fi->unread_message_count = get_folder_status (imap_store, fi->full_name, "UNSEEN"); - - CAMEL_IMAP_STORE_UNLOCK (imap_store, command_lock); - } - - g_ptr_array_free (folders, TRUE); - return tree; } @@ -2412,12 +2494,10 @@ camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *e return -1; } -#if d(!)0 if (camel_verbose_debug) { fprintf (stderr, "received: "); fwrite (ba->data, 1, ba->len, stderr); } -#endif /* camel-imap-command.c:imap_read_untagged expects the CRLFs to be stripped off and be nul-terminated *sigh* */ diff --git a/camel/providers/imap/camel-imap-store.h b/camel/providers/imap/camel-imap-store.h index 9ebbc31ad7..bf3003265e 100644 --- a/camel/providers/imap/camel-imap-store.h +++ b/camel/providers/imap/camel-imap-store.h @@ -71,6 +71,11 @@ enum { #define CAMEL_IMAP_STORE_CHECK_ALL (CAMEL_IMAP_STORE_ARG_CHECK_ALL | CAMEL_ARG_INT) #define CAMEL_IMAP_STORE_FILTER_INBOX (CAMEL_IMAP_STORE_ARG_FILTER_INBOX | CAMEL_ARG_INT) +/* CamelFolderInfo flags */ +#define CAMEL_IMAP_FOLDER_MARKED (1<<16) +#define CAMEL_IMAP_FOLDER_UNMARKED (1<<17) +#define CAMEL_IMAP_FOLDER_NOCHILDREN (1<<18) + typedef enum { IMAP_LEVEL_UNKNOWN, diff --git a/camel/providers/imap/camel-imap-utils.c b/camel/providers/imap/camel-imap-utils.c index 0d5d45c30e..4370d890ba 100644 --- a/camel/providers/imap/camel-imap-utils.c +++ b/camel/providers/imap/camel-imap-utils.c @@ -315,13 +315,17 @@ imap_parse_list_response (CamelImapStore *store, const char *buf, int *flags, ch len = strcspn (word, " )"); if (flags) { if (!g_strncasecmp (word, "\\NoInferiors", len)) - *flags |= IMAP_LIST_FLAG_NOINFERIORS; + *flags |= CAMEL_FOLDER_NOINFERIORS; else if (!g_strncasecmp (word, "\\NoSelect", len)) - *flags |= IMAP_LIST_FLAG_NOSELECT; + *flags |= CAMEL_FOLDER_NOSELECT; else if (!g_strncasecmp (word, "\\Marked", len)) - *flags |= IMAP_LIST_FLAG_MARKED; + *flags |= CAMEL_IMAP_FOLDER_MARKED; else if (!g_strncasecmp (word, "\\Unmarked", len)) - *flags |= IMAP_LIST_FLAG_UNMARKED; + *flags |= CAMEL_IMAP_FOLDER_UNMARKED; + else if (!g_strncasecmp (word, "\\HasChildren", len)) + *flags |= CAMEL_FOLDER_CHILDREN; + else if (!g_strncasecmp (word, "\\HasNoChildren", len)) + *flags |= CAMEL_IMAP_FOLDER_NOCHILDREN; } word += len; diff --git a/camel/providers/imap/camel-imap-utils.h b/camel/providers/imap/camel-imap-utils.h index 9692cc4e38..e8f570137f 100644 --- a/camel/providers/imap/camel-imap-utils.h +++ b/camel/providers/imap/camel-imap-utils.h @@ -50,11 +50,6 @@ struct _namespaces { void imap_namespaces_destroy (struct _namespaces *namespaces); struct _namespaces *imap_parse_namespace_response (const char *response); -#define IMAP_LIST_FLAG_NOINFERIORS (1 << 0) -#define IMAP_LIST_FLAG_NOSELECT (1 << 1) -#define IMAP_LIST_FLAG_MARKED (1 << 2) -#define IMAP_LIST_FLAG_UNMARKED (1 << 3) - gboolean imap_parse_list_response (CamelImapStore *store, const char *buf, int *flags, char *sep, char **folder); -- cgit v1.2.3