changeset 14898:a16d77a075bb

lib-storage: Added mailbox-list-notify API for tracking changes in all mailboxes. Requires mailbox_list_index=yes to work.
author Timo Sirainen <tss@iki.fi>
date Mon, 13 Aug 2012 15:20:33 +0300
parents 026b688b379f
children f9d0ea98157f
files src/lib-storage/Makefile.am src/lib-storage/index/imapc/imapc-list.c src/lib-storage/index/shared/shared-list.c src/lib-storage/list/Makefile.am src/lib-storage/list/mailbox-list-fs.c src/lib-storage/list/mailbox-list-index-notify.c src/lib-storage/list/mailbox-list-index-status.c src/lib-storage/list/mailbox-list-index-sync.c src/lib-storage/list/mailbox-list-index.c src/lib-storage/list/mailbox-list-index.h src/lib-storage/list/mailbox-list-maildir.c src/lib-storage/list/mailbox-list-none.c src/lib-storage/list/mailbox-list-notify-tree.c src/lib-storage/list/mailbox-list-notify-tree.h src/lib-storage/mailbox-list-notify.c src/lib-storage/mailbox-list-notify.h src/lib-storage/mailbox-list-private.h
diffstat 17 files changed, 1111 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-storage/Makefile.am	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/Makefile.am	Mon Aug 13 15:20:33 2012 +0300
@@ -43,6 +43,7 @@
 	mailbox-keywords.c \
 	mailbox-list.c \
 	mailbox-list-iter.c \
+	mailbox-list-notify.c \
 	mailbox-search-result.c \
 	mailbox-tree.c \
 	mailbox-uidvalidity.c
@@ -70,6 +71,7 @@
 	mailbox-guid-cache.h \
 	mailbox-list.h \
 	mailbox-list-private.h \
+	mailbox-list-notify.h \
 	mailbox-search-result-private.h \
 	mailbox-tree.h \
 	mailbox-uidvalidity.h
--- a/src/lib-storage/index/imapc/imapc-list.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/index/imapc/imapc-list.c	Mon Aug 13 15:20:33 2012 +0300
@@ -720,6 +720,7 @@
 		imapc_list_delete_mailbox,
 		imapc_list_delete_dir,
 		imapc_list_delete_symlink,
-		imapc_list_rename_mailbox
+		imapc_list_rename_mailbox,
+		NULL, NULL, NULL, NULL
 	}
 };
--- a/src/lib-storage/index/shared/shared-list.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/index/shared/shared-list.c	Mon Aug 13 15:20:33 2012 +0300
@@ -359,6 +359,7 @@
 		shared_list_delete_mailbox,
 		shared_list_delete_dir,
 		shared_list_delete_symlink,
-		shared_list_rename_mailbox
+		shared_list_rename_mailbox,
+		NULL, NULL, NULL, NULL
 	}
 };
--- a/src/lib-storage/list/Makefile.am	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/Makefile.am	Mon Aug 13 15:20:33 2012 +0300
@@ -15,11 +15,13 @@
 	mailbox-list-fs-iter.c \
 	mailbox-list-index.c \
 	mailbox-list-index-iter.c \
+	mailbox-list-index-notify.c \
 	mailbox-list-index-status.c \
 	mailbox-list-index-sync.c \
 	mailbox-list-maildir.c \
 	mailbox-list-maildir-iter.c \
 	mailbox-list-none.c \
+	mailbox-list-notify-tree.c \
 	mailbox-list-subscriptions.c \
 	subscription-file.c
 
@@ -28,6 +30,7 @@
 	mailbox-list-fs.h \
 	mailbox-list-index.h \
 	mailbox-list-maildir.h \
+	mailbox-list-notify-tree.h \
 	mailbox-list-subscriptions.h \
 	subscription-file.h
 
--- a/src/lib-storage/list/mailbox-list-fs.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-fs.c	Mon Aug 13 15:20:33 2012 +0300
@@ -651,6 +651,7 @@
 		fs_list_delete_mailbox,
 		fs_list_delete_dir,
 		mailbox_list_delete_symlink_default,
