changeset 1650:bc76e08a9c9d HEAD

API change: Mailbox list sorting must now always done by storage itself if it's needed. Maildir listing rewritten.
author Timo Sirainen <tss@iki.fi>
date Sun, 27 Jul 2003 02:53:05 +0300
parents 27f68eecfb35
children 43fdcf8d9a0d
files src/imap/cmd-list.c src/lib-storage/Makefile.am src/lib-storage/index/maildir/maildir-list.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-storage.h src/lib-storage/index/mbox/mbox-list.c src/lib-storage/index/mbox/mbox-storage.h src/lib-storage/mail-storage.h src/lib-storage/mailbox-tree.c src/lib-storage/mailbox-tree.h src/lib-storage/proxy-mail-storage.c
diffstat 11 files changed, 433 insertions(+), 429 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/cmd-list.c	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/imap/cmd-list.c	Sun Jul 27 02:53:05 2003 +0300
@@ -7,42 +7,15 @@
 #include "imap-match.h"
 #include "commands.h"
 
-struct list_node {
-	struct list_node *next;
-	struct list_node *children;
-
-	char *name; /* escaped */
-	enum mailbox_flags flags;
-};
-
-struct list_context {
-	pool_t pool;
-	struct list_node *nodes;
-	struct mail_storage *storage;
-};
-
-struct list_send_context {
-	struct client *client;
-	const char *response_name;
-	const char *sep;
-	char sep_chr;
-	struct imap_match_glob *glob;
-	int listext, no_placeholder;
-};
-
-static const char *mailbox_flags2str(enum mailbox_flags flags,
-				     int listext, int no_placeholder)
+static const char *mailbox_flags2str(enum mailbox_flags flags, int listext)
 {
 	const char *str;
 
 	if (flags & MAILBOX_PLACEHOLDER) {
-		if ((flags & ~MAILBOX_CHILDREN) == MAILBOX_PLACEHOLDER) {
-			if (!listext || no_placeholder)
-				flags = MAILBOX_NOSELECT;
-		} else {
-			/* it was at one point, but then we got better specs */
-			flags &= ~MAILBOX_PLACEHOLDER;
-		}
+		i_assert((flags & ~MAILBOX_CHILDREN) == MAILBOX_PLACEHOLDER);
+
+		if (!listext)
+			flags = MAILBOX_NOSELECT;
 		flags |= MAILBOX_CHILDREN;
 	}
 	if ((flags & MAILBOX_NONEXISTENT) != 0 && !listext)
@@ -61,177 +34,33 @@
 	return *str == '\0' ? "" : str+1;
 }
 
