changeset 12637:4e4c7f982fd5

lib-storage: Cleaned up subscription listing internally. The subscription listing code is now mostly separated from the mailbox listing code.
author Timo Sirainen <tss@iki.fi>
date Wed, 02 Feb 2011 05:33:04 +0200
parents fa4b84059ae2
children 951d89021e5f
files src/lib-storage/index/imapc/imapc-list.c src/lib-storage/index/shared/shared-list.c src/lib-storage/list/index-mailbox-list.c src/lib-storage/list/mailbox-list-fs-iter.c src/lib-storage/list/mailbox-list-fs.c src/lib-storage/list/mailbox-list-maildir-iter.c src/lib-storage/list/mailbox-list-maildir.c src/lib-storage/list/mailbox-list-none.c src/lib-storage/list/mailbox-list-subscriptions.c src/lib-storage/list/mailbox-list-subscriptions.h src/lib-storage/list/subscription-file.c src/lib-storage/list/subscription-file.h src/lib-storage/mailbox-list-private.h src/lib-storage/mailbox-list.c src/lib-storage/mailbox-tree.c src/lib-storage/mailbox-tree.h
diffstat 16 files changed, 339 insertions(+), 253 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-storage/index/imapc/imapc-list.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/index/imapc/imapc-list.c	Wed Feb 02 05:33:04 2011 +0200
@@ -296,6 +296,12 @@
 	return ret;
 }
 
+static int
+imapc_list_subscriptions_refresh(struct mailbox_list *_list ATTR_UNUSED)
+{
+	return 0;
+}
+
 static int imapc_list_set_subscribed(struct mailbox_list *_list,
 				     const char *name, bool set)
 {
@@ -391,6 +397,7 @@
 		imapc_list_iter_deinit,
 		NULL,
 		NULL,
+		imapc_list_subscriptions_refresh,
 		imapc_list_set_subscribed,
 		imapc_list_create_mailbox_dir,
 		imapc_list_delete_mailbox,
--- a/src/lib-storage/index/shared/shared-list.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/index/shared/shared-list.c	Wed Feb 02 05:33:04 2011 +0200
@@ -210,6 +210,12 @@
 	return 0;
 }
 
+static int
+shared_list_subscriptions_refresh(struct mailbox_list *list ATTR_UNUSED)
+{
+	return 0;
+}
+
 static int shared_list_set_subscribed(struct mailbox_list *list,
 				      const char *name, bool set)
 {
@@ -329,6 +335,7 @@
 		shared_list_iter_deinit,
 		NULL,
 		NULL,
+		shared_list_subscriptions_refresh,
 		shared_list_set_subscribed,
 		shared_list_create_mailbox_dir,
 		shared_list_delete_mailbox,
--- a/src/lib-storage/list/index-mailbox-list.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/index-mailbox-list.c	Wed Feb 02 05:33:04 2011 +0200
@@ -205,8 +205,8 @@
 		      MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0) {
 		/* we'll need to know the subscriptions */
 		ctx->subs_tree = mailbox_tree_init(sep);
-		if (mailbox_list_subscriptions_fill(&ctx->ctx, ctx->subs_tree,
-						    ctx->glob, FALSE) < 0) {
+		/*if (mailbox_list_subscriptions_fill(&ctx->ctx, ctx->subs_tree,
+						    ctx->glob, FALSE) < 0)*/ {
 			/* let the backend handle this failure */
 			return FALSE;
 		}
--- a/src/lib-storage/list/mailbox-list-fs-iter.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-fs-iter.c	Wed Feb 02 05:33:04 2011 +0200
@@ -36,8 +36,6 @@
 	struct mailbox_list_iterate_context ctx;
 
 	ARRAY_DEFINE(valid_patterns, char *);
-	struct mailbox_tree_context *subs_tree;
-	struct mailbox_tree_iterate_context *tree_iter;
 	char sep;
 
 	enum mailbox_info_flags inbox_flags;
@@ -54,8 +52,6 @@
 };
 
 static const struct mailbox_info *
-fs_list_subs(struct fs_list_iterate_context *ctx);
-static const struct mailbox_info *
 fs_list_next(struct fs_list_iterate_context *ctx);
 
 static int
@@ -217,6 +213,13 @@
 	unsigned int prefix_len;
 	int ret;
 
+	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+		/* we're listing only subscribed mailboxes. we can't optimize
+		   it, so just use the generic code. */
+		return mailbox_list_subscriptions_iter_init(_list, patterns,
+							    flags);
+	}
+
 	ctx = i_new(struct fs_list_iterate_context, 1);
 	ctx->ctx.list = _list;
 	ctx->ctx.flags = flags;