-		fs_list_rename_mailbox
+		fs_list_rename_mailbox,
+		NULL, NULL, NULL, NULL
 	}
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/list/mailbox-list-index-notify.c	Mon Aug 13 15:20:33 2012 +0300
@@ -0,0 +1,699 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-index-private.h"
+#include "mail-transaction-log-private.h"
+#include "mail-storage.h"
+#include "mailbox-list-notify.h"
+#include "mailbox-list-notify-tree.h"
+#include "mailbox-list-index.h"
+
+#include <sys/stat.h>
+
+#define NOTIFY_DELAY_MSECS 500
+
+enum ilist_ext_type {
+	ILIST_EXT_NONE,
+	ILIST_EXT_BASE,
+	ILIST_EXT_MSGS,
+	ILIST_EXT_HIGHESTMODSEQ,
+	ILIST_EXT_UNKNOWN
+};
+
+struct mailbox_list_notify_rename {
+	uint32_t old_uid, new_uid;
+};
+
+struct mailbox_list_inotify_entry {
+	uint32_t uid;
+	guid_128_t guid;
+	bool expunge;
+};
+
+struct mailbox_list_notify_index {
+	struct mailbox_list_notify notify;
+
+	struct mailbox_list_notify_tree *tree;
+	struct mail_index_view *view, *old_view;
+	struct mail_index_view_sync_ctx *sync_ctx;
+	enum ilist_ext_type cur_ext;
+	uint32_t cur_ext_id;
+
+	void (*wait_callback)(void *context);
+	void *wait_context;
+	struct io *io_wait;
+	struct timeout *to_wait, *to_notify;
+
+	ARRAY_TYPE(seq_range) new_uids, expunged_uids, changed_uids;
+	ARRAY_DEFINE(renames, struct mailbox_list_notify_rename);
+	struct seq_range_iter new_uids_iter, expunged_uids_iter;
+	struct seq_range_iter changed_uids_iter;
+	unsigned int new_uids_n, expunged_uids_n, changed_uids_n;
+	unsigned int rename_idx;
+
+	struct mailbox_list_notify_rec notify_rec;
+	string_t *rec_name;
+
+	struct stat last_st;
+
+	unsigned int initialized:1;
+	unsigned int read_failed:1;
+};
+
+int mailbox_list_index_notify_init(struct mailbox_list *list,
+				   enum mailbox_list_notify_event mask,
+				   struct mailbox_list_notify **notify_r)
+{
+	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
+	struct mailbox_list_notify_index *inotify;
+
+	if (ilist == NULL) {
+		/* can't do this without mailbox list indexes */
+		return -1;
+	}
+
+	(void)mailbox_list_index_refresh(list);
+
+	inotify = i_new(struct mailbox_list_notify_index, 1);
+	inotify->notify.list = list;
+	inotify->notify.mask = mask;
+	inotify->view = mail_index_view_open(ilist->index);
+	inotify->old_view = mail_index_view_dup_private(inotify->view);
+	inotify->tree = mailbox_list_notify_tree_init(list);
+	i_array_init(&inotify->new_uids, 8);
+	i_array_init(&inotify->expunged_uids, 8);
+	i_array_init(&inotify->changed_uids, 16);
+	i_array_init(&inotify->renames, 16);
+	inotify->rec_name = str_new(default_pool, 64);
+
+	*notify_r = &inotify->notify;
+	return 1;
+}
+
+void mailbox_list_index_notify_deinit(struct mailbox_list_notify *notify)
+{
+	struct mailbox_list_notify_index *inotify =
+		(struct mailbox_list_notify_index *)notify;
+	bool b;
+
+	if (inotify->io_wait != NULL)
+		io_remove(&inotify->io_wait);
+	if (inotify->to_wait != NULL)
+		timeout_remove(&inotify->to_wait);
+	if (inotify->to_notify != NULL)
+		timeout_remove(&inotify->to_notify);
+	if (inotify->sync_ctx != NULL)
+		(void)mail_index_view_sync_commit(&inotify->sync_ctx, &b);
+	mail_index_view_close(&inotify->view);
+	mail_index_view_close(&inotify->old_view);
+	mailbox_list_notify_tree_deinit(&inotify->tree);
+	array_free(&inotify->new_uids);
+	array_free(&inotify->expunged_uids);
+	array_free(&inotify->changed_uids);
+	array_free(&inotify->renames);
+	str_free(&inotify->rec_name);
+	i_free(inotify);
+}
+
+static struct mailbox_list_index_node *
+notify_lookup_guid(struct mailbox_list_notify_index *inotify,
+		   struct mail_index_view *view,
+		   uint32_t uid, enum mailbox_status_items items,
+		   struct mailbox_status *status_r, guid_128_t guid_r)
+{
+	struct mailbox_list_index *ilist =
+		INDEX_LIST_CONTEXT(inotify->notify.list);
+	struct mailbox_list_index_node *index_node;
+	uint32_t seq;
+
+	if (!mail_index_lookup_seq(view, uid, &seq))
+		return NULL;
+
+	index_node = mailbox_list_index_lookup_uid(ilist, uid);
+	if (index_node == NULL) {
+		/* re-parse the index list using the given view. we could be
+		   jumping here between old and new view. */
+		(void)mailbox_list_index_parse(ilist, view, FALSE);
+		index_node = mailbox_list_index_lookup_uid(ilist, uid);
+		if (index_node == NULL)
+			return NULL;
+	}
+
+	/* get GUID */
+	memset(status_r, 0, sizeof(*status_r));
+	memset(guid_r, 0, GUID_128_SIZE);
+	(void)mailbox_list_index_status(inotify->notify.list, view, seq,
+					items, status_r, guid_r);
+	return index_node;
+}
+
+static void notify_update_stat(struct mailbox_list_notify_index *inotify)
+{
+	struct mailbox_list_index *ilist =
+		INDEX_LIST_CONTEXT(inotify->notify.list);
+	const char *path = ilist->index->log->filepath;
+
+	if (stat(path, &inotify->last_st) < 0 && errno != ENOENT) {
+		i_error("stat(%s) failed: %m", path);
+		mailbox_list_index_notify_wait(&inotify->notify, NULL, NULL);
+	}
+}
+
+static void
+mailbox_list_index_notify_sync_init(struct mailbox_list_notify_index *inotify)
+{
+	struct mail_index_view_sync_rec sync_rec;
+
+	notify_update_stat(inotify);
+	(void)mail_index_refresh(inotify->view->index);
+
+	/* sync the view so that map extensions gets updated */
+	inotify->sync_ctx = mail_index_view_sync_begin(inotify->view, 0);
+	mail_transaction_log_view_mark(inotify->view->log_view);
+	while (mail_index_view_sync_next(inotify->sync_ctx, &sync_rec)) ;
+	mail_transaction_log_view_rewind(inotify->view->log_view);
+
+	inotify->cur_ext = ILIST_EXT_NONE;
+	inotify->cur_ext_id = (uint32_t)-1;
+}
+
+static bool notify_ext_rec(struct mailbox_list_notify_index *inotify,
+			   uint32_t uid)
+{
+	struct mailbox_list_notify *notify = &inotify->notify;
+
+	switch (inotify->cur_ext) {
+	case ILIST_EXT_NONE:
+		i_unreached();
+	case ILIST_EXT_BASE:
+		/* UIDVALIDITY changed */
+		if ((notify->mask & MAILBOX_LIST_NOTIFY_UIDVALIDITY) == 0)
+			return FALSE;
+		break;
+	case ILIST_EXT_MSGS:
+		/* APPEND, EXPUNGE, \Seen or \Recent flag change */
+		if ((notify->mask & MAILBOX_LIST_NOTIFY_STATUS) == 0)
+			return FALSE;
+		break;
+	case ILIST_EXT_HIGHESTMODSEQ:
+		/* when this doesn't come with EXT_MSGS update,
+		   it can only be a flag change or an explicit
+		   modseq change */
+		if ((notify->mask & MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES) == 0)
+			return FALSE;
+		break;
+	case ILIST_EXT_UNKNOWN:
+		return FALSE;
+	}
+	seq_range_array_add(&inotify->changed_uids, uid);
+	return TRUE;
+}
+
+static int
+mailbox_list_index_notify_read_next(struct mailbox_list_notify_index *inotify)
+{
+	struct mailbox_list_notify *notify = &inotify->notify;
+	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(notify->list);
+	const struct mail_transaction_header *hdr;
+	const void *data;
+	int ret;
+
+	ret = mail_transaction_log_view_next(inotify->view->log_view,
+					     &hdr, &data);
+	if (ret <= 0)
+		return ret;
+
+	if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
+		/* all mailbox index updates are external */
+		return 1;
+	}
+	switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
+	case MAIL_TRANSACTION_APPEND: {
+		/* mailbox added or renamed */
+		const struct mail_index_record *rec, *end;
+
+		if ((notify->mask & (MAILBOX_LIST_NOTIFY_CREATE |
+				     MAILBOX_LIST_NOTIFY_RENAME)) == 0)
+			break;
+
+		end = CONST_PTR_OFFSET(data, hdr->size);
+		for (rec = data; rec != end; rec++)
+			seq_range_array_add(&inotify->new_uids, rec->uid);
+		break;
+	}
+	case MAIL_TRANSACTION_EXPUNGE_GUID: {
+		/* mailbox deleted or renamed */
+		const struct mail_transaction_expunge_guid *rec, *end;
+
+		if ((notify->mask & (MAILBOX_LIST_NOTIFY_DELETE |
+				     MAILBOX_LIST_NOTIFY_RENAME)) == 0)
+			break;
+
+		end = CONST_PTR_OFFSET(data, hdr->size);
+		for (rec = data; rec != end; rec++)
+			seq_range_array_add(&inotify->expunged_uids, rec->uid);
+		break;
+	}
+	case MAIL_TRANSACTION_EXT_INTRO: {
+		struct mail_index_map *map = inotify->view->map;
+		const struct mail_transaction_ext_intro *rec = data;
+		const struct mail_index_ext *ext = NULL;
+		const char *name;
+		uint32_t ext_map_idx;
+
+		if (!array_is_created(&map->extensions))
+			break;
+		/* we want to know what extension the future
+		   ext-rec-updates are changing. we're assuming here that
+		   there is only one ext-intro record before those,
+		   which is true at least for now. */
+		if (rec->ext_id != (uint32_t)-1 &&
+		    rec->ext_id < array_count(&map->extensions)) {
+			/* get extension by id */
+			ext = array_idx(&map->extensions, rec->ext_id);
+		} else if (rec->name_size > 0) {
+			/* by name */
+			name = t_strndup(rec+1, rec->name_size);
+			if (mail_index_map_lookup_ext(map, name, &ext_map_idx))
+				ext = array_idx(&map->extensions, ext_map_idx);
+		}
+		if (ext != NULL) {
+			if (ext->index_idx == ilist->ext_id)
+				inotify->cur_ext = ILIST_EXT_BASE;
+			else if (ext->index_idx == ilist->msgs_ext_id)
+				inotify->cur_ext = ILIST_EXT_MSGS;
+			else if (ext->index_idx == ilist->hmodseq_ext_id)
+				inotify->cur_ext = ILIST_EXT_HIGHESTMODSEQ;
+			else
+				inotify->cur_ext = ILIST_EXT_UNKNOWN;
+			inotify->cur_ext_id = ext->index_idx;
+		}
+		break;
+	}
+	case MAIL_TRANSACTION_EXT_REC_UPDATE: {
+		const struct mail_index_registered_ext *ext;
+		const struct mail_transaction_ext_rec_update *rec;
+		unsigned int i, record_size;
+
+		if (inotify->cur_ext == ILIST_EXT_NONE) {
+			i_error("%s: Missing ext-intro for ext-rec-update",
+				ilist->index->filepath);
+			break;
+		}
+
+		/* the record is padded to 32bits in the transaction log */
+		ext = array_idx(&inotify->view->index->extensions,
+				inotify->cur_ext_id);
+		record_size = (sizeof(*rec) + ext->record_size + 3) & ~3;
+		for (i = 0; i < hdr->size; i += record_size) {
+			rec = CONST_PTR_OFFSET(data, i);
+
+			if (i + record_size > hdr->size)
+				break;
+			if (!notify_ext_rec(inotify, rec->uid))
+				break;
+		}
+		break;
+	}
+	}
+	return 1;
+}
+
+static int
+mailbox_list_inotify_entry_guid_cmp(const struct mailbox_list_inotify_entry *r1,
+				    const struct mailbox_list_inotify_entry *r2)
+{
+	int ret;
+
+	ret = memcmp(r1->guid, r2->guid, sizeof(r1->guid));
+	if (ret != 0)
+		return ret;
+
+	if (r1->expunge == r2->expunge) {
+		/* this really shouldn't happen */
+		return 0;
+	}
+	return r1->expunge ? -1 : 1;
+}
+
+static void
+mailbox_list_index_notify_find_renames(struct mailbox_list_notify_index *inotify)
+{
+	struct mailbox_list_index *ilist =
+		INDEX_LIST_CONTEXT(inotify->notify.list);
+	ARRAY_DEFINE(entries, struct mailbox_list_inotify_entry);
+	struct mailbox_status status;
+	struct mailbox_list_notify_rename *rename;
+	struct mailbox_list_inotify_entry *entry;
+	const struct mailbox_list_inotify_entry *e;
+	unsigned int i, count;
+	guid_128_t guid;
+	uint32_t uid;
+
+	/* first get all of the added and expunged GUIDs */
+	t_array_init(&entries, array_count(&inotify->new_uids) +
+		     array_count(&inotify->expunged_uids));
+	while (seq_range_array_iter_nth(&inotify->expunged_uids_iter,
+					inotify->expunged_uids_n++, &uid)) {
+		if (notify_lookup_guid(inotify, inotify->old_view, uid,
+				       0, &status, guid) != NULL &&
+		    !guid_128_is_empty(guid)) {
+			entry = array_append_space(&entries);
+			entry->uid = uid;
+			entry->expunge = TRUE;
+			memcpy(entry->guid, guid, sizeof(entry->guid));
+		}
+	}
+
+	(void)mailbox_list_index_parse(ilist, inotify->view, TRUE);
+	while (seq_range_array_iter_nth(&inotify->new_uids_iter,
+					inotify->new_uids_n++, &uid)) {
+		if (notify_lookup_guid(inotify, inotify->view, uid,
+				       0, &status, guid) != NULL &&
+		    !guid_128_is_empty(guid)) {
+			entry = array_append_space(&entries);
+			entry->uid = uid;
+			memcpy(entry->guid, guid, sizeof(entry->guid));
+		}
+	}
+
+	/* now sort the entries by GUID and find those that have been both
+	   added and expunged */
+	array_sort(&entries, mailbox_list_inotify_entry_guid_cmp);
+
+	e = array_get(&entries, &count);
+	for (i = 1; i < count; i++) {
+		if (e[i-1].expunge && !e[i].expunge &&
+		    memcmp(e[i-1].guid, e[i].guid, sizeof(e[i].guid)) == 0) {
+			rename = array_append_space(&inotify->renames);
+			rename->old_uid = e[i-1].uid;
+			rename->new_uid = e[i].uid;
+
+			seq_range_array_remove(&inotify->expunged_uids,
+					       rename->old_uid);
+			seq_range_array_remove(&inotify->new_uids,
+					       rename->new_uid);
+		}
+	}
+}
+
+static void
+mailbox_list_index_notify_reset_iters(struct mailbox_list_notify_index *inotify)
+{
+	seq_range_array_iter_init(&inotify->new_uids_iter,
+				  &inotify->new_uids);
+	seq_range_array_iter_init(&inotify->expunged_uids_iter,
+				  &inotify->expunged_uids);
+	seq_range_array_iter_init(&inotify->changed_uids_iter,
+				  &inotify->changed_uids);
+	inotify->changed_uids_n = 0;
+	inotify->new_uids_n = 0;
+	inotify->expunged_uids_n = 0;
+	inotify->rename_idx = 0;
+}
+
+static void
+mailbox_list_index_notify_read_init(struct mailbox_list_notify_index *inotify)
+{
+	bool b;
+	int ret;
+
+	mailbox_list_index_notify_sync_init(inotify);
+
+	/* read all changes from .log file */
+	while ((ret = mailbox_list_index_notify_read_next(inotify)) > 0) ;
+	inotify->read_failed = ret < 0;
+
+	(void)mail_index_view_sync_commit(&inotify->sync_ctx, &b);
+
+	/* remove changes for already deleted mailboxes */
+	seq_range_array_remove_seq_range(&inotify->new_uids,
+					 &inotify->expunged_uids);
+	seq_range_array_remove_seq_range(&inotify->changed_uids,
+					 &inotify->expunged_uids);
+	mailbox_list_index_notify_reset_iters(inotify);
+	if (array_count(&inotify->new_uids) > 0 &&
+	    array_count(&inotify->expunged_uids) > 0) {
+		mailbox_list_index_notify_find_renames(inotify);
+		mailbox_list_index_notify_reset_iters(inotify);
+	}
+
+	inotify->initialized = TRUE;
+}
+
+static void
+mailbox_list_index_notify_read_deinit(struct mailbox_list_notify_index *inotify)
+{
+	/* save the old view so we can look up expunged records */
+	mail_index_view_close(&inotify->old_view);
+	inotify->old_view = mail_index_view_dup_private(inotify->view);
+
+	array_clear(&inotify->new_uids);
+	array_clear(&inotify->expunged_uids);
+	array_clear(&inotify->changed_uids);
+	array_clear(&inotify->renames);
+
+	inotify->initialized = FALSE;
+}
+
+static bool
+mailbox_list_index_notify_lookup(struct mailbox_list_notify_index *inotify,
+				 struct mail_index_view *view,
+				 uint32_t uid, enum mailbox_status_items items,
+				 struct mailbox_status *status_r,
+				 struct mailbox_list_notify_rec **rec_r)
+{
+	struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+	struct mailbox_list_index_node *index_node;
+	const char *storage_name;
+	char ns_sep = mailbox_list_get_hierarchy_sep(inotify->notify.list);
+
+	memset(rec, 0, sizeof(*rec));
+	index_node = notify_lookup_guid(inotify, view, uid,
+					items, status_r, rec->guid);
+	if (index_node == NULL)
+		return FALSE;
+
+	/* get storage_name */
+	str_truncate(inotify->rec_name, 0);
+	mailbox_list_index_node_get_path(index_node, ns_sep, inotify->rec_name);
+	storage_name = str_c(inotify->rec_name);
+
+	rec->storage_name = storage_name;
+	rec->vname = mailbox_list_get_vname(inotify->notify.list,
+					    rec->storage_name);
+	*rec_r = rec;
+	return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_rename(struct mailbox_list_notify_index *inotify,
+				 unsigned int idx)
+{
+	const struct mailbox_list_notify_rename *rename;
+	struct mailbox_list_notify_rec *rec;
+	struct mailbox_status status;
+	const char *old_vname;
+
+	rename = array_idx(&inotify->renames, idx);
+
+	/* lookup the old name */
+	if (!mailbox_list_index_notify_lookup(inotify, inotify->old_view,
+					      rename->old_uid, 0, &status, &rec))
+		return FALSE;
+	old_vname = t_strdup(rec->vname);
+
+	/* return using the new name */
+	if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+					      rename->new_uid, 0, &status, &rec))
+		return FALSE;
+
+	rec->old_vname = old_vname;
+	rec->event = MAILBOX_LIST_NOTIFY_RENAME;
+	return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_expunge(struct mailbox_list_notify_index *inotify,
+				  uint32_t uid)
+{
+	struct mailbox_list_notify_rec *rec;
+	struct mailbox_status status;
+
+	if (!mailbox_list_index_notify_lookup(inotify, inotify->old_view,
+					      uid, 0, &status, &rec))
+		return FALSE;
+	rec->event = MAILBOX_LIST_NOTIFY_DELETE;
+	return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_new(struct mailbox_list_notify_index *inotify,
+			      uint32_t uid)
+{
+	struct mailbox_list_notify_rec *rec;
+	struct mailbox_status status;
+
+	if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+					      uid, 0, &status, &rec))
+		i_unreached();
+	rec->event = MAILBOX_LIST_NOTIFY_CREATE;
+	return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_change(struct mailbox_list_notify_index *inotify,
+				 uint32_t uid)
+{
+	const enum mailbox_status_items status_items =
+		STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES |
+		STATUS_UNSEEN | STATUS_HIGHESTMODSEQ;
+	struct mailbox_list_notify_rec *rec;
+	struct mailbox_notify_node *nnode;
+	struct mailbox_status status;
+
+	if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+					      uid, status_items, &status, &rec))
+		i_unreached();
+
+	/* get the old status */
+	nnode = mailbox_list_notify_tree_lookup(inotify->tree,
+						rec->storage_name);
+	if (nnode == NULL || nnode->uidvalidity != status.uidvalidity)
+		rec->event = MAILBOX_LIST_NOTIFY_UIDVALIDITY;
+	else if (nnode->uidnext != status.uidnext)
+		rec->event = MAILBOX_LIST_NOTIFY_APPENDS;
+	else if (nnode->messages > status.messages)
+		rec->event = MAILBOX_LIST_NOTIFY_EXPUNGES;
+	else if (nnode->unseen != status.unseen)
+		rec->event = MAILBOX_LIST_NOTIFY_SEEN_CHANGES;
+	else if (nnode->highest_modseq < status.highest_modseq)
+		rec->event = MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES;
+	else {
+		/* nothing changed */
+		return FALSE;
+	}
+
+	/* update internal state */
+	if (nnode != NULL) {
+		nnode->uidvalidity = status.uidvalidity;
+		nnode->uidnext = status.uidnext;
+		nnode->messages = status.messages;
+		nnode->unseen = status.unseen;
+		nnode->highest_modseq = status.highest_modseq;
+	}
+	return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_try_next(struct mailbox_list_notify_index *inotify)
+{
+	uint32_t uid;
+
+	/* first show mailbox deletes */
+	if (seq_range_array_iter_nth(&inotify->expunged_uids_iter,
+				     inotify->expunged_uids_n++, &uid))
+		return mailbox_list_index_notify_expunge(inotify, uid);
+
+	/* mailbox renames */
+	if (inotify->rename_idx < array_count(&inotify->renames)) {
+		return mailbox_list_index_notify_rename(inotify,
+							inotify->rename_idx++);
+	}
+
+	/* next mailbox creates */
+	if (seq_range_array_iter_nth(&inotify->new_uids_iter,
+				     inotify->new_uids_n++, &uid))
+		return mailbox_list_index_notify_new(inotify, uid);
+
+	/* STATUS updates */
+	while (seq_range_array_iter_nth(&inotify->changed_uids_iter,
+					inotify->changed_uids_n++, &uid)) {
+		if (mailbox_list_index_notify_change(inotify, uid))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+int mailbox_list_index_notify_next(struct mailbox_list_notify *notify,
+				   const struct mailbox_list_notify_rec **rec_r)
+{
+	struct mailbox_list_notify_index *inotify =
+		(struct mailbox_list_notify_index *)notify;
+
+	if (!inotify->initialized)
+		mailbox_list_index_notify_read_init(inotify);
+	while (mailbox_list_index_notify_try_next(inotify)) {
+		if ((inotify->notify_rec.event & inotify->notify.mask) != 0) {
+			*rec_r = &inotify->notify_rec;
+			return 1;
+		} else {
+			/* caller doesn't care about this change */
+		}
+	}
+
+	mailbox_list_index_notify_read_deinit(inotify);
+	return inotify->read_failed ? -1 : 0;
+}
+
+static void notify_now_callback(struct mailbox_list_notify_index *inotify)
+{
+	timeout_remove(&inotify->to_notify);
+	inotify->wait_callback(inotify->wait_context);
+}
+
+static void notify_callback(struct mailbox_list_notify_index *inotify)
+{
+	struct stat prev_st = inotify->last_st;
+
+	notify_update_stat(inotify);
+	if (inotify->last_st.st_mtime != prev_st.st_mtime ||
+	    ST_MTIME_NSEC(inotify->last_st) != ST_MTIME_NSEC(prev_st) ||
+	    inotify->last_st.st_size != prev_st.st_size ||
+	    inotify->last_st.st_ino != prev_st.st_ino) {
+		/* log has changed. call the callback with a small delay
+		   to allow bundling multiple changes together */
+		if (inotify->to_notify != NULL) {
+			/* already doing this */
+			return;
+		}
+		inotify->to_notify =
+			timeout_add_short(NOTIFY_DELAY_MSECS,
+					  notify_now_callback, inotify);
+	}
+}
+
+void mailbox_list_index_notify_wait(struct mailbox_list_notify *notify,
+				    void (*callback)(void *context),
+				    void *context)
+{
+	struct mailbox_list_notify_index *inotify =
+		(struct mailbox_list_notify_index *)notify;
+	const char *path;
+	unsigned int check_interval;
+
+	inotify->wait_callback = callback;
+	inotify->wait_context = context;
+
+	if (callback == NULL) {
+		if (inotify->io_wait != NULL)
+			io_remove(&inotify->io_wait);
+		if (inotify->to_wait != NULL)
+			timeout_remove(&inotify->to_wait);
+		if (inotify->to_notify != NULL)
+			timeout_remove(&inotify->to_notify);
+	} else if (inotify->to_wait == NULL) {
+		path = inotify->view->index->log->filepath;
+		(void)io_add_notify(path, notify_callback, inotify,
+				    &inotify->io_wait);
+		/* check with timeout as well, in case io_add_notify()
+		   doesn't work (e.g. NFS) */
+		check_interval = notify->list->mail_set->mailbox_idle_check_interval;
+		i_assert(check_interval > 0);
+		inotify->to_wait = timeout_add(check_interval * 1000,
+					       notify_callback, inotify);
+		notify_update_stat(inotify);
+	}
+}
--- a/src/lib-storage/list/mailbox-list-index-status.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-index-status.c	Mon Aug 13 15:20:33 2012 +0300
@@ -60,13 +60,13 @@
 	return 1;
 }
 
-static bool ATTR_NULL(6)
-index_list_get_view_status(struct mailbox *box, struct mail_index_view *view,
-			   uint32_t seq, enum mailbox_status_items items,
-			   struct mailbox_status *status_r,
-			   uint8_t *mailbox_guid)
+bool mailbox_list_index_status(struct mailbox_list *list,
+			       struct mail_index_view *view,
+			       uint32_t seq, enum mailbox_status_items items,
+			       struct mailbox_status *status_r,
+			       uint8_t *mailbox_guid)
 {
-	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
+	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
 	const void *data;
 	bool expunged;
 	bool ret = TRUE;
@@ -77,10 +77,14 @@
 		mail_index_lookup_ext(view, seq, ilist->ext_id,
 				      &data, &expunged);
 		rec = data;
-		if (rec == NULL || rec->uid_validity == 0)
+		if (rec == NULL)
 			ret = FALSE;
 		else {
-			status_r->uidvalidity = rec->uid_validity;
+			if ((items & STATUS_UIDVALIDITY) != 0 &&
+			    rec->uid_validity == 0)
+				ret = FALSE;
+			else
+				status_r->uidvalidity = rec->uid_validity;
 			if (mailbox_guid != NULL)
 				memcpy(mailbox_guid, rec->guid, GUID_128_SIZE);
 		}
@@ -131,8 +135,8 @@
 	if (ret <= 0)
 		return ret;
 
-	ret = index_list_get_view_status(box, view, seq, items,
-					 status_r, NULL) ? 1 : 0;
+	ret = mailbox_list_index_status(box->list, view, seq, items,
+					status_r, NULL) ? 1 : 0;
 	mail_index_view_close(&view);
 	return ret;
 }
@@ -154,17 +158,23 @@
 static int
 index_list_get_cached_guid(struct mailbox *box, guid_128_t guid_r)
 {
+	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
 	struct mailbox_status status;
 	struct mail_index_view *view;
 	uint32_t seq;
 	int ret;
 
+	if (ilist->syncing) {
+		/* syncing wants to know the GUID for a new mailbox. */
+		return 0;
+	}
+
 	ret = index_list_open_view(box, &view, &seq);
 	if (ret <= 0)
 		return ret;
 
-	ret = index_list_get_view_status(box, view, seq, 0,
-					 &status, guid_r) ? 1 : 0;
+	ret = mailbox_list_index_status(box->list, view, seq, 0,
+					&status, guid_r) ? 1 : 0;
 	if (ret > 0 && guid_128_is_empty(guid_r))
 		ret = 0;
 	mail_index_view_close(&view);
@@ -204,11 +214,14 @@
 		memset(&metadata, 0, sizeof(metadata));
 
 	memset(&old_status, 0, sizeof(old_status));
-	(void)index_list_get_view_status(box, view, seq, CACHED_STATUS_ITEMS,
-					 &old_status, mailbox_guid);
+	memset(mailbox_guid, 0, sizeof(mailbox_guid));
+	(void)mailbox_list_index_status(box->list, view, seq, CACHED_STATUS_ITEMS,
+					&old_status, mailbox_guid);
 
-	rec_changed = old_status.uidvalidity != status->uidvalidity ||
-		memcmp(metadata.guid, mailbox_guid, sizeof(metadata.guid)) == 0;
+	rec_changed = old_status.uidvalidity != status->uidvalidity;
+	if (memcmp(metadata.guid, mailbox_guid, sizeof(metadata.guid)) != 0 &&
+	    guid_128_is_empty(metadata.guid))
+		rec_changed = TRUE;
 	msgs_changed = old_status.messages != status->messages ||
 		old_status.unseen != status->unseen ||
 		old_status.recent != status->recent ||
@@ -249,7 +262,8 @@
 		memcpy(&rec, old_data, sizeof(rec));
 
 		rec.uid_validity = status->uidvalidity;
-		memcpy(rec.guid, mailbox_guid, sizeof(rec.guid));
+		if (!guid_128_is_empty(metadata.guid))
+			memcpy(rec.guid, metadata.guid, sizeof(rec.guid));
 		mail_index_update_ext(trans, seq, ilist->ext_id, &rec, NULL);
 	}
 
@@ -386,8 +400,8 @@
 	}
 
 	status.recent = 0;