-static void list_node_update(pool_t pool, struct list_node **node,
-			     const char *path, char separator,
-			     enum mailbox_flags flags)
-{
-	const char *name, *parent;
-
-	parent = NULL;
-
-	for (name = path;; path++) {
-		if (*path != separator && *path != '\0')
-			continue;
-
-		t_push();
-
-		name = t_strdup_until(name, path);
-
-		/* find the node */
-		while (*node != NULL) {
-			if (strcmp((*node)->name, name) == 0)
-				break;
-
-			node = &(*node)->next;
-		}
-
-		if (*node == NULL) {
-			/* not found, create it */
-			*node = p_new(pool, struct list_node, 1);
-			(*node)->name = p_strdup(pool, name);
-			(*node)->flags = *path == '\0' ? flags :
-				MAILBOX_PLACEHOLDER;
-		} else {
-			if (*path == '\0') {
-				if (((*node)->flags & MAILBOX_NOSELECT) != 0 &&
-				    (flags & MAILBOX_NOSELECT) == 0) {
-					/* overrides previous flag */
-					(*node)->flags &= ~MAILBOX_NOSELECT;
-				}
-
-				(*node)->flags |= flags;
-			}
-		}
-
-		t_pop();
-
-		if (*path == '\0')
-			break;
-
-		name = path+1;
-		parent = (*node)->name;
-		node = &(*node)->children;
-	}
-}
-
-static void list_send(struct list_send_context *ctx, struct list_node *node,
-		      const char *path)
+static int mailbox_list(struct client *client, const char *mask,
+			const char *sep, const char *reply,
+			enum mailbox_list_flags list_flags, int listext)
 {
-	const char *name, *send_name, *flagstr;
-	enum imap_match_result match;
-	string_t *str;
-
-	for (; node != NULL; node = node->next) {
-		t_push();
-
-		/* Send INBOX always uppercased */
-		if (path != NULL) {
-			name = t_strdup_printf("%s%c%s", path, ctx->sep_chr,
-					       node->name);
-		} else if (strcasecmp(node->name, "INBOX") == 0)
-			name = "INBOX";
-		else
-			name = node->name;
-		send_name = name;
-
-		if ((node->flags & MAILBOX_PLACEHOLDER) == 0 &&
-		    (node->flags & MAILBOX_NOSELECT) == 0)
-			match = IMAP_MATCH_YES;
-		else {
-			/* make sure the placeholder matches. */
-			const char *buf;
-
-			buf = str_unescape(t_strdup_noconst(name));
-			match = imap_match(ctx->glob, buf);
-			/* FIXME: IMAP spec says this should be done, but
-			   a) this is broken, we shouldn't give \NoSelect for
-			      this folder if it actually works.
-			   b) at least mozilla's subscriptions list breaks if
-			      this is sent
-			   c) cyrus and courier doesn't do this either..
-
-			if (match == IMAP_MATCH_CHILDREN) {
-				send_name = t_strdup_printf("%s%c", name,
-							    ctx->sep);
-				buf = str_unescape(t_strdup_noconst(send_name));
-				match = imap_match(ctx->glob, buf);
-			}*/
-		}
-
-		if (match == IMAP_MATCH_YES) {
-			/* node->name should already be escaped */
-			flagstr = mailbox_flags2str(node->flags, ctx->listext,
-						    ctx->no_placeholder);
-			t_push();
-			str = t_str_new(256);
-			str_printfa(str, "* %s (%s) \"%s\" ",
-				    ctx->response_name, flagstr, ctx->sep);
-			imap_quote_append_string(str, send_name, FALSE);
-			client_send_line(ctx->client, str_c(str));
-			t_pop();
-		}
-
-		if (node->children != NULL)
-			list_send(ctx, node->children,  name);
-
-		t_pop();
-	}
-}
-
-static void list_and_sort(struct client *client,
-			  struct mailbox_list_context *ctx,
-			  const char *response_name, const char *mask,
-			  const char *sep, char sep_chr,
-			  enum mailbox_list_flags list_flags, int listext)
-{
-	struct mailbox_list *list;
-	struct list_node *nodes;
-	struct list_send_context send_ctx;
-	pool_t pool;
-
-	pool = pool_alloconly_create("list_mailboxes", 10240);
-	nodes = NULL;
-
-	while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
-		list_node_update(pool, &nodes, list->name,
-				 client->storage->hierarchy_sep,
-				 list->flags);
-	}
-
-	send_ctx.client = client;
-	send_ctx.response_name = response_name;
-	send_ctx.sep = sep;
-	send_ctx.sep_chr = sep_chr;
-	send_ctx.glob = imap_match_init(data_stack_pool, mask, TRUE,
-					client->storage->hierarchy_sep);
-	send_ctx.listext = listext;
-	send_ctx.no_placeholder = (list_flags & MAILBOX_LIST_SUBSCRIBED) == 0;
-
-	list_send(&send_ctx, nodes, NULL);
-	imap_match_deinit(send_ctx.glob);
-	pool_unref(pool);
-}
-
-static void list_unsorted(struct client *client,
-			  struct mailbox_list_context *ctx,
-			  const char *reply, const char *sep, int listext)
-{
+	struct mailbox_list_context *ctx;
 	struct mailbox_list *list;
 	string_t *str;
 
+	ctx = client->storage->list_mailbox_init(client->storage, mask,
+						 list_flags);
+	if (ctx == NULL)
+		return FALSE;
+
+	str = t_str_new(256);
 	while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
-		t_push();
-		str = t_str_new(256);
+		str_truncate(str, 0);
 		str_printfa(str, "* %s (%s) \"%s\" ", reply,
-			    mailbox_flags2str(list->flags, listext, FALSE),
+			    mailbox_flags2str(list->flags, listext),
 			    sep);
 		if (strcasecmp(list->name, "INBOX") == 0)
 			str_append(str, "INBOX");
 		else
 			imap_quote_append_string(str, list->name, FALSE);
 		client_send_line(client, str_c(str));
-		t_pop();
 	}
+
+	return client->storage->list_mailbox_deinit(ctx);
 }
 
 static int parse_list_flags(struct client *client, struct imap_arg *args,
@@ -266,10 +95,9 @@
 {
 	struct imap_arg *args;
         enum mailbox_list_flags list_flags;
-	struct mailbox_list_context *ctx;
 	const char *ref, *mask;
 	char sep_chr, sep[3];
-	int failed, sorted, listext;
+	int failed, listext;
 
 	sep_chr = client->storage->hierarchy_sep;
 	if (IS_ESCAPED_CHAR(sep_chr)) {
@@ -330,24 +158,9 @@
 			}
 		}
 
-		ctx = client->storage->list_mailbox_init(client->storage, mask,
-							 list_flags, &sorted);
-		if (ctx == NULL)
-			failed = TRUE;
-		else {
-			const char *response_name = lsub ? "LSUB" : "LIST";
-
-			if (sorted) {
-				list_unsorted(client, ctx, response_name, sep,
-					      listext);
-			} else {
-				list_and_sort(client, ctx, response_name, mask,
-					      sep, sep_chr, list_flags,
-					      listext);
-			}
-
-			failed = !client->storage->list_mailbox_deinit(ctx);
-		}
+		failed = !mailbox_list(client, mask, sep,
+				       lsub ? "LSUB" : "LIST",
+				       list_flags, listext);
 	}
 
 	if (failed)