@@ -261,25 +264,6 @@
 	ctx->ctx.glob = imap_match_init_multiple(default_pool, patterns, TRUE,
 						 ctx->sep);
 
-	if ((flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
-		      MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0) {
-		/* we want to return MAILBOX_SUBSCRIBED flags, possibly for all
-		   mailboxes. Build a mailbox tree of all the subscriptions. */
-		ctx->subs_tree = mailbox_tree_init(ctx->sep);
-		if (mailbox_list_subscriptions_fill(&ctx->ctx, ctx->subs_tree,
-						    ctx->ctx.glob, FALSE) < 0) {
-			ctx->ctx.failed = TRUE;
-			return &ctx->ctx;
-		}
-	}
-
-	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
-		ctx->next = fs_list_subs;
-		ctx->tree_iter = mailbox_tree_iterate_init(ctx->subs_tree, NULL,
-							   MAILBOX_MATCHED);
-		return &ctx->ctx;
-	}
-
 	vpath = _list->ns->prefix;
 	rootdir = list_get_rootdir(ctx, &vpath);
 	if (rootdir == NULL) {
@@ -319,6 +303,9 @@
 	unsigned int i, count;
 	int ret = ctx->ctx.failed ? -1 : 0;
 
+	if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+		return mailbox_list_subscriptions_iter_deinit(_ctx);
+
 	patterns = array_get_modifiable(&ctx->valid_patterns, &count);
 	for (i = 0; i < count; i++)
 		i_free(patterns[i]);
@@ -331,10 +318,6 @@
                 list_dir_context_free(dir);
 	}
 
-	if (ctx->tree_iter != NULL)
-		mailbox_tree_iterate_deinit(&ctx->tree_iter);
-	if (ctx->subs_tree != NULL)
-		mailbox_tree_deinit(&ctx->subs_tree);
 	if (ctx->info_pool != NULL)
 		pool_unref(&ctx->info_pool);
 	if (ctx->ctx.glob != NULL)
@@ -352,7 +335,10 @@
 		(struct fs_list_iterate_context *)_ctx;
 	const struct mailbox_info *info;
 
-	if (ctx->ctx.failed)
+	if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+		return mailbox_list_subscriptions_iter_next(_ctx);
+
+	if (_ctx->failed)
 		return NULL;
 
 	T_BEGIN {
@@ -362,37 +348,6 @@
 	return info;
 }
 
-static void
-path_split(const char *path, const char **dir_r, const char **fname_r)
-{
-	const char *p;
-
-	p = strrchr(path, '/');
-	if (p == NULL) {
-		*dir_r = "";
-		*fname_r = path;
-	} else {
-		*dir_r = t_strdup_until(path, p);
-		*fname_r = p + 1;
-	}
-}
-
-static enum mailbox_info_flags
-fs_list_get_subscription_flags(struct fs_list_iterate_context *ctx,
-			       const char *mailbox)
-{
-	struct mailbox_node *node;
-
-	if (ctx->subs_tree == NULL)
-		return 0;
-
-	node = mailbox_tree_lookup(ctx->subs_tree, mailbox);
-	if (node == NULL)
-		return 0;
-
-	return node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
-}
-
 static void inbox_flags_set(struct fs_list_iterate_context *ctx)
 {
 	struct mail_namespace *ns = ctx->ctx.list->ns;
@@ -433,7 +388,8 @@
 	    (ctx->info.flags & MAILBOX_NONEXISTENT) != 0)
 		return NULL;
 
-	ctx->info.flags |= fs_list_get_subscription_flags(ctx, "INBOX");
+	mailbox_list_set_subscription_flags(ctx->ctx.list, "INBOX",
+					    &ctx->info.flags);
 	inbox_flags_set(ctx);
 	/* we got here because we didn't see INBOX among other mailboxes,
 	   which means it has no children. */
@@ -613,7 +569,8 @@
 		return 1;
 	}
 
-	ctx->info.flags |= fs_list_get_subscription_flags(ctx, list_path);
+	mailbox_list_set_subscription_flags(ctx->ctx.list, list_path,
+					    &ctx->info.flags);
 
 	/* make sure we give only one correct INBOX */
 	if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
@@ -646,61 +603,6 @@
 	return 0;
 }
 