-	(void)index_list_get_view_status(box, view, seq, STATUS_RECENT,
-					 &status, NULL);
+	(void)mailbox_list_index_status(box->list, view, seq, STATUS_RECENT,
+					&status, NULL);
 	mail_index_view_close(&view);
 
 	if (status.recent != 0)
--- a/src/lib-storage/list/mailbox-list-index-sync.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-index-sync.c	Mon Aug 13 15:20:33 2012 +0300
@@ -3,10 +3,13 @@
 #include "lib.h"
 #include "ioloop.h"
 #include "hash.h"
+#include "str.h"
 #include "mail-index.h"
+#include "mail-storage.h"
 #include "mailbox-list-index.h"
 
 struct mailbox_list_index_sync_context {
+	struct mailbox_list *list;
 	struct mailbox_list_index *ilist;
 	char sep[2];
 	uint32_t next_uid;
@@ -17,6 +20,25 @@
 };
 
 static void
+node_lookup_guid(struct mailbox_list_index_sync_context *ctx,
+		 const struct mailbox_list_index_node *node, guid_128_t guid_r)
+{
+	struct mailbox *box;
+	struct mailbox_metadata metadata;
+	const char *vname;
+	string_t *str = t_str_new(128);
+	char ns_sep = mailbox_list_get_hierarchy_sep(ctx->list);
+
+	mailbox_list_index_node_get_path(node, ns_sep, str);
+
+	vname = mailbox_list_get_vname(ctx->list, str_c(str));
+	box = mailbox_alloc(ctx->list, vname, 0);
+	if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) == 0)
+		memcpy(guid_r, metadata.guid, GUID_128_SIZE);
+	mailbox_free(&box);
+}
+
+static void
 node_add_to_index(struct mailbox_list_index_sync_context *ctx,
 		  const struct mailbox_list_index_node *node, uint32_t *seq_r)
 {
@@ -28,6 +50,12 @@
 	if (node->parent != NULL)
 		irec.parent_uid = node->parent->uid;
 
+	/* get mailbox GUID if possible. we need to do this early in here to
+	   make mailbox rename detection work in NOTIFY */
+	T_BEGIN {
+		node_lookup_guid(ctx, node, irec.guid);
+	} T_END;
+
 	mail_index_append(ctx->trans, node->uid, &seq);
 	mail_index_update_flags(ctx->trans, seq, MODIFY_REPLACE,
 		(enum mail_flags)MAILBOX_LIST_INDEX_FLAG_NONEXISTENT);
@@ -237,6 +265,7 @@
 	mailbox_list_index_reset(ilist);
 
 	memset(&sync_ctx, 0, sizeof(sync_ctx));
+	sync_ctx.list = list;
 	sync_ctx.ilist = ilist;
 	sync_ctx.sep[0] = mailbox_list_get_hierarchy_sep(list);
 	if (mail_index_sync_begin(ilist->index, &sync_ctx.sync_ctx,
@@ -269,6 +298,7 @@
 
 	/* don't include autocreated mailboxes in index until they're
 	   actually created. */
+	ilist->syncing = TRUE;
 	patterns[0] = "*"; patterns[1] = NULL;
 	iter = ilist->module_ctx.super.
 		iter_init(list, patterns, MAILBOX_LIST_ITER_NO_AUTO_BOXES);
@@ -294,6 +324,7 @@
 	}
 	if (ilist->module_ctx.super.iter_deinit(iter) < 0) {
 		mail_index_sync_rollback(&sync_ctx.sync_ctx);
+		ilist->syncing = FALSE;
 		return -1;
 	}
 
@@ -313,6 +344,7 @@
 			offsetof(struct mailbox_list_index_header, refresh_flag),
 			&new_hdr.refresh_flag, sizeof(new_hdr.refresh_flag));
 	}