--- a/src/lib-storage/Makefile.am	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/Makefile.am	Sun Jul 27 02:53:05 2003 +0300
@@ -11,6 +11,7 @@
 	mail-save.c \
 	mail-search.c \
 	mail-storage.c \
+	mailbox-tree.c \
 	proxy-mail.c \
 	proxy-mail-storage.c \
 	proxy-mailbox.c
@@ -19,6 +20,7 @@
 	mail-save.h \
 	mail-search.h \
 	mail-storage.h \
+	mailbox-tree.h \
 	proxy-mail.h \
 	proxy-mail-storage.h \
 	proxy-mailbox.h
--- a/src/lib-storage/index/maildir/maildir-list.c	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/index/maildir/maildir-list.c	Sun Jul 27 02:53:05 2003 +0300
@@ -1,187 +1,85 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "lib.h"
-#include "hostpid.h"
+#include "ioloop.h"
+#include "str.h"
 #include "home-expand.h"
 #include "unlink-directory.h"
 #include "imap-match.h"
 #include "subscription-file/subscription-file.h"
-#include "maildir-index.h"
 #include "maildir-storage.h"
+#include "mailbox-tree.h"
 
 #include <dirent.h>
 #include <sys/stat.h>
 
+#define MAILBOX_FLAG_MATCHED 0x40000000
+
 struct mailbox_list_context {
-	pool_t pool, list_pool;
+	pool_t pool;
 
 	struct mail_storage *storage;
 	const char *dir, *prefix;
         enum mailbox_list_flags flags;
 
-	DIR *dirp;
-	struct imap_match_glob *glob;
-	struct subsfile_list_context *subsfile_ctx;
+        struct mailbox_tree_context *tree_ctx;
 
-	struct mailbox_list *(*next)(struct mailbox_list_context *ctx);
-
+	string_t *node_path;
+	size_t parent_pos;
+	struct mailbox_node *root, *next_node;
 	struct mailbox_list list;
 	int failed;
 };
 