-static const struct mailbox_info *
-fs_list_subs(struct fs_list_iterate_context *ctx)
-{
-	struct mailbox_node *node;
-	enum mailbox_info_flags flags;
-	struct mail_namespace *ns;
-	const char *path, *dir, *fname, *subs_name, *storage_name;
-	unsigned int len;
-	struct stat st;
-
-	node = mailbox_tree_iterate_next(ctx->tree_iter, &ctx->info.name);
-	if (node == NULL)
-		return NULL;
-
-	/* subscription list has real knowledge of only subscription flags */
-	flags = node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
-
-	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
-	    (ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) {
-		ctx->info.flags = flags;
-		return &ctx->info;
-	}
-
-	/* see if this is for another subscriptions=no namespace */
-	subs_name = ctx->info.name;
-	ns = mail_namespace_find_unsubscribable(ctx->info.ns->user->namespaces,
-						subs_name);
-	if (ns == NULL)
-		ns = ctx->info.ns;
-
-	/* if name ends with hierarchy separator, drop the separator */
-	len = strlen(subs_name);
-	if (len > 0 && subs_name[len-1] == mail_namespace_get_sep(ns))
-		subs_name = t_strndup(subs_name, len-1);
-
-	storage_name = mailbox_list_get_storage_name(ns->list, subs_name);
-	if (!mailbox_list_is_valid_pattern(ns->list, storage_name)) {
-		/* broken entry in subscriptions file */
-		ctx->info.flags = MAILBOX_NONEXISTENT;
-	} else {
-		struct mailbox_list *list = ns->list;
-
-		path = mailbox_list_get_path(list, storage_name,
-					     MAILBOX_LIST_PATH_TYPE_DIR);
-		path_split(path, &dir, &fname);
-		if (list->v.get_mailbox_flags(list, dir, fname,
-					      MAILBOX_LIST_FILE_TYPE_UNKNOWN,
-					      &st, &ctx->info.flags) < 0)
-			ctx->ctx.failed = TRUE;
-	}
-
-	ctx->info.flags |= flags;
-	return &ctx->info;
-}
-
 static const struct list_dir_entry *
 fs_list_dir_next(struct fs_list_iterate_context *ctx)
 {
--- a/src/lib-storage/list/mailbox-list-fs.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-fs.c	Wed Feb 02 05:33:04 2011 +0200
@@ -6,6 +6,7 @@
 #include "mailbox-log.h"
 #include "subscription-file.h"
 #include "mail-storage.h"
+#include "mailbox-list-subscriptions.h"
 #include "mailbox-list-delete.h"
 #include "mailbox-list-fs.h"
 
@@ -593,6 +594,7 @@
 		fs_list_iter_deinit,
 		fs_list_get_mailbox_flags,
 		NULL,
+		mailbox_list_subscriptions_refresh,
 		fs_list_set_subscribed,
 		fs_list_create_mailbox_dir,
 		fs_list_delete_mailbox,
--- a/src/lib-storage/list/mailbox-list-maildir-iter.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-maildir-iter.c	Wed Feb 02 05:33:04 2011 +0200
@@ -388,55 +388,6 @@
 	}
 }
 
-static int
-maildir_fill_other_ns_subscriptions(struct maildir_list_iterate_context *ctx,
-				    struct mail_namespace *ns)
-{
-	struct mailbox_list_iterate_context *iter;
-	const struct mailbox_info *info;
-	struct mailbox_node *node;
-
-	iter = mailbox_list_iter_init(ns->list, "*",
-				      MAILBOX_LIST_ITER_RETURN_CHILDREN);
-	while ((info = mailbox_list_iter_next(iter)) != NULL) {
-		node = mailbox_tree_lookup(ctx->tree_ctx, info->name);
-		if (node != NULL) {
-			node->flags &= ~MAILBOX_NONEXISTENT;
-			node->flags |= info->flags;
-		}
-	}
-	if (mailbox_list_iter_deinit(&iter) < 0) {
-		enum mail_error error;
-		const char *errstr;
-
-		errstr = mailbox_list_get_last_error(ns->list, &error);
-		mailbox_list_set_error(ctx->ctx.list, error, errstr);
-		return -1;
-	}
-	return 0;
-}
-
-static int
-maildir_fill_other_subscriptions(struct maildir_list_iterate_context *ctx)
-{
-	struct mail_namespace *ns;
-	const char *path;
-
-	ns = ctx->ctx.list->ns->user->namespaces;
-	for (; ns != NULL; ns = ns->next) {
-		if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0 ||
-		    ns->prefix_len == 0)
-			continue;
-
-		path = t_strndup(ns->prefix, ns->prefix_len-1);
-		if (mailbox_tree_lookup(ctx->tree_ctx, path) != NULL) {
-			if (maildir_fill_other_ns_subscriptions(ctx, ns) < 0)
-				return -1;
-		}
-	}
-	return 0;
-}
-
 struct mailbox_list_iterate_context *
 maildir_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
 		       enum mailbox_list_iter_flags flags)
@@ -466,11 +417,7 @@
 	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
 		/* Listing only subscribed mailboxes.
 		   Flags are set later if needed. */