+	ilist->syncing = FALSE;
 
 	if (mail_index_sync_commit(&sync_ctx.sync_ctx) < 0) {
 		mailbox_list_index_set_index_error(list);
--- a/src/lib-storage/list/mailbox-list-index.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-index.c	Mon Aug 13 15:20:33 2012 +0300
@@ -1,11 +1,15 @@
 /* Copyright (c) 2006-2012 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "ioloop.h"
 #include "hash.h"
-#include "mail-index.h"
+#include "str.h"
+#include "mail-index-view-private.h"
 #include "mail-storage-hooks.h"
 #include "mailbox-list-index.h"
 
+#define MAILBOX_LIST_INDEX_REFRESH_DELAY_MSECS 1000
+
 struct mailbox_list_index_module mailbox_list_index_module =
 	MODULE_CONTEXT_INIT(&mailbox_list_module_register);
 
@@ -104,6 +108,22 @@
 	return node;
 }
 
+struct mailbox_list_index_node *
+mailbox_list_index_lookup_uid(struct mailbox_list_index *ilist, uint32_t uid)
+{
+	return hash_table_lookup(ilist->mailbox_hash, POINTER_CAST(uid));
+}
+
+void mailbox_list_index_node_get_path(const struct mailbox_list_index_node *node,
+				      char sep, string_t *str)
+{
+	if (node->parent != NULL) {
+		mailbox_list_index_node_get_path(node->parent, sep, str);
+		str_append_c(str, sep);
+	}
+	str_append(str, node->name);
+}
+
 static int mailbox_list_index_parse_header(struct mailbox_list_index *ilist,
 					   struct mail_index_view *view)
 {
@@ -112,7 +132,7 @@
 	uint32_t id, prev_id = 0;
 	char *name;
 
-	mail_index_get_header_ext(view, ilist->ext_id, &data, &size);
+	mail_index_map_get_header_ext(view, view->map, ilist->ext_id, &data, &size);
 	if (size == 0)
 		return 0;
 
@@ -178,8 +198,8 @@
 			return -1;
 
 		if (irec->parent_uid != 0) {
-			node->parent = hash_table_lookup(ilist->mailbox_hash,
-					POINTER_CAST(irec->parent_uid));
+			node->parent = mailbox_list_index_lookup_uid(ilist,
+							irec->parent_uid);
 			if (node->parent == NULL)
 				return -1;
 			node->next = node->parent->children;
@@ -266,6 +286,14 @@
 	return ret;
 }
 
+static void mailbox_list_index_refresh_timeout(struct mailbox_list *list)
+{
+	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
+
+	timeout_remove(&ilist->to_refresh);
+	(void)mailbox_list_index_refresh(list);
+}
+
 void mailbox_list_index_refresh_later(struct mailbox_list *list)
 {
 	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
@@ -289,12 +317,20 @@
 
 	}
 	mail_index_view_close(&view);
+
+	if (ilist->to_refresh == NULL) {
+		ilist->to_refresh =
+			timeout_add(MAILBOX_LIST_INDEX_REFRESH_DELAY_MSECS,
+				    mailbox_list_index_refresh_timeout, list);
+	}
 }
 
 static void mailbox_list_index_deinit(struct mailbox_list *list)
 {
 	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
 
+	if (ilist->to_refresh != NULL)
+		timeout_remove(&ilist->to_refresh);
 	hash_table_destroy(&ilist->mailbox_hash);
 	hash_table_destroy(&ilist->mailbox_names);
 	pool_unref(&ilist->mailbox_pool);
@@ -386,6 +422,11 @@
 	list->v.delete_dir = mailbox_list_index_delete_dir;
 	list->v.rename_mailbox = mailbox_list_index_rename_mailbox;
 
+	list->v.notify_init = mailbox_list_index_notify_init;
+	list->v.notify_next = mailbox_list_index_notify_next;
+	list->v.notify_deinit = mailbox_list_index_notify_deinit;
+	list->v.notify_wait = mailbox_list_index_notify_wait;
+
 	MODULE_CONTEXT_SET(list, mailbox_list_index_module, ilist);
 
 	ilist->path = dir == NULL ? "(in-memory mailbox list index)" :
--- a/src/lib-storage/list/mailbox-list-index.h	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-index.h	Mon Aug 13 15:20:33 2012 +0300
@@ -23,6 +23,7 @@
 */
 
 #include "module-context.h"
+#include "mail-types.h"
 #include "mailbox-list-private.h"
 
 #define MAILBOX_LIST_INDEX_PREFIX "dovecot.list.index"
@@ -30,8 +31,10 @@
 #define INDEX_LIST_CONTEXT(obj) \
 	MODULE_CONTEXT(obj, mailbox_list_index_module)
 
+enum mailbox_status_items;
 struct mail_index_view;
 struct mailbox;
+struct mailbox_status;
 
 /* stored in mail_index_record.flags: */
 enum mailbox_list_index_flags {
@@ -96,14 +99,15 @@
 
 	uint32_t sync_log_file_seq;
 	uoff_t sync_log_file_offset;
-
 	uint32_t sync_stamp;
+	struct timeout *to_refresh;
 
 	/* uint32_t uid => struct mailbox_list_index_node* */
 	struct hash_table *mailbox_hash;
 	struct mailbox_list_index_node *mailbox_tree;
 
 	unsigned int opened:1;
+	unsigned int syncing:1;
 };
 
 struct mailbox_list_index_iterate_context {
@@ -127,6 +131,10 @@
 void mailbox_list_index_set_index_error(struct mailbox_list *list);
 struct mailbox_list_index_node *
 mailbox_list_index_lookup(struct mailbox_list *list, const char *name);
+struct mailbox_list_index_node *
+mailbox_list_index_lookup_uid(struct mailbox_list_index *ilist, uint32_t uid);
+void mailbox_list_index_node_get_path(const struct mailbox_list_index_node *node,
+				      char sep, string_t *str);
 
 bool mailbox_list_index_need_refresh(struct mailbox_list_index *ilist,
 				     struct mail_index_view *view);
@@ -149,9 +157,24 @@
 mailbox_list_index_iter_next(struct mailbox_list_iterate_context *ctx);
 int mailbox_list_index_iter_deinit(struct mailbox_list_iterate_context *ctx);
 
+bool mailbox_list_index_status(struct mailbox_list *list,
+			       struct mail_index_view *view,
+			       uint32_t seq, enum mailbox_status_items items,
+			       struct mailbox_status *status_r,
+			       uint8_t *mailbox_guid);
 void mailbox_list_index_status_set_info_flags(struct mailbox *box, uint32_t uid,
 					      enum mailbox_info_flags *flags);
 
+int mailbox_list_index_notify_init(struct mailbox_list *list,
+				   enum mailbox_list_notify_event mask,
+				   struct mailbox_list_notify **notify_r);
+void mailbox_list_index_notify_deinit(struct mailbox_list_notify *notify);
+int mailbox_list_index_notify_next(struct mailbox_list_notify *notify,
+				   const struct mailbox_list_notify_rec **rec_r);
+void mailbox_list_index_notify_wait(struct mailbox_list_notify *notify,
+				    void (*callback)(void *context),
+				    void *context);
+
 void mailbox_list_index_status_init(void);
 void mailbox_list_index_status_init_list(struct mailbox_list *list);
 
--- a/src/lib-storage/list/mailbox-list-maildir.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-maildir.c	Mon Aug 13 15:20:33 2012 +0300
@@ -664,7 +664,8 @@
 		maildir_list_delete_mailbox,
 		maildir_list_delete_dir,
 		mailbox_list_delete_symlink_default,
-		maildir_list_rename_mailbox
+		maildir_list_rename_mailbox,
+		NULL, NULL, NULL, NULL
 	}
 };
 