-static struct mailbox_list *maildir_list_subs(struct mailbox_list_context *ctx);
-static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx);
-
-static enum mailbox_flags maildir_get_marked_flags(const char *dir)
-{
-	struct stat st_new, st_cur;
-
-	/* assume marked if new/ has been modified later than cur/ */
-	if (stat(t_strconcat(dir, "/new", NULL), &st_new) < 0)
-		return MAILBOX_UNMARKED;
-
-	if (stat(t_strconcat(dir, "/cur", NULL), &st_cur) < 0)
-		return MAILBOX_UNMARKED;
-
-	return st_new.st_mtime <= st_cur.st_mtime ?
-		MAILBOX_UNMARKED : MAILBOX_MARKED;
-}
-
-struct mailbox_list_context *
-maildir_list_mailbox_init(struct mail_storage *storage,
-			  const char *mask, enum mailbox_list_flags flags,
-			  int *sorted)
+static void maildir_nodes_fix(struct mailbox_node *node, int is_subs)
 {
-        struct mailbox_list_context *ctx;
-	pool_t pool;
-	const char *dir, *p;
-
-	*sorted = FALSE;
-	mail_storage_clear_error(storage);
-
-	pool = pool_alloconly_create("maildir_list", 1024);
-	ctx = p_new(pool, struct mailbox_list_context, 1);
-	ctx->pool = pool;
-	ctx->storage = storage;
-	ctx->flags = flags;
-
-	if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
-		ctx->glob = imap_match_init(pool, mask, TRUE, '.');
-		ctx->subsfile_ctx = subsfile_list_init(storage);
-		ctx->next = maildir_list_subs;
-		if (ctx->subsfile_ctx == NULL) {
-			pool_unref(pool);
-			return NULL;
+	while (node != NULL) {
+		if (node->children != NULL) {
+			node->flags |= MAILBOX_CHILDREN;
+			maildir_nodes_fix(node->children, is_subs);
+		} else if ((node->flags & MAILBOX_PLACEHOLDER) != 0) {
+			if (!is_subs) {
+				node->flags &= ~MAILBOX_PLACEHOLDER;
+				node->flags |= MAILBOX_NOSELECT;
+			}
+		} else {
+			if ((node->flags & MAILBOX_CHILDREN) == 0)
+				node->flags |= MAILBOX_NOCHILDREN;
 		}
-		return ctx;
+		node = node->next;
 	}
-
-	if (!full_filesystem_access || (p = strrchr(mask, '/')) == NULL) {
-		ctx->dir = storage->dir;
-		ctx->prefix = "";
-	} else {
-		dir = t_strdup_until(mask, p);
-		ctx->prefix = t_strdup_until(mask, p+1);
-
-		if (*mask != '/' && *mask != '~')
-			dir = t_strconcat(storage->dir, "/", dir, NULL);
-		ctx->dir = p_strdup(pool, home_expand(dir));
-	}
-
-	ctx->dirp = opendir(ctx->dir);
-	if (ctx->dirp == NULL && errno != ENOENT) {
-		mail_storage_set_critical(storage, "opendir(%s) failed: %m",
-					  ctx->dir);
-		pool_unref(pool);
-		return NULL;
-	}
-
-	ctx->list_pool = pool_alloconly_create("maildir_list.list", 4096);
-	ctx->glob = imap_match_init(pool, mask, TRUE, '.');
-	ctx->next = maildir_list_next;
-	return ctx;
 }
 
-int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx)
+static int maildir_fill_readdir(struct mailbox_list_context *ctx,
+				struct imap_match_glob *glob, int update_only)
 {
-	int failed;
-
-	if (ctx->subsfile_ctx != NULL)
-		failed = !subsfile_list_deinit(ctx->subsfile_ctx);
-	else
-		failed = ctx->failed;
+	DIR *dirp;
+	struct dirent *d;
+	const char *path, *p;
+	string_t *mailbox;
+	enum imap_match_result match;
+	struct mailbox_node *node;
+	int created;
 
-	if (ctx->dirp != NULL)
-		(void)closedir(ctx->dirp);
-	if (ctx->list_pool != NULL)
-		pool_unref(ctx->list_pool);
-	imap_match_deinit(ctx->glob);
-	pool_unref(ctx->pool);
-
-	return !failed;
-}
-
-static struct mailbox_list *maildir_list_subs(struct mailbox_list_context *ctx)
-{
-	struct stat st;
-	const char *name, *path, *p;
-	enum imap_match_result match = IMAP_MATCH_NO;
-
-	while ((name = subsfile_list_next(ctx->subsfile_ctx)) != NULL) {
-		match = imap_match(ctx->glob, name);
-		if (match == IMAP_MATCH_YES || match == IMAP_MATCH_PARENT)
-			break;
+	dirp = opendir(ctx->dir);
+	if (dirp == NULL) {
+		if (errno != ENOENT) {
+			mail_storage_set_critical(ctx->storage,
+				"opendir(%s) failed: %m", ctx->dir);
+			return FALSE;
+		}
 	}
 
-	if (name == NULL)
-		return NULL;
-
-	ctx->list.flags = 0;
-	ctx->list.name = name;
-
-	if (match == IMAP_MATCH_PARENT) {
-		/* placeholder */
-		ctx->list.flags = MAILBOX_PLACEHOLDER;
-		while ((p = strrchr(name, '.')) != NULL) {
-			name = t_strdup_until(name, p);
-			if (imap_match(ctx->glob, name) > 0) {
-				ctx->list.name = name;
-				return &ctx->list;
-			}
-		}
-		i_unreached();
+	/* INBOX exists always */
+	if (imap_match(glob, "INBOX") > 0 && !update_only) {
+		node = mailbox_tree_get(ctx->tree_ctx, "INBOX", NULL);
+		node->flags |= MAILBOX_FLAG_MATCHED;
+		node->flags &= ~(MAILBOX_PLACEHOLDER | MAILBOX_NONEXISTENT);
 	}
 
-	if ((ctx->flags & MAILBOX_LIST_FAST_FLAGS) != 0)
-		return &ctx->list;
-
-	t_push();
-	path = maildir_get_path(ctx->storage, ctx->list.name);
-	if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
-		ctx->list.flags = maildir_get_marked_flags(path);
-	else {
-		if (strcasecmp(ctx->list.name, "INBOX") == 0)
-			ctx->list.flags = 0;
-		else
-			ctx->list.flags = MAILBOX_NONEXISTENT;
-	}
-	t_pop();
-	return &ctx->list;
-}
-
-static struct mailbox_list *maildir_list_next(struct mailbox_list_context *ctx)
-{
-	struct dirent *d;
-	struct stat st;
-	const char *fname, *p;
-	char path[PATH_MAX];
-	enum imap_match_result match;
-
-	if (ctx->dirp == NULL)
-		return NULL;
-
-	while ((d = readdir(ctx->dirp)) != NULL) {
-		fname = d->d_name;
+	mailbox = t_str_new(PATH_MAX);
+	while ((d = readdir(dirp)) != NULL) {
+		const char *fname = d->d_name;
 
 		if (fname[0] != '.')
 			continue;
@@ -190,31 +88,9 @@
 		if (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))
 			continue;
 
-		/* make sure the mask matches - dirs beginning with ".."
-		   should be deleted and we always want to check those. */
-		t_push();
-		match = imap_match(ctx->glob,
-				   t_strconcat(ctx->prefix, fname+1, NULL));
-		t_pop();
-		if (fname[1] != '.' && match != IMAP_MATCH_YES &&
-		    match != IMAP_MATCH_PARENT)
-			continue;
-
-		if (str_path(path, sizeof(path), ctx->dir, fname) < 0)
-			continue;
-
-		/* make sure it's a directory */
-		if (stat(path, &st) < 0) {
-			if (errno == ENOENT)
-				continue; /* just deleted, ignore */
-
-			mail_storage_set_critical(ctx->storage,
-						  "stat(%s) failed: %m", path);
-			ctx->failed = TRUE;
-			return NULL;
-		}
-
-		if (!S_ISDIR(st.st_mode))
+		/* FIXME: kludges. these files must be renamed later */
+		if (strcmp(fname, ".customflags") == 0 ||
+		    strcmp(fname, ".subscriptions") == 0)
 			continue;
 
 		fname++;
@@ -225,58 +101,245 @@
 			   delete it ourself if it's been there longer than
 			   one hour. don't touch it if it's outside our
 			   mail root dir. */
-			if (st.st_mtime < 3600 && *ctx->prefix == '\0')
+			struct stat st;
+
+			if (*ctx->prefix == '\0')
+				continue;
+
+			t_push();
+			path = t_strdup_printf("%s/%s", ctx->dir, fname);
+			if (stat(path, &st) == 0 &&
+			    st.st_mtime < ioloop_time - 3600)
 				(void)unlink_directory(path, TRUE);
+			t_pop();
 			continue;
 		}
 
-		if (strcasecmp(fname, "INBOX") == 0)
+		/* make sure the mask matches */
+		str_truncate(mailbox, 0);
+		str_append(mailbox, ctx->prefix);
+		str_append(mailbox, fname);
+
+		match = imap_match(glob, str_c(mailbox));
+
+		if (match != IMAP_MATCH_YES &&
+		    (match != IMAP_MATCH_PARENT || update_only))
+			continue;
+
+		if (strcasecmp(str_c(mailbox), "INBOX") == 0)
 			continue; /* ignore inboxes */
 
 		if (match == IMAP_MATCH_PARENT) {
-			ctx->list.flags =
-				MAILBOX_PLACEHOLDER | MAILBOX_CHILDREN;
+			t_push();
 			while ((p = strrchr(fname, '.')) != NULL) {
 				fname = t_strdup_until(fname, p);
 				p = t_strconcat(ctx->prefix, fname, NULL);
-				if (imap_match(ctx->glob, p) > 0) {
-					ctx->list.name = p;
-					return &ctx->list;
-				}
+				if (imap_match(glob, p) > 0)
+					break;
 			}
-			i_unreached();
+			i_assert(p != NULL);
+
+			node = mailbox_tree_get(ctx->tree_ctx, p, &created);
+			if (created)
+				node->flags = MAILBOX_PLACEHOLDER;
+			node->flags |= MAILBOX_CHILDREN | MAILBOX_FLAG_MATCHED;
+
+			t_pop();
+		} else {
+			p = str_c(mailbox);
+			if (update_only)
+				node = mailbox_tree_update(ctx->tree_ctx, p);
+			else
+				node = mailbox_tree_get(ctx->tree_ctx, p, NULL);
+
+			if (node != NULL) {
+				node->flags &= ~(MAILBOX_PLACEHOLDER |
+						 MAILBOX_NONEXISTENT);
+				node->flags |= MAILBOX_FLAG_MATCHED;
+			}
 		}
+	}
 
-		p_clear(ctx->list_pool);
-                ctx->list.flags = (ctx->flags & MAILBOX_LIST_FAST_FLAGS) == 0 ?
-			maildir_get_marked_flags(path) : 0;
-		ctx->list.name = p_strconcat(ctx->list_pool,
-					     ctx->prefix, fname, NULL);
-		return &ctx->list;
+	if (closedir(dirp) < 0) {
+		mail_storage_set_critical(ctx->storage,
+					  "readdir(%s) failed: %m", ctx->dir);
+		return FALSE;
+	}
+
+	maildir_nodes_fix(mailbox_tree_get(ctx->tree_ctx, NULL, NULL),
+			  (ctx->flags & MAILBOX_LIST_SUBSCRIBED) != 0);
+	return TRUE;
+}
+
+static int maildir_fill_subscribed(struct mailbox_list_context *ctx,
+				   struct imap_match_glob *glob,
+				   int nonexistent)
+{
+	struct subsfile_list_context *subsfile_ctx;
+	const char *name, *p;
+	struct mailbox_node *node;
+	int created;
+
+	subsfile_ctx = subsfile_list_init(ctx->storage);
+	if (subsfile_ctx == NULL)
+		return FALSE;
+
+	while ((name = subsfile_list_next(subsfile_ctx)) != NULL) {
+		switch (imap_match(glob, name)) {
+		case IMAP_MATCH_YES:
+			node = mailbox_tree_get(ctx->tree_ctx, name, NULL);
+			node->flags = MAILBOX_FLAG_MATCHED;
+			if (nonexistent && strcasecmp(name, "INBOX") != 0)
+				node->flags |= MAILBOX_NONEXISTENT;
+			break;
+		case IMAP_MATCH_PARENT:
+			/* placeholder */
+			while ((p = strrchr(name, '.')) != NULL) {
+				name = t_strdup_until(name, p);
+				if (imap_match(glob, name) > 0)
+					break;
+			}
+			i_assert(p != NULL);
+
+			node = mailbox_tree_get(ctx->tree_ctx, name, &created);
+			if (created) node->flags = MAILBOX_PLACEHOLDER;
+			node->flags |= MAILBOX_FLAG_MATCHED;
+			break;
+		default:
+			break;
+		}
 	}
 
-	if (closedir(ctx->dirp) < 0) {
-		mail_storage_set_critical(ctx->storage,
-					  "closedir(%s) failed: %m", ctx->dir);
-		ctx->failed = TRUE;
-	}
-	ctx->dirp = NULL;
+	return subsfile_list_deinit(subsfile_ctx);
+
+}
+
+struct mailbox_list_context *
+maildir_list_mailbox_init(struct mail_storage *storage,
+			  const char *mask, enum mailbox_list_flags flags)
+{
+        struct mailbox_list_context *ctx;
+        struct imap_match_glob *glob;
+	const char *dir, *p;
+	int nonexistent;
+	pool_t pool;
+
+	mail_storage_clear_error(storage);
+
+	pool = pool_alloconly_create("maildir_list", 1024);
+	ctx = p_new(pool, struct mailbox_list_context, 1);
+	ctx->pool = pool;
+	ctx->storage = storage;
+	ctx->flags = flags;
+	ctx->tree_ctx = mailbox_tree_init('.');
 
-	if (imap_match(ctx->glob, "INBOX") > 0) {
-		const char *path = maildir_get_path(ctx->storage, "INBOX");
+	glob = imap_match_init(pool, mask, TRUE, '.');
+
+	if ((flags & MAILBOX_LIST_SUBSCRIBED) != 0) {
+		ctx->dir = storage->dir;
+		ctx->prefix = "";
 
-		ctx->list.flags = (ctx->flags & MAILBOX_LIST_FAST_FLAGS) == 0 ?
-			maildir_get_marked_flags(path) : 0;
-		ctx->list.name = "INBOX";
-		return &ctx->list;
+		nonexistent = (flags & MAILBOX_LIST_FAST_FLAGS) == 0;
+		if (!maildir_fill_subscribed(ctx, glob, nonexistent)) {
+                        mailbox_tree_deinit(ctx->tree_ctx);
+			pool_unref(pool);
+			return NULL;
+		}
+	} else {
+		if (!full_filesystem_access ||
+		    (p = strrchr(mask, '/')) == NULL) {
+			ctx->dir = storage->dir;
+			ctx->prefix = "";
+		} else {
+			dir = t_strdup_until(mask, p);
+			ctx->prefix = t_strdup_until(mask, p+1);
+
+			if (*mask != '/' && *mask != '~')
+				dir = t_strconcat(storage->dir, "/", dir, NULL);
+			ctx->dir = p_strdup(pool, home_expand(dir));
+		}
 	}
 
-	/* we're finished */
+	if ((flags & MAILBOX_LIST_SUBSCRIBED) == 0 ||
+	    (ctx->flags & MAILBOX_LIST_FAST_FLAGS) == 0) {
+		int update_only = (flags & MAILBOX_LIST_SUBSCRIBED) != 0;
+		if (!maildir_fill_readdir(ctx, glob, update_only)) {
+			mailbox_tree_deinit(ctx->tree_ctx);
+			pool_unref(pool);
+			return NULL;
+		}
+	}
+
+	ctx->node_path = str_new(pool, 256);
+	ctx->root = mailbox_tree_get(ctx->tree_ctx, NULL, NULL);
+	return ctx;
+}
+
+int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx)
+{
+	mailbox_tree_deinit(ctx->tree_ctx);
+	pool_unref(ctx->pool);
+	return TRUE;
+}
+
+static struct mailbox_node *find_next(struct mailbox_node **node,
+				      string_t *path)
+{
+	struct mailbox_node *child;
+	size_t len;
+
+	while (*node != NULL) {
+		if (((*node)->flags & MAILBOX_FLAG_MATCHED) != 0)
+			return *node;
+
+		if ((*node)->children != NULL) {
+			len = str_len(path);
+			if (len != 0)
+				str_append_c(path, '.');
+			str_append(path, (*node)->name);
+
+			child = find_next(&(*node)->children, path);
+			if (child != NULL)
+				return child;
+
+			str_truncate(path, len);
+		}
+
+		*node = (*node)->next;
+	}
+
 	return NULL;
 }
 
 struct mailbox_list *
 maildir_list_mailbox_next(struct mailbox_list_context *ctx)
 {
-	return ctx->next(ctx);
+	struct mailbox_node *node;
+
+	for (node = ctx->next_node; node != NULL; node = node->next) {
+		if ((node->flags & MAILBOX_FLAG_MATCHED) != 0)
+			break;
+	}
+
+	if (node == NULL) {
+		str_truncate(ctx->node_path, 0);
+		node = find_next(&ctx->root, ctx->node_path);
+                ctx->parent_pos = str_len(ctx->node_path);
+
+		if (node == NULL)
+			return NULL;
+	}
+	ctx->next_node = node->next;
+
+	i_assert((node->flags & MAILBOX_FLAG_MATCHED) != 0);
+	node->flags &= ~MAILBOX_FLAG_MATCHED;
+
+	str_truncate(ctx->node_path, ctx->parent_pos);
+	if (ctx->parent_pos != 0)
+		str_append_c(ctx->node_path, '.');
+	str_append(ctx->node_path, node->name);
+
+	ctx->list.name = str_c(ctx->node_path);
+	ctx->list.flags = node->flags;
+	return &ctx->list;
 }
--- a/src/lib-storage/index/maildir/maildir-storage.c	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Sun Jul 27 02:53:05 2003 +0300
@@ -536,14 +536,14 @@
         struct mailbox_list *list;
 	const char *oldpath, *newpath, *new_listname;
 	size_t oldnamelen;
-	int sorted, ret;
+	int ret;
 
 	ret = 0;
 	oldnamelen = strlen(oldname);
 
 	ctx = storage->list_mailbox_init(storage,
 					 t_strconcat(oldname, ".*", NULL),
-					 MAILBOX_LIST_FAST_FLAGS, &sorted);
+					 MAILBOX_LIST_FAST_FLAGS);
 	while ((list = maildir_list_mailbox_next(ctx)) != NULL) {
 		i_assert(oldnamelen <= strlen(list->name));
 
--- a/src/lib-storage/index/maildir/maildir-storage.h	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/index/maildir/maildir-storage.h	Sun Jul 27 02:53:05 2003 +0300
@@ -17,8 +17,7 @@
 
 struct mailbox_list_context *
 maildir_list_mailbox_init(struct mail_storage *storage,
-			  const char *mask, enum mailbox_list_flags flags,
-			  int *sorted);
+			  const char *mask, enum mailbox_list_flags flags);
 int maildir_list_mailbox_deinit(struct mailbox_list_context *ctx);
 struct mailbox_list *
 maildir_list_mailbox_next(struct mailbox_list_context *ctx);