-		if (mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree_ctx,
-						    ctx->ctx.glob, FALSE) < 0) {
-			ctx->ctx.failed = TRUE;
-			return &ctx->ctx;
-		}
+		mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree_ctx);
 	}
 
 	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 ||
@@ -489,28 +436,6 @@
 		}
 	}
 
-	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 &&
-	    (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0) {
-		/* if there are subscriptions=no namespaces, we may have some
-		   of their subscriptions whose flags need to be filled */
-		ret = maildir_fill_other_subscriptions(ctx);
-		if (ret < 0) {
-			ctx->ctx.failed = TRUE;
-			return &ctx->ctx;
-		}
-	}
-
-	if ((flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0 &&
-	    (flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) {
-		/* we're listing all mailboxes but we want to know
-		   \Subscribed flags */
-		if (mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree_ctx,
-						    ctx->ctx.glob, TRUE) < 0) {
-			ctx->ctx.failed = TRUE;
-			return &ctx->ctx;
-		}
-	}
-
 	ctx->tree_iter = mailbox_tree_iterate_init(ctx->tree_ctx, NULL,
 						   MAILBOX_MATCHED);
 	return &ctx->ctx;
@@ -544,5 +469,12 @@
 		return NULL;
 
 	ctx->info.flags = node->flags;
+	if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0 &&
+	    (_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) {
+		/* we're listing all mailboxes but we want to know
+		   \Subscribed flags */
+		mailbox_list_set_subscription_flags(_ctx->list, ctx->info.name,
+						    &ctx->info.flags);
+	}
 	return &ctx->info;
 }
--- a/src/lib-storage/list/mailbox-list-maildir.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-maildir.c	Wed Feb 02 05:33:04 2011 +0200
@@ -7,6 +7,7 @@
 #include "mkdir-parents.h"
 #include "str.h"
 #include "subscription-file.h"
+#include "mailbox-list-subscriptions.h"
 #include "mailbox-list-delete.h"
 #include "mailbox-list-maildir.h"
 
@@ -635,6 +636,7 @@
 		maildir_list_iter_deinit,
 		maildir_list_get_mailbox_flags,
 		NULL,
+		mailbox_list_subscriptions_refresh,
 		maildir_list_set_subscribed,
 		maildir_list_create_mailbox_dir,
 		maildir_list_delete_mailbox,
@@ -668,6 +670,7 @@
 		maildir_list_iter_deinit,
 		maildir_list_get_mailbox_flags,
 		NULL,
+		mailbox_list_subscriptions_refresh,
 		maildir_list_set_subscribed,
 		maildir_list_create_mailbox_dir,
 		maildir_list_delete_mailbox,
--- a/src/lib-storage/list/mailbox-list-none.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-none.c	Wed Feb 02 05:33:04 2011 +0200
@@ -71,6 +71,12 @@
 	return GLOBAL_TEMP_PREFIX;
 }
 
+static int
+none_list_subscriptions_refresh(struct mailbox_list *list ATTR_UNUSED)
+{
+	return 0;
+}
+
 static int none_list_set_subscribed(struct mailbox_list *list,
 				    const char *name ATTR_UNUSED,
 				    bool set ATTR_UNUSED)
@@ -180,6 +186,7 @@
 		none_list_iter_deinit,
 		none_list_get_mailbox_flags,
 		NULL,
+		none_list_subscriptions_refresh,
 		none_list_set_subscribed,
 		none_list_create_mailbox_dir,
 		none_list_delete_mailbox,
--- a/src/lib-storage/list/mailbox-list-subscriptions.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-subscriptions.c	Wed Feb 02 05:33:04 2011 +0200
@@ -1,18 +1,33 @@
 /* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "imap-match.h"
 #include "subscription-file.h"
+#include "mailbox-tree.h"
 #include "mailbox-list-private.h"
 #include "mailbox-list-subscriptions.h"
 
+#include <sys/stat.h>
+
+struct subscriptions_mailbox_list_iterate_context {
+	struct mailbox_list_iterate_context ctx;
+	struct mailbox_tree_context *tree;
+	struct mailbox_tree_iterate_context *iter;
+	struct mailbox_info info;
+};
+
 static int
-mailbox_list_subscription_fill_one(struct mailbox_list_iter_update_context *update_ctx,
-				   struct mail_namespace *default_ns,
+mailbox_list_subscription_fill_one(struct mailbox_list *list,
 				   const char *name)
 {
+	struct mail_namespace *ns, *default_ns = list->ns;
 	struct mail_namespace *namespaces = default_ns->user->namespaces;
-	struct mail_namespace *ns;
+	struct mailbox_node *node;
 	const char *vname;
+	unsigned int len;
+	bool created;
 
 	/* default_ns is whatever namespace we're currently listing.
 	   if we have e.g. prefix="" and prefix=pub/ namespaces with
@@ -38,7 +53,7 @@
 		/* we'll need to get the namespace autocreated.
 		   one easy way is to just ask if a mailbox name under
 		   it is valid, and it gets created */
