[evolution/wip/webkit2] Bug 681353 - Use per-account Templates folder setting



commit 5f28378da977876def7350e6b53787204b708010
Author: Milan Crha <mcrha redhat com>
Date:   Wed May 4 16:13:08 2016 +0200

    Bug 681353 - Use per-account Templates folder setting

 composer/mail-composer.error.xml      |    5 +
 mail/e-mail-config-defaults-page.c    |   41 +-
 mail/em-folder-tree-model.c           |   52 +-
 plugins/templates/Makefile.am         |    5 +-
 plugins/templates/e-templates-store.c | 2282 +++++++++++++++++++++++++++++++++
 plugins/templates/e-templates-store.h |   95 ++
 plugins/templates/templates.c         |  475 +++-----
 po/POTFILES.in                        |    1 +
 8 files changed, 2592 insertions(+), 364 deletions(-)
---
diff --git a/composer/mail-composer.error.xml b/composer/mail-composer.error.xml
index 27e00fd..722d9ad 100644
--- a/composer/mail-composer.error.xml
+++ b/composer/mail-composer.error.xml
@@ -102,4 +102,9 @@
   <button _label="Lose _formatting" response="GTK_RESPONSE_YES"/>
  </error>
 
+ <error id="failed-save-template" type="error">
+  <_primary>An error occurred while saving to your Templates folder.</_primary>
+  <_secondary>The reported error was &quot;{0}&quot;. The message has most likely not been 
saved.</_secondary>
+ </error>
+
 </error-list>
diff --git a/mail/e-mail-config-defaults-page.c b/mail/e-mail-config-defaults-page.c
index 5c2cf71..3bbd045 100644
--- a/mail/e-mail-config-defaults-page.c
+++ b/mail/e-mail-config-defaults-page.c
@@ -43,6 +43,7 @@ struct _EMailConfigDefaultsPagePrivate {
        GtkWidget *drafts_button;  /* not referenced */
        GtkWidget *sent_button;    /* not referenced */
        GtkWidget *archive_button; /* not referenced */
+       GtkWidget *templates_button; /* not referenced */
        GtkWidget *replies_toggle; /* not referenced */
        GtkWidget *trash_toggle;   /* not referenced */
        GtkWidget *junk_toggle;    /* not referenced */
@@ -275,6 +276,11 @@ mail_config_defaults_page_restore_folders (EMailConfigDefaultsPage *page)
        folder_uri = e_mail_session_get_local_folder_uri (session, type);
        em_folder_selection_button_set_folder_uri (button, folder_uri);
 
+       type = E_MAIL_LOCAL_FOLDER_TEMPLATES;
+       button = EM_FOLDER_SELECTION_BUTTON (page->priv->templates_button);
+       folder_uri = e_mail_session_get_local_folder_uri (session, type);
+       em_folder_selection_button_set_folder_uri (button, folder_uri);
+
        if (gtk_widget_is_sensitive (page->priv->sent_button)) {
                type = E_MAIL_LOCAL_FOLDER_SENT;
                button = EM_FOLDER_SELECTION_BUTTON (page->priv->sent_button);
@@ -775,6 +781,7 @@ mail_config_defaults_page_constructed (GObject *object)
        GEnumValue *enum_value;
        EMdnResponsePolicy policy;
        CamelProvider *provider = NULL;
+       CamelStore *store;
        const gchar *extension_name;
        const gchar *text;
        gchar *markup;
@@ -922,8 +929,36 @@ mail_config_defaults_page_constructed (GObject *object)
                G_BINDING_BIDIRECTIONAL |
                G_BINDING_SYNC_CREATE);
 
+       text = _("_Templates Folder:");
+       widget = gtk_label_new_with_mnemonic (text);
+       gtk_widget_set_margin_left (widget, 12);
+       gtk_size_group_add_widget (size_group, widget);
+       gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+       gtk_grid_attach (GTK_GRID (container), widget, 0, 5, 1, 1);
+       gtk_widget_show (widget);
+
+       label = GTK_LABEL (widget);
+
+       text = _("Choose a folder to use for template messages to.");
+       widget = em_folder_selection_button_new (session, "", text);
+       store = mail_config_defaults_page_ref_store (page);
+       if (store)
+               em_folder_selection_button_set_store (EM_FOLDER_SELECTION_BUTTON (widget), store);
+       g_clear_object (&store);
+       gtk_widget_set_hexpand (widget, TRUE);
+       gtk_label_set_mnemonic_widget (label, widget);
+       gtk_grid_attach (GTK_GRID (container), widget, 1, 5, 1, 1);
+       page->priv->templates_button = widget;  /* not referenced */
+       gtk_widget_show (widget);
+
+       e_binding_bind_object_text_property (
+               composition_ext, "templates-folder",
+               widget, "folder-uri",
+               G_BINDING_BIDIRECTIONAL |
+               G_BINDING_SYNC_CREATE);
+
        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
-       gtk_grid_attach (GTK_GRID (container), hbox, 1, 7, 1, 1);
+       gtk_grid_attach (GTK_GRID (container), hbox, 1, 8, 1, 1);
        gtk_widget_show (hbox);
 
        widget = gtk_button_new_with_mnemonic (_("_Restore Defaults"));
@@ -965,7 +1000,7 @@ mail_config_defaults_page_constructed (GObject *object)
                _("Choose a folder for deleted messages."),
                "real-trash-path", "use-real-trash-path");
        if (widget != NULL) {
-               gtk_grid_attach (GTK_GRID (container), widget, 0, 5, 2, 1);
+               gtk_grid_attach (GTK_GRID (container), widget, 0, 6, 2, 1);
                gtk_widget_show (widget);
        }
 
@@ -975,7 +1010,7 @@ mail_config_defaults_page_constructed (GObject *object)
                _("Choose a folder for junk messages."),
                "real-junk-path", "use-real-junk-path");
        if (widget != NULL) {
-               gtk_grid_attach (GTK_GRID (container), widget, 0, 6, 2, 1);
+               gtk_grid_attach (GTK_GRID (container), widget, 0, 7, 2, 1);
                gtk_widget_show (widget);
        }
 
diff --git a/mail/em-folder-tree-model.c b/mail/em-folder-tree-model.c
index 6bce0e9..37480d9 100644
--- a/mail/em-folder-tree-model.c
+++ b/mail/em-folder-tree-model.c
@@ -1056,17 +1056,20 @@ em_folder_tree_model_set_session (EMFolderTreeModel *model,
        g_object_notify (G_OBJECT (model), "session");
 }
 
-/* Helper for em_folder_tree_model_set_folder_info() */
 static void