--- a/src/lib-storage/index/mbox/mbox-list.c	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/index/mbox/mbox-list.c	Sun Jul 27 02:53:05 2003 +0300
@@ -98,14 +98,12 @@
 
 struct mailbox_list_context *
 mbox_list_mailbox_init(struct mail_storage *storage, const char *mask,
-		       enum mailbox_list_flags flags, int *sorted)
+		       enum mailbox_list_flags flags)
 {
 	struct mailbox_list_context *ctx;
 	const char *path, *virtual_path;
 	DIR *dirp;
 
-	*sorted = (flags & MAILBOX_LIST_SUBSCRIBED) == 0;
-
 	/* check that we're not trying to do any "../../" lists */
 	if (!mbox_is_valid_mask(mask)) {
 		mail_storage_set_error(storage, "Invalid mask");
--- a/src/lib-storage/index/mbox/mbox-storage.h	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/index/mbox/mbox-storage.h	Sun Jul 27 02:53:05 2003 +0300
@@ -16,7 +16,7 @@
 
 struct mailbox_list_context *
 mbox_list_mailbox_init(struct mail_storage *storage, const char *mask,
-		       enum mailbox_list_flags flags, int *sorted);
+		       enum mailbox_list_flags flags);
 int mbox_list_mailbox_deinit(struct mailbox_list_context *ctx);
 struct mailbox_list *mbox_list_mailbox_next(struct mailbox_list_context *ctx);
 
--- a/src/lib-storage/mail-storage.h	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/mail-storage.h	Sun Jul 27 02:53:05 2003 +0300
@@ -179,14 +179,11 @@
 
 	/* Initialize new mailbox list request. mask may contain '%' and '*'
 	   wildcards as defined in RFC2060. Matching against "INBOX" is
-	   case-insensitive, but anything else is not. *sorted is set to TRUE
-	   if the output will contain parent mailboxes always before their
-	   children. */
+	   case-insensitive, but anything else is not. */
 	struct mailbox_list_context *
 		(*list_mailbox_init)(struct mail_storage *storage,
 				     const char *mask,
-				     enum mailbox_list_flags flags,
-				     int *sorted);
+				     enum mailbox_list_flags flags);
 	/* Deinitialize mailbox list request. Returns FALSE if some error
 	   occured while listing. */
 	int (*list_mailbox_deinit)(struct mailbox_list_context *ctx);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mailbox-tree.c	Sun Jul 27 02:53:05 2003 +0300