-		(void)mailbox_list_is_valid_existing_name(ns->list, name);
+		(void)mailbox_list_is_valid_existing_name(list, name);
 		ns = mail_namespace_find_unsubscribable(namespaces, name);
 		i_assert(ns != NULL &&
 			 (ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0);
@@ -53,75 +68,220 @@
 		ns = default_ns;
 	}
 
-	if (!mailbox_list_is_valid_existing_name(ns->list, name)) {
+	len = strlen(name);
+	if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) {
+		/* entry ends with hierarchy separator, remove it.
+		   this exists mainly for backwards compatibility with old
+		   Dovecot versions and non-Dovecot software that added them */
+		name = t_strndup(name, len-1);
+	}
+
+	if (!mailbox_list_is_valid_existing_name(list, name)) {
 		/* we'll only get into trouble if we show this */
 		return -1;
 	} else {
-		vname = mailbox_list_get_vname(ns->list, name);
-		mailbox_list_iter_update(update_ctx, vname);
+		vname = mailbox_list_get_vname(list, name);
+		node = mailbox_tree_get(list->subscriptions, vname, &created);
+		node->flags = MAILBOX_SUBSCRIBED;
 	}
 	return 0;
 }
 
-static int
-mailbox_list_subscriptions_fill_real(struct mailbox_list_iterate_context *ctx,
-				     struct mailbox_tree_context *tree_ctx,
-				     struct imap_match_glob *glob,
-				     bool update_only)
+int mailbox_list_subscriptions_refresh(struct mailbox_list *list)
 {
-	struct mail_namespace *ns, *default_ns = ctx->list->ns;
-	struct mailbox_list *list = ctx->list;
-	struct mailbox_list_iter_update_context update_ctx;
+	struct mail_namespace *ns = list->ns;
 	struct subsfile_list_context *subsfile_ctx;
+	struct stat st;
 	const char *path, *name;
 
-	if ((ctx->list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0) {
-		/* need to list these using another namespace */
-		ns = mail_namespace_find_subscribable(default_ns->user->namespaces,
-						      default_ns->prefix);
+	if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0) {
+		/* no subscriptions in this namespace. find where they are. */
+		ns = mail_namespace_find_subscribable(ns->user->namespaces,
+						      ns->prefix);
 		if (ns == NULL) {
 			/* no subscriptions */
 			return 0;
 		}
-		list = ns->list;
 	}
 
-	path = t_strconcat(list->set.control_dir != NULL ?
-			   list->set.control_dir : list->set.root_dir,
-			   "/", list->set.subscription_fname, NULL);
-	subsfile_ctx = subsfile_list_init(list, path);
+	path = t_strconcat(ns->list->set.control_dir != NULL ?
+			   ns->list->set.control_dir : ns->list->set.root_dir,
+			   "/", ns->list->set.subscription_fname, NULL);
+	if (stat(path, &st) < 0) {
+		if (errno == ENOENT) {
+			/* no subscriptions */
+			mailbox_tree_clear(list->subscriptions);
+			list->subscriptions_mtime = 0;
+			return 0;
+		}
+		mailbox_list_set_critical(list, "stat(%s) failed: %m", path);
+		return -1;
+	}
+	if (st.st_mtime == list->subscriptions_mtime &&
+	    st.st_mtime < list->subscriptions_read_time-1) {
+		/* we're up to date */
+		return 0;
+	}
 
-	memset(&update_ctx, 0, sizeof(update_ctx));
-	update_ctx.iter_ctx = ctx;
-	update_ctx.tree_ctx = tree_ctx;
-	update_ctx.glob = glob;
-	update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
-	update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
-	update_ctx.update_only = update_only;
-	update_ctx.match_parents =
-		(ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
+	mailbox_tree_clear(list->subscriptions);
+	list->subscriptions_read_time = ioloop_time;
 
+	subsfile_ctx = subsfile_list_init(list, path);
+	if (subsfile_list_fstat(subsfile_ctx, &st) == 0)
+		list->subscriptions_mtime = st.st_mtime;
 	while ((name = subsfile_list_next(subsfile_ctx)) != NULL) T_BEGIN {
-		if (mailbox_list_subscription_fill_one(&update_ctx, default_ns,
-						       name) < 0) {
+		if (mailbox_list_subscription_fill_one(list, name) < 0) {
 			i_warning("Subscriptions file %s: "
 				  "Ignoring invalid entry: %s",
 				  path, name);
 		}
 	} T_END;
-	return subsfile_list_deinit(subsfile_ctx);
+
+	if (subsfile_list_deinit(&subsfile_ctx) < 0) {
+		list->subscriptions_mtime = (time_t)-1;
+		return -1;
+	}
+	return 0;
+}
+
+void mailbox_list_set_subscription_flags(struct mailbox_list *list,
+					 const char *vname,
+					 enum mailbox_info_flags *flags)
+{
+	struct mailbox_node *node;
+
+	*flags &= ~(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+
+	node = mailbox_tree_lookup(list->subscriptions, vname);
+	if (node != NULL) {
+		*flags |= node->flags & MAILBOX_SUBSCRIBED;
+
+		/* the only reason why node might have a child is if one of
+		   them is subscribed */
+		if (node->children != NULL)
+			*flags |= MAILBOX_CHILD_SUBSCRIBED;
+	}
+}
+
+void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
+				     struct mailbox_tree_context *tree)
+{
+	struct mailbox_list_iter_update_context update_ctx;
+	struct mailbox_tree_iterate_context *iter;
+	struct mailbox_node *node;
+	const char *name;
+
+	memset(&update_ctx, 0, sizeof(update_ctx));
+	update_ctx.iter_ctx = ctx;
+	update_ctx.tree_ctx = tree;
+	update_ctx.glob = ctx->glob;
+	update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
+	update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
+	update_ctx.match_parents =
+		(ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
+
+	iter = mailbox_tree_iterate_init(ctx->list->subscriptions, NULL,
+					 MAILBOX_SUBSCRIBED);
+	while ((node = mailbox_tree_iterate_next(iter, &name)) != NULL)
+		mailbox_list_iter_update(&update_ctx, name);
+	mailbox_tree_iterate_deinit(&iter);
+}
+
+struct mailbox_list_iterate_context *
+mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
+				     const char *const *patterns,
+				     enum mailbox_list_iter_flags flags)
+{
+	struct subscriptions_mailbox_list_iterate_context *ctx;
+	char sep = mail_namespace_get_sep(list->ns);
+
+	ctx = i_new(struct subscriptions_mailbox_list_iterate_context, 1);
+	ctx->ctx.list = list;
+	ctx->ctx.flags = flags;
+	ctx->ctx.glob = imap_match_init_multiple(default_pool, patterns,
+						 TRUE, sep);
+	array_create(&ctx->ctx.module_contexts, default_pool, sizeof(void *), 5);
+
+	ctx->tree = mailbox_tree_init(sep);
+	mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree);
+
+	ctx->info.ns = list->ns;
+	ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, 0);
+	return &ctx->ctx;
 }
 
-int mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
-				    struct mailbox_tree_context *tree_ctx,
-				    struct imap_match_glob *glob,
-				    bool update_only)
+static void
+path_split(const char *path, const char **dir_r, const char **fname_r)
+{
+	const char *p;
+
+	p = strrchr(path, '/');
+	if (p == NULL) {
+		*dir_r = "";
+		*fname_r = path;
+	} else {
+		*dir_r = t_strdup_until(path, p);
+		*fname_r = p + 1;
+	}
+}
+
+const struct mailbox_info *
+mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *_ctx)
 {
-	int ret;
+	struct subscriptions_mailbox_list_iterate_context *ctx =
+		(struct subscriptions_mailbox_list_iterate_context *)_ctx;
+	struct mailbox_list *list = _ctx->list;
+	struct mailbox_node *node;
+	enum mailbox_info_flags subs_flags;
+	const char *path, *vname, *dir, *fname, *storage_name;
+	struct stat st;
+
+	node = mailbox_tree_iterate_next(ctx->iter, &vname);
+	if (node == NULL)
+		return NULL;
+
+	ctx->info.name = vname;
+	subs_flags = node->flags & (MAILBOX_SUBSCRIBED |
+				    MAILBOX_CHILD_SUBSCRIBED);
+
+	if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
+	    (_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) {
+		/* don't care about flags, just return it */
+		ctx->info.flags = subs_flags;
+		return &ctx->info;
+	}
 
-	T_BEGIN {
-		ret = mailbox_list_subscriptions_fill_real(ctx, tree_ctx, glob,
-							   update_only);
-	} T_END;
+	storage_name = mailbox_list_get_storage_name(list, vname);
+	if (!mailbox_list_is_valid_pattern(list, storage_name)) {
+		/* broken entry in subscriptions file */
+		ctx->info.flags = MAILBOX_NONEXISTENT;
+	} else {
+		path = mailbox_list_get_path(list, storage_name,
+					     MAILBOX_LIST_PATH_TYPE_DIR);
+		path_split(path, &dir, &fname);
+		if (list->v.get_mailbox_flags(list, dir, fname,
+					      MAILBOX_LIST_FILE_TYPE_UNKNOWN,
+					      &st, &ctx->info.flags) < 0)
+			_ctx->failed = TRUE;
+	}
+
+	ctx->info.flags &= ~(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+	ctx->info.flags |=
+		node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+	return &ctx->info;
+}
+
+int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+	struct subscriptions_mailbox_list_iterate_context *ctx =
+		(struct subscriptions_mailbox_list_iterate_context *)_ctx;
+	int ret = _ctx->failed ? -1 : 0;
+
+	mailbox_tree_iterate_deinit(&ctx->iter);
+	mailbox_tree_deinit(&ctx->tree);
+	if (_ctx->glob != NULL)
+		imap_match_deinit(&_ctx->glob);
+	array_free(&_ctx->module_contexts);
+	i_free(ctx);
 	return ret;
 }