@@ -699,6 +700,7 @@
 		maildir_list_delete_mailbox,
 		maildir_list_delete_dir,
 		mailbox_list_delete_symlink_default,
-		maildir_list_rename_mailbox
+		maildir_list_rename_mailbox,
+		NULL, NULL, NULL, NULL
 	}
 };
--- a/src/lib-storage/list/mailbox-list-none.c	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/list/mailbox-list-none.c	Mon Aug 13 15:20:33 2012 +0300
@@ -212,6 +212,7 @@
 		none_list_delete_mailbox,
 		none_list_delete_dir,
 		none_list_delete_dir,
-		none_list_rename_mailbox
+		none_list_rename_mailbox,
+		NULL, NULL, NULL, NULL
 	}
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/list/mailbox-list-notify-tree.c	Mon Aug 13 15:20:33 2012 +0300
@@ -0,0 +1,129 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-index.h"
+#include "mail-storage.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-index.h"
+#include "mailbox-list-notify-tree.h"
+
+struct mailbox_list_notify_tree {
+	struct mailbox_list *list;
+	struct mailbox_tree_context *mailboxes;
+
+	struct mail_index_view *view;
+	bool failed;
+};
+
+static void
+mailbox_list_notify_node_get_status(struct mailbox_list_notify_tree *tree,
+				    struct mailbox_notify_node *nnode)
+{
+	struct mailbox_status status;
+	uint32_t seq;
+
+	if (!mail_index_lookup_seq(tree->view, nnode->index_uid, &seq))
+		return;
+
+	memset(&status, 0, sizeof(status));
+	(void)mailbox_list_index_status(tree->list, tree->view, seq,
+		STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES |
+		STATUS_UNSEEN | STATUS_HIGHESTMODSEQ, &status, nnode->guid);
+	nnode->uidvalidity = status.uidvalidity;
+	nnode->uidnext = status.uidnext;
+	nnode->messages = status.messages;
+	nnode->unseen = status.unseen;
+	nnode->highest_modseq = status.highest_modseq;
+}
+
+static void
+mailbox_list_notify_node_build(struct mailbox_list_notify_tree *tree,
+			       struct mailbox_list_index_node *index_node,
+			       string_t *path)
+{
+	struct mailbox_node *node;
+	struct mailbox_notify_node *nnode;
+	unsigned int prefix_len;
+	bool created;
+
+	str_append(path, index_node->name);
+
+	node = mailbox_tree_get(tree->mailboxes, str_c(path), &created);
+	nnode = (struct mailbox_notify_node *)node;
+	nnode->index_uid = index_node->uid;
+
+	if ((index_node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0)
+		node->flags = MAILBOX_NONEXISTENT;
+	else if ((index_node->flags & MAILBOX_LIST_INDEX_FLAG_NOSELECT) != 0)
+		node->flags = MAILBOX_NOSELECT;
+	else {
+		node->flags = 0;
+		mailbox_list_notify_node_get_status(tree, nnode);
+	}
+	if ((index_node->flags & MAILBOX_LIST_INDEX_FLAG_NOINFERIORS) != 0)
+		node->flags |= MAILBOX_NOINFERIORS;
+
+	if (index_node->children != NULL) {
+		str_append_c(path, mailbox_list_get_hierarchy_sep(tree->list));
+		prefix_len = str_len(path);
+		index_node = index_node->children;
+		for (; index_node != NULL; index_node = index_node->next) {
+			str_truncate(path, prefix_len);
+			mailbox_list_notify_node_build(tree, index_node, path);
+		}
+	}
+}
+
+static void
+mailbox_list_notify_tree_build(struct mailbox_list_notify_tree *tree)
+{
+	struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(tree->list);
+	struct mailbox_list_index_node *index_node;
+	string_t *path = t_str_new(128);
+
+	if (mailbox_list_index_refresh(tree->list) < 0)
+		tree->failed = TRUE;
+
+	tree->view = mail_index_view_open(ilist->index);
+	index_node = ilist->mailbox_tree;
+	for (; index_node != NULL; index_node = index_node->next) {
+		str_truncate(path, 0);
+		mailbox_list_notify_node_build(tree, index_node, path);
+	}
+	mail_index_view_close(&tree->view);
+}
+
+struct mailbox_list_notify_tree *
+mailbox_list_notify_tree_init(struct mailbox_list *list)
+{
+	struct mailbox_list_notify_tree *tree;
+
+	tree = i_new(struct mailbox_list_notify_tree, 1);
+	tree->list = list;
+	tree->mailboxes =
+		mailbox_tree_init_size(mailbox_list_get_hierarchy_sep(list),
+				       sizeof(struct mailbox_notify_node));
+	mailbox_list_notify_tree_build(tree);
+	return tree;
+}
+
+void mailbox_list_notify_tree_deinit(struct mailbox_list_notify_tree **_tree)
+{
+	struct mailbox_list_notify_tree *tree = *_tree;
+
+	*_tree = NULL;
+
+	mailbox_tree_deinit(&tree->mailboxes);
+	i_free(tree);
+}
+
+struct mailbox_notify_node *
+mailbox_list_notify_tree_lookup(struct mailbox_list_notify_tree *tree,
+				const char *storage_name)
+{
+	struct mailbox_node *node;
+
+	node = mailbox_tree_lookup(tree->mailboxes, storage_name);
+	return (struct mailbox_notify_node *)node;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/list/mailbox-list-notify-tree.h	Mon Aug 13 15:20:33 2012 +0300
@@ -0,0 +1,27 @@
+#ifndef MAILBOX_LIST_NOTIFY_TREE_H
+#define MAILBOX_LIST_NOTIFY_TREE_H
+
+#include "mailbox-tree.h"
+
+struct mailbox_notify_node {
+	struct mailbox_node node;
+
+	guid_128_t guid;
+	uint32_t index_uid;
+
+	uint32_t uidvalidity;
+	uint32_t uidnext;
+	uint32_t messages;
+	uint32_t unseen;
+	uint64_t highest_modseq;
+};
+
+struct mailbox_list_notify_tree *
+mailbox_list_notify_tree_init(struct mailbox_list *list);
+void mailbox_list_notify_tree_deinit(struct mailbox_list_notify_tree **tree);
+
+struct mailbox_notify_node *
+mailbox_list_notify_tree_lookup(struct mailbox_list_notify_tree *tree,
+				const char *storage_name);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mailbox-list-notify.c	Mon Aug 13 15:20:33 2012 +0300
@@ -0,0 +1,35 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-notify.h"
+
+int mailbox_list_notify_init(struct mailbox_list *list,
+			     enum mailbox_list_notify_event mask,
+			     struct mailbox_list_notify **notify_r)
+{
+	if (list->v.notify_init == NULL)
+		return -1;
+	return list->v.notify_init(list, mask, notify_r);
+}
+
+void mailbox_list_notify_deinit(struct mailbox_list_notify **_notify)
+{
+	struct mailbox_list_notify *notify = *_notify;
+
+	*_notify = NULL;
+
+	notify->list->v.notify_deinit(notify);
+}
+
+int mailbox_list_notify_next(struct mailbox_list_notify *notify,
+			     const struct mailbox_list_notify_rec **rec_r)
+{
+	return notify->list->v.notify_next(notify, rec_r);
+}
+
+void mailbox_list_notify_wait(struct mailbox_list_notify *notify,
+			      void (*callback)(void *context), void *context)
+{
+	notify->list->v.notify_wait(notify, callback, context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mailbox-list-notify.h	Mon Aug 13 15:20:33 2012 +0300
@@ -0,0 +1,58 @@
+#ifndef MAILBOX_LIST_NOTIFY_H
+#define MAILBOX_LIST_NOTIFY_H
+
+#include "guid.h"
+
+struct mailbox_list_notify;
+
+enum mailbox_list_notify_event {
+	MAILBOX_LIST_NOTIFY_CREATE		= 0x01,
+	MAILBOX_LIST_NOTIFY_DELETE		= 0x02,
+	MAILBOX_LIST_NOTIFY_RENAME		= 0x04,
+	MAILBOX_LIST_NOTIFY_SUBSCRIPTION_CHANGE = 0x08,
+
+	MAILBOX_LIST_NOTIFY_UIDVALIDITY		= 0x10,
+	MAILBOX_LIST_NOTIFY_APPENDS		= 0x20,
+	MAILBOX_LIST_NOTIFY_EXPUNGES		= 0x40,
+	MAILBOX_LIST_NOTIFY_SEEN_CHANGES	= 0x80,
+	MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES	= 0x100
+#define MAILBOX_LIST_NOTIFY_STATUS \
+	(MAILBOX_LIST_NOTIFY_APPENDS | \
+	 MAILBOX_LIST_NOTIFY_EXPUNGES | \
+	 MAILBOX_LIST_NOTIFY_SEEN_CHANGES | \
+	 MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES)
+};
+
+struct mailbox_list_notify {
+	struct mailbox_list *list;
+	enum mailbox_list_notify_event mask;
+};
+
+struct mailbox_list_notify_rec {
+	enum mailbox_list_notify_event event;
+
+	/* For all events: */
+	const char *storage_name, *vname;
+	/* For selectable mailboxes: */
+	guid_128_t guid;
+
+	/* For rename: */
+	const char *old_vname;
+};
+
+/* Monitor for specified changes in the mailbox list.
+   Returns 0 if ok, -1 if notifications aren't supported. */
+int mailbox_list_notify_init(struct mailbox_list *list,
+			     enum mailbox_list_notify_event mask,
+			     struct mailbox_list_notify **notify_r);
+void mailbox_list_notify_deinit(struct mailbox_list_notify **notify);
+
+/* Get the next change. Returns 1 if record was returned, 0 if there are no
+   more changes currently or -1 if some error occurred */
+int mailbox_list_notify_next(struct mailbox_list_notify *notify,
+			     const struct mailbox_list_notify_rec **rec_r);
+/* Call the specified callback when something changes. */
+void mailbox_list_notify_wait(struct mailbox_list_notify *notify,
+			      void (*callback)(void *context), void *context);
+
+#endif
--- a/src/lib-storage/mailbox-list-private.h	Mon Aug 13 15:15:07 2012 +0300
+++ b/src/lib-storage/mailbox-list-private.h	Mon Aug 13 15:20:33 2012 +0300
@@ -12,10 +12,13 @@
 #define MAILBOX_LOG_FILE_NAME "dovecot.mailbox.log"
 
 enum mailbox_log_record_type;
+enum mailbox_list_notify_event;
 struct stat;
 struct dirent;
 struct imap_match_glob;
 struct mailbox_tree_context;
+struct mailbox_list_notify;
+struct mailbox_list_notify_rec;
 
 #define MAILBOX_INFO_FLAGS_FINISHED(flags) \
 	(((flags) & (MAILBOX_SELECT | MAILBOX_NOSELECT | \
@@ -85,6 +88,15 @@
 	int (*rename_mailbox)(struct mailbox_list *oldlist, const char *oldname,
 			      struct mailbox_list *newlist, const char *newname,
 			      bool rename_children);
+
+	int (*notify_init)(struct mailbox_list *list,
+			   enum mailbox_list_notify_event mask,
+			   struct mailbox_list_notify **notify_r);
+	int (*notify_next)(struct mailbox_list_notify *notify,
+			   const struct mailbox_list_notify_rec **rec_r);
+	void (*notify_deinit)(struct mailbox_list_notify *notify);
+	void (*notify_wait)(struct mailbox_list_notify *notify,
+			    void (*callback)(void *context), void *context);
 };
 
 struct mailbox_list_module_register {