@@ -0,0 +1,108 @@
+/* Copyright (C) 2003 Timo Sirainen */
+
+#include "lib.h"
+#include "str.h"
+#include "mailbox-tree.h"
+
+struct mailbox_tree_context {
+	pool_t pool;
+	char separator;
+	struct mailbox_node *nodes;
+};
+
+struct mailbox_tree_context *mailbox_tree_init(char separator)
+{
+	struct mailbox_tree_context *ctx;
+	pool_t pool;
+
+	pool = pool_alloconly_create("mailbox_tree", 10240);
+
+	ctx = p_new(pool, struct mailbox_tree_context, 1);
+	ctx->pool = pool;
+	ctx->separator = separator;
+	return ctx;
+}
+
+void mailbox_tree_deinit(struct mailbox_tree_context *ctx)
+{
+	pool_unref(ctx->pool);
+}
+
+static struct mailbox_node *
+mailbox_tree_traverse(struct mailbox_tree_context *ctx, const char *path,
+		      int create, int *created)
+{
+	struct mailbox_node **node;
+	const char *name;
+	string_t *str;
+
+	if (created != NULL)
+		*created = FALSE;
+
+	if (path == NULL)
+		return ctx->nodes;
+
+	t_push();
+
+	if (strncasecmp(path, "INBOX", 5) == 0 &&
+	    (path[5] == '\0' || path[5] == ctx->separator))
+		path = t_strdup_printf("INBOX%s", path+5);
+
+	node = &ctx->nodes;
+
+	str = t_str_new(strlen(path)+1);
+	for (name = path;; path++) {
+		if (*path != ctx->separator && *path != '\0')
+			continue;
+
+		str_truncate(str, 0);
+		str_append_n(str, name, (size_t) (path - name));
+		name = str_c(str);
+
+		/* find the node */
+		while (*node != NULL) {
+			if (strcmp((*node)->name, name) == 0)
+				break;
+
+			node = &(*node)->next;
+		}
+
+		if (*node == NULL) {
+			/* not found, create it */
+			if (!create)
+				break;
+
+			*node = p_new(ctx->pool, struct mailbox_node, 1);
+			(*node)->name = p_strdup(ctx->pool, name);
+
+			if (*path != '\0')
+				(*node)->flags = MAILBOX_PLACEHOLDER;
+			else {
+				if (created != NULL)
+					*created = TRUE;
+			}
+		}
+
+		if (*path == '\0')
+			break;
+
+		name = path+1;
+		node = &(*node)->children;
+	}
+	t_pop();
+
+	return *node;
+}
+
+struct mailbox_node *
+mailbox_tree_get(struct mailbox_tree_context *ctx, const char *path,
+		 int *created)
+{
+	return mailbox_tree_traverse(ctx, path, TRUE, created);
+}
+
+struct mailbox_node *
+mailbox_tree_update(struct mailbox_tree_context *ctx, const char *path)
+{
+	return mailbox_tree_traverse(ctx, path, FALSE, NULL);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mailbox-tree.h	Sun Jul 27 02:53:05 2003 +0300
@@ -0,0 +1,24 @@
+#ifndef __MAILBOX_TREE_H
+#define __MAILBOX_TREE_H
+
+#include "mail-storage.h"
+
+struct mailbox_node {
+	struct mailbox_node *next;
+	struct mailbox_node *children;
+
+	char *name;
+	enum mailbox_flags flags;
+};
+
+struct mailbox_tree_context *mailbox_tree_init(char separator);
+void mailbox_tree_deinit(struct mailbox_tree_context *ctx);
+
+struct mailbox_node *
+mailbox_tree_get(struct mailbox_tree_context *ctx, const char *path,
+		 int *created);
+
+struct mailbox_node *
+mailbox_tree_update(struct mailbox_tree_context *ctx, const char *path);
+
+#endif
--- a/src/lib-storage/proxy-mail-storage.c	Sun Jul 27 02:28:05 2003 +0300
+++ b/src/lib-storage/proxy-mail-storage.c	Sun Jul 27 02:53:05 2003 +0300
@@ -53,11 +53,11 @@
 
 static struct mailbox_list_context *
 _list_mailbox_init(struct mail_storage *storage, const char *mask,
-		   enum mailbox_list_flags flags, int *sorted)
+		   enum mailbox_list_flags flags)
 {
 	struct proxy_mail_storage *s = (struct proxy_mail_storage *) storage;
 
-	return s->storage->list_mailbox_init(s->storage, mask, flags, sorted);
+	return s->storage->list_mailbox_init(s->storage, mask, flags);
 }
 
 static int _set_subscribed(struct mail_storage *storage,