--- a/src/lib-storage/list/mailbox-list-subscriptions.h	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/mailbox-list-subscriptions.h	Wed Feb 02 05:33:04 2011 +0200
@@ -1,12 +1,31 @@
 #ifndef MAILBOX_LIST_SUBSCRIPTIONS_H
 #define MAILBOX_LIST_SUBSCRIPTIONS_H
 
+enum mailbox_info_flags;
+enum mailbox_list_iter_flags;
+struct mailbox_tree_context;
 struct mailbox_list_iterate_context;
-struct mailbox_tree_context;
+
+int mailbox_list_subscriptions_refresh(struct mailbox_list *list);
+
+/* Set MAILBOX_SUBSCRIBED and MAILBOX_CHILD_SUBSCRIBED flags,
+   clearing them if they already are there when they shouldn't. */
+void mailbox_list_set_subscription_flags(struct mailbox_list *list,
+					 const char *vname,
+					 enum mailbox_info_flags *flags);
 
-int mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
-				    struct mailbox_tree_context *tree_ctx,
-				    struct imap_match_glob *glob,
-				    bool update_only);
+/* Add subscriptions matching the iteration to the given tree */
+void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
+				     struct mailbox_tree_context *tree);
+
+/* Iterate through subscriptions, call mailbox_list.get_mailbox_flags()
+   if necessary for mailboxes to get their flags. */
+struct mailbox_list_iterate_context *
+mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
+				     const char *const *patterns,
+				     enum mailbox_list_iter_flags flags);
+const struct mailbox_info *
+mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *ctx);
+int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *ctx);
 
 #endif