-folder_tree_model_get_drafts_folder_uri (ESourceRegistry *registry,
-                                         CamelStore *store,
-                                         gchar **drafts_folder_uri)
+folder_tree_model_get_special_folders_uri (ESourceRegistry *registry,
+                                          CamelStore *store,
+                                          gchar **drafts_folder_uri,
+                                          gchar **templates_folder_uri,
+                                          gchar **sent_folder_uri)
 {
        ESource *source;
        const gchar *extension_name;
 
        /* In case we fail... */
        *drafts_folder_uri = NULL;
+       *templates_folder_uri = NULL;
+       *sent_folder_uri = NULL;
 
        source = em_utils_ref_mail_identity_for_store (registry, store);
        if (source == NULL)
@@ -1078,37 +1081,17 @@ folder_tree_model_get_drafts_folder_uri (ESourceRegistry *registry,
 
                extension = e_source_get_extension (source, extension_name);
 
-               *drafts_folder_uri =
-                       e_source_mail_composition_dup_drafts_folder (extension);
+               *drafts_folder_uri = e_source_mail_composition_dup_drafts_folder (extension);
+               *templates_folder_uri = e_source_mail_composition_dup_templates_folder (extension);
        }
 
-       g_object_unref (source);
-}
-
-/* Helper for em_folder_tree_model_set_folder_info() */
-static void
-folder_tree_model_get_sent_folder_uri (ESourceRegistry *registry,
-                                       CamelStore *store,
-                                       gchar **sent_folder_uri)
-{
-       ESource *source;
-       const gchar *extension_name;
-
-       /* In case we fail... */
-       *sent_folder_uri = NULL;
-
-       source = em_utils_ref_mail_identity_for_store (registry, store);
-       if (source == NULL)
-               return;
-
        extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
        if (e_source_has_extension (source, extension_name)) {
                ESourceMailSubmission *extension;
 
                extension = e_source_get_extension (source, extension_name);
 
-               *sent_folder_uri =
-                       e_source_mail_submission_dup_sent_folder (extension);
+               *sent_folder_uri = e_source_mail_submission_dup_sent_folder (extension);
        }
 
        g_object_unref (source);
@@ -1240,13 +1223,11 @@ em_folder_tree_model_set_folder_info (EMFolderTreeModel *model,
 
        if ((flags & CAMEL_FOLDER_TYPE_MASK) == 0) {
                gchar *drafts_folder_uri;
+               gchar *templates_folder_uri;
                gchar *sent_folder_uri;
 
-               folder_tree_model_get_drafts_folder_uri (
-                       registry, store, &drafts_folder_uri);
-
-               folder_tree_model_get_sent_folder_uri (
-                       registry, store, &sent_folder_uri);
+               folder_tree_model_get_special_folders_uri (registry, store,
+                       &drafts_folder_uri, &templates_folder_uri, &sent_folder_uri);
 
                if (!folder_is_drafts && drafts_folder_uri != NULL) {
                        folder_is_drafts = e_mail_folder_uri_equal (
@@ -1254,6 +1235,12 @@ em_folder_tree_model_set_folder_info (EMFolderTreeModel *model,
                                uri, drafts_folder_uri);
                }
 
+               if (!folder_is_templates && templates_folder_uri != NULL) {
+                       folder_is_templates = e_mail_folder_uri_equal (
+                               CAMEL_SESSION (session),
+                               uri, templates_folder_uri);
+               }
+
                if (sent_folder_uri != NULL) {
                        if (e_mail_folder_uri_equal (
                                CAMEL_SESSION (session),
@@ -1263,6 +1250,7 @@ em_folder_tree_model_set_folder_info (EMFolderTreeModel *model,
                }
 
                g_free (drafts_folder_uri);
+               g_free (templates_folder_uri);
                g_free (sent_folder_uri);
        }
 
diff --git a/plugins/templates/Makefile.am b/plugins/templates/Makefile.am
index 18d25ea..18e3f99 100644
--- a/plugins/templates/Makefile.am
+++ b/plugins/templates/Makefile.am
@@ -14,7 +14,10 @@ liborg_gnome_templates_la_CPPFLAGS =                 \
        $(CODE_COVERAGE_CFLAGS)                         \
        $(NULL)
 
-liborg_gnome_templates_la_SOURCES = templates.c
+liborg_gnome_templates_la_SOURCES =                    \
+       templates.c                                     \
+       e-templates-store.h                             \
+       e-templates-store.c
 
 liborg_gnome_templates_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED) $(CODE_COVERAGE_LDFLAGS)
 
diff --git a/plugins/templates/e-templates-store.c b/plugins/templates/e-templates-store.c
new file mode 100644
index 0000000..15a815d
--- /dev/null
+++ b/plugins/templates/e-templates-store.c
@@ -0,0 +1,2282 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <camel/camel.h>
+#include <libedataserver/libedataserver.h>
+
+#include "shell/e-shell-view.h"
+
+#include "e-templates-store.h"
+
+struct _ETemplatesStorePrivate {
+       GWeakRef *account_store_weakref; /* EMailAccountStore * */
+
+       gulong service_enabled_handler_id;
+       gulong service_disabled_handler_id;
+       gulong service_removed_handler_id;
+       gulong source_changed_handler_id;
+
+       GMutex busy_lock;
+       GCancellable *cancellable;
+       GSList *stores; /* TmplStoreData *, sorted by account_store options; those with set templates dir */
+       guint menu_refresh_idle_id;
+};
+
+G_DEFINE_TYPE (ETemplatesStore, e_templates_store, G_TYPE_OBJECT);
+
+enum {
+       PROP_0,
+       PROP_ACCOUNT_STORE
+};
+
+enum {
+       CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+templates_store_lock (ETemplatesStore *templates_store)
+{
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+
+       g_mutex_lock (&templates_store->priv->busy_lock);
+}
+
+static void
+templates_store_unlock (ETemplatesStore *templates_store)
+{
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+
+       g_mutex_unlock (&templates_store->priv->busy_lock);
+}
+
+static void
+templates_store_emit_changed (ETemplatesStore *templates_store)
+{
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+
+       g_signal_emit (templates_store, signals[CHANGED], 0, NULL);
+}
+
+static void
+tmpl_folder_data_folder_changed_cb (CamelFolder *folder,
+                                   CamelFolderChangeInfo *change_info,
+                                   gpointer user_data);
+
+static void
+tmpl_store_data_folder_created_cb (CamelStore *store,
+                                  CamelFolderInfo *folder_info,
+                                  gpointer user_data);
+static void
+tmpl_store_data_folder_deleted_cb (CamelStore *store,
+                                  CamelFolderInfo *folder_info,
+                                  gpointer user_data);
+static void
+tmpl_store_data_folder_renamed_cb (CamelStore *store,
+                                  const gchar *old_name,
+                                  CamelFolderInfo *folder_info,
+                                  gpointer user_data);
+
+static void
+tmpl_store_data_notify_display_name_cb (CamelService *service,
+                                       GParamSpec *param,
+                                       gpointer user_data);
+
+typedef struct _TmplMessageData {
+       const gchar *subject; /* Allocated by camel-pstring */
+       const gchar *uid; /* Allocated by camel-pstring */
+} TmplMessageData;
+
+static const gchar *
+tmpl_sanitized_subject (const gchar *subject)
+{
+       if (!subject || !*subject)
+               subject = _("No Title");
+
+       return subject;
+}
+
+static TmplMessageData *
+tmpl_message_data_new (CamelMessageInfo *info)
+{
+       TmplMessageData *tmd;
+
+       g_return_val_if_fail (info != NULL, NULL);
+
+       tmd = g_new0 (TmplMessageData, 1);
+       tmd->subject = camel_pstring_strdup (tmpl_sanitized_subject (camel_message_info_subject (info)));
+       tmd->uid = camel_pstring_strdup (camel_message_info_uid (info));
+
+       return tmd;
+}
+
+static void
+tmpl_message_data_free (gpointer ptr)
+{
+       TmplMessageData *tmd = ptr;
+
+       if (tmd) {
+               camel_pstring_free (tmd->subject);
+               camel_pstring_free (tmd->uid);
+               g_free (tmd);
+       }
+}
+
+static void
+tmpl_message_data_change_subject (TmplMessageData *tmd,
+                                 const gchar *subject)
+{
+       g_return_if_fail (tmd != NULL);
+
+       if (subject != tmd->subject) {
+               camel_pstring_free (tmd->subject);
+               tmd->subject = camel_pstring_strdup (tmpl_sanitized_subject (subject));
+       }
+}
+
+static gint
+tmpl_message_data_compare (gconstpointer ptr1,
+                          gconstpointer ptr2)
+{
+       const TmplMessageData *tmd1 = ptr1, *tmd2 = ptr2;
+
+       if (!tmd1 || !tmd2) {
+               if (tmd1 == tmd2)
+                       return 0;
+               if (tmd1)
+                       return -1;
+               return 1;
+       }
+
+       return g_utf8_collate (tmd1->subject ? tmd1->subject : "", tmd2->subject ? tmd2->subject : "");
+}
+
+typedef struct _TmplFolderData {
+       volatile gint ref_count;
+       GWeakRef *templates_store_weakref; /* ETemplatesStore * */
+       CamelFolder *folder;
+       gulong changed_handler_id;
+
+       GMutex busy_lock;
+       /* This might look inefficient, but the rebuild of the menu is called
+          much more often then the remove of a message from the folder, thus
+          it's cheaper to traverse one by one here, then re-sort the content
+          every time the menu is being rebuild. */
+       GSList *messages; /* TmplMessageData *, ordered by data->subject */
+} TmplFolderData;
+
+static TmplFolderData *
+tmpl_folder_data_new (ETemplatesStore *templates_store,
+                     CamelFolder *folder)
+{
+       TmplFolderData *tfd;
+
+       g_return_val_if_fail (E_IS_TEMPLATES_STORE (templates_store), NULL);
+       g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+       tfd = g_new0 (TmplFolderData, 1);
+       tfd->ref_count = 1;
+       tfd->templates_store_weakref = e_weak_ref_new (templates_store);
+       tfd->folder = g_object_ref (folder);
+       tfd->changed_handler_id = g_signal_connect (folder, "changed",
+               G_CALLBACK (tmpl_folder_data_folder_changed_cb), tfd);
+       g_mutex_init (&tfd->busy_lock);
+       tfd->messages = NULL;
+
+       return tfd;
+}
+
+static TmplFolderData *
+tmpl_folder_data_ref (TmplFolderData *tfd)
+{
+       g_return_val_if_fail (tfd != NULL, NULL);
+
+       g_atomic_int_inc (&tfd->ref_count);
+
+       return tfd;
+}
+
+static void
+tmpl_folder_data_unref (gpointer ptr)
+{
+       TmplFolderData *tfd = ptr;
+
+       if (tfd) {
+               if (!g_atomic_int_dec_and_test (&tfd->ref_count))
+                       return;
+
+               if (tfd->folder && tfd->changed_handler_id) {
+                       g_signal_handler_disconnect (tfd->folder, tfd->changed_handler_id);
+                       tfd->changed_handler_id = 0;
+               }
+
+               if (tfd->templates_store_weakref) {
+                       e_weak_ref_free (tfd->templates_store_weakref);
+                       tfd->templates_store_weakref = NULL;
+               }
+
+               g_clear_object (&tfd->folder);
+
+               g_mutex_clear (&tfd->busy_lock);
+               g_slist_free_full (tfd->messages, tmpl_message_data_free);
+               tfd->messages = NULL;
+
+               g_free (tfd);
+       }
+}
+
+static void
+tmpl_folder_data_lock (TmplFolderData *tfd)
+{
+       g_return_if_fail (tfd != NULL);
+
+       g_mutex_lock (&tfd->busy_lock);
+}
+
+static void
+tmpl_folder_data_unlock (TmplFolderData *tfd)
+{
+       g_return_if_fail (tfd != NULL);
+
+       g_mutex_unlock (&tfd->busy_lock);
+}
+
+static void
+tmpl_folder_data_add_message (TmplFolderData *tfd,
+                             CamelMessageInfo *info)
+{
+       TmplMessageData *tmd;
+
+       g_return_if_fail (tfd != NULL);
+       g_return_if_fail (info != NULL);
+
+       tmd = tmpl_message_data_new (info);
+       g_return_if_fail (tmd != NULL);
+
+       /* The caller is responsible to call tmpl_folder_data_sort() */
+       tfd->messages = g_slist_prepend (tfd->messages, tmd);
+}
+
+static TmplMessageData *
+tmpl_folder_data_find_message (TmplFolderData *tfd,
+                              const gchar *uid)
+{
+       GSList *link;
+
+       g_return_val_if_fail (tfd != NULL, NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       for (link = tfd->messages; link; link = g_slist_next (link)) {
+               TmplMessageData *tmd = link->data;
+
+               if (!tmd)
+                       continue;
+
+               if (uid == tmd->uid || g_strcmp0 (uid, tmd->uid) == 0)
+                       return tmd;
+       }
+
+       return NULL;
+}
+
+static gboolean
+tmpl_folder_data_remove_message (TmplFolderData *tfd,
+                                const gchar *uid)
+{
+       TmplMessageData *tmd;
+
+       g_return_val_if_fail (tfd != NULL, FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       tmd = tmpl_folder_data_find_message (tfd, uid);
+       if (tmd) {
+               tfd->messages = g_slist_remove (tfd->messages, tmd);
+               tmpl_message_data_free (tmd);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static gboolean
+tmpl_folder_data_change_message (TmplFolderData *tfd,
+                                CamelMessageInfo *info)
+{
+       TmplMessageData *tmd;
+       const gchar *subject;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (tfd != NULL, FALSE);
+       g_return_val_if_fail (info != NULL, FALSE);
+
+       tmd = tmpl_folder_data_find_message (tfd, camel_message_info_uid (info));
+       if (!tmd) {
+               if (!(camel_message_info_flags (info) & (CAMEL_MESSAGE_JUNK | CAMEL_MESSAGE_DELETED))) {
+                       tmpl_folder_data_add_message (tfd, info);
+                       return TRUE;
+               }
+
+               return FALSE;
+       }
+
+       if ((camel_message_info_flags (info) & (CAMEL_MESSAGE_JUNK | CAMEL_MESSAGE_DELETED)) != 0) {
+               return tmpl_folder_data_remove_message (tfd, camel_message_info_uid (info));
+       }
+
+       subject = tmpl_sanitized_subject (camel_message_info_subject (info));
+
+       if (g_strcmp0 (subject, tmd->subject) != 0) {
+               tmpl_message_data_change_subject (tmd, subject);
+               /* The caller is responsible to call tmpl_folder_data_sort() */
+               changed = TRUE;
+       }
+
+       return changed;
+}
+
+static void
+tmpl_folder_data_sort (TmplFolderData *tfd)
+{
+       g_return_if_fail (tfd != NULL);
+
+       tfd->messages = g_slist_sort (tfd->messages, tmpl_message_data_compare);
+}
+
+static gint
+tmpl_folder_data_compare (gconstpointer ptr1,
+                         gconstpointer ptr2)
+{
+       const TmplFolderData *tfd1 = ptr1, *tfd2 = ptr2;
+       const gchar *display_name1, *display_name2;
+
+       if (!tfd1 || !tfd2) {
+               if (tfd1 == tfd2)
+                       return 0;
+
+               return tfd1 ? -1 : 1;
+       }
+
+       display_name1 = camel_folder_get_display_name (tfd1->folder);
+       display_name2 = camel_folder_get_display_name (tfd2->folder);
+
+       return g_utf8_collate (display_name1 ? display_name1 : "", display_name2 ? display_name2 : "");
+}
+
+static void
+tmpl_folder_data_update_done_cb (GObject *source,
+                                GAsyncResult *result,
+                                gpointer user_data)
+{
+       TmplFolderData *tfd = user_data;
+       GError *local_error = NULL;
+
+       g_return_if_fail (tfd != NULL);
+       g_return_if_fail (g_task_is_valid (result, source));
+
+       if (g_task_propagate_boolean (G_TASK (result), &local_error)) {
+               /* Content changed, rebuild menu when needed */
+               ETemplatesStore *templates_store;
+
+               templates_store = g_weak_ref_get (tfd->templates_store_weakref);
+               if (templates_store) {
+                       templates_store_emit_changed (templates_store);
+                       g_object_unref (templates_store);
+               }
+       } else if (local_error) {
+               g_debug ("%s: Failed with error: %s", G_STRFUNC, local_error->message);
+       }
+
+       g_clear_error (&local_error);
+}
+
+typedef struct _TfdUpdateData {
+       TmplFolderData *tfd;
+       GPtrArray *added_uids;
+       GPtrArray *changed_uids;
+} TfdUpdateData;
+
+static void
+tfd_update_data_free (gpointer ptr)
+{
+       TfdUpdateData *tud = ptr;
+
+       if (tud) {
+               tmpl_folder_data_unref (tud->tfd);
+               g_ptr_array_free (tud->added_uids, TRUE);
+               g_ptr_array_free (tud->changed_uids, TRUE);
+               g_free (tud);
+       }
+}
+
+static gboolean
+tmpl_folder_data_update_sync (TmplFolderData *tfd,
+                             const GPtrArray *added_uids,
+                             const GPtrArray *changed_uids,
+                             GCancellable *cancellable)
+{
+       GPtrArray *all_uids = NULL;
+       CamelMessageInfo *info;
+       guint ii;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (tfd != NULL, FALSE);
+       g_return_val_if_fail (CAMEL_IS_FOLDER (tfd->folder), FALSE);
+
+       if (!added_uids || !changed_uids || added_uids->len + changed_uids->len > 10)
+               camel_folder_summary_prepare_fetch_all (tfd->folder->summary, NULL);
+
+       if (!added_uids && !changed_uids) {
+               all_uids = camel_folder_summary_get_array (tfd->folder->summary);
+               added_uids = all_uids;
+       }
+
+       tmpl_folder_data_lock (tfd);
+
+       for (ii = 0; added_uids && ii < added_uids->len; ii++) {
+               const gchar *uid = added_uids->pdata[ii];
+
+               info = camel_folder_summary_get (tfd->folder->summary, uid);
+               if (info) {
+                       if (!(camel_message_info_flags (info) & (CAMEL_MESSAGE_JUNK | 
CAMEL_MESSAGE_DELETED))) {
+                               /* Sometimes the 'add' notification can come after the 'change',
+                                  thus use the change_message() which covers both cases. */
+                               changed = tmpl_folder_data_change_message (tfd, info) || changed;
+                       } else {
+                               changed = tmpl_folder_data_remove_message (tfd, camel_message_info_uid 
(info)) || changed;
+                       }
+
+                       camel_message_info_unref (info);
+               }
+       }
+
+       for (ii = 0; changed_uids && ii < changed_uids->len; ii++) {
+               const gchar *uid = changed_uids->pdata[ii];
+
+               info = camel_folder_summary_get (tfd->folder->summary, uid);
+               if (info) {
+                       changed = tmpl_folder_data_change_message (tfd, info) || changed;
+                       camel_message_info_unref (info);
+               }
+       }
+
+       if (changed)
+               tmpl_folder_data_sort (tfd);
+
+       if (all_uids)
+               camel_folder_summary_free_array (all_uids);
+
+       tmpl_folder_data_unlock (tfd);
+
+       return changed;
+}
+
+static void
+tmpl_folder_data_update_thread (GTask *task,
+                               gpointer source_object,
+                               gpointer task_data,
+                               GCancellable *cancellable)
+{
+       TfdUpdateData *tud = task_data;
+       gboolean changed;
+
+       g_return_if_fail (tud != NULL);
+       g_return_if_fail (tud->tfd != NULL);
+       g_return_if_fail (tud->added_uids != NULL);
+       g_return_if_fail (tud->changed_uids != NULL);
+
+       changed = tmpl_folder_data_update_sync (tud->tfd, tud->added_uids, tud->changed_uids, cancellable);
+
+       g_task_return_boolean (task, changed);
+}
+
+static void
+tmpl_folder_data_schedule_update (TmplFolderData *tfd,
+                                 CamelFolderChangeInfo *change_info)
+{
+       ETemplatesStore *templates_store;
+       TfdUpdateData *tud;
+       GTask *task;
+       guint ii;
+
+       g_return_if_fail (tfd != NULL);
+
+       templates_store = g_weak_ref_get (tfd->templates_store_weakref);
+       if (!templates_store)
+               return;
+
+       tud = g_new0 (TfdUpdateData, 1);
+       tud->tfd = tmpl_folder_data_ref (tfd);
+       tud->added_uids = g_ptr_array_new_full (
+               change_info->uid_added ? change_info->uid_added->len : 0,
+               (GDestroyNotify) camel_pstring_free);
+       tud->changed_uids = g_ptr_array_new_full (
+               (change_info->uid_changed ? change_info->uid_changed->len : 0),
+               (GDestroyNotify) camel_pstring_free);
+
+       for (ii = 0; change_info->uid_added && ii < change_info->uid_added->len; ii++) {
+               const gchar *uid = change_info->uid_added->pdata[ii];
+
+               if (uid && *uid)
+                       g_ptr_array_add (tud->added_uids, (gpointer) camel_pstring_strdup (uid));
+       }
+
+       for (ii = 0; change_info->uid_changed && ii < change_info->uid_changed->len; ii++) {
+               const gchar *uid = change_info->uid_changed->pdata[ii];
+
+               if (uid && *uid)
+                       g_ptr_array_add (tud->changed_uids, (gpointer) camel_pstring_strdup (uid));
+       }
+
+       task = g_task_new (NULL, templates_store->priv->cancellable, tmpl_folder_data_update_done_cb, tfd);
+       g_task_set_task_data (task, tud, tfd_update_data_free);
+       g_task_run_in_thread (task, tmpl_folder_data_update_thread);
+       g_object_unref (task);
+
+       g_object_unref (templates_store);
+}
+
+static void
+tmpl_folder_data_folder_changed_cb (CamelFolder *folder,
+                                   CamelFolderChangeInfo *change_info,
+                                   gpointer user_data)
+{
+       TmplFolderData *tfd = user_data;
+
+       g_return_if_fail (CAMEL_IS_FOLDER (folder));
+       g_return_if_fail (change_info != NULL);
+       g_return_if_fail (tfd != NULL);
+
+       tmpl_folder_data_ref (tfd);
+
+       if ((change_info->uid_added && change_info->uid_added->len) ||
+           (change_info->uid_changed && change_info->uid_changed->len)) {
+               tmpl_folder_data_schedule_update (tfd, change_info);
+       } else if (change_info->uid_removed && change_info->uid_removed->len) {
+               ETemplatesStore *templates_store;
+
+               templates_store = g_weak_ref_get (tfd->templates_store_weakref);
+               if (templates_store) {
+                       guint ii;
+
+                       tmpl_folder_data_lock (tfd);
+
+                       for (ii = 0; ii < change_info->uid_removed->len; ii++) {
+                               const gchar *uid = change_info->uid_removed->pdata[ii];
+
+                               if (uid && *uid)
+                                       tmpl_folder_data_remove_message (tfd, uid);
+                       }
+
+                       tmpl_folder_data_unlock (tfd);
+
+                       templates_store_emit_changed (templates_store);
+
+                       g_object_unref (templates_store);
+               }
+       }
+
+       tmpl_folder_data_unref (tfd);
+}
+
+static gboolean
+tmpl_store_data_traverse_to_free_cb (GNode *node,
+                                    gpointer user_data)
+{
+       if (node && node->data) {
+               tmpl_folder_data_unref (node->data);
+               node->data = NULL;
+       }
+
+       return FALSE;
+}
+
+typedef struct _TmplStoreData {
+       volatile gint ref_count;
+       GWeakRef *templates_store_weakref; /* ETemplatesStore * */
+       GWeakRef *store_weakref; /* CamelStore * */
+
+       gulong folder_created_handler_id;
+       gulong folder_deleted_handler_id;
+       gulong folder_renamed_handler_id;
+       gulong notify_display_name_id;
+
+       GMutex busy_lock;
+       gchar *root_folder_path;
+       gchar *templates_folder_uri;
+       gchar *identity_source_uid;
+       GNode *folders; /* data is TmplFolderData * */
+} TmplStoreData;
+
+static void
+tmpl_store_data_set_root_folder_path (TmplStoreData *tsd,
+                                     const gchar *root_folder_path);
+
+static TmplStoreData *
+tmpl_store_data_new (ETemplatesStore *templates_store,
+                    CamelStore *store,
+                    const gchar *root_folder_path,
+                    const gchar *templates_folder_uri,
+                    const gchar *identity_source_uid)
+{
+       TmplStoreData *tsd;
+
+       g_return_val_if_fail (E_IS_TEMPLATES_STORE (templates_store), NULL);
+       g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+       g_return_val_if_fail (root_folder_path && *root_folder_path, NULL);
+       g_return_val_if_fail (templates_folder_uri && *templates_folder_uri, NULL);
+
+       tsd = g_new0 (TmplStoreData, 1);
+       tsd->ref_count = 1;
+       tsd->templates_store_weakref = e_weak_ref_new (templates_store);
+       tsd->store_weakref = e_weak_ref_new (store);
+       g_mutex_init (&tsd->busy_lock);
+       tsd->root_folder_path = NULL;
+       tsd->templates_folder_uri = g_strdup (templates_folder_uri);
+       tsd->identity_source_uid = g_strdup (identity_source_uid);
+       tsd->folders = g_node_new (NULL);
+
+       if (CAMEL_IS_SUBSCRIBABLE (store)) {
+               tsd->folder_created_handler_id = g_signal_connect (store, "folder-subscribed",
+                       G_CALLBACK (tmpl_store_data_folder_created_cb), tsd);
+               tsd->folder_deleted_handler_id = g_signal_connect (store, "folder-unsubscribed",
+                       G_CALLBACK (tmpl_store_data_folder_deleted_cb), tsd);
+       } else {
+               tsd->folder_created_handler_id = g_signal_connect (store, "folder-created",
+                       G_CALLBACK (tmpl_store_data_folder_created_cb), tsd);
+               tsd->folder_deleted_handler_id = g_signal_connect (store, "folder-deleted",
+                       G_CALLBACK (tmpl_store_data_folder_deleted_cb), tsd);
+       }
+
+       tsd->folder_renamed_handler_id = g_signal_connect (store, "folder-renamed",
+               G_CALLBACK (tmpl_store_data_folder_renamed_cb), tsd);
+
+       tsd->notify_display_name_id = e_signal_connect_notify (store, "notify::display-name",
+               G_CALLBACK (tmpl_store_data_notify_display_name_cb), tsd);
+
+       tmpl_store_data_set_root_folder_path (tsd, root_folder_path);
+
+       return tsd;
+}
+
+static TmplStoreData *
+tmpl_store_data_ref (TmplStoreData *tsd)
+{
+       g_return_val_if_fail (tsd != NULL, NULL);
+
+       g_atomic_int_inc (&tsd->ref_count);
+
+       return tsd;
+}
+
+static void
+tmpl_store_data_unref (gpointer ptr)
+{
+       TmplStoreData *tsd = ptr;
+
+       if (tsd) {
+               if (!g_atomic_int_dec_and_test (&tsd->ref_count))
+                       return;
+
+               if (tsd->templates_store_weakref) {
+                       e_weak_ref_free (tsd->templates_store_weakref);
+                       tsd->templates_store_weakref = NULL;
+               }
+
+               if (tsd->store_weakref) {
+                       CamelStore *store;
+
+                       store = g_weak_ref_get (tsd->store_weakref);
+                       if (store) {
+                               if (tsd->folder_created_handler_id) {
+                                       g_signal_handler_disconnect (store, tsd->folder_created_handler_id);
+                                       tsd->folder_created_handler_id = 0;
+                               }
+
+                               if (tsd->folder_deleted_handler_id) {
+                                       g_signal_handler_disconnect (store, tsd->folder_deleted_handler_id);
+                                       tsd->folder_deleted_handler_id = 0;
+                               }
+
+                               if (tsd->folder_renamed_handler_id) {
+                                       g_signal_handler_disconnect (store, tsd->folder_renamed_handler_id);
+                                       tsd->folder_renamed_handler_id = 0;
+                               }
+
+                               e_signal_disconnect_notify_handler (store, &tsd->notify_display_name_id);
+
+                               g_clear_object (&store);
+                       }
+
+                       e_weak_ref_free (tsd->store_weakref);
+                       tsd->store_weakref = NULL;
+               }
+
+               g_mutex_clear (&tsd->busy_lock);
+
+               g_free (tsd->root_folder_path);
+               tsd->root_folder_path = NULL;
+
+               g_free (tsd->templates_folder_uri);
+               tsd->templates_folder_uri = NULL;
+
+               g_free (tsd->identity_source_uid);
+               tsd->identity_source_uid = NULL;
+
+               if (tsd->folders) {
+                       g_node_traverse (tsd->folders, G_IN_ORDER, G_TRAVERSE_ALL, -1, 
tmpl_store_data_traverse_to_free_cb, NULL);
+                       g_node_destroy (tsd->folders);
+                       tsd->folders = NULL;
+               }
+
+               g_free (tsd);
+       }
+}
+
+static void
+tmpl_store_data_lock (TmplStoreData *tsd)
+{
+       g_return_if_fail (tsd != NULL);
+
+       g_mutex_lock (&tsd->busy_lock);
+}
+
+static void
+tmpl_store_data_unlock (TmplStoreData *tsd)
+{
+       g_return_if_fail (tsd != NULL);
+
+       g_mutex_unlock (&tsd->busy_lock);
+}
+
+static gint
+tmpl_store_data_compare (gconstpointer ptr1,
+                        gconstpointer ptr2,
+                        gpointer user_data)
+{
+       const TmplStoreData *tsd1 = ptr1, *tsd2 = ptr2;
+       EMailAccountStore *account_store = user_data;
+       CamelService *service1, *service2;
+       gint res;
+
+       service1 = tsd1 ? g_weak_ref_get (tsd1->store_weakref) : NULL;
+       service2 = tsd2 ? g_weak_ref_get (tsd2->store_weakref) : NULL;
+
+       if (account_store && service1 && service2)
+               res = e_mail_account_store_compare_services (account_store, service1, service2);
+       else
+               res = g_utf8_collate (service1 ? camel_service_get_display_name (service1) : "",
+                                     service2 ? camel_service_get_display_name (service2) : "");
+
+       g_clear_object (&service1);
+       g_clear_object (&service2);
+
+       return res;
+}
+
+static void
+tmpl_store_data_set_root_folder_path (TmplStoreData *tsd,
+                                     const gchar *root_folder_path)
+{
+       g_return_if_fail (tsd != NULL);
+       g_return_if_fail (root_folder_path && *root_folder_path);
+
+       tmpl_store_data_lock (tsd);
+
+       if (g_strcmp0 (tsd->root_folder_path, root_folder_path) != 0) {
+               guint len;
+
+               g_free (tsd->root_folder_path);
+               tsd->root_folder_path = g_strdup (root_folder_path);
+
+               len = strlen (tsd->root_folder_path);
+               if (tsd->root_folder_path[len - 1] == '/')
+                       tsd->root_folder_path[len - 1] = '\0';
+       }
+
+       tmpl_store_data_unlock (tsd);
+}
+
+static GNode *
+tmpl_store_data_find_parent_node_locked (TmplStoreData *tsd,
+                                        const gchar *full_name,
+                                        gboolean for_insert)
+{
+       GNode *from_node, *node, *parent;
+
+       g_return_val_if_fail (tsd != NULL, NULL);
+       g_return_val_if_fail (full_name != NULL, NULL);
+
+       parent = tsd->folders;
+       from_node = tsd->folders;
+       while (from_node) {
+               node = g_node_first_child (from_node);
+               from_node = NULL;
+
+               while (node) {
+                       TmplFolderData *tfd = node->data;
+
+                       if (tfd && tfd->folder &&
+                           g_str_has_prefix (full_name, camel_folder_get_full_name (tfd->folder)) &&
+                           g_strcmp0 (full_name, camel_folder_get_full_name (tfd->folder)) != 0) {
+                               parent = node;
+                               from_node = node;
+                               break;
+                       }
+
+                       node = g_node_next_sibling (node);
+               }
+       }
+
+       if (for_insert && parent) {
+               GNode *node;
+
+               if (parent->data) {
+                       TmplFolderData *tfd = parent->data;
+
+                       if (g_strcmp0 (full_name, camel_folder_get_full_name (tfd->folder)) == 0) {
+                               /* The folder is already in the list of folders */
+                               parent = NULL;
+                       }
+               }
+
+               for (node = parent ? parent->children : NULL; node; node = node->next) {
+                       TmplFolderData *tfd = node->data;
+
+                       if (!tfd)
+                               continue;
+
+                       if (g_strcmp0 (full_name, camel_folder_get_full_name (tfd->folder)) == 0) {
+                               /* The folder is already in the list of folders */
+                               parent = NULL;
+                               break;
+                       }
+               }
+       }
+
+       return parent;
+}
+
+static GNode *
+tmpl_store_data_find_node_locked (TmplStoreData *tsd,
+                                 const gchar *full_name)
+{
+       GNode *node, *parent;
+
+       g_return_val_if_fail (tsd != NULL, NULL);
+       g_return_val_if_fail (full_name != NULL, NULL);
+
+       parent = tmpl_store_data_find_parent_node_locked (tsd, full_name, FALSE);
+       if (!parent)
+               return NULL;
+
+       for (node = g_node_first_child (parent); node; node = g_node_next_sibling (node)) {
+               TmplFolderData *tfd = node->data;
+
+               if (!tfd)
+                       continue;
+
+               if (tfd->folder && g_strcmp0 (full_name, camel_folder_get_full_name (tfd->folder)) == 0) {
+                       return node;
+               }
+       }
+
+       return NULL;
+}
+
+static GNode *
+tmpl_store_data_find_node_with_folder_locked (TmplStoreData *tsd,
+                                             CamelFolder *folder)
+{
+       GNode *node;
+
+       g_return_val_if_fail (tsd != NULL, NULL);
+       g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+
+       node = tsd->folders;
+       while (node) {
+               TmplFolderData *tfd = node->data;
+               GNode *next;
+
+               if (tfd && tfd->folder == folder) {
+                       return node;
+               }
+
+               /* Traverse the tree */
+               next = node->children;
+               if (!next)
+                       next = node->next;
+               if (!next) {
+                       next = node->parent;
+                       while (next) {
+                               GNode *sibl = next->next;
+
+                               if (sibl) {
+                                       next = sibl;
+                                       break;
+                               } else {
+                                       next = next->parent;
+                               }
+                       }
+               }
+
+               node = next;
+       }
+
+       return NULL;
+}
+
+static void
+tmpl_store_data_update_done_cb (GObject *source,
+                               GAsyncResult *result,
+                               gpointer user_data)
+{
+       TmplStoreData *tsd = user_data;
+       GError *local_error = NULL;
+
+       g_return_if_fail (tsd != NULL);
+       g_return_if_fail (g_task_is_valid (result, source));
+
+       if (g_task_propagate_boolean (G_TASK (result), &local_error)) {
+               /* Content changed, rebuild menu when needed */
+               ETemplatesStore *templates_store;
+
+               templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+               if (templates_store) {
+                       templates_store_emit_changed (templates_store);
+                       g_object_unref (templates_store);
+               }
+       } else if (local_error) {
+               g_debug ("%s: Failed with error: %s", G_STRFUNC, local_error->message);
+       }
+
+       g_clear_error (&local_error);
+}
+
+static void
+tmpl_store_data_initial_setup_thread (GTask *task,
+                                     gpointer source_object,
+                                     gpointer task_data,
+                                     GCancellable *cancellable)
+{
+       ETemplatesStore *templates_store;
+       TmplStoreData *tsd = task_data;
+       CamelStore *store;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (tsd != NULL);
+
+       templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+       store = g_weak_ref_get (tsd->store_weakref);
+       if (store && templates_store) {
+               CamelFolderInfo *folder_info = NULL, *fi;
+               gchar *root_folder_path;
+               GError *local_error = NULL;
+
+               tmpl_store_data_lock (tsd);
+               root_folder_path = g_strdup (tsd->root_folder_path);
+               tmpl_store_data_unlock (tsd);
+
+               if (root_folder_path) {
+                       folder_info = camel_store_get_folder_info_sync (
+                               store, root_folder_path,
+                               CAMEL_STORE_FOLDER_INFO_RECURSIVE |
+                               CAMEL_STORE_FOLDER_INFO_SUBSCRIBED |
+                               CAMEL_STORE_FOLDER_INFO_FAST, cancellable, &local_error);
+
+                       if (local_error) {
+                               g_debug ("%s: Failed to get folder info for '%s : %s': %s", G_STRFUNC,
+                                       camel_service_get_display_name (CAMEL_SERVICE (store)), 
root_folder_path, local_error->message);
+                       }
+
+                       g_clear_error (&local_error);
+               }
+
+               fi = folder_info;
+               while (fi && !g_cancellable_is_cancelled (cancellable)) {
+                       CamelFolderInfo *next;
+                       CamelFolder *folder;
+
+                       folder = camel_store_get_folder_sync (store, fi->full_name, 0, cancellable, 
&local_error);
+                       if (folder) {
+                               GNode *parent;
+
+                               tmpl_store_data_lock (tsd);
+
+                               parent = tmpl_store_data_find_parent_node_locked (tsd, fi->full_name, TRUE);
+                               if (parent) {
+                                       TmplFolderData *tfd;
+
+                                       tfd = tmpl_folder_data_new (templates_store, folder);
+                                       if (tfd) {
+                                               changed = tmpl_folder_data_update_sync (tfd, NULL, NULL, 
cancellable) || changed;
+
+                                               g_node_append_data (parent, tfd);
+                                       }
+                               }
+
+                               tmpl_store_data_unlock (tsd);
+                       }
+
+                       if (local_error)
+                               g_debug ("%s: Failed to get folder '%s': %s", G_STRFUNC, fi->full_name, 
local_error->message);
+
+                       g_clear_object (&folder);
+                       g_clear_error (&local_error);
+
+                       /* Traverse the tree of folders */
+                       next = fi->child;
+                       if (!next)
+                               next = fi->next;
+                       if (!next) {
+                               next = fi->parent;
+                               while (next) {
+                                       CamelFolderInfo *sibl = next->next;
+
+                                       if (sibl) {
+                                               next = sibl;
+                                               break;
+                                       } else {
+                                               next = next->parent;
+                                       }
+                               }
+                       }
+
+                       fi = next;
+               }
+
+               camel_folder_info_free (folder_info);
+               g_free (root_folder_path);
+       }
+
+       g_clear_object (&templates_store);
+       g_clear_object (&store);
+
+       g_task_return_boolean (task, changed);
+}
+
+static void
+tmpl_store_data_schedule_initial_setup (TmplStoreData *tsd)
+{
+       ETemplatesStore *templates_store;
+       GTask *task;
+
+       g_return_if_fail (tsd != NULL);
+
+       templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+       if (!templates_store)
+               return;
+
+       tmpl_store_data_ref (tsd);
+
+       task = g_task_new (NULL, templates_store->priv->cancellable, tmpl_store_data_update_done_cb, tsd);
+       g_task_set_task_data (task, tsd, tmpl_store_data_unref);
+       g_task_run_in_thread (task, tmpl_store_data_initial_setup_thread);
+       g_object_unref (task);
+
+       g_object_unref (templates_store);
+}
+
+typedef struct _TsdFolderData {
+       TmplStoreData *tsd;
+       gchar *fullname;
+       gchar *old_fullname; /* If set, then it's "rename", otherwise it's "create" */
+} TsdFolderData;
+
+static void
+tsd_folder_data_free (gpointer ptr)
+{
+       TsdFolderData *fd = ptr;
+
+       if (fd) {
+               tmpl_store_data_unref (fd->tsd);
+               g_free (fd->fullname);
+               g_free (fd->old_fullname);
+               g_free (fd);
+       }
+}
+
+static void
+tmpl_store_data_folder_thread (GTask *task,
+                              gpointer source_object,
+                              gpointer task_data,
+                              GCancellable *cancellable)
+{
+       ETemplatesStore *templates_store;
+       TsdFolderData *fd = task_data;
+       CamelStore *store;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (fd != NULL);
+       g_return_if_fail (fd->tsd != NULL);
+       g_return_if_fail (fd->fullname != NULL);
+
+       templates_store = g_weak_ref_get (fd->tsd->templates_store_weakref);
+       store = g_weak_ref_get (fd->tsd->store_weakref);
+       if (store && templates_store) {
+               GError *local_error = NULL;
+               CamelFolder *folder;
+
+               folder = camel_store_get_folder_sync (store, fd->fullname, 0, cancellable, &local_error);
+               if (folder) {
+                       GNode *parent = NULL;
+
+                       tmpl_store_data_lock (fd->tsd);
+
+                       if (fd->old_fullname) {
+                               GNode *node;
+
+                               node = tmpl_store_data_find_node_locked (fd->tsd, fd->old_fullname);
+                               if (!node) {
+                                       /* Sometimes the CamelFolder can be renamed in-place,
+                                          thus lookup with the CamelFolder structure as well. */
+                                       node = tmpl_store_data_find_node_with_folder_locked (fd->tsd, folder);
+                               }
+                               if (node) {
+                                       TmplFolderData *tfd = node->data;
+
+                                       changed = TRUE;
+
+                                       tmpl_folder_data_lock (tfd);
+
+                                       if (tfd->folder != folder) {
+                                               g_clear_object (&tfd->folder);
+                                               tfd->folder = g_object_ref (folder);
+                                       }
+
+                                       parent = tmpl_store_data_find_parent_node_locked (fd->tsd, 
fd->fullname, FALSE);
+                                       if (parent && node->parent != parent) {
+                                               g_node_unlink (node);
+                                               g_node_append (parent, node);
+                                       }
+
+                                       tmpl_folder_data_unlock (tfd);
+                               }
+                       } else {
+                               parent = tmpl_store_data_find_parent_node_locked (fd->tsd, fd->fullname, 
TRUE);
+                               if (parent) {
+                                       TmplFolderData *tfd;
+
+                                       tfd = tmpl_folder_data_new (templates_store, folder);
+                                       if (tfd) {
+                                               changed = tmpl_folder_data_update_sync (tfd, NULL, NULL, 
cancellable);
+
+                                               g_node_append_data (parent, tfd);
+                                       }
+                               }
+                       }
+
+                       if (parent) {
+                               GSList *data = NULL, *link;
+                               GNode *node;
+
+                               for (node = parent->children; node; node = node->next) {
+                                       if (node->data)
+                                               data = g_slist_prepend (data, node->data);
+                               }
+
+                               data = g_slist_sort (data, tmpl_folder_data_compare);
+
+                               for (node = parent->children, link = data; node && link; node = node->next) {
+                                       if (node->data) {
+                                               node->data = link->data;
+                                               link = g_slist_next (link);
+                                       }
+                               }
+
+                               g_slist_free (data);
+                       }
+
+                       tmpl_store_data_unlock (fd->tsd);
+               }
+
+               if (local_error)
+                       g_debug ("%s: Failed to get folder '%s': %s", G_STRFUNC, fd->fullname, 
local_error->message);
+
+               g_clear_object (&folder);
+               g_clear_error (&local_error);
+       }
+
+       g_clear_object (&templates_store);
+       g_clear_object (&store);
+
+       g_task_return_boolean (task, changed);
+}
+
+static void
+tmpl_store_data_folder_created_cb (CamelStore *store,
+                                  CamelFolderInfo *folder_info,
+                                  gpointer user_data)
+{
+       TmplStoreData *tsd = user_data;
+       ETemplatesStore *templates_store;
+
+       g_return_if_fail (CAMEL_IS_STORE (store));
+       g_return_if_fail (folder_info != NULL);
+       g_return_if_fail (folder_info->full_name != NULL);
+       g_return_if_fail (tsd != NULL);
+
+       templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+
+       tmpl_store_data_lock (tsd);
+
+       if (templates_store && g_str_has_prefix (folder_info->full_name, tsd->root_folder_path)) {
+               GNode *parent;
+
+               parent = tmpl_store_data_find_parent_node_locked (tsd, folder_info->full_name, TRUE);
+               if (parent) {
+                       TsdFolderData *fd;
+                       GTask *task;
+
+                       fd = g_new0 (TsdFolderData, 1);
+                       fd->tsd = tmpl_store_data_ref (tsd);
+                       fd->fullname = g_strdup (folder_info->full_name);
+                       fd->old_fullname = NULL;
+
+                       task = g_task_new (NULL, templates_store->priv->cancellable, 
tmpl_store_data_update_done_cb, tsd);
+                       g_task_set_task_data (task, fd, tsd_folder_data_free);
+                       g_task_run_in_thread (task, tmpl_store_data_folder_thread);
+                       g_object_unref (task);
+               }
+       }
+
+       tmpl_store_data_unlock (tsd);
+       g_clear_object (&templates_store);
+}
+
+static void
+tmpl_store_data_folder_deleted_cb (CamelStore *store,
+                                  CamelFolderInfo *folder_info,
+                                  gpointer user_data)
+{
+       TmplStoreData *tsd = user_data;
+       ETemplatesStore *templates_store;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (CAMEL_IS_STORE (store));
+       g_return_if_fail (folder_info != NULL);
+       g_return_if_fail (tsd != NULL);
+
+       templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+
+       tmpl_store_data_lock (tsd);
+
+       if (templates_store && g_str_has_prefix (folder_info->full_name, tsd->root_folder_path)) {
+               GNode *node;
+
+               node = tmpl_store_data_find_node_locked (tsd, folder_info->full_name);
+               if (node) {
+                       g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_ALL, -1, 
tmpl_store_data_traverse_to_free_cb, NULL);
+                       g_node_destroy (node);
+
+                       changed = TRUE;
+               }
+       }
+
+       tmpl_store_data_unlock (tsd);
+
+       if (changed)
+               templates_store_emit_changed (templates_store);
+
+       g_clear_object (&templates_store);
+}
+
+static void
+tmpl_store_data_folder_renamed_cb (CamelStore *store,
+                                  const gchar *old_name,
+                                  CamelFolderInfo *folder_info,
+                                  gpointer user_data)
+{
+       TmplStoreData *tsd = user_data;
+       ETemplatesStore *templates_store;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (CAMEL_IS_STORE (store));
+       g_return_if_fail (old_name != NULL);
+       g_return_if_fail (folder_info != NULL);
+       g_return_if_fail (tsd != NULL);
+
+       templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+
+       tmpl_store_data_lock (tsd);
+
+       if (templates_store && g_str_has_prefix (old_name, tsd->root_folder_path)) {
+               if (g_str_has_prefix (folder_info->full_name, tsd->root_folder_path)) {
+                       TsdFolderData *fd;
+                       GTask *task;
+
+                       fd = g_new0 (TsdFolderData, 1);
+                       fd->tsd = tmpl_store_data_ref (tsd);
+                       fd->fullname = g_strdup (folder_info->full_name);
+                       fd->old_fullname = g_strdup (old_name);
+
+                       task = g_task_new (NULL, templates_store->priv->cancellable, 
tmpl_store_data_update_done_cb, tsd);
+                       g_task_set_task_data (task, fd, tsd_folder_data_free);
+                       g_task_run_in_thread (task, tmpl_store_data_folder_thread);
+                       g_object_unref (task);
+               } else {
+                       GNode *node;
+
+                       node = tmpl_store_data_find_node_locked (tsd, old_name);
+                       if (node) {
+                               g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_ALL, -1, 
tmpl_store_data_traverse_to_free_cb, NULL);
+                               g_node_destroy (node);
+
+                               changed = TRUE;
+                       }
+               }
+       } else if (templates_store && g_str_has_prefix (folder_info->full_name, tsd->root_folder_path)) {
+               TsdFolderData *fd;
+               GTask *task;
+
+               fd = g_new0 (TsdFolderData, 1);
+               fd->tsd = tmpl_store_data_ref (tsd);
+               fd->fullname = g_strdup (folder_info->full_name);
+               fd->old_fullname = NULL;
+
+               task = g_task_new (NULL, templates_store->priv->cancellable, tmpl_store_data_update_done_cb, 
tsd);
+               g_task_set_task_data (task, fd, tsd_folder_data_free);
+               g_task_run_in_thread (task, tmpl_store_data_folder_thread);
+               g_object_unref (task);
+       }
+
+       tmpl_store_data_unlock (tsd);
+
+       if (changed)
+               templates_store_emit_changed (templates_store);
+
+       g_clear_object (&templates_store);
+}
+
+static void
+tmpl_store_data_notify_display_name_cb (CamelService *service,
+                                       GParamSpec *param,
+                                       gpointer user_data)
+{
+       TmplStoreData *tsd = user_data;
+       ETemplatesStore *templates_store;
+
+       g_return_if_fail (CAMEL_IS_SERVICE (service));
+       g_return_if_fail (tsd != NULL);
+
+       templates_store = g_weak_ref_get (tsd->templates_store_weakref);
+       if (templates_store) {
+               EMailAccountStore *account_store;
+               gboolean changed = FALSE;
+
+               account_store = e_templates_store_ref_account_store (templates_store);
+
+               templates_store_lock (templates_store);
+
+               changed = templates_store->priv->stores && templates_store->priv->stores->next;
+               templates_store->priv->stores = g_slist_sort_with_data (templates_store->priv->stores,
+                       tmpl_store_data_compare, account_store);
+
+               templates_store_unlock (templates_store);
+
+               if (changed)
+                       templates_store_emit_changed (templates_store);
+
+               g_object_unref (templates_store);
+               g_clear_object (&account_store);
+       }
+}
+
+static gchar *
+templates_store_find_custom_templates_root_folder_path (ETemplatesStore *templates_store,
+                                                       CamelStore *store,
+                                                       EMailSession *mail_session,
+                                                       ESource **out_identity_source,
+                                                       CamelStore **out_use_store,
+                                                       gchar **out_templates_folder_uri)
+{
+       ESource *identity_source;
+       gchar *root_folder_path = NULL;
+
+       g_return_val_if_fail (E_IS_TEMPLATES_STORE (templates_store), NULL);
+       g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+       g_return_val_if_fail (out_identity_source != NULL, NULL);
+       g_return_val_if_fail (out_use_store != NULL, NULL);
+       g_return_val_if_fail (out_templates_folder_uri != NULL, NULL);
+
+       *out_identity_source = NULL;
+       *out_use_store = NULL;
+       *out_templates_folder_uri = NULL;
+
+       if (g_strcmp0 (E_MAIL_SESSION_LOCAL_UID, camel_service_get_uid (CAMEL_SERVICE (store))) == 0) {
+               *out_templates_folder_uri = g_strdup (e_mail_session_get_local_folder_uri (mail_session, 
E_MAIL_LOCAL_FOLDER_TEMPLATES));
+               return g_strdup ("Templates");
+       }
+
+       identity_source = em_utils_ref_mail_identity_for_store (e_mail_session_get_registry (mail_session), 
store);
+       if (identity_source) {
+               if (e_source_has_extension (identity_source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
+                       ESourceMailComposition *mail_composition;
+                       CamelStore *templates_store = NULL;
+                       gchar *templates_folder;
+                       GError *local_error = NULL;
+
+                       mail_composition = e_source_get_extension (identity_source, 
E_SOURCE_EXTENSION_MAIL_COMPOSITION);
+                       templates_folder = e_source_mail_composition_dup_templates_folder (mail_composition);
+
+                       if (templates_folder && *templates_folder &&
+                           g_strcmp0 (templates_folder, e_mail_session_get_local_folder_uri (mail_session, 
E_MAIL_LOCAL_FOLDER_TEMPLATES)) != 0 &&
+                           e_mail_folder_uri_parse (CAMEL_SESSION (mail_session), templates_folder, 
&templates_store, &root_folder_path, &local_error)) {
+                               if (g_strcmp0 (E_MAIL_SESSION_LOCAL_UID, camel_service_get_uid (CAMEL_SERVICE 
(templates_store))) == 0 &&
+                                   g_strcmp0 (root_folder_path, "Templates") == 0) {
+                                       g_free (root_folder_path);
+                                       root_folder_path = NULL;
+                               } else {
+                                       *out_identity_source = g_object_ref (identity_source);
+                                       *out_use_store = g_object_ref (templates_store);
+                                       *out_templates_folder_uri = g_strdup (templates_folder);
+                               }
+
+                               g_clear_object (&templates_store);
+                       }
+
+                       if (local_error) {
+                               g_debug ("%s: Failed to parse templates folder URI '%s': %s", G_STRFUNC, 
templates_folder, local_error->message);
+                               g_clear_error (&local_error);
+                       }
+
+                       g_free (templates_folder);
+               }
+       }
+
+       g_clear_object (&identity_source);
+
+       return root_folder_path;
+}
+
+static void
+templates_store_maybe_add_store (ETemplatesStore *templates_store,
+                                CamelStore *store)
+{
+       ESource *identity_source = NULL;
+       EMailAccountStore *account_store;
+       EMailSession *mail_session;
+       CamelStore *use_store = NULL;
+       gchar *root_folder_path, *templates_folder_uri = NULL;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+       g_return_if_fail (CAMEL_IS_STORE (store));
+
+       account_store = e_templates_store_ref_account_store (templates_store);
+       if (!account_store)
+               return;
+
+       mail_session = e_mail_account_store_get_session (account_store);
+
+       templates_store_lock (templates_store);
+
+       root_folder_path = templates_store_find_custom_templates_root_folder_path (
+               templates_store, store, mail_session, &identity_source, &use_store, &templates_folder_uri);
+
+       if (root_folder_path) {
+               TmplStoreData *tsd;
+               GSList *link;
+
+               for (link = templates_store->priv->stores; link; link = g_slist_next (link)) {
+                       CamelStore *tsd_store;
+
+                       tsd = link->data;
+
+                       if (!tsd)
+                               continue;
+
+                       tsd_store = g_weak_ref_get (tsd->store_weakref);
+                       if (tsd_store == (use_store ? use_store : store) &&
+                           g_strcmp0 (tsd->root_folder_path, root_folder_path) == 0) {
+                               g_clear_object (&tsd_store);
+                               break;
+                       }
+
+                       g_clear_object (&tsd_store);
+               }
+
+               /* The store is not in the list of stores yet */
+               if (!link) {
+                       tsd = tmpl_store_data_new (templates_store, use_store ? use_store : store, 
root_folder_path,
+                               templates_folder_uri, identity_source ? e_source_get_uid (identity_source) : 
NULL);
+
+                       templates_store->priv->stores = g_slist_insert_sorted_with_data 
(templates_store->priv->stores,
+                               tsd, tmpl_store_data_compare, account_store);
+
+                       tmpl_store_data_schedule_initial_setup (tsd);
+
+                       changed = TRUE;
+               }
+       }
+
+       templates_store_unlock (templates_store);
+
+       if (changed)
+               templates_store_emit_changed (templates_store);
+
+       g_free (root_folder_path);
+       g_free (templates_folder_uri);
+       g_clear_object (&use_store);
+       g_clear_object (&identity_source);
+       g_clear_object (&account_store);
+}
+
+static void
+templates_store_maybe_remove_store (ETemplatesStore *templates_store,
+                                   CamelStore *store)
+{
+       GSList *link;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+       g_return_if_fail (CAMEL_IS_STORE (store));
+
+       templates_store_lock (templates_store);
+
+       for (link = templates_store->priv->stores; link && !changed; link = g_slist_next (link)) {
+               TmplStoreData *tsd = link->data;
+               CamelStore *other_store;
+
+               if (!tsd)
+                       continue;
+
+               other_store = g_weak_ref_get (tsd->store_weakref);
+               if (other_store == store) {
+                       changed = TRUE;
+                       templates_store->priv->stores = g_slist_remove (templates_store->priv->stores, tsd);
+                       tmpl_store_data_unref (tsd);
+
+                       g_object_unref (other_store);
+                       break;
+               }
+
+               g_clear_object (&other_store);
+       }
+
+       templates_store_unlock (templates_store);
+
+       if (changed)
+               templates_store_emit_changed (templates_store);
+}
+
+static void
+templates_store_maybe_add_enabled_services (ETemplatesStore *templates_store)
+{
+       EMailAccountStore *account_store;
+       GQueue queue = G_QUEUE_INIT;
+
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+       g_return_if_fail (templates_store->priv->stores == NULL);
+
+       account_store = e_templates_store_ref_account_store (templates_store);
+       g_return_if_fail (account_store != NULL);
+
+       e_mail_account_store_queue_enabled_services (account_store, &queue);
+
+       while (!g_queue_is_empty (&queue)) {
+               CamelService *service;
+
+               service = g_queue_pop_head (&queue);
+
+               if (CAMEL_IS_STORE (service))
+                       templates_store_maybe_add_store (templates_store, CAMEL_STORE (service));
+       }
+
+       g_clear_object (&account_store);
+}
+
+static void
+templates_store_service_enabled_cb (EMailAccountStore *account_store,
+                                   CamelService *service,
+                                   GWeakRef *weak_ref)
+{
+       ETemplatesStore *templates_store;
+
+       if (!CAMEL_IS_STORE (service))
+               return;
+
+       templates_store = g_weak_ref_get (weak_ref);
+
+       if (templates_store) {
+               templates_store_maybe_add_store (templates_store, CAMEL_STORE (service));
+               g_object_unref (templates_store);
+       }
+}
+
+static void
+templates_store_service_disabled_cb (EMailAccountStore *account_store,
+                                    CamelService *service,
+                                    GWeakRef *weak_ref)
+{
+       ETemplatesStore *templates_store;
+
+       if (!CAMEL_IS_STORE (service))
+               return;
+
+       templates_store = g_weak_ref_get (weak_ref);
+
+       if (templates_store) {
+               templates_store_maybe_remove_store (templates_store, CAMEL_STORE (service));
+               g_object_unref (templates_store);
+       }
+}
+
+static void
+templates_store_service_removed_cb (EMailAccountStore *account_store,
+                                   CamelService *service,
+                                   GWeakRef *weak_ref)
+{
+       ETemplatesStore *templates_store;
+
+       if (!CAMEL_IS_STORE (service))
+               return;
+
+       templates_store = g_weak_ref_get (weak_ref);
+
+       if (templates_store) {
+               templates_store_maybe_remove_store (templates_store, CAMEL_STORE (service));
+               g_object_unref (templates_store);
+       }
+}
+
+static void
+templates_store_source_changed_cb (ESourceRegistry *registry,
+                                  ESource *source,
+                                  GWeakRef *weak_ref)
+{
+       ETemplatesStore *templates_store;
+
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       if (!e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION))
+               return;
+
+       templates_store = g_weak_ref_get (weak_ref);
+
+       if (templates_store) {
+               TmplStoreData *corresponding_tsd = NULL;
+               ESourceMailComposition *mail_composition;
+               gboolean rebuild_all = FALSE;
+               gchar *templates_folder;
+               GSList *link;
+
+               mail_composition = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
+               templates_folder = e_source_mail_composition_dup_templates_folder (mail_composition);
+
+               templates_store_lock (templates_store);
+
+               for (link = templates_store->priv->stores; link; link = g_slist_next (link)) {
+                       TmplStoreData *tsd = link->data;
+
+                       if (!tsd)
+                               continue;
+
+                       if (g_strcmp0 (tsd->identity_source_uid, e_source_get_uid (source)) == 0) {
+                               g_warn_if_fail (!corresponding_tsd);
+                               corresponding_tsd = tsd;
+                               break;
+                       }
+               }
+
+               if (corresponding_tsd) {
+                       if (g_strcmp0 (templates_folder, corresponding_tsd->templates_folder_uri) != 0) {
+                               /* Should not happen that often (inefficient, but avoids code complexity). */
+                               rebuild_all = TRUE;
+                       }
+               } else if (templates_folder && *templates_folder) {
+                       EMailAccountStore *account_store;
+                       EMailSession *mail_session;
+                       CamelStore *found_store = NULL;
+                       gchar *root_folder_path = NULL;
+                       GError *local_error = NULL;
+
+                       account_store = g_weak_ref_get (templates_store->priv->account_store_weakref);
+
+                       if (account_store && (mail_session = e_mail_account_store_get_session 
(account_store)) != NULL &&
+                           g_strcmp0 (templates_folder, e_mail_session_get_local_folder_uri (mail_session, 
E_MAIL_LOCAL_FOLDER_TEMPLATES)) != 0 &&
+                           e_mail_folder_uri_parse (CAMEL_SESSION (mail_session), templates_folder, 
&found_store, &root_folder_path, &local_error)) {
+                               if (g_strcmp0 (E_MAIL_SESSION_LOCAL_UID, camel_service_get_uid (CAMEL_SERVICE 
(found_store))) == 0 &&
+                                   g_strcmp0 (root_folder_path, "Templates") == 0) {
+                                       g_free (root_folder_path);
+                                       root_folder_path = NULL;
+                               } else {
+                                       /* One of the templates folders had been changed to a real 
non-default folder;
+                                          rebuild everything in this case (inefficient, but avoids code 
complexity). */
+                                       rebuild_all = TRUE;
+                               }
+                       }
+
+                       if (local_error) {
+                               g_debug ("%s: Failed to parse templates folder URI '%s': %s", G_STRFUNC, 
templates_folder, local_error->message);
+                               g_clear_error (&local_error);
+                       }
+
+                       g_clear_object (&found_store);
+                       g_clear_object (&account_store);
+                       g_free (root_folder_path);
+               }
+
+               if (rebuild_all) {
+                       g_slist_free_full (templates_store->priv->stores, tmpl_store_data_unref);
+                       templates_store->priv->stores = NULL;
+               }
+
+               templates_store_unlock (templates_store);
+
+               if (rebuild_all)
+                       templates_store_maybe_add_enabled_services (templates_store);
+
+               g_object_unref (templates_store);
+               g_free (templates_folder);
+       }
+}
+
+static void
+templates_store_set_account_store (ETemplatesStore *templates_store,
+                                  EMailAccountStore *account_store)
+{
+       g_return_if_fail (E_IS_MAIL_ACCOUNT_STORE (account_store));
+
+       g_weak_ref_set (templates_store->priv->account_store_weakref, account_store);
+}
+
+static void
+templates_store_set_property (GObject *object,
+                             guint property_id,
+                             const GValue *value,
+                             GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ACCOUNT_STORE:
+                       templates_store_set_account_store (
+                               E_TEMPLATES_STORE (object),
+                               g_value_get_object (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+templates_store_get_property (GObject *object,
+                             guint property_id,
+                             GValue *value,
+                             GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ACCOUNT_STORE:
+                       g_value_take_object (
+                               value,
+                               e_templates_store_ref_account_store (
+                               E_TEMPLATES_STORE (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+templates_store_dispose (GObject *object)
+{
+       ETemplatesStore *templates_store;
+       EMailAccountStore *account_store;
+
+       templates_store = E_TEMPLATES_STORE (object);
+
+       account_store = e_templates_store_ref_account_store (templates_store);
+
+       if (account_store) {
+               if (templates_store->priv->service_enabled_handler_id) {
+                       g_signal_handler_disconnect (account_store, 
templates_store->priv->service_enabled_handler_id);
+                       templates_store->priv->service_enabled_handler_id = 0;
+               }
+
+               if (templates_store->priv->service_disabled_handler_id) {
+                       g_signal_handler_disconnect (account_store, 
templates_store->priv->service_disabled_handler_id);
+                       templates_store->priv->service_disabled_handler_id = 0;
+               }
+
+               if (templates_store->priv->service_removed_handler_id) {
+                       g_signal_handler_disconnect (account_store, 
templates_store->priv->service_removed_handler_id);
+                       templates_store->priv->service_removed_handler_id = 0;
+               }
+
+               if (templates_store->priv->source_changed_handler_id) {
+                       EMailSession *session;
+                       ESourceRegistry *registry;
+
+                       session = e_mail_account_store_get_session (account_store);
+                       registry = e_mail_session_get_registry (session);
+
+                       g_signal_handler_disconnect (registry, 
templates_store->priv->source_changed_handler_id);
+                       templates_store->priv->source_changed_handler_id = 0;
+               }
+       }
+
+       if (templates_store->priv->cancellable) {
+               g_cancellable_cancel (templates_store->priv->cancellable);
+               g_clear_object (&templates_store->priv->cancellable);
+       }
+
+       g_clear_object (&account_store);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_templates_store_parent_class)->dispose (object);
+}
+
+static void
+templates_store_finalize (GObject *object)
+{
+       ETemplatesStore *templates_store;
+
+       templates_store = E_TEMPLATES_STORE (object);
+
+       g_slist_free_full (templates_store->priv->stores, tmpl_store_data_unref);
+       templates_store->priv->stores = NULL;
+
+       e_weak_ref_free (templates_store->priv->account_store_weakref);
+       templates_store->priv->account_store_weakref = NULL;
+
+       g_mutex_clear (&templates_store->priv->busy_lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_templates_store_parent_class)->finalize (object);
+}
+
+static void
+templates_store_constructed (GObject *object)
+{
+       ETemplatesStore *templates_store;
+       ESourceRegistry *registry;
+       EMailAccountStore *account_store;
+       EMailSession *session;
+
+       templates_store = E_TEMPLATES_STORE (object);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_templates_store_parent_class)->constructed (object);
+
+       templates_store->priv->cancellable = g_cancellable_new ();
+
+       account_store = e_templates_store_ref_account_store (templates_store);
+       g_return_if_fail (account_store != NULL);
+
+       session = e_mail_account_store_get_session (account_store);
+       registry = e_mail_session_get_registry (session);
+
+       templates_store->priv->service_enabled_handler_id = g_signal_connect_data (
+               account_store, "service-enabled",
+               G_CALLBACK (templates_store_service_enabled_cb),
+               e_weak_ref_new (templates_store),
+               (GClosureNotify) e_weak_ref_free, 0);
+
+       templates_store->priv->service_disabled_handler_id = g_signal_connect_data (
+               account_store, "service-disabled",
+               G_CALLBACK (templates_store_service_disabled_cb),
+               e_weak_ref_new (templates_store),
+               (GClosureNotify) e_weak_ref_free, 0);
+
+       templates_store->priv->service_removed_handler_id = g_signal_connect_data (
+               account_store, "service-removed",
+               G_CALLBACK (templates_store_service_removed_cb),
+               e_weak_ref_new (templates_store),
+               (GClosureNotify) e_weak_ref_free, 0);
+
+       templates_store->priv->source_changed_handler_id = g_signal_connect_data (
+               registry, "source-changed",
+               G_CALLBACK (templates_store_source_changed_cb),
+               e_weak_ref_new (templates_store),
+               (GClosureNotify) e_weak_ref_free, 0);
+
+       templates_store_maybe_add_enabled_services (templates_store);
+
+       g_clear_object (&account_store);
+}
+
+static void
+e_templates_store_class_init (ETemplatesStoreClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (ETemplatesStorePrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = templates_store_set_property;
+       object_class->get_property = templates_store_get_property;
+       object_class->dispose = templates_store_dispose;
+       object_class->finalize = templates_store_finalize;
+       object_class->constructed = templates_store_constructed;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_ACCOUNT_STORE,
+               g_param_spec_object (
+                       "account-store",
+                       "Account Store",
+                       "EMailAccountStore",
+                       E_TYPE_MAIL_ACCOUNT_STORE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       signals[CHANGED] = g_signal_new (
+               "changed",
+               G_TYPE_FROM_CLASS (class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ETemplatesStoreClass, changed),
+               NULL, NULL, NULL,
+               G_TYPE_NONE, 0, G_TYPE_NONE);
+}
+
+static void
+e_templates_store_init (ETemplatesStore *templates_store)
+{
+       templates_store->priv = G_TYPE_INSTANCE_GET_PRIVATE (templates_store, E_TYPE_TEMPLATES_STORE, 
ETemplatesStorePrivate);
+
+       g_mutex_init (&templates_store->priv->busy_lock);
+       templates_store->priv->account_store_weakref = e_weak_ref_new (NULL);
+}
+
+ETemplatesStore *
+e_templates_store_ref_default (EMailAccountStore *account_store)
+{
+       static gpointer def_templates_store = NULL;
+
+       g_return_val_if_fail (E_IS_MAIL_ACCOUNT_STORE (account_store), NULL);
+
+       if (def_templates_store) {
+               g_object_ref (def_templates_store);
+       } else {
+               def_templates_store = g_object_new (E_TYPE_TEMPLATES_STORE,
+                       "account-store", account_store,
+                       NULL);
+
+               g_object_add_weak_pointer (G_OBJECT (def_templates_store), &def_templates_store);
+       }
+
+       return def_templates_store;
+}
+
+EMailAccountStore *
+e_templates_store_ref_account_store (ETemplatesStore *templates_store)
+{
+       g_return_val_if_fail (E_IS_TEMPLATES_STORE (templates_store), NULL);
+
+       return g_weak_ref_get (templates_store->priv->account_store_weakref);
+}
+
+static gboolean
+tmpl_store_data_folder_has_messages_cb (GNode *node,
+                                       gpointer user_data)
+{
+       TmplFolderData *tfd;
+       gint *pmultiple_accounts = user_data;
+
+       g_return_val_if_fail (node != NULL, TRUE);
+       g_return_val_if_fail (pmultiple_accounts != NULL, TRUE);
+
+       if (!node->data)
+               return FALSE;
+
+       tfd = node->data;
+
+       if (tfd->messages) {
+               *pmultiple_accounts = *pmultiple_accounts + 1;
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+typedef struct _TmplActionData {
+       ETemplatesStore *templates_store; /* not referenced */
+       CamelFolder *folder;
+       const gchar *uid; /* from camel_pstring */
+       ETemplatesStoreActionFunc action_cb;
+       gpointer action_cb_user_data;
+} TmplActionData;
+
+static TmplActionData *
+tmpl_action_data_new (ETemplatesStore *templates_store,
+                     CamelFolder *folder,
+                     const gchar *uid,
+                     ETemplatesStoreActionFunc action_cb,
+                     gpointer action_cb_user_data)
+{
+       TmplActionData *tad;
+
+       g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+       g_return_val_if_fail (uid && *uid, NULL);
+
+       tad = g_new0 (TmplActionData, 1);
+       tad->templates_store = templates_store;
+       tad->folder = g_object_ref (folder);
+       tad->uid = camel_pstring_strdup (uid);
+       tad->action_cb = action_cb;
+       tad->action_cb_user_data = action_cb_user_data;
+
+       return tad;
+}
+
+static void
+tmpl_action_data_free (gpointer ptr)
+{
+       TmplActionData *tad = ptr;
+
+       if (tad) {
+               g_clear_object (&tad->folder);
+               camel_pstring_free (tad->uid);
+               g_free (tad);
+       }
+}
+
+static void
+templates_store_action_activated_cb (GtkAction *action,
+                                    TmplActionData *tad)
+{
+       g_return_if_fail (tad != NULL);
+       g_return_if_fail (tad->action_cb != NULL);
+
+       tad->action_cb (tad->templates_store, tad->folder, tad->uid, tad->action_cb_user_data);
+}
+
+static void
+templates_store_add_to_menu_recurse (ETemplatesStore *templates_store,
+                                    GNode *node,
+                                    GtkUIManager *ui_manager,
+                                    GtkActionGroup *action_group,
+                                    const gchar *base_menu_path,
+                                    guint merge_id,
+                                    ETemplatesStoreActionFunc action_cb,
+                                    gpointer action_cb_user_data,
+                                    gboolean with_folder_menu,
+                                    guint *action_count)
+{
+       TmplFolderData *tfd;
+
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+       g_return_if_fail (node != NULL);
+
+       while (node) {
+               tfd = node->data;
+               if (tfd) {
+                       tmpl_folder_data_lock (tfd);
+
+                       if (tfd->folder) {
+                               GtkAction *action;
+                               gchar *action_name, *menu_path = NULL;
+                               const gchar *use_menu_path;
+                               GSList *link;
+
+                               if (with_folder_menu) {
+                                       action_name = g_strdup_printf ("templates-menu-%d", *action_count);
+                                       *action_count = *action_count + 1;
+
+                                       action = gtk_action_new (action_name, camel_folder_get_display_name 
(tfd->folder), NULL, NULL);
+                                       gtk_action_group_add_action (action_group, action);
+
+                                       gtk_ui_manager_add_ui (ui_manager, merge_id, base_menu_path, 
action_name,
+                                               action_name, GTK_UI_MANAGER_MENU, FALSE);
+
+                                       menu_path = g_strdup_printf ("%s/%s", base_menu_path, action_name);
+                                       use_menu_path = menu_path;
+
+                                       g_object_unref (action);
+                                       g_free (action_name);
+                               } else {
+                                       use_menu_path = base_menu_path;
+                               }
+
+                               if (node->children) {
+                                       templates_store_add_to_menu_recurse (templates_store, node->children,
+                                               ui_manager, action_group, use_menu_path, merge_id,
+                                               action_cb, action_cb_user_data, TRUE, action_count);
+                               }
+
+                               for (link = tfd->messages; link; link = g_slist_next (link)) {
+                                       TmplMessageData *tmd = link->data;
+
+                                       if (tmd && tmd->uid && tmd->subject) {
+                                               action_name = g_strdup_printf ("templates-item-%d", 
*action_count);
+                                               *action_count = *action_count + 1;
+
+                                               action = gtk_action_new (action_name, tmd->subject, NULL, 
NULL);
+
+                                               g_signal_connect_data (
+                                                       action, "activate",
+                                                       G_CALLBACK (templates_store_action_activated_cb),
+                                                       tmpl_action_data_new (templates_store, tfd->folder, 
tmd->uid, action_cb, action_cb_user_data),
+                                                       (GClosureNotify) tmpl_action_data_free, 0);
+
+                                               gtk_action_group_add_action (action_group, action);
+
+                                               gtk_ui_manager_add_ui (
+                                                       ui_manager, merge_id, use_menu_path, action_name,
+                                                       action_name, GTK_UI_MANAGER_MENUITEM, FALSE);
+
+                                               g_object_unref (action);
+                                               g_free (action_name);
+                                       }
+                               }
+
+                               g_free (menu_path);
+                       }
+
+                       tmpl_folder_data_unlock (tfd);
+               }
+
+               node = node->next;
+       }
+}
+
+void
+e_templates_store_build_menu (ETemplatesStore *templates_store,
+                             EShellView *shell_view,
+                             GtkUIManager *ui_manager,
+                             GtkActionGroup *action_group,
+                             const gchar *base_menu_path,
+                             guint merge_id,
+                             ETemplatesStoreActionFunc action_cb,
+                             gpointer action_cb_user_data)
+{
+       GSList *link;
+       GtkAction *action;
+       gint multiple_accounts = 0;
+       guint action_count = 0;
+       const gchar *main_menu_path = base_menu_path;
+       gchar *tmp_menu_path = NULL;
+       gchar *action_name;
+
+       g_return_if_fail (E_IS_TEMPLATES_STORE (templates_store));
+       g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
+       g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+       g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+       g_return_if_fail (base_menu_path != NULL);
+       g_return_if_fail (merge_id != 0);
+       g_return_if_fail (action_cb != NULL);
+
+       templates_store_lock (templates_store);
+
+       gtk_ui_manager_remove_ui (ui_manager, merge_id);
+       e_action_group_remove_all_actions (action_group);
+
+       for (link = templates_store->priv->stores; link && multiple_accounts <= 1; link = g_slist_next 
(link)) {
+               TmplStoreData *tsd = link->data;
+
+               if (!tsd)
+                       continue;
+
+               tmpl_store_data_lock (tsd);
+
+               if (tsd->folders && tsd->folders->children) {
+                       CamelStore *store;
+
+                       store = g_weak_ref_get (tsd->store_weakref);
+                       if (store) {
+                               g_node_traverse (tsd->folders, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+                                       tmpl_store_data_folder_has_messages_cb, &multiple_accounts);
+                       }
+
+                       g_clear_object (&store);
+               }
+
+               tmpl_store_data_unlock (tsd);
+       }
+
+       if (multiple_accounts > 0) {
+               action_name = g_strdup_printf ("templates-menu-%d", action_count);
+               action_count++;
+
+               action = gtk_action_new (action_name, _("Templates"), NULL, NULL);
+
+               gtk_action_group_add_action (action_group, action);
+
+               gtk_ui_manager_add_ui (
+                       ui_manager, merge_id, base_menu_path, action_name,
+                       action_name, GTK_UI_MANAGER_MENU, FALSE);
+
+               tmp_menu_path = g_strdup_printf ("%s/%s", base_menu_path, action_name);
+               main_menu_path = tmp_menu_path;
+
+               g_object_unref (action);
+               g_free (action_name);
+       }
+
+       for (link = templates_store->priv->stores; link && multiple_accounts > 0; link = g_slist_next (link)) 
{
+               TmplStoreData *tsd = link->data;
+
+               if (!tsd)
+                       continue;
+
+               tmpl_store_data_lock (tsd);
+
+               if (tsd->folders && tsd->folders->children) {
+                       CamelStore *store;
+
+                       store = g_weak_ref_get (tsd->store_weakref);
+                       if (store) {
+                               gchar *menu_path = NULL;
+                               const gchar *use_menu_path = main_menu_path;
+
+                               if (multiple_accounts > 1) {
+                                       action_name = g_strdup_printf ("templates-menu-%d", action_count);
+                                       action_count++;
+
+                                       action = gtk_action_new (action_name, camel_service_get_display_name 
(CAMEL_SERVICE (store)), NULL, NULL);
+
+                                       gtk_action_group_add_action (action_group, action);
+
+                                       gtk_ui_manager_add_ui (
+                                               ui_manager, merge_id, main_menu_path, action_name,
+                                               action_name, GTK_UI_MANAGER_MENU, FALSE);
+
+                                       menu_path = g_strdup_printf ("%s/%s", main_menu_path, action_name);
+                                       use_menu_path = menu_path;
+
+                                       g_object_unref (action);
+                                       g_free (action_name);
+                               }
+
+                               templates_store_add_to_menu_recurse (templates_store, tsd->folders->children,
+                                       ui_manager, action_group, use_menu_path, merge_id,
+                                       action_cb, action_cb_user_data, FALSE, &action_count);
+
+                               g_free (menu_path);
+                       }
+
+                       g_clear_object (&store);
+               }
+
+               tmpl_store_data_unlock (tsd);
+       }
+
+       templates_store_unlock (templates_store);
+
+       gtk_ui_manager_ensure_update (ui_manager);
+
+       g_free (tmp_menu_path);
+}
diff --git a/plugins/templates/e-templates-store.h b/plugins/templates/e-templates-store.h
new file mode 100644
index 0000000..76019c5
--- /dev/null
+++ b/plugins/templates/e-templates-store.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef E_TEMPLATES_STORE_H
+#define E_TEMPLATES_STORE_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <camel/camel.h>
+#include <libedataserver/libedataserver.h>
+#include <libemail-engine/libemail-engine.h>
+#include <mail/e-mail-account-store.h>
+#include <shell/e-shell-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TEMPLATES_STORE \
+       (e_templates_store_get_type ())
+#define E_TEMPLATES_STORE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_TEMPLATES_STORE, ETemplatesStore))
+#define E_TEMPLATES_STORE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_TEMPLATES_STORE, ETemplatesStoreClass))
+#define E_IS_TEMPLATES_STORE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_TEMPLATES_STORE))
+#define E_IS_TEMPLATES_STORE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_TEMPLATES_STORE))
+#define E_TEMPLATES_STORE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_TEMPLATES_STORE, ETemplatesStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETemplatesStore ETemplatesStore;
+typedef struct _ETemplatesStoreClass ETemplatesStoreClass;
+typedef struct _ETemplatesStorePrivate ETemplatesStorePrivate;
+
+/**
+ * ETemplatesStore:
+ *
+ * Contains only private data that should be read and manipulated using
+ * the functions below.
+ **/
+struct _ETemplatesStore {
+       GObject parent;
+       ETemplatesStorePrivate *priv;
+};
+
+struct _ETemplatesStoreClass {
+       GObjectClass parent_class;
+
+       /* Signals */
+       void            (*changed)              (ETemplatesStore *templates_store);
+};
+
+typedef void   (* ETemplatesStoreActionFunc)   (ETemplatesStore *templates_store,
+                                                CamelFolder *folder,
+                                                const gchar *message_uid,
+                                                gpointer user_data);
+
+GType          e_templates_store_get_type      (void) G_GNUC_CONST;
+ETemplatesStore *
+               e_templates_store_ref_default   (EMailAccountStore *account_store);
+EMailAccountStore *
+               e_templates_store_ref_account_store
+                                               (ETemplatesStore *templates_store);
+void           e_templates_store_build_menu    (ETemplatesStore *templates_store,
+                                                EShellView *shell_view,
+                                                GtkUIManager *ui_manager,
+                                                GtkActionGroup *action_group,
+                                                const gchar *base_menu_path,
+                                                guint merge_id,
+                                                ETemplatesStoreActionFunc action_cb,
+                                                gpointer action_cb_user_data);
+
+G_END_DECLS
+
+#endif /* E_TEMPLATES_STORE_H */
diff --git a/plugins/templates/templates.c b/plugins/templates/templates.c
index 5953f41..57229dd 100644
--- a/plugins/templates/templates.c
+++ b/plugins/templates/templates.c
@@ -34,12 +34,15 @@
 #include <shell/e-shell-view.h>
 
 #include <mail/e-mail-reader.h>
+#include <mail/e-mail-ui-session.h>
 #include <mail/em-composer-utils.h>
 #include <mail/em-utils.h>
 #include <mail/message-list.h>
 
 #include <composer/e-msg-composer.h>
 
+#include "e-templates-store.h"
+
 #define CONF_KEY_TEMPLATE_PLACEHOLDERS "template-placeholders"
 
 typedef struct _AsyncContext AsyncContext;
@@ -78,6 +81,31 @@ gboolean     init_shell_actions              (GtkUIManager *ui_manager,
 gint           e_plugin_lib_enable             (EPlugin *plugin,
                                                 gboolean enabled);
 
+#define TEMPLATES_DATA_KEY "templates::data"
+
+typedef struct _TemplatesData {
+       ETemplatesStore *templates_store;
+       gulong changed_handler_id;
+       gboolean changed;
+       guint merge_id;
+} TemplatesData;
+
+static void
+templates_data_free (gpointer ptr)
+{
+       TemplatesData *td = ptr;
+
+       if (td) {
+               if (td->templates_store && td->changed_handler_id) {
+                       g_signal_handler_disconnect (td->templates_store, td->changed_handler_id);
+                       td->changed_handler_id = 0;
+               }
+
+               g_clear_object (&td->templates_store);
+               g_free (td);
+       }
+}
+
 /* Thanks to attachment reminder plugin for this*/
 static void commit_changes (UIData *ui);
 
@@ -90,20 +118,9 @@ static void  value_cell_edited_callback (GtkCellRendererText *cell, gchar *path_
 static gboolean clue_foreach_check_isempty (GtkTreeModel *model, GtkTreePath
                                        *path, GtkTreeIter *iter, UIData *ui);
 
-static void templates_folder_msg_changed_cb (CamelFolder *folder,
-                             CamelFolderChangeInfo *change_info,
-                             EShellWindow *shell_window);
-
 static gboolean plugin_enabled;
 
 static void
-disconnect_signals_on_dispose (gpointer object_with_signal,
-                               GObject *signal_data)
-{
-       g_signal_handlers_disconnect_by_data (object_with_signal, signal_data);
-}
-
-static void
 async_context_free (AsyncContext *context)
 {
        if (context->activity != NULL)
@@ -1032,19 +1049,20 @@ template_got_source_message (CamelFolder *folder,
 }
 
 static void
-action_reply_with_template_cb (GtkAction *action,
-                               EShellView *shell_view)
+action_reply_with_template_cb (ETemplatesStore *templates_store,
+                              CamelFolder *template_folder,
+                              const gchar *template_message_uid,
+                              gpointer user_data)
 {
        EActivity *activity;
        AsyncContext *context;
        GCancellable *cancellable;
        CamelFolder *folder;
-       CamelFolder *template_folder;
+       EShellView *shell_view = user_data;
        EShellContent *shell_content;
        EMailReader *reader;
        GPtrArray *uids;
        const gchar *message_uid;
-       const gchar *template_message_uid;
 
        shell_content = e_shell_view_get_shell_content (shell_view);
        reader = E_MAIL_READER (shell_content);
@@ -1053,11 +1071,6 @@ action_reply_with_template_cb (GtkAction *action,
        g_return_if_fail (uids != NULL && uids->len == 1);
        message_uid = g_ptr_array_index (uids, 0);
 
-       template_folder = g_object_get_data (
-               G_OBJECT (action), "template-folder");
-       template_message_uid = g_object_get_data (
-               G_OBJECT (action), "template-uid");
-
        activity = e_mail_reader_new_activity (reader);
        cancellable = e_activity_get_cancellable (activity);
 
@@ -1087,196 +1100,86 @@ action_reply_with_template_cb (GtkAction *action,
        g_ptr_array_unref (uids);
 }
 
-static gint
-compare_ptr_array_uids_by_subject (gconstpointer ptr1,
-                                  gconstpointer ptr2,
-                                  gpointer user_data)
+static gchar *
+get_account_templates_folder_uri (EMsgComposer *composer)
 {
-       CamelFolderSummary *summary = user_data;
-       CamelMessageInfo *mi1, *mi2;
-       const gchar * const *puid1 = ptr1, * const *puid2 = ptr2;
-       const gchar *subject1, *subject2;
-       gint res;
-
-       if (!puid1 || !*puid1) {
-               if (!puid2 || !*puid2)
-                       return 0;
-
-               return -1;
-       } else if (!puid2 || !*puid2) {
-               return 1;
-       }
+       EComposerHeaderTable *table;
+       ESource *source;
+       const gchar *identity_uid;
+       gchar *templates_folder_uri = NULL;
 
-       mi1 = camel_folder_summary_get (summary, *puid1);
-       mi2 = camel_folder_summary_get (summary, *puid2);
+       g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
 
-       if (!mi1) {
-               if (!mi2)
-                       return 0;
+       table = e_msg_composer_get_header_table (composer);
+       identity_uid = e_composer_header_table_get_identity_uid (table);
+       source = e_composer_header_table_ref_source (table, identity_uid);
 
-               camel_message_info_unref (mi2);
-               return -1;
-       } else if (!mi2) {
-               camel_message_info_unref (mi1);
-               return 1;
-       }
+       /* Get the selected identity's preferred Templates folder. */
+       if (source != NULL) {
+               ESourceMailComposition *extension;
+               const gchar *extension_name;
 
-       subject1 = camel_message_info_subject (mi1);
-       subject2 = camel_message_info_subject (mi2);
+               extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
+               extension = e_source_get_extension (source, extension_name);
+               templates_folder_uri = e_source_mail_composition_dup_templates_folder (extension);
 
-       if (!subject1)
-               subject1 = "";
-       if (!subject2)
-               subject2 = "";
+               g_object_unref (source);
+       }
 
-       res = g_utf8_collate (subject1, subject2);
+       return templates_folder_uri;
+}
 
-       camel_message_info_unref (mi1);
-       camel_message_info_unref (mi2);
+typedef struct _SaveTemplateAsyncData {
+       EMsgComposer *composer;
+       EMailSession *session;
+       CamelMimeMessage *message;
+       CamelMessageInfo *info;
+       gchar *templates_folder_uri;
+} SaveTemplateAsyncData;
 
-       return res;
+static void
+save_template_async_data_free (gpointer ptr)
+{
+       SaveTemplateAsyncData *sta = ptr;
+
+       if (sta) {
+               g_clear_object (&sta->composer);
+               g_clear_object (&sta->session);
+               g_clear_object (&sta->message);
+               camel_message_info_unref (sta->info);
+               g_free (sta->templates_folder_uri);
+               g_free (sta);
+       }
 }
 
 static void
-build_template_menus_recurse (CamelStore *local_store,
-                              GtkUIManager *ui_manager,
-                              GtkActionGroup *action_group,
-                              const gchar *menu_path,
-                              guint *action_count,
-                              guint merge_id,
-                              CamelFolderInfo *folder_info,
-                              EShellView *shell_view)
+save_template_thread (EAlertSinkThreadJobData *job_data,
+                     gpointer user_data,
+                     GCancellable *cancellable,
+                     GError **error)
 {
-       EShellWindow *shell_window;
-
-       shell_window = e_shell_view_get_shell_window (shell_view);
-
-       while (folder_info != NULL) {
-               CamelFolder *folder;
-               GPtrArray *uids;
-               GtkAction *action;
-               const gchar *action_label;
-               const gchar *display_name;
-               gchar *action_name;
-               gchar *path;
-               guint ii;
-
-               display_name = folder_info->display_name;
-
-               /* FIXME Not passing a GCancellable or GError here. */
-               folder = camel_store_get_folder_sync (
-                       local_store, folder_info->full_name, 0, NULL, NULL);
-
-               action_name = g_strdup_printf (
-                       "templates-menu-%d", *action_count);
-               *action_count = *action_count + 1;
-
-               /* To avoid having a Templates dir, we ignore the top level */
-               if (g_str_has_suffix (display_name, "Templates"))
-                       action_label = _("Templates");
-               else
-                       action_label = display_name;
-
-               action = gtk_action_new (
-                       action_name, action_label, NULL, NULL);
-
-               gtk_action_group_add_action (action_group, action);
-
-               gtk_ui_manager_add_ui (
-                       ui_manager, merge_id, menu_path, action_name,
-                       action_name, GTK_UI_MANAGER_MENU, FALSE);
-
-               /* Disconnect previous connection to avoid possible multiple calls because
-                * folder is a persistent structure */
-               if (g_signal_handlers_disconnect_by_func (
-                       folder, G_CALLBACK (templates_folder_msg_changed_cb), shell_window))
-                       g_object_weak_unref (G_OBJECT (shell_window), disconnect_signals_on_dispose, folder);
-               g_signal_connect (
-                       folder, "changed",
-                       G_CALLBACK (templates_folder_msg_changed_cb),
-                       shell_window);
-               g_object_weak_ref (G_OBJECT (shell_window), disconnect_signals_on_dispose, folder);
-
-               path = g_strdup_printf ("%s/%s", menu_path, action_name);
-
-               g_object_unref (action);
-               g_free (action_name);
-
-               /* Add submenus, if any. */
-               if (folder_info->child != NULL)
-                       build_template_menus_recurse (
-                               local_store,
-                               ui_manager, action_group,
-                               path, action_count, merge_id,
-                               folder_info->child, shell_view);
-
-               if (!folder) {
-                       g_free (path);
-                       folder_info = folder_info->next;
-                       continue;
-               }
-
-               /* Get the UIDs for this folder and add them to the menu. */
-               uids = camel_folder_get_uids (folder);
-               if (uids && folder->summary)
-                       g_ptr_array_sort_with_data (uids, compare_ptr_array_uids_by_subject, folder->summary);
-
-               for (ii = 0; uids && ii < uids->len; ii++) {
-                       CamelMimeMessage *template;
-                       const gchar *uid = uids->pdata[ii];
-                       guint32 flags;
-
-                       /* If the UIDs is marked for deletion, skip it. */
-                       flags = camel_folder_get_message_flags (folder, uid);
-                       if (flags & CAMEL_MESSAGE_DELETED)
-                               continue;
-
-                       /* FIXME Not passing a GCancellable or GError here. */
-                       template = camel_folder_get_message_sync (
-                               folder, uid, NULL, NULL);
-
-                       /* FIXME Do something more intelligent with errors. */
-                       if (template == NULL)
-                               continue;
-
-                       action_label =
-                               camel_mime_message_get_subject (template);
-                       if (action_label == NULL || *action_label == '\0')
-                               action_label = _("No Title");
-
-                       action_name = g_strdup_printf (
-                               "templates-item-%d", *action_count);
-                       *action_count = *action_count + 1;
-
-                       action = gtk_action_new (
-                               action_name, action_label, NULL, NULL);
-
-                       g_object_set_data (G_OBJECT (action), "template-uid", (gpointer) uid);
-
-                       g_object_set_data (G_OBJECT (action), "template-folder", folder);
-
-                       g_signal_connect (
-                               action, "activate",
-                               G_CALLBACK (action_reply_with_template_cb),
-                               shell_view);
-
-                       gtk_action_group_add_action (action_group, action);
-
-                       gtk_ui_manager_add_ui (
-                               ui_manager, merge_id, path, action_name,
-                               action_name, GTK_UI_MANAGER_MENUITEM, FALSE);
-
-                       g_object_unref (action);
-                       g_free (action_name);
-                       g_object_unref (template);
-               }
-
-               camel_folder_free_uids (folder, uids);
-               g_object_unref (folder);
-               g_free (path);
+       SaveTemplateAsyncData *sta = user_data;
+       CamelFolder *templates_folder = NULL;
+
+       if (sta->templates_folder_uri && *sta->templates_folder_uri) {
+               templates_folder = e_mail_session_uri_to_folder_sync (sta->session,
+                       sta->templates_folder_uri, 0, cancellable, error);
+               if (!templates_folder)
+                       return;
+       }
 
-               folder_info = folder_info->next;
+       if (!templates_folder) {
+               e_mail_session_append_to_local_folder_sync (
+                       sta->session, E_MAIL_LOCAL_FOLDER_TEMPLATES,
+                       sta->message, sta->info,
+                       NULL, cancellable, error);
+       } else {
+               e_mail_folder_append_message_sync (
+                       templates_folder, sta->message, sta->info,
+                       NULL, cancellable, error);
        }
+
+       g_clear_object (&templates_folder);
 }
 
 static void
@@ -1287,8 +1190,11 @@ got_message_draft_cb (EMsgComposer *composer,
        EShellBackend *shell_backend;
        EMailBackend *backend;
        EMailSession *session;
+       EHTMLEditor *html_editor;
        CamelMimeMessage *message;
        CamelMessageInfo *info;
+       SaveTemplateAsyncData *sta;
+       EActivity *activity;
        GError *error = NULL;
 
        message = e_msg_composer_get_message_draft_finish (
@@ -1325,16 +1231,24 @@ got_message_draft_cb (EMsgComposer *composer,
         * which flags to modify.  In this case, ~0 means all flags.
         * So it clears all the flags and then sets SEEN and DRAFT. */
        camel_message_info_set_flags (
-               info, CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DRAFT, ~0);
+               info, CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DRAFT |
+               (camel_mime_message_has_attachment (message) ? CAMEL_MESSAGE_ATTACHMENTS : 0), ~0);
+
+       sta = g_new0 (SaveTemplateAsyncData, 1);
+       sta->composer = g_object_ref (composer);
+       sta->session = g_object_ref (session);
+       sta->message = message;
+       sta->info = info;
+       sta->templates_folder_uri = get_account_templates_folder_uri (composer);
 
-       /* FIXME Should submit an EActivity for this
-        *       operation, same as saving to Outbox. */
-       e_mail_session_append_to_local_folder (
-               session, E_MAIL_LOCAL_FOLDER_TEMPLATES,
-               message, info, G_PRIORITY_DEFAULT,
-               NULL, (GAsyncReadyCallback) NULL, NULL);
+       html_editor = e_msg_composer_get_editor (composer);
 
-       g_object_unref (message);
+       activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (html_editor),
+                       _("Saving message template"),
+                       "mail-composer:failed-save-template",
+                       NULL, save_template_thread, sta, save_template_async_data_free);
+
+       g_clear_object (&activity);
 }
 
 static void
@@ -1358,73 +1272,33 @@ static GtkActionEntry composer_entries[] = {
 };
 
 static void
-build_menu (EShellWindow *shell_window,
-            GtkActionGroup *action_group)
-{
-       EShellView *shell_view;
-       EShellBackend *shell_backend;
-       EMailBackend *backend;
-       EMailSession *session;
-       CamelFolder *folder;
-       CamelStore *local_store;
-       CamelFolderInfo *folder_info;
-       GtkUIManager *ui_manager;
-       guint merge_id;
-       guint action_count = 0;
-       const gchar *full_name;
-
-       ui_manager = e_shell_window_get_ui_manager (shell_window);
-       shell_view = e_shell_window_get_shell_view (shell_window, "mail");
-       shell_backend = e_shell_view_get_shell_backend (shell_view);
-
-       backend = E_MAIL_BACKEND (shell_backend);
-       session = e_mail_backend_get_session (backend);
-       local_store = e_mail_session_get_local_store (session);
-
-       merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action_group), "merge-id"));
-
-       /* Now recursively build template submenus in the pop-up menu. */
-       folder = e_mail_session_get_local_folder (
-               session, E_MAIL_LOCAL_FOLDER_TEMPLATES);
-       full_name = camel_folder_get_full_name (folder);
-
-       /* FIXME Not passing a GCancellable or GError here. */
-       folder_info = camel_store_get_folder_info_sync (
-               local_store, full_name,
-               CAMEL_STORE_FOLDER_INFO_RECURSIVE |
-               CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
-
-       build_template_menus_recurse (
-               local_store, ui_manager, action_group,
-               "/mail-message-popup/mail-message-templates",
-               &action_count, merge_id, folder_info,
-               shell_view);
-
-       camel_folder_info_free (folder_info);
-}
-
-static void
-update_actions_cb (EShellView *shell_view,
-                   GtkActionGroup *action_group)
+templates_update_actions_cb (EShellView *shell_view,
+                            GtkActionGroup *action_group)
 {
-       GList *list;
-       gint length;
+       TemplatesData *td;
 
        if (!plugin_enabled)
                return;
 
-       list = gtk_action_group_list_actions (action_group);
-       length = g_list_length (list);
+       td = g_object_get_data (G_OBJECT (shell_view), TEMPLATES_DATA_KEY);
+       if (td) {
+               if (td->changed) {
+                       EShellWindow *shell_window;
+                       GtkUIManager *ui_manager;
+
+                       td->changed = FALSE;
 
-       if (!length) {
-               EShellWindow *shell_window = e_shell_view_get_shell_window (shell_view);
-               build_menu (shell_window, action_group);
+                       shell_window = e_shell_view_get_shell_window (shell_view);
+                       ui_manager = e_shell_window_get_ui_manager (shell_window);
+
+                       e_templates_store_build_menu (td->templates_store, shell_view, ui_manager, 
action_group,
+                               "/mail-message-popup/mail-message-templates", td->merge_id,
+                               action_reply_with_template_cb, shell_view);
+               }
        }
 
        gtk_action_group_set_sensitive (action_group, TRUE);
        gtk_action_group_set_visible (action_group, TRUE);
-
-       g_list_free (list);
 }
 
 gboolean
@@ -1444,49 +1318,14 @@ init_composer_actions (GtkUIManager *ui_manager,
 }
 
 static void
-rebuild_template_menu (EShellWindow *shell_window)
+templates_store_changed_cb (ETemplatesStore *templates_store,
+                           gpointer user_data)
 {
-       GtkUIManager *ui_manager;
-       GtkActionGroup *action_group;
-       guint merge_id;
-
-       ui_manager = e_shell_window_get_ui_manager (shell_window);
-
-       action_group = e_lookup_action_group (ui_manager, "templates");
-       merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action_group), "merge-id"));
+       TemplatesData *td = user_data;
 
-       gtk_ui_manager_remove_ui (ui_manager, merge_id);
-       e_action_group_remove_all_actions (action_group);
-       gtk_ui_manager_ensure_update (ui_manager);
+       g_return_if_fail (td != NULL);
 
-       build_menu (shell_window, action_group);
-}
-
-static void
-templates_folder_msg_changed_cb (CamelFolder *folder,
-                                 CamelFolderChangeInfo *change_info,
-                                 EShellWindow *shell_window)
-{
-       rebuild_template_menu (shell_window);
-}
-
-static void
-templates_folder_changed_cb (CamelStore *store,
-                             CamelFolderInfo *folder_info,
-                             EShellWindow *shell_window)
-{
-       if (folder_info->full_name && strstr (folder_info->full_name, _("Templates")) != NULL)
-               rebuild_template_menu (shell_window);
-}
-
-static void
-templates_folder_renamed_cb (CamelStore *store,
-                             const gchar *old_name,
-                             CamelFolderInfo *folder_info,
-                             EShellWindow *shell_window)
-{
-       if (folder_info->full_name && strstr (folder_info->full_name, _("Templates")) != NULL)
-               rebuild_template_menu (shell_window);
+       td->changed = TRUE;
 }
 
 static void
@@ -1498,48 +1337,28 @@ mail_shell_view_created_cb (EShellWindow *shell_window,
        EShellBackend *shell_backend;
        GtkUIManager *ui_manager;
        GtkActionGroup *action_group;
-       CamelFolder *folder;
-       CamelStore *local_store;
-       guint merge_id;
+       TemplatesData *td;
 
        ui_manager = e_shell_window_get_ui_manager (shell_window);
        e_shell_window_add_action_group (shell_window, "templates");
        action_group = e_lookup_action_group (ui_manager, "templates");
 
-       merge_id = gtk_ui_manager_new_merge_id (ui_manager);
-
-       g_object_set_data (
-               G_OBJECT (action_group), "merge-id",
-               GUINT_TO_POINTER (merge_id));
-
        shell_backend = e_shell_view_get_shell_backend (shell_view);
 
        backend = E_MAIL_BACKEND (shell_backend);
        session = e_mail_backend_get_session (backend);
-       local_store = e_mail_session_get_local_store (session);
 
-       folder = e_mail_session_get_local_folder (
-               session, E_MAIL_LOCAL_FOLDER_TEMPLATES);
-
-       g_signal_connect (
-               folder, "changed",
-               G_CALLBACK (templates_folder_msg_changed_cb), shell_window);
-       g_signal_connect (
-               local_store, "folder-created",
-               G_CALLBACK (templates_folder_changed_cb), shell_window);
-       g_signal_connect (
-               local_store, "folder-deleted",
-               G_CALLBACK (templates_folder_changed_cb), shell_window);
-       g_signal_connect (
-               local_store, "folder-renamed",
-               G_CALLBACK (templates_folder_renamed_cb), shell_window);
+       td = g_new0 (TemplatesData, 1);
+       td->templates_store = e_templates_store_ref_default (e_mail_ui_session_get_account_store 
(E_MAIL_UI_SESSION (session)));
+       td->changed_handler_id = g_signal_connect (td->templates_store, "changed", G_CALLBACK 
(templates_store_changed_cb), td);
+       td->merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+       td->changed = TRUE;
 
-       g_object_weak_ref (G_OBJECT (shell_window), disconnect_signals_on_dispose, folder);
-       g_object_weak_ref (G_OBJECT (shell_window), disconnect_signals_on_dispose, local_store);
+       g_object_set_data_full (G_OBJECT (shell_view), TEMPLATES_DATA_KEY, td, templates_data_free);
 
        g_signal_connect (
                shell_view, "update-actions",
-               G_CALLBACK (update_actions_cb), action_group);
+               G_CALLBACK (templates_update_actions_cb), action_group);
 }
 
 gboolean
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4d1da85..643b2a7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -505,6 +505,7 @@ plugins/save-calendar/ical-format.c
 plugins/save-calendar/org-gnome-save-calendar.eplug.xml
 plugins/save-calendar/rdf-format.c
 plugins/save-calendar/save-calendar.c
+plugins/templates/e-templates-store.c
 plugins/templates/org-gnome-templates.eplug.xml
 plugins/templates/templates.c
 shell/e-shell-backend.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]