--- a/src/lib-storage/list/subscription-file.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/subscription-file.c	Wed Feb 02 05:33:04 2011 +0200
@@ -224,10 +224,13 @@
 	return ctx;
 }
 
-int subsfile_list_deinit(struct subsfile_list_context *ctx)
+int subsfile_list_deinit(struct subsfile_list_context **_ctx)
 {
+	struct subsfile_list_context *ctx = *_ctx;
 	int ret = ctx->failed ? -1 : 0;
 
+	*_ctx = NULL;
+
 	if (ctx->input != NULL)
 		i_stream_destroy(&ctx->input);
 	i_free(ctx->path);
@@ -235,6 +238,21 @@
 	return ret;
 }
 
+int subsfile_list_fstat(struct subsfile_list_context *ctx, struct stat *st_r)
+{
+	const struct stat *st;
+
+	if (ctx->failed)
+		return -1;
+
+	if ((st = i_stream_stat(ctx->input, FALSE)) == NULL) {
+		ctx->failed = TRUE;
+		return -1;
+	}
+	*st_r = *st;
+	return 0;
+}
+
 const char *subsfile_list_next(struct subsfile_list_context *ctx)
 {
         const char *line;
--- a/src/lib-storage/list/subscription-file.h	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/list/subscription-file.h	Wed Feb 02 05:33:04 2011 +0200
@@ -1,15 +1,19 @@
 #ifndef SUBSCRIPTION_FILE_H
 #define SUBSCRIPTION_FILE_H
 
+struct stat;
 struct mailbox_list;
 
 /* Initialize new subscription file listing. */
 struct subsfile_list_context *
 subsfile_list_init(struct mailbox_list *list, const char *path);
-
 /* Deinitialize subscription file listing. Returns 0 if ok, or -1 if some
    error occurred while listing. */
-int subsfile_list_deinit(struct subsfile_list_context *ctx);
+int subsfile_list_deinit(struct subsfile_list_context **ctx);
+
+/* Call fstat() for subscription file */
+int subsfile_list_fstat(struct subsfile_list_context *ctx, struct stat *st_r);
+
 /* Returns the next subscribed mailbox, or NULL. */
 const char *subsfile_list_next(struct subsfile_list_context *ctx);
 
--- a/src/lib-storage/mailbox-list-private.h	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/mailbox-list-private.h	Wed Feb 02 05:33:04 2011 +0200
@@ -72,6 +72,7 @@
 	   If it does, mailbox deletion assumes it can safely delete it. */
 	bool (*is_internal_name)(struct mailbox_list *list, const char *name);
 
+	int (*subscriptions_refresh)(struct mailbox_list *list);
 	int (*set_subscribed)(struct mailbox_list *list,
 			      const char *name, bool set);
 	int (*create_mailbox_dir)(struct mailbox_list *list, const char *name,
@@ -112,6 +113,9 @@
 	/* origin (e.g. path) where the file_create_gid was got from */
 	const char *file_create_gid_origin;
 
+	struct mailbox_tree_context *subscriptions;
+	time_t subscriptions_mtime, subscriptions_read_time;
+
 	struct mailbox_log *changelog;
 	time_t changelog_timestamp;
 
--- a/src/lib-storage/mailbox-list.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/mailbox-list.c	Wed Feb 02 05:33:04 2011 +0200
@@ -151,6 +151,7 @@
 	list->dir_create_mode = (mode_t)-1;
 	list->file_create_gid = (gid_t)-1;
 	list->changelog_timestamp = (time_t)-1;
+	list->subscriptions = mailbox_tree_init(mail_namespace_get_sep(ns));
 
 	/* copy settings */
 	if (set->root_dir != NULL) {
@@ -461,6 +462,7 @@
 	*_list = NULL;
 	i_free_and_null(list->error_string);
 
+	mailbox_tree_deinit(&list->subscriptions);
 	if (list->changelog != NULL)
 		mailbox_log_free(&list->changelog);
 	list->v.deinit(list);
@@ -863,9 +865,19 @@
 				const char *const *patterns,
 				enum mailbox_list_iter_flags flags)
 {
+	struct mailbox_list_iterate_context *ctx;
+	int ret = 0;
+
 	i_assert(*patterns != NULL);
 
-	return list->v.iter_init(list, patterns, flags);
+	if ((flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+		      MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0)
+		ret = list->v.subscriptions_refresh(list);
+
+	ctx = list->v.iter_init(list, patterns, flags);
+	if (ret < 0)
+		ctx->failed = TRUE;
+	return ctx;
 }
 
 static bool
@@ -1232,6 +1244,9 @@
 	uint8_t guid[MAIL_GUID_128_SIZE];
 	int ret;
 
+	/* make sure we'll refresh the file on next list */
+	list->subscriptions_mtime = (time_t)-1;
+
 	if ((ret = list->v.set_subscribed(list, name, set)) <= 0)
 		return ret;
 
--- a/src/lib-storage/mailbox-tree.c	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/mailbox-tree.c	Wed Feb 02 05:33:04 2011 +0200
@@ -27,12 +27,9 @@
 struct mailbox_tree_context *mailbox_tree_init(char separator)
 {
 	struct mailbox_tree_context *tree;
-	pool_t pool;
 
-	pool = pool_alloconly_create(MEMPOOL_GROWING"mailbox_tree", 10240);
-
-	tree = p_new(pool, struct mailbox_tree_context, 1);
-	tree->pool = pool;
+	tree = i_new(struct mailbox_tree_context, 1);
+	tree->pool = pool_alloconly_create(MEMPOOL_GROWING"mailbox_tree", 10240);
 	tree->separator = separator;
 	return tree;
 }
@@ -43,6 +40,13 @@
 
 	*_tree = NULL;
 	pool_unref(&tree->pool);
+	i_free(tree);
+}
+
+void mailbox_tree_clear(struct mailbox_tree_context *tree)
+{
+	p_clear(tree->pool);
+	tree->nodes = NULL;
 }
 
 static struct mailbox_node *
--- a/src/lib-storage/mailbox-tree.h	Wed Feb 02 05:31:46 2011 +0200
+++ b/src/lib-storage/mailbox-tree.h	Wed Feb 02 05:33:04 2011 +0200
@@ -15,6 +15,8 @@
 struct mailbox_tree_context *mailbox_tree_init(char separator);
 void mailbox_tree_deinit(struct mailbox_tree_context **tree);
 
+void mailbox_tree_clear(struct mailbox_tree_context *tree);
+
 struct mailbox_node *
 mailbox_tree_get(struct mailbox_tree_context *tree, const char *path,
 		 bool *created);