[evolution] Add a WebDAV browser into the Account Editor



commit ef833811ce7cc9f8839bfe22f105f20eaa14d6d7
Author: Milan Crha <mcrha redhat com>
Date:   Tue Aug 1 17:14:08 2017 +0200

    Add a WebDAV browser into the Account Editor
    
    Users can create, edit or delete books, calendars or collections
    on servers which support it.

 po/POTFILES.in                                     |    2 +
 src/e-util/CMakeLists.txt                          |    2 +
 src/e-util/e-system.error.xml                      |   12 +
 src/e-util/e-util.h                                |    1 +
 src/e-util/e-webdav-browser.c                      | 2915 ++++++++++++++++++++
 src/e-util/e-webdav-browser.h                      |   83 +
 src/modules/accounts-window/CMakeLists.txt         |    2 +
 src/modules/accounts-window/accounts-window.c      |    2 +
 .../accounts-window/e-webdav-browser-page.c        |  236 ++
 .../accounts-window/e-webdav-browser-page.h        |   29 +
 10 files changed, 3284 insertions(+), 0 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index da15e6d..b8d6340 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -285,6 +285,7 @@ src/e-util/e-tree.c
 src/e-util/e-tree-selection-model.c
 src/e-util/e-url-entry.c
 src/e-util/evolution-source-viewer.c
+src/e-util/e-webdav-browser.c
 src/e-util/e-web-view.c
 src/e-util/e-widget-undo.c
 src/e-util/filter.error.xml
@@ -383,6 +384,7 @@ src/mail/message-list.etspec
 src/mail/searchtypes.xml.in
 src/mail/vfoldertypes.xml.in
 src/modules/accounts-window/e-accounts-window-editors.c
+src/modules/accounts-window/e-webdav-browser-page.c
 src/modules/addressbook/autocompletion-config.c
 src/modules/addressbook/eab-composer-util.c
 src/modules/addressbook/e-book-shell-backend.c
diff --git a/src/e-util/CMakeLists.txt b/src/e-util/CMakeLists.txt
index 3b3740b..4b2b939 100644
--- a/src/e-util/CMakeLists.txt
+++ b/src/e-util/CMakeLists.txt
@@ -263,6 +263,7 @@ set(SOURCES
        e-unicode.c
        e-url-entry.c
        e-util-private.h
+       e-webdav-browser.c
        e-web-view-preview.c
        e-web-view.c
        e-widget-undo.c
@@ -529,6 +530,7 @@ set(HEADERS
        e-unicode.h
        e-url-entry.h
        e-util-enums.h
+       e-webdav-browser.h
        e-web-view-preview.h
        e-web-view.h
        e-widget-undo.h
diff --git a/src/e-util/e-system.error.xml b/src/e-util/e-system.error.xml
index bb88067..5e10494 100644
--- a/src/e-util/e-system.error.xml
+++ b/src/e-util/e-system.error.xml
@@ -1,5 +1,9 @@
 <?xml version="1.0"?>
 <error-list domain="system">
+  <error type="error" id="generic-error">
+    <primary>{0}</primary>
+    <secondary>{1}</secondary>
+  </error>
 
   <error id="simple-info" type="info">
     <secondary>{0}</secondary>
@@ -89,4 +93,12 @@
    <_primary>Something has gone wrong</_primary>
    <_secondary>A WebKitWebProcess crashed when displaying the content. You can try again by reopening the 
window. If the issue persists, please file a bug report in the GNOME bugzilla.</_secondary>
  </error>
+
+ <error id="prompt-delete-remote-collection" type="question" default="GTK_RESPONSE_CANCEL">
+   <_primary>Are you sure you want to delete remote collection “{0}”?</_primary>
+   <_secondary>This will permanently remove the collection “{0}” from the server. Are you sure you want to 
proceed?</_secondary>
+   <button _label="Do _Not Delete" response="GTK_RESPONSE_CANCEL"/>
+   <button _label="_Delete From Server" response="GTK_RESPONSE_YES"/>
+ </error>
+
 </error-list>
diff --git a/src/e-util/e-util.h b/src/e-util/e-util.h
index b448f5f..6d95cf3 100644
--- a/src/e-util/e-util.h
+++ b/src/e-util/e-util.h
@@ -258,6 +258,7 @@
 #include <e-util/e-url-entry.h>
 #include <e-util/e-util-enums.h>
 #include <e-util/e-util-enumtypes.h>
+#include <e-util/e-webdav-browser.h>
 #ifndef E_UTIL_INCLUDE_WITHOUT_WEBKIT
 #include <e-util/e-web-view-preview.h>
 #include <e-util/e-web-view.h>
diff --git a/src/e-util/e-webdav-browser.c b/src/e-util/e-webdav-browser.c
new file mode 100644
index 0000000..b990bdc
--- /dev/null
+++ b/src/e-util/e-webdav-browser.c
@@ -0,0 +1,2915 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION: e-webdav-browser
+ * @include: e-util/e-util.h
+ * @short_description: WebDAV server browser
+ *
+ * #EWebDAVBrowser allows to browse WebDAV servers and manage (create/edit/remove)
+ * collections there, like calendars and address books, if the server supports it.
+ **/
+
+#include "evolution-config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <libedataserver/libedataserver.h>
+#include <libedataserverui/libedataserverui.h>
+
+#include "e-activity.h"
+#include "e-activity-bar.h"
+#include "e-alert.h"
+#include "e-alert-bar.h"
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+#include "e-spell-text-view.h"
+
+#include "e-webdav-browser.h"
+
+struct _EWebDAVBrowserPrivate {
+       ECredentialsPrompter *credentials_prompter;
+
+       GMutex property_lock;
+       EWebDAVSession *session;
+       GCancellable *cancellable;
+
+       guint update_ui_id;
+       GSList *resources; /* ResourceData * */
+
+       GHashTable *href_to_reference; /* gchar *href ~> GtkTreeRowReference * */
+
+       GtkLabel *url_label;
+       GtkWidget *tree_view;
+       GtkWidget *create_book_button;
+       GtkWidget *create_calendar_button;
+       GtkWidget *create_collection_button;
+       GtkWidget *edit_button;
+       GtkWidget *delete_button;
+       /* GtkWidget *permissions_button; */
+       GtkWidget *refresh_button;
+
+       EAlertBar *alert_bar;
+       EActivityBar *activity_bar;
+
+       GtkWidget *create_edit_popover;
+       GtkWidget *create_edit_name_entry;
+       GtkWidget *create_edit_color_label;
+       GtkWidget *create_edit_color_chooser;
+       GtkWidget *create_edit_support_label;
+       GtkWidget *create_edit_event_check;
+       GtkWidget *create_edit_memo_check;
+       GtkWidget *create_edit_task_check;
+       GtkWidget *create_edit_description_label;
+       GtkWidget *create_edit_description_scrolled_window;
+       GtkWidget *create_edit_description_textview;
+       GtkWidget *create_edit_save_button;
+       GtkWidget *create_edit_hint_popover;
+       GtkWidget *create_edit_hint_label;
+};
+
+enum {
+       PROP_0,
+       PROP_CREDENTIALS_PROMPTER,
+       PROP_SOURCE
+};
+
+static void webdav_browser_alert_sink_init (EAlertSinkInterface *iface);
+static void webdav_browser_change_busy_state (EWebDAVBrowser *webdav_browser, gboolean is_busy);
+
+G_DEFINE_TYPE_WITH_CODE (EWebDAVBrowser, e_webdav_browser, GTK_TYPE_GRID,
+       G_IMPLEMENT_INTERFACE (E_TYPE_ALERT_SINK, webdav_browser_alert_sink_init))
+
+typedef enum {
+       E_EDITING_FLAG_NONE             = 0,
+       E_EDITING_FLAG_IS_LOADING_ROW   = 1 << 0,
+       E_EDITING_FLAG_HAS_OPTIONS      = 1 << 1,
+       E_EDITING_FLAG_MKCOL            = 1 << 2,
+       E_EDITING_FLAG_EXMKCOL          = 1 << 3,
+       E_EDITING_FLAG_MKCALENDAR       = 1 << 4,
+       E_EDITING_FLAG_CAN_BOOK         = 1 << 5,
+       E_EDITING_FLAG_CAN_CALENDAR     = 1 << 6,
+       E_EDITING_FLAG_CAN_ACL          = 1 << 7,
+       E_EDITING_FLAG_CAN_DELETE       = 1 << 8,
+       E_EDITING_FLAG_IS_BOOK          = 1 << 9,
+       E_EDITING_FLAG_IS_CALENDAR      = 1 << 10
+} EEditingFlags;
+
+enum {
+       COLUMN_STRING_DISPLAY_NAME = 0,
+       COLUMN_STRING_TYPE,
+       COLUMN_STRING_HREF,
+       COLUMN_STRING_DESCRIPTION,
+       COLUMN_STRING_ICON_NAME,
+       COLUMN_BOOL_ICON_VISIBLE,
+       COLUMN_RGBA_COLOR,
+       COLUMN_BOOL_COLOR_VISIBLE,
+       COLUMN_BOOL_CHILDREN_LOADED,
+       COLUMN_UINT_EDITING_FLAGS,
+       COLUMN_UINT_SUPPORTS,
+       N_COLUMNS
+};
+
+typedef struct _ResourceData {
+       guint32 editing_flags; /* bit-or of EEditingFlags */
+       EWebDAVResource *resource;
+} ResourceData;
+
+static void
+resource_data_free (gpointer ptr)
+{
+       ResourceData *rd = ptr;
+
+       if (rd) {
+               e_webdav_resource_free (rd->resource);
+               g_free (rd);
+       }
+}
+
+static gint
+resource_data_compare (gconstpointer aa,
+                      gconstpointer bb)
+{
+       const ResourceData *rda = aa, *rdb = bb;
+
+       if (!rda || !rdb) {
+               if (rda == rdb)
+                       return 0;
+               if (rda)
+                       return -1;
+
+               return 1;
+       }
+
+       g_return_val_if_fail (rda->resource != NULL, 0);
+       g_return_val_if_fail (rdb->resource != NULL, 0);
+
+       return g_strcmp0 (rda->resource->href, rdb->resource->href);
+}
+
+static void
+webdav_browser_add_alert (EWebDAVBrowser *webdav_browser,
+                         const gchar *primary_text,
+                         const gchar *secondary_text)
+{
+       EAlert *alert;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (primary_text != NULL);
+
+       alert = e_alert_new ("system:general-error", primary_text, secondary_text ? secondary_text : "", 
NULL);
+
+       e_alert_bar_add_alert (webdav_browser->priv->alert_bar, alert);
+
+       g_object_unref (alert);
+}
+
+typedef struct _LoginErrorsData {
+       EWebDAVBrowser *webdav_browser;
+       EWebDAVSession *session;
+       GCancellable *cancellable;
+       const GError *error;
+
+       gboolean run_trust_prompt;
+       gchar *certificate_pem;
+       GTlsCertificateFlags certificate_errors;
+
+       EFlag *flag;
+       gboolean repeat;
+} LoginErrorsData;
+
+static void
+webdav_browser_trust_prompt_done_cb (GObject *source_object,
+                                    GAsyncResult *result,
+                                    gpointer user_data)
+{
+       ETrustPromptResponse response = E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+       ESource *source;
+       LoginErrorsData *led = user_data;
+
+       g_return_if_fail (E_IS_SOURCE (source_object));
+       g_return_if_fail (led != NULL);
+
+       source = E_SOURCE (source_object);
+       if (e_trust_prompt_run_for_source_finish (source, result, &response, NULL) && (
+           response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
+           response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY)) {
+               led->repeat = TRUE;
+       }
+
+       e_flag_set (led->flag);
+}
+
+static void
+webdav_browser_credentials_prompt_done_cb (GObject *source_object,
+                                          GAsyncResult *result,
+                                          gpointer user_data)
+{
+       LoginErrorsData *led = user_data;
+       ENamedParameters *credentials = NULL;
+       ESource *source = NULL;
+
+       g_return_if_fail (led != NULL);
+       g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (source_object));
+
+       if (e_credentials_prompter_prompt_finish (E_CREDENTIALS_PROMPTER (source_object), result, &source, 
&credentials, NULL)) {
+               e_soup_session_set_credentials (E_SOUP_SESSION (led->session), credentials);
+               led->repeat = credentials != NULL;
+       }
+
+       e_named_parameters_free (credentials);
+
+       e_flag_set (led->flag);
+}
+
+static gboolean
+webdav_browser_manage_login_error_cb (gpointer user_data)
+{
+       LoginErrorsData *led = user_data;
+       ESource *source;
+
+       g_return_val_if_fail (led != NULL, FALSE);
+       g_return_val_if_fail (led->flag != NULL, FALSE);
+
+       source = e_soup_session_get_source (E_SOUP_SESSION (led->session));
+       if (!E_IS_SOURCE (source)) {
+               e_flag_set (led->flag);
+               return FALSE;
+       }
+
+       if (led->run_trust_prompt) {
+               GtkWindow *parent;
+               GtkWidget *widget;
+
+               widget = gtk_widget_get_toplevel (GTK_WIDGET (led->webdav_browser));
+               parent = widget ? GTK_WINDOW (widget) : NULL;
+
+               e_trust_prompt_run_for_source (parent, source, led->certificate_pem, led->certificate_errors,
+                       NULL, FALSE, led->cancellable, webdav_browser_trust_prompt_done_cb, led);
+       } else {
+               ENamedParameters *credentials;
+
+               credentials = e_soup_session_dup_credentials (E_SOUP_SESSION (led->session));
+
+               e_credentials_prompter_prompt (led->webdav_browser->priv->credentials_prompter, source,
+                       led->error ? led->error->message : NULL,
+                       credentials ? E_CREDENTIALS_PROMPTER_PROMPT_FLAG_NONE:
+                       E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_STORED_CREDENTIALS,
+                       webdav_browser_credentials_prompt_done_cb, led);
+
+               e_named_parameters_free (credentials);
+       }
+
+       return FALSE;
+}
+
+static gboolean
+webdav_browser_manage_login_errors (EWebDAVBrowser *webdav_browser,
+                                   EWebDAVSession *session,
+                                   GCancellable *cancellable,
+                                   const GError *error)
+{
+       LoginErrorsData led;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), FALSE);
+       g_return_val_if_fail (E_IS_WEBDAV_SESSION (session), FALSE);
+
+       led.webdav_browser = webdav_browser;
+       led.session = session;
+       led.cancellable = cancellable;
+       led.error = error;
+       led.run_trust_prompt = FALSE;
+       led.certificate_pem = NULL;
+       led.certificate_errors = 0;
+       led.flag = NULL;
+       led.repeat = FALSE;
+
+       if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED) &&
+           e_soup_session_get_ssl_error_details (E_SOUP_SESSION (session), &led.certificate_pem, 
&led.certificate_errors)) {
+               led.run_trust_prompt = TRUE;
+               led.flag = e_flag_new ();
+       } else if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+               led.flag = e_flag_new ();
+       }
+
+       if (led.flag) {
+               g_timeout_add (100, webdav_browser_manage_login_error_cb, &led);
+
+               e_flag_wait (led.flag);
+               e_flag_free (led.flag);
+       }
+
+       return led.repeat;
+}
+
+/* This has the property_lock already locked */
+static void
+webdav_browser_update_ui (EWebDAVBrowser *webdav_browser)
+{
+       GtkTreeModel *model;
+       GtkTreeStore *tree_store;
+       GSList *added_iters = NULL, *link;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (webdav_browser->priv->tree_view));
+       model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model));
+       tree_store = GTK_TREE_STORE (model);
+
+       webdav_browser->priv->resources = g_slist_sort (webdav_browser->priv->resources, 
resource_data_compare);
+
+       for (link = webdav_browser->priv->resources; link; link = g_slist_next (link)) {
+               ResourceData *rd = link->data;
+               GtkTreeRowReference *reference;
+               GtkTreeIter parent_iter, iter, *piter;
+               GtkTreePath *path;
+               GdkRGBA rgba;
+               GString *type_info;
+               const gchar *icon_name = NULL;
+               gchar *parent_href, *ptr;
+               gboolean has_parent_iter = FALSE, has_color, is_loaded_row = FALSE, is_existing_row = FALSE;
+               gint len;
+
+               if (!rd || !rd->resource || !rd->resource->href)
+                       continue;
+
+               parent_href = g_strdup (rd->resource->href);
+               len = strlen (parent_href);
+
+               if (len <= 0) {
+                       g_free (parent_href);
+                       continue;
+               }
+
+               parent_href[len - 1] = '\0';
+
+               ptr = strrchr (parent_href, '/');
+               if (!ptr) {
+                       g_free (parent_href);
+                       continue;
+               }
+
+               ptr[1] = '\0';
+
+               reference = g_hash_table_lookup (webdav_browser->priv->href_to_reference, parent_href);
+               if (reference) {
+                       path = gtk_tree_row_reference_get_path (reference);
+                       has_parent_iter = gtk_tree_model_get_iter (model, &parent_iter, path);
+                       g_warn_if_fail (has_parent_iter);
+                       gtk_tree_path_free (path);
+               }
+
+               reference = g_hash_table_lookup (webdav_browser->priv->href_to_reference, rd->resource->href);
+               if (reference) {
+                       path = gtk_tree_row_reference_get_path (reference);
+                       if (gtk_tree_model_get_iter (model, &iter, path)) {
+                               is_existing_row = TRUE;
+                               gtk_tree_model_get (model, &iter, COLUMN_BOOL_CHILDREN_LOADED, 
&is_loaded_row, -1);
+                       } else
+                               gtk_tree_store_append (tree_store, &iter, has_parent_iter ? &parent_iter : 
NULL);
+                       gtk_tree_path_free (path);
+               } else {
+                       gtk_tree_store_append (tree_store, &iter, has_parent_iter ? &parent_iter : NULL);
+               }
+
+               if (has_parent_iter) {
+                       gboolean is_loaded = FALSE;
+
+                       gtk_tree_model_get (model, &parent_iter, COLUMN_BOOL_CHILDREN_LOADED, &is_loaded, -1);
+
+                       if (!is_loaded) {
+                               GtkTreeIter child;
+
+                               gtk_tree_store_set (tree_store, &parent_iter, COLUMN_BOOL_CHILDREN_LOADED, 
TRUE, -1);
+
+                               path = gtk_tree_model_get_path (model, &parent_iter);
+                               if (path) {
+                                       gtk_tree_view_expand_row (GTK_TREE_VIEW 
(webdav_browser->priv->tree_view), path, FALSE);
+                                       gtk_tree_path_free (path);
+                               }
+
+                               /* And remove "Loading…" row */
+                               if (gtk_tree_model_iter_nth_child (model, &child, &parent_iter, 0)) {
+                                       do {
+                                               guint flags = E_EDITING_FLAG_NONE;
+
+                                               gtk_tree_model_get (model, &child, COLUMN_UINT_EDITING_FLAGS, 
&flags, -1);
+
+                                               if (flags == E_EDITING_FLAG_IS_LOADING_ROW) {
+                                                       gtk_tree_store_remove (tree_store, &child);
+                                                       break;
+                                               }
+                                       } while (gtk_tree_model_iter_next (model, &child));
+                               }
+                       }
+
+                       if (!(rd->editing_flags & E_EDITING_FLAG_HAS_OPTIONS)) {
+                               guint parent_editing_flags = E_EDITING_FLAG_NONE;
+
+                               gtk_tree_model_get (model, &parent_iter, COLUMN_UINT_EDITING_FLAGS, 
&parent_editing_flags, -1);
+
+                               rd->editing_flags = (parent_editing_flags & ~(E_EDITING_FLAG_IS_BOOK | 
E_EDITING_FLAG_IS_CALENDAR)) |
+                                                   (rd->editing_flags & (E_EDITING_FLAG_IS_BOOK | 
E_EDITING_FLAG_IS_CALENDAR));
+                       }
+               }
+
+               if (!is_existing_row) {
+                       piter = g_new0 (GtkTreeIter, 1);
+                       *piter = iter;
+                       added_iters = g_slist_prepend (added_iters, piter);
+               }
+
+               path = gtk_tree_model_get_path (model, &iter);
+               reference = gtk_tree_row_reference_new (model, path);
+               gtk_tree_path_free (path);
+
+               g_hash_table_insert (webdav_browser->priv->href_to_reference, g_strdup (rd->resource->href), 
reference);
+
+               type_info = g_string_new ("");
+
+               if (rd->resource->kind == E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK) {
+                       icon_name = "x-office-address-book";
+                       g_string_append (type_info, _("Address book"));
+               } else if (rd->resource->kind == E_WEBDAV_RESOURCE_KIND_CALENDAR) {
+                       icon_name = "x-office-calendar";
+
+                       #define append_if_set(_flag, _str) \
+                               if ((rd->resource->supports & (_flag)) != 0) { \
+                                       if (type_info->len) \
+                                               g_string_append (type_info, " "); \
+                                       g_string_append (type_info, _str); \
+                               }
+
+                       append_if_set (E_WEBDAV_RESOURCE_SUPPORTS_EVENTS, _("Events"));
+                       append_if_set (E_WEBDAV_RESOURCE_SUPPORTS_MEMOS, _("Memos"));
+                       append_if_set (E_WEBDAV_RESOURCE_SUPPORTS_TASKS, _("Tasks"));
+
+                       #undef append_if_set
+
+                       if (type_info->len) {
+                               g_string_prepend (type_info, " (");
+                               g_string_append (type_info, ")");
+                       }
+
+                       g_string_prepend (type_info, _("Calendar"));
+
+               } else if (rd->resource->kind == E_WEBDAV_RESOURCE_KIND_COLLECTION) {
+                       icon_name = "folder";
+                       g_string_append (type_info, _("Collection"));
+               }
+
+               has_color = rd->resource->color && gdk_rgba_parse (&rgba, rd->resource->color);
+               if (!has_color && rd->resource->color && strlen (rd->resource->color) == 9 && 
rd->resource->color[0] == '#') {
+                       rd->resource->color[7] = '\0';
+                       has_color = gdk_rgba_parse (&rgba, rd->resource->color);
+               }
+
+               gtk_tree_store_set (tree_store, &iter,
+                       COLUMN_STRING_DISPLAY_NAME, rd->resource->display_name,
+                       COLUMN_STRING_TYPE, type_info->str,
+                       COLUMN_STRING_HREF, rd->resource->href,
+                       COLUMN_STRING_DESCRIPTION, rd->resource->description,
+                       COLUMN_STRING_ICON_NAME, icon_name,
+                       COLUMN_BOOL_ICON_VISIBLE, icon_name != NULL,
+                       COLUMN_RGBA_COLOR, has_color ? &rgba : NULL,
+                       COLUMN_BOOL_COLOR_VISIBLE, has_color,
+                       COLUMN_BOOL_CHILDREN_LOADED, is_loaded_row,
+                       COLUMN_UINT_EDITING_FLAGS, rd->editing_flags,
+                       COLUMN_UINT_SUPPORTS, rd->resource->supports,
+                       -1);
+
+               g_string_free (type_info, TRUE);
+               g_free (parent_href);
+       }
+
+       g_slist_free_full (webdav_browser->priv->resources, resource_data_free);
+       webdav_browser->priv->resources = NULL;
+
+       for (link = added_iters; link; link = g_slist_next (link)) {
+               GtkTreeIter *piter = link->data;
+               gboolean is_loaded = TRUE;
+
+               gtk_tree_model_get (model, piter, COLUMN_BOOL_CHILDREN_LOADED, &is_loaded, -1);
+
+               if (!is_loaded) {
+                       GtkTreeIter iter;
+
+                       gtk_tree_store_append (tree_store, &iter, piter);
+                       gtk_tree_store_set (tree_store, &iter,
+                               COLUMN_STRING_DISPLAY_NAME, _("Loading…"),
+                               COLUMN_UINT_EDITING_FLAGS, E_EDITING_FLAG_IS_LOADING_ROW,
+                               -1);
+               }
+       }
+
+       g_slist_free_full (added_iters, g_free);
+}
+
+typedef void (* UpdateUICallback) (EWebDAVBrowser *webdav_browser, gpointer user_data);
+
+typedef struct _UpdateUIData {
+       GWeakRef *webdav_browser_weakref;
+       UpdateUICallback callback;
+       gpointer user_data;
+       GDestroyNotify free_user_data;
+} UpdateUIData;
+
+static void
+update_ui_data_free (gpointer ptr)
+{
+       UpdateUIData *uud = ptr;
+
+       if (uud) {
+               e_weak_ref_free (uud->webdav_browser_weakref);
+               if (uud->free_user_data)
+                       uud->free_user_data (uud->user_data);
+               g_free (uud);
+       }
+}
+
+static gboolean
+webdav_browser_update_ui_timeout_cb (gpointer user_data)
+{
+       UpdateUIData *uud = user_data;
+       EWebDAVBrowser *webdav_browser;
+
+       g_return_val_if_fail (uud != NULL, FALSE);
+
+       if (g_source_is_destroyed (g_main_current_source ()))
+               return FALSE;
+
+       webdav_browser = g_weak_ref_get (uud->webdav_browser_weakref);
+       if (!webdav_browser)
+               return FALSE;
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+
+       webdav_browser->priv->update_ui_id = 0;
+
+       webdav_browser_update_ui (webdav_browser);
+
+       if (uud->callback)
+               uud->callback (webdav_browser, uud->user_data);
+
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+       webdav_browser_change_busy_state (webdav_browser, FALSE);
+
+       g_object_unref (webdav_browser);
+
+       return FALSE;
+}
+
+static EWebDAVSession *
+webdav_browser_ref_session (EWebDAVBrowser *webdav_browser)
+{
+       EWebDAVSession *session;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), NULL);
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+
+       session = webdav_browser->priv->session;
+       if (session)
+               g_object_ref (session);
+
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+       return session;
+}
+
+static void
+webdav_browser_schedule_ui_update (EWebDAVBrowser *webdav_browser,
+                                  UpdateUICallback callback,
+                                  gpointer user_data,
+                                  GDestroyNotify free_user_data)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+
+       /* This should not be called recursively/multiple times when one is waiting */
+       g_warn_if_fail (!webdav_browser->priv->update_ui_id);
+
+       if (!webdav_browser->priv->update_ui_id) {
+               UpdateUIData *uud;
+
+               uud = g_new0 (UpdateUIData, 1);
+               uud->webdav_browser_weakref = e_weak_ref_new (webdav_browser);
+               uud->callback = callback;
+               uud->user_data = user_data;
+               uud->free_user_data = free_user_data;
+
+               webdav_browser->priv->update_ui_id = g_timeout_add_full (G_PRIORITY_DEFAULT, 100,
+                       webdav_browser_update_ui_timeout_cb, uud, update_ui_data_free);
+       }
+
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+}
+
+typedef struct _SearchHomeData {
+       GHashTable *covered_todo_hrefs;
+       GHashTable *covered_home_hrefs;
+       GSList *todo_hrefs;
+       GSList *home_hrefs;
+} SearchHomeData;
+
+static gboolean
+webdav_browser_search_home_hrefs_cb (EWebDAVSession *webdav,
+                                    xmlXPathContextPtr xpath_ctx,
+                                    const gchar *xpath_prop_prefix,
+                                    const SoupURI *request_uri,
+                                    const gchar *href,
+                                    guint status_code,
+                                    gpointer user_data)
+{
+       SearchHomeData *shd = user_data;
+
+       g_return_val_if_fail (shd != NULL, FALSE);
+
+       if (!xpath_prop_prefix) {
+               e_xml_xpath_context_register_namespaces (xpath_ctx,
+                       "C", E_WEBDAV_NS_CALDAV,
+                       "A", E_WEBDAV_NS_CARDDAV,
+                       NULL);
+       } else if (status_code == SOUP_STATUS_OK) {
+               xmlXPathObjectPtr xpath_obj;
+               gchar *principal_href, *full_href;
+
+               xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/A:addressbook-home-set/D:href", 
xpath_prop_prefix);
+               if (xpath_obj) {
+                       gint ii, length;
+
+                       length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
+
+                       for (ii = 0; ii < length; ii++) {
+                               gchar *home_set_href;
+
+                               full_href = NULL;
+
+                               home_set_href = e_xml_xpath_eval_as_string (xpath_ctx, 
"%s/A:addressbook-home-set/D:href[%d]", xpath_prop_prefix, ii + 1);
+                               if (home_set_href && *home_set_href) {
+                                       full_href = e_webdav_session_ensure_full_uri (webdav, request_uri, 
home_set_href);
+                                       if (full_href && *full_href && !g_hash_table_contains 
(shd->covered_home_hrefs, full_href)) {
+                                               shd->home_hrefs = g_slist_prepend (shd->home_hrefs, 
full_href);
+                                               g_hash_table_insert (shd->covered_home_hrefs, g_strdup 
(full_href), NULL);
+                                               full_href = NULL;
+                                       }
+                               }
+
+                               g_free (home_set_href);
+                               g_free (full_href);
+                       }
+
+                       xmlXPathFreeObject (xpath_obj);
+               }
+
+               xpath_obj = e_xml_xpath_eval (xpath_ctx, "%s/C:calendar-home-set/D:href", xpath_prop_prefix);
+               if (xpath_obj) {
+                       gint ii, length;
+
+                       length = xmlXPathNodeSetGetLength (xpath_obj->nodesetval);
+
+                       for (ii = 0; ii < length; ii++) {
+                               gchar *home_set_href, *full_href = NULL;
+
+                               home_set_href = e_xml_xpath_eval_as_string (xpath_ctx, 
"%s/C:calendar-home-set/D:href[%d]", xpath_prop_prefix, ii + 1);
+                               if (home_set_href && *home_set_href) {
+                                       full_href = e_webdav_session_ensure_full_uri (webdav, request_uri, 
home_set_href);
+                                       if (full_href && *full_href && !g_hash_table_contains 
(shd->covered_home_hrefs, full_href)) {
+                                               shd->home_hrefs = g_slist_prepend (shd->home_hrefs, 
full_href);
+                                               g_hash_table_insert (shd->covered_home_hrefs, g_strdup 
(full_href), NULL);
+                                               full_href = NULL;
+                                       }
+                               }
+
+                               g_free (home_set_href);
+                               g_free (full_href);
+                       }
+
+                       xmlXPathFreeObject (xpath_obj);
+               }
+
+               principal_href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:current-user-principal/D:href", 
xpath_prop_prefix);
+               if (principal_href && *principal_href) {
+                       full_href = e_webdav_session_ensure_full_uri (webdav, request_uri, principal_href);
+
+                       if (full_href && *full_href &&
+                           !g_hash_table_contains (shd->covered_todo_hrefs, full_href)) {
+                               g_hash_table_insert (shd->covered_todo_hrefs, full_href, NULL);
+                               shd->todo_hrefs = g_slist_prepend (shd->todo_hrefs, g_strdup (full_href));
+                               full_href = NULL;
+                       }
+
+                       g_free (full_href);
+                       g_free (principal_href);
+
+                       return TRUE;
+               }
+
+               g_free (principal_href);
+
+               principal_href = e_xml_xpath_eval_as_string (xpath_ctx, "%s/D:principal-URL/D:href", 
xpath_prop_prefix);
+               if (principal_href && *principal_href) {
+                       full_href = e_webdav_session_ensure_full_uri (webdav, request_uri, principal_href);
+
+                       if (full_href && *full_href &&
+                           !g_hash_table_contains (shd->covered_todo_hrefs, full_href)) {
+                               g_hash_table_insert (shd->covered_todo_hrefs, full_href, NULL);
+                               shd->todo_hrefs = g_slist_prepend (shd->todo_hrefs, g_strdup (full_href));
+                               full_href = NULL;
+                       }
+
+                       g_free (full_href);
+                       g_free (principal_href);
+
+                       return TRUE;
+               }
+
+               g_free (principal_href);
+       }
+
+       return TRUE;
+}
+
+static guint32
+webdav_browser_options_to_editing_flags (GHashTable *capabilities,
+                                        GHashTable *allows)
+{
+       guint32 editing_flags = 0;
+
+       if (!capabilities || !allows)
+               return 0;
+
+       editing_flags |= E_EDITING_FLAG_HAS_OPTIONS;
+
+       if (g_hash_table_contains (allows, SOUP_METHOD_MKCOL)) {
+               editing_flags |= E_EDITING_FLAG_MKCOL;
+
+               if (g_hash_table_contains (capabilities, E_WEBDAV_CAPABILITY_EXTENDED_MKCOL))
+                       editing_flags |= E_EDITING_FLAG_EXMKCOL;
+       }
+
+       if (g_hash_table_contains (allows, "MKCALENDAR"))
+               editing_flags |= E_EDITING_FLAG_MKCALENDAR;
+
+       if (g_hash_table_contains (capabilities, E_WEBDAV_CAPABILITY_ADDRESSBOOK))
+               editing_flags |= E_EDITING_FLAG_CAN_BOOK;
+
+       if (g_hash_table_contains (capabilities, E_WEBDAV_CAPABILITY_CALENDAR_ACCESS))
+               editing_flags |= E_EDITING_FLAG_CAN_CALENDAR;
+
+       if (g_hash_table_contains (allows, "ACL"))
+               editing_flags |= E_EDITING_FLAG_CAN_ACL;
+
+       if (g_hash_table_contains (allows, SOUP_METHOD_DELETE))
+               editing_flags |= E_EDITING_FLAG_CAN_DELETE;
+
+       return editing_flags;
+}
+
+static gboolean
+webdav_browser_gather_href_resources_sync (EWebDAVBrowser *webdav_browser,
+                                          EWebDAVSession *session,
+                                          const gchar *href,
+                                          gboolean options_first,
+                                          gboolean with_children,
+                                          GCancellable *cancellable,
+                                          GError **error)
+{
+       gboolean done = FALSE, success = TRUE;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), FALSE);
+       g_return_val_if_fail (E_IS_WEBDAV_SESSION (session), FALSE);
+       g_return_val_if_fail (href != NULL, FALSE);
+
+       while (!done && success) {
+               GSList *resources = NULL;
+               guint32 top_editing_flags = 0;
+               GError *local_error = NULL;
+
+               done = TRUE;
+
+               if (options_first) {
+                       GHashTable *capabilities = NULL;
+                       GHashTable *allows = NULL;
+
+                       /* Some servers do not allow OPTIONS on each collection, thus ignore all but login 
errors */
+                       if (!e_webdav_session_options_sync (session, href, &capabilities, &allows, 
cancellable, &local_error)) {
+                               if (webdav_browser_manage_login_errors (webdav_browser, session, cancellable, 
local_error)) {
+                                       done = FALSE;
+                                       g_clear_error (&local_error);
+                                       continue;
+                               }
+
+                               g_clear_error (&local_error);
+                       }
+
+                       top_editing_flags = webdav_browser_options_to_editing_flags (capabilities, allows);
+
+                       if (capabilities)
+                               g_hash_table_destroy (capabilities);
+                       if (allows)
+                               g_hash_table_destroy (allows);
+               }
+
+               if (e_webdav_session_list_sync (session, href, with_children ? 
E_WEBDAV_DEPTH_THIS_AND_CHILDREN : E_WEBDAV_DEPTH_THIS, E_WEBDAV_LIST_ALL,
+                       &resources, cancellable, &local_error)) {
+                       GSList *link;
+
+                       for (link = resources; link && !g_cancellable_is_cancelled (cancellable); link = 
g_slist_next (link)) {
+                               EWebDAVResource *resource = link->data;
+                               GHashTable *capabilities = NULL;
+                               GHashTable *allows = NULL;
+                               guint32 editing_flags = E_EDITING_FLAG_NONE;
+                               ResourceData *rd;
+                               gchar *tmp;
+
+                               if (!resource ||
+                                   !resource->href || (
+                                   resource->kind != E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK &&
+                                   resource->kind != E_WEBDAV_RESOURCE_KIND_CALENDAR &&
+                                   resource->kind != E_WEBDAV_RESOURCE_KIND_COLLECTION &&
+                                   resource->kind != E_WEBDAV_RESOURCE_KIND_PRINCIPAL)) {
+                                       continue;
+                               }
+
+                               /* Some servers do not allow OPTIONS on each collection, thus ignore all 
errors;
+                                  there might not be any login errors here, but even if, then bad luck. */
+                               if (e_webdav_session_options_sync (session, resource->href, &capabilities, 
&allows, cancellable, NULL)) {
+                                       editing_flags = webdav_browser_options_to_editing_flags 
(capabilities, allows);
+                               }
+
+                               if (capabilities)
+                                       g_hash_table_destroy (capabilities);
+                               if (allows)
+                                       g_hash_table_destroy (allows);
+
+                               if (!(editing_flags & E_EDITING_FLAG_HAS_OPTIONS))
+                                       editing_flags = top_editing_flags;
+
+                               if (resource->kind == E_WEBDAV_RESOURCE_KIND_ADDRESSBOOK)
+                                       editing_flags |= E_EDITING_FLAG_IS_BOOK;
+
+                               if (resource->kind == E_WEBDAV_RESOURCE_KIND_CALENDAR)
+                                       editing_flags |= E_EDITING_FLAG_IS_CALENDAR;
+
+                               if (!g_str_has_suffix (resource->href, "/")) {
+                                       tmp = g_strconcat (resource->href, "/", NULL);
+
+                                       g_free (resource->href);
+                                       resource->href = tmp;
+                               }
+
+                               /* Because for example Google server returns the '@' sometimes encoded and 
sometimes not,
+                                  which breaks lookup based on href in the code. */
+                               tmp = soup_uri_normalize (resource->href, "@");
+                               g_free (resource->href);
+                               resource->href = tmp;
+
+                               rd = g_new0 (ResourceData, 1);
+                               rd->editing_flags = editing_flags;
+                               rd->resource = resource;
+
+                               g_mutex_lock (&webdav_browser->priv->property_lock);
+                               webdav_browser->priv->resources = g_slist_prepend 
(webdav_browser->priv->resources, rd);
+                               g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+                               link->data = NULL;
+                       }
+               } else if (webdav_browser_manage_login_errors (webdav_browser, session, cancellable, 
local_error)) {
+                       done = FALSE;
+                       g_clear_error (&local_error);
+               } else if (local_error) {
+                       g_propagate_error (error, local_error);
+                       success = FALSE;
+               }
+
+               g_slist_free_full (resources, e_webdav_resource_free);
+       }
+
+       return success;
+}
+
+typedef struct _SearchChildrenData {
+       GWeakRef *webdav_browser_weakref;
+       GtkTreeRowReference *loading_row;
+       gchar *href;
+} SearchChildrenData;
+
+static void
+search_children_data_free (gpointer ptr)
+{
+       SearchChildrenData *scd = ptr;
+
+       if (scd) {
+               if (scd->webdav_browser_weakref)
+                       e_weak_ref_free (scd->webdav_browser_weakref);
+               if (scd->loading_row)
+                       gtk_tree_row_reference_free (scd->loading_row);
+               g_free (scd->href);
+               g_free (scd);
+       }
+}
+
+static void
+webdav_browser_finish_search_children (EWebDAVBrowser *webdav_browser,
+                                      gpointer user_data)
+{
+       SearchChildrenData *scd = user_data;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (scd);
+
+       if (gtk_tree_row_reference_valid (scd->loading_row)) {
+               GtkTreeIter sort_iter, iter;
+               GtkTreePath *path;
+               GtkTreeModel *model;
+
+               model = gtk_tree_row_reference_get_model (scd->loading_row);
+               path = gtk_tree_row_reference_get_path (scd->loading_row);
+               if (path && gtk_tree_model_get_iter (model, &sort_iter, path)) {
+                       gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model), &iter, 
&sort_iter);
+
+                       model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model));
+
+                       gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+               }
+
+               if (path)
+                       gtk_tree_path_free (path);
+
+               if (scd->href) {
+                       GtkTreeRowReference *reference;
+
+                       reference = g_hash_table_lookup (webdav_browser->priv->href_to_reference, scd->href);
+                       if (reference) {
+                               model = gtk_tree_row_reference_get_model (reference);
+                               path = gtk_tree_row_reference_get_path (reference);
+                               if (path && gtk_tree_model_get_iter (model, &iter, path)) {
+                                       gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 
COLUMN_BOOL_CHILDREN_LOADED, TRUE, -1);
+                               }
+
+                               if (path)
+                                       gtk_tree_path_free (path);
+                       }
+               }
+       }
+}
+
+static void
+webdav_browser_search_children_thread (EAlertSinkThreadJobData *job_data,
+                                      gpointer user_data,
+                                      GCancellable *cancellable,
+                                      GError **error)
+{
+       SearchChildrenData *scd = user_data, *scd2;
+       EWebDAVBrowser *webdav_browser;
+       EWebDAVSession *session;
+
+       g_return_if_fail (scd != NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       webdav_browser = g_weak_ref_get (scd->webdav_browser_weakref);
+       if (!webdav_browser)
+               return;
+
+       session = webdav_browser_ref_session (webdav_browser);
+       if (!session) {
+               g_object_unref (webdav_browser);
+               return;
+       }
+
+       webdav_browser_gather_href_resources_sync (webdav_browser, session, scd->href, FALSE, TRUE, 
cancellable, error);
+
+       scd2 = g_new0 (SearchChildrenData, 1);
+       scd2->loading_row = scd->loading_row;
+       scd2->href = scd->href;
+
+       scd->loading_row = NULL;
+       scd->href = NULL;
+
+       webdav_browser_schedule_ui_update (webdav_browser,
+               webdav_browser_finish_search_children, scd2, search_children_data_free);
+
+       g_object_unref (webdav_browser);
+       g_object_unref (session);
+}
+
+static gboolean
+webdav_browser_is_any_parent_covered (GHashTable *covered_hrefs,
+                                     const gchar *href)
+{
+       gchar *path;
+
+       g_return_val_if_fail (covered_hrefs != NULL, FALSE);
+       g_return_val_if_fail (href != NULL, FALSE);
+
+       if (!g_hash_table_size (covered_hrefs))
+               return FALSE;
+
+       path = g_strdup (href);
+       if (path) {
+               gint pos;
+
+               for (pos = strlen (path) - 1; pos > 0; pos--) {
+                       if (path[pos] == '/' && path[pos + 1] != '\0') {
+                               path[pos + 1] = '\0';
+
+                               if (g_hash_table_contains (covered_hrefs, path)) {
+                                       g_free (path);
+                                       return TRUE;
+                               }
+                       }
+               }
+
+               g_free (path);
+       }
+
+       return FALSE;
+}
+
+static void
+webdav_browser_search_user_home_thread (EAlertSinkThreadJobData *job_data,
+                                       gpointer user_data,
+                                       GCancellable *cancellable,
+                                       GError **error)
+{
+       EWebDAVBrowser *webdav_browser;
+       EWebDAVSession *session;
+       EXmlDocument *xml;
+       SearchHomeData shd;
+       GHashTable *checked_tops;
+       ESource *source;
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       webdav_browser = g_weak_ref_get (user_data);
+       if (!webdav_browser)
+               return;
+
+       session = webdav_browser_ref_session (webdav_browser);
+       if (!session) {
+               g_object_unref (webdav_browser);
+               return;
+       }
+
+       xml = e_xml_document_new (E_WEBDAV_NS_DAV, "propfind");
+       g_return_if_fail (xml != NULL);
+
+       e_xml_document_start_element (xml, E_WEBDAV_NS_DAV, "prop");
+       e_xml_document_add_empty_element (xml, E_WEBDAV_NS_DAV, "current-user-principal");
+       e_xml_document_add_empty_element (xml, E_WEBDAV_NS_DAV, "principal-URL");
+       e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CALDAV, "calendar-home-set");
+       e_xml_document_add_empty_element (xml, E_WEBDAV_NS_CARDDAV, "addressbook-home-set");
+       e_xml_document_end_element (xml); /* prop */
+
+       shd.covered_todo_hrefs = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, 
NULL);
+       shd.covered_home_hrefs = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, 
NULL);
+       shd.todo_hrefs = NULL;
+       shd.home_hrefs = NULL;
+
+       source = e_soup_session_get_source (E_SOUP_SESSION (session));
+       if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
+               ESourceWebdav *webdav_extension;
+               SoupURI *suri;
+
+               webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+               suri = e_source_webdav_dup_soup_uri (webdav_extension);
+
+               if (suri) {
+                       gchar *path;
+
+                       soup_uri_set_user (suri, NULL);
+                       path = soup_uri_to_string (suri, FALSE);
+                       if (path) {
+                               shd.home_hrefs = g_slist_prepend (shd.home_hrefs, g_strdup (path));
+                               g_hash_table_insert (shd.covered_home_hrefs, path, NULL);
+                       }
+
+                       path = g_strdup (soup_uri_get_path (suri));
+                       if (path) {
+                               gint len, pos;
+                               gint levels_back = 0;
+
+                               /* There is no guarantee that the parent folder is a WebDAV collection,
+                                  but let's try it, just in case. */
+                               len = strlen (path);
+                               for (pos = len - 1; pos > 0; pos--) {
+                                       if (path[pos] == '/' && path[pos + 1] != '\0') {
+                                               levels_back++;
+
+                                               /* Do not go back too far, most servers has URIs like 
"/dav/user/collection/" */
+                                               if (levels_back > 2)
+                                                       break;
+
+                                               path[pos + 1] = '\0';
+
+                                               soup_uri_set_path (suri, path);
+                                               shd.todo_hrefs = g_slist_prepend (shd.todo_hrefs, 
soup_uri_to_string (suri, FALSE));
+                                       }
+                               }
+
+                               g_free (path);
+                       }
+               }
+
+               if (suri && (!soup_uri_get_path (suri) || !strstr (soup_uri_get_path (suri), 
"/.well-known/"))) {
+                       soup_uri_set_path (suri, "/.well-known/caldav");
+                       shd.todo_hrefs = g_slist_prepend (shd.todo_hrefs, soup_uri_to_string (suri, FALSE));
+
+                       soup_uri_set_path (suri, "/.well-known/carddav");
+                       shd.todo_hrefs = g_slist_prepend (shd.todo_hrefs, soup_uri_to_string (suri, FALSE));
+               }
+
+               if (suri) {
+                       soup_uri_set_path (suri, "");
+                       shd.todo_hrefs = g_slist_prepend (shd.todo_hrefs, soup_uri_to_string (suri, FALSE));
+
+                       soup_uri_free (suri);
+               }
+       }
+
+       /* Try the URL provided in the ESource first */
+       shd.todo_hrefs = g_slist_prepend (shd.todo_hrefs, NULL);
+
+       checked_tops = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
+
+       while (shd.todo_hrefs &&
+              !g_cancellable_set_error_if_cancelled (cancellable, error)) {
+               gchar *top_href;
+               gboolean done;
+               GError *local_error = NULL;
+
+               top_href = shd.todo_hrefs->data;
+               shd.todo_hrefs = g_slist_remove (shd.todo_hrefs, top_href);
+
+               done = top_href && g_hash_table_contains (checked_tops, top_href);
+
+               if (top_href)
+                       g_hash_table_insert (checked_tops, g_strdup (top_href), NULL);
+
+               while (!done) {
+                       done = TRUE;
+
+                       if (e_webdav_session_propfind_sync (session, top_href, E_WEBDAV_DEPTH_THIS, xml,
+                               webdav_browser_search_home_hrefs_cb, &shd, cancellable, &local_error)) {
+                       } else if (webdav_browser_manage_login_errors (webdav_browser, session, cancellable, 
local_error)) {
+                               done = FALSE;
+                       }
+
+                       /* Ignore all but login errors here, because some of the URIs are just guesses */
+                       g_clear_error (&local_error);
+               }
+
+               g_free (top_href);
+       }
+
+       g_hash_table_destroy (checked_tops);
+
+       if (!shd.home_hrefs) {
+               ESourceWebdav *webdav_extension;
+               SoupURI *suri;
+
+               webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+               suri = e_source_webdav_dup_soup_uri (webdav_extension);
+
+               if (suri) {
+                       gchar *path;
+
+                       soup_uri_set_user (suri, NULL);
+
+                       path = g_strdup (soup_uri_get_path (suri));
+                       if (path) {
+                               gint len, pos;
+                               gint levels_back = 0;
+
+                               shd.home_hrefs = g_slist_prepend (shd.home_hrefs, soup_uri_to_string (suri, 
FALSE));
+
+                               /* There is no guarantee that the parent folder is a WebDAV collection,
+                                  but let's try it, just in case. */
+                               len = strlen (path);
+                               for (pos = len - 1; pos > 0; pos--) {
+                                       if (path[pos] == '/' && path[pos + 1] != '\0') {
+                                               gchar *href;
+
+                                               levels_back++;
+
+                                               /* Do not go back too far, most servers has URIs like 
"/dav/user/collection/" */
+                                               if (levels_back > 2)
+                                                       break;
+
+                                               path[pos + 1] = '\0';
+
+                                               soup_uri_set_path (suri, path);
+                                               href = soup_uri_to_string (suri, FALSE);
+
+                                               if (!g_hash_table_contains (shd.covered_home_hrefs, href))
+                                                       shd.home_hrefs = g_slist_prepend (shd.home_hrefs, 
href);
+                                               else
+                                                       g_free (href);
+                                       }
+                               }
+
+                               g_free (path);
+                       }
+
+                       soup_uri_free (suri);
+               }
+       }
+
+       g_hash_table_remove_all (shd.covered_home_hrefs);
+       shd.home_hrefs = g_slist_sort (shd.home_hrefs, (GCompareFunc) g_strcmp0);
+
+       while (!g_cancellable_is_cancelled (cancellable) && shd.home_hrefs) {
+               gchar *home_href;
+
+               home_href = shd.home_hrefs->data;
+               shd.home_hrefs = g_slist_remove (shd.home_hrefs, home_href);
+
+               if (webdav_browser_is_any_parent_covered (shd.covered_home_hrefs, home_href)) {
+                       g_free (home_href);
+               } else {
+                       /* Ignore errors here as well */
+                       webdav_browser_gather_href_resources_sync (webdav_browser, session, home_href, TRUE, 
TRUE, cancellable, NULL);
+                       g_hash_table_insert (shd.covered_home_hrefs, home_href, NULL);
+               }
+       }
+
+       webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+
+       g_hash_table_destroy (shd.covered_todo_hrefs);
+       g_hash_table_destroy (shd.covered_home_hrefs);
+       g_slist_free_full (shd.todo_hrefs, g_free);
+       g_slist_free_full (shd.home_hrefs, g_free);
+       g_object_unref (webdav_browser);
+       g_object_unref (session);
+       g_clear_object (&xml);
+}
+
+static void
+webdav_browser_selection_changed_cb (GtkTreeSelection *selection,
+                                    gpointer user_data)
+{
+       EWebDAVBrowser *webdav_browser = user_data;
+       GtkTreeModel *model = NULL;
+       GtkTreeIter iter;
+       guint editing_flags = E_EDITING_FLAG_NONE;
+       gboolean has_parent = FALSE;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+               GtkTreeIter parent;
+
+               gtk_tree_model_get (model, &iter,
+                       COLUMN_UINT_EDITING_FLAGS, &editing_flags,
+                       -1);
+
+               has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
+       }
+
+       #define has_set(x) ((editing_flags & (x)) == (x))
+
+       gtk_widget_set_sensitive (webdav_browser->priv->create_book_button,
+               has_set (E_EDITING_FLAG_EXMKCOL | E_EDITING_FLAG_CAN_BOOK));
+
+       gtk_widget_set_sensitive (webdav_browser->priv->create_calendar_button,
+               has_set (E_EDITING_FLAG_MKCALENDAR | E_EDITING_FLAG_CAN_CALENDAR));
+
+       gtk_widget_set_sensitive (webdav_browser->priv->create_collection_button,
+               has_set (E_EDITING_FLAG_MKCOL));
+
+       gtk_widget_set_sensitive (webdav_browser->priv->edit_button,
+               (editing_flags & (E_EDITING_FLAG_IS_BOOK | E_EDITING_FLAG_IS_CALENDAR)) != 0);
+
+       gtk_widget_set_sensitive (webdav_browser->priv->delete_button,
+               has_set (E_EDITING_FLAG_CAN_DELETE) && has_parent);
+
+       /* gtk_widget_set_sensitive (webdav_browser->priv->permissions_button,
+               has_set (E_EDITING_FLAG_CAN_ACL)); */
+
+       #undef has_set
+}
+
+static void
+webdav_browser_change_busy_state (EWebDAVBrowser *webdav_browser,
+                                 gboolean is_busy)
+{
+       gtk_widget_set_sensitive (webdav_browser->priv->tree_view, !is_busy);
+
+       if (is_busy) {
+               gtk_widget_set_sensitive (webdav_browser->priv->create_book_button, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->create_calendar_button, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->create_collection_button, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->edit_button, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->delete_button, FALSE);
+               /* gtk_widget_set_sensitive (webdav_browser->priv->permissions_button, FALSE); */
+               gtk_widget_set_sensitive (webdav_browser->priv->refresh_button, FALSE);
+
+               e_alert_bar_clear (webdav_browser->priv->alert_bar);
+       } else {
+               GtkTreeView *tree_view;
+
+               tree_view = GTK_TREE_VIEW (webdav_browser->priv->tree_view);
+
+               webdav_browser_selection_changed_cb (gtk_tree_view_get_selection (tree_view), webdav_browser);
+
+               gtk_widget_set_sensitive (webdav_browser->priv->refresh_button, webdav_browser->priv->session 
!= NULL);
+       }
+}
+
+static void
+webdav_browser_row_expanded_cb (GtkTreeView *tree_view,
+                               GtkTreeIter *iter,
+                               GtkTreePath *path,
+                               gpointer user_data)
+{
+       EWebDAVBrowser *webdav_browser = user_data;
+       EActivity *activity;
+       GtkTreeModel *model;
+       GtkTreeIter loading_child;
+       GtkTreePath *loading_path;
+       SearchChildrenData *scd;
+       gboolean is_loaded = TRUE;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+       g_return_if_fail (iter);
+
+       model = gtk_tree_view_get_model (tree_view);
+       gtk_tree_model_get (model, iter, COLUMN_BOOL_CHILDREN_LOADED, &is_loaded, -1);
+
+       if (is_loaded)
+               return;
+
+       g_return_if_fail (gtk_tree_model_iter_nth_child (model, &loading_child, iter, 0));
+       g_return_if_fail (webdav_browser->priv->session);
+
+       scd = g_new0 (SearchChildrenData, 1);
+       scd->webdav_browser_weakref = e_weak_ref_new (webdav_browser);
+
+       loading_path = gtk_tree_model_get_path (model, &loading_child);
+       scd->loading_row = gtk_tree_row_reference_new (model, loading_path);
+       gtk_tree_path_free (loading_path);
+
+       gtk_tree_model_get (model, iter, COLUMN_STRING_HREF, &scd->href, -1);
+
+       e_webdav_browser_abort (webdav_browser);
+
+       g_clear_object (&webdav_browser->priv->cancellable);
+
+       webdav_browser_change_busy_state (webdav_browser, TRUE);
+
+       activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (webdav_browser),
+               _("Searching collection children…"),
+               "system:generic-error",
+               _("Failed to search for collection children"),
+               webdav_browser_search_children_thread, scd,
+               search_children_data_free);
+
+       if (activity) {
+               webdav_browser->priv->cancellable = e_activity_get_cancellable (activity);
+
+               if (webdav_browser->priv->cancellable)
+                       g_object_ref (webdav_browser->priv->cancellable);
+
+               e_activity_bar_set_activity (webdav_browser->priv->activity_bar, activity);
+
+               g_object_unref (activity);
+       } else {
+               webdav_browser_change_busy_state (webdav_browser, FALSE);
+               webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+       }
+}
+
+static void
+webdav_browser_search_user_home (EWebDAVBrowser *webdav_browser)
+{
+       EActivity *activity;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (webdav_browser->priv->session);
+
+       e_webdav_browser_abort (webdav_browser);
+
+       g_clear_object (&webdav_browser->priv->cancellable);
+
+       webdav_browser_change_busy_state (webdav_browser, TRUE);
+
+       activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (webdav_browser),
+               _("Searching for user home, please wait…"),
+               "system:generic-error",
+               _("Failed to search for user home"),
+               webdav_browser_search_user_home_thread,
+               e_weak_ref_new (webdav_browser),
+               (GDestroyNotify) e_weak_ref_free);
+
+       if (activity) {
+               webdav_browser->priv->cancellable = e_activity_get_cancellable (activity);
+
+               if (webdav_browser->priv->cancellable)
+                       g_object_ref (webdav_browser->priv->cancellable);
+
+               e_activity_bar_set_activity (webdav_browser->priv->activity_bar, activity);
+
+               g_object_unref (activity);
+       } else {
+               webdav_browser_change_busy_state (webdav_browser, FALSE);
+               webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+       }
+}
+
+static gchar *
+webdav_browser_dup_selected_href (EWebDAVBrowser *webdav_browser)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GtkTreeSelection *selection;
+       gchar *href = NULL;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), NULL);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (webdav_browser->priv->tree_view));
+       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+               return NULL;
+
+       gtk_tree_model_get (model, &iter, COLUMN_STRING_HREF, &href, -1);
+
+       return href;
+}
+
+static gboolean
+webdav_browser_get_selected_loaded (EWebDAVBrowser *webdav_browser)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GtkTreeSelection *selection;
+       gboolean is_loaded = FALSE;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), FALSE);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (webdav_browser->priv->tree_view));
+       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+               return FALSE;
+
+       gtk_tree_model_get (model, &iter, COLUMN_BOOL_CHILDREN_LOADED, &is_loaded, -1);
+
+       return is_loaded;
+}
+
+static gboolean
+webdav_browser_any_parent_is_book_or_calendar (EWebDAVBrowser *webdav_browser)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter, parent;
+       GtkTreeSelection *selection;
+       gboolean done;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), FALSE);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (webdav_browser->priv->tree_view));
+       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+               return FALSE;
+
+       do {
+               guint editing_flags = E_EDITING_FLAG_NONE;
+
+               gtk_tree_model_get (model, &iter, COLUMN_UINT_EDITING_FLAGS, &editing_flags, -1);
+
+               if ((editing_flags & (E_EDITING_FLAG_IS_BOOK | E_EDITING_FLAG_IS_CALENDAR)) != 0)
+                       return TRUE;
+
+               done = !gtk_tree_model_iter_parent (model, &parent, &iter);
+               if (!done)
+                       iter = parent;
+       } while (!done);
+
+       return FALSE;
+}
+
+static void
+webdav_browser_drop_loading_node_for_href (EWebDAVBrowser *webdav_browser,
+                                          gpointer user_data)
+{
+       GtkTreeRowReference *reference;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter, child;
+       const gchar *href = user_data;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (href != NULL);
+
+       reference = g_hash_table_lookup (webdav_browser->priv->href_to_reference, href);
+       if (!reference && !g_str_has_suffix (href, "/")) {
+               gchar *tmp;
+
+               tmp = g_strconcat (href, "/", NULL);
+               reference = g_hash_table_lookup (webdav_browser->priv->href_to_reference, tmp);
+               g_free (tmp);
+       }
+
+       if (!reference)
+               return;
+
+       model = gtk_tree_row_reference_get_model (reference);
+       path = gtk_tree_row_reference_get_path (reference);
+
+       if (!path)
+               return;
+
+       if (gtk_tree_model_get_iter (model, &iter, path)) {
+               gtk_tree_store_set (GTK_TREE_STORE (model), &iter, COLUMN_BOOL_CHILDREN_LOADED, TRUE, -1);
+
+               /* Remove "Loading…" row */
+               if (gtk_tree_model_iter_nth_child (model, &child, &iter, 0)) {
+                       do {
+                               guint flags = E_EDITING_FLAG_NONE;
+
+                               gtk_tree_model_get (model, &child, COLUMN_UINT_EDITING_FLAGS, &flags, -1);
+
+                               if (flags == E_EDITING_FLAG_IS_LOADING_ROW) {
+                                       gtk_tree_store_remove (GTK_TREE_STORE (model), &child);
+                                       break;
+                               }
+                       } while (gtk_tree_model_iter_next (model, &child));
+               }
+       }
+
+       gtk_tree_path_free (path);
+}
+
+typedef struct _SaveChangesData {
+       GWeakRef *webdav_browser_weakref;
+       gchar *href;
+       gboolean is_edit; /* TRUE for save changes, FALSE to create new under href */
+       gboolean load_first;
+       gchar *name;
+       GdkRGBA rgba;
+       guint32 supports; /* bit-or of EWebDAVResourceSupports */
+       gchar *description;
+} SaveChangesData;
+
+static void
+save_changes_data_free (gpointer ptr)
+{
+       SaveChangesData *scd = ptr;
+
+       if (scd) {
+               e_weak_ref_free (scd->webdav_browser_weakref);
+               g_free (scd->href);
+               g_free (scd->name);
+               g_free (scd->description);
+               g_free (scd);
+       }
+}
+
+static void
+webdav_browser_save_changes_thread (EAlertSinkThreadJobData *job_data,
+                                   gpointer user_data,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       EWebDAVBrowser *webdav_browser;
+       EWebDAVSession *session;
+       SaveChangesData *scd = user_data;
+       gchar *new_href = NULL;
+       gboolean success = FALSE;
+
+       g_return_if_fail (scd != NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       webdav_browser = g_weak_ref_get (scd->webdav_browser_weakref);
+       if (!webdav_browser)
+               return;
+
+       session = webdav_browser_ref_session (webdav_browser);
+       if (!session) {
+               g_object_unref (webdav_browser);
+               return;
+       }
+
+       if (scd->load_first)
+               webdav_browser_gather_href_resources_sync (webdav_browser, session, scd->href, FALSE, TRUE, 
cancellable, NULL);
+
+       if (scd->is_edit) {
+               GSList *changes = NULL;
+
+               changes = g_slist_append (changes, e_webdav_property_change_new_set (E_WEBDAV_NS_DAV, 
"displayname", scd->name));
+
+               if ((scd->supports & E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS) != 0) {
+                       if (scd->description && *scd->description)
+                               changes = g_slist_append (changes, e_webdav_property_change_new_set 
(E_WEBDAV_NS_CARDDAV, "addressbook-description", scd->description));
+                       else
+                               changes = g_slist_append (changes, e_webdav_property_change_new_remove 
(E_WEBDAV_NS_CARDDAV, "addressbook-description"));
+               } else if ((scd->supports & (E_WEBDAV_RESOURCE_SUPPORTS_EVENTS | 
E_WEBDAV_RESOURCE_SUPPORTS_MEMOS | E_WEBDAV_RESOURCE_SUPPORTS_TASKS)) != 0) {
+                       gchar *color;
+
+                       color = g_strdup_printf ("#%02x%02x%02x",
+                               (gint) CLAMP (scd->rgba.red * 0xFF, 0, 0xFF),
+                               (gint) CLAMP (scd->rgba.green * 0xFF, 0, 0xFF),
+                               (gint) CLAMP (scd->rgba.blue * 0xFF, 0, 0xFF));
+
+                       changes = g_slist_append (changes, e_webdav_property_change_new_set 
(E_WEBDAV_NS_ICAL, "calendar-color", color));
+
+                       if (scd->description && *scd->description)
+                               changes = g_slist_append (changes, e_webdav_property_change_new_set 
(E_WEBDAV_NS_CALDAV, "calendar-description", scd->description));
+                       else
+                               changes = g_slist_append (changes, e_webdav_property_change_new_remove 
(E_WEBDAV_NS_CALDAV, "calendar-description"));
+
+                       g_free (color);
+               }
+
+               success = e_webdav_session_update_properties_sync (session, scd->href, changes, cancellable, 
error);
+
+               g_slist_free_full (changes, e_webdav_property_change_free);
+       } else {
+               SoupURI *suri;
+               GString *path;
+               gchar *encoded;
+
+               suri = soup_uri_new (scd->href);
+               path = g_string_new (soup_uri_get_path (suri));
+
+               if (path->len && path->str[path->len - 1] != '/')
+                       g_string_append_c (path, '/');
+
+               encoded = soup_uri_encode (scd->name, NULL);
+               g_string_append (path, encoded);
+               g_free (encoded);
+
+               soup_uri_set_path (suri, path->str);
+
+               new_href = soup_uri_to_string (suri, FALSE);
+
+               if ((scd->supports & E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS) != 0) {
+                       success = e_webdav_session_mkcol_addressbook_sync (session, new_href,
+                               scd->name, scd->description, cancellable, error);
+               } else if ((scd->supports & (E_WEBDAV_RESOURCE_SUPPORTS_EVENTS | 
E_WEBDAV_RESOURCE_SUPPORTS_MEMOS | E_WEBDAV_RESOURCE_SUPPORTS_TASKS)) != 0) {
+                       gchar *color;
+
+                       color = g_strdup_printf ("#%02x%02x%02x",
+                               (gint) CLAMP (scd->rgba.red * 0xFF, 0, 0xFF),
+                               (gint) CLAMP (scd->rgba.green * 0xFF, 0, 0xFF),
+                               (gint) CLAMP (scd->rgba.blue * 0xFF, 0, 0xFF));
+
+                       success = e_webdav_session_mkcalendar_sync (session, new_href,
+                               scd->name, scd->description, color,
+                               scd->supports, cancellable, error);
+
+                       g_free (color);
+               } else {
+                       success = e_webdav_session_mkcol_sync (session, new_href, cancellable, error);
+               }
+
+               g_string_free (path, TRUE);
+               soup_uri_free (suri);
+       }
+
+       if (success) {
+               const gchar *href = new_href ? new_href : scd->href;
+
+               if (scd->load_first) {
+                       GSList *link;
+
+                       for (link = webdav_browser->priv->resources; link; link = g_slist_next (link)) {
+                               ResourceData *rd = link->data;
+
+                               if (rd && rd->resource && rd->resource->href &&
+                                   g_strcmp0 (rd->resource->href, href) == 0) {
+                                       webdav_browser->priv->resources = g_slist_remove 
(webdav_browser->priv->resources, rd);
+                                       resource_data_free (rd);
+                                       break;
+                               }
+                       }
+               }
+
+               webdav_browser_gather_href_resources_sync (webdav_browser, session, href, FALSE, FALSE, 
cancellable, error);
+
+               if (!scd->is_edit)
+                       webdav_browser_schedule_ui_update (webdav_browser,
+                               webdav_browser_drop_loading_node_for_href, g_strdup (href), g_free);
+               else
+                       webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+       } else {
+               webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+       }
+
+       g_object_unref (webdav_browser);
+       g_object_unref (session);
+       g_free (new_href);
+}
+
+static void
+webdav_browser_save_clicked (EWebDAVBrowser *webdav_browser,
+                            gboolean is_book,
+                            gboolean is_calendar,
+                            gboolean is_edit)
+{
+       EActivity *activity;
+       SaveChangesData *scd;
+       GtkTextBuffer *buffer;
+       GtkTextIter start, end;
+       guint32 supports = 0;
+       gchar *text, *href;
+       const gchar *description;
+       const gchar *error_message;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       text = g_strdup (gtk_entry_get_text (GTK_ENTRY (webdav_browser->priv->create_edit_name_entry)));
+       if (text)
+               text = g_strstrip (text);
+
+       if (!text || !*text) {
+               gtk_widget_hide (webdav_browser->priv->create_edit_hint_popover);
+
+               gtk_label_set_text (GTK_LABEL (webdav_browser->priv->create_edit_hint_label), _("Name cannot 
be empty"));
+               gtk_popover_set_relative_to (GTK_POPOVER (webdav_browser->priv->create_edit_hint_popover),
+                       webdav_browser->priv->create_edit_name_entry);
+
+               gtk_widget_show (webdav_browser->priv->create_edit_hint_popover);
+
+               g_free (text);
+
+               return;
+       } else if (is_calendar &&
+                  !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_event_check)) &&
+                  !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_memo_check)) &&
+                  !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_task_check))) {
+               gtk_widget_hide (webdav_browser->priv->create_edit_hint_popover);
+
+               gtk_label_set_text (GTK_LABEL (webdav_browser->priv->create_edit_hint_label), _("At least one 
component type should be set"));
+               gtk_popover_set_relative_to (GTK_POPOVER (webdav_browser->priv->create_edit_hint_popover),
+                       webdav_browser->priv->create_edit_task_check);
+
+               gtk_widget_show (webdav_browser->priv->create_edit_hint_popover);
+
+               g_free (text);
+
+               return;
+       }
+
+       gtk_widget_hide (webdav_browser->priv->create_edit_popover);
+
+       href = webdav_browser_dup_selected_href (webdav_browser);
+       if (!href || !*href) {
+               g_free (href);
+               g_free (text);
+
+               webdav_browser_add_alert (webdav_browser, _("Failed to get selected collection HREF"), NULL);
+
+               return;
+       }
+
+       if (is_book) {
+               supports = E_WEBDAV_RESOURCE_SUPPORTS_CONTACTS;
+       } else if (is_calendar) {
+               if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_event_check)))
+                       supports |= E_WEBDAV_RESOURCE_SUPPORTS_EVENTS;
+               if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_memo_check)))
+                       supports |= E_WEBDAV_RESOURCE_SUPPORTS_MEMOS;
+               if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_task_check)))
+                       supports |= E_WEBDAV_RESOURCE_SUPPORTS_TASKS;
+       }
+
+       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW 
(webdav_browser->priv->create_edit_description_textview));
+       gtk_text_buffer_get_start_iter (buffer, &start);
+       gtk_text_buffer_get_end_iter (buffer, &end);
+
+       scd = g_new0 (SaveChangesData, 1);
+       scd->webdav_browser_weakref = e_weak_ref_new (webdav_browser);
+       scd->href = href;
+       scd->is_edit = is_edit;
+       scd->load_first = !webdav_browser_get_selected_loaded (webdav_browser);
+       scd->name = text;
+       gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (webdav_browser->priv->create_edit_color_chooser), 
&scd->rgba);
+       scd->supports = supports;
+       scd->description = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+       if (is_edit) {
+               description = _("Saving changes…");
+               error_message = _("Failed to save changes");
+       } else if (is_book) {
+               description = _("Creating new book…");
+               error_message = _("Failed to create new book");
+       } else if (is_calendar) {
+               description = _("Creating new calendar…");
+               error_message = _("Failed to create new calendar");
+       } else {
+               description = _("Creating new collection…");
+               error_message = _("Failed to create new collection");
+       }
+
+       e_webdav_browser_abort (webdav_browser);
+
+       g_clear_object (&webdav_browser->priv->cancellable);
+
+       webdav_browser_change_busy_state (webdav_browser, TRUE);
+
+       activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (webdav_browser),
+               description, "system:generic-error", error_message,
+               webdav_browser_save_changes_thread, scd,
+               save_changes_data_free);
+
+       if (activity) {
+               webdav_browser->priv->cancellable = e_activity_get_cancellable (activity);
+
+               if (webdav_browser->priv->cancellable)
+                       g_object_ref (webdav_browser->priv->cancellable);
+
+               e_activity_bar_set_activity (webdav_browser->priv->activity_bar, activity);
+
+               g_object_unref (activity);
+       } else {
+               webdav_browser_change_busy_state (webdav_browser, FALSE);
+               webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+       }
+}
+
+static void
+webdav_browser_create_book_save_clicked_cb (GtkWidget *button,
+                                           EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       webdav_browser_save_clicked (webdav_browser, TRUE, FALSE, FALSE);
+}
+
+static void
+webdav_browser_create_calendar_save_clicked_cb (GtkWidget *button,
+                                               EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       webdav_browser_save_clicked (webdav_browser, FALSE, TRUE, FALSE);
+}
+
+static void
+webdav_browser_create_collection_save_clicked_cb (GtkWidget *button,
+                                                 EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       gtk_widget_hide (webdav_browser->priv->create_edit_popover);
+
+       webdav_browser_save_clicked (webdav_browser, FALSE, FALSE, FALSE);
+}
+
+static void
+webdav_browser_edit_book_save_clicked_cb (GtkWidget *button,
+                                         EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       webdav_browser_save_clicked (webdav_browser, TRUE, FALSE, TRUE);
+}
+
+static void
+webdav_browser_edit_calendar_save_clicked_cb (GtkWidget *button,
+                                             EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       webdav_browser_save_clicked (webdav_browser, FALSE, TRUE, TRUE);
+}
+
+static void
+webdav_browser_edit_collection_save_clicked_cb (GtkWidget *button,
+                                               EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       gtk_widget_hide (webdav_browser->priv->create_edit_popover);
+
+       webdav_browser_save_clicked (webdav_browser, FALSE, FALSE, TRUE);
+}
+
+static void
+webdav_browser_prepare_popover (EWebDAVBrowser *webdav_browser,
+                               gboolean for_book,
+                               gboolean for_calendar)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       gtk_widget_hide (webdav_browser->priv->create_edit_popover);
+
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_color_label, for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_color_chooser, for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_support_label, for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_event_check, for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_memo_check, for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_task_check, for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_description_label, for_book || 
for_calendar);
+       gtk_widget_set_visible (webdav_browser->priv->create_edit_description_scrolled_window, for_book || 
for_calendar);
+
+       gtk_widget_set_sensitive (webdav_browser->priv->create_edit_support_label, TRUE);
+       gtk_widget_set_sensitive (webdav_browser->priv->create_edit_event_check, TRUE);
+       gtk_widget_set_sensitive (webdav_browser->priv->create_edit_memo_check, TRUE);
+       gtk_widget_set_sensitive (webdav_browser->priv->create_edit_task_check, TRUE);
+
+       gtk_widget_hide (webdav_browser->priv->create_edit_hint_popover);
+
+       gtk_entry_set_text (GTK_ENTRY (webdav_browser->priv->create_edit_name_entry), "");
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (webdav_browser->priv->create_edit_event_check), 
FALSE);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (webdav_browser->priv->create_edit_memo_check), 
FALSE);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (webdav_browser->priv->create_edit_task_check), 
FALSE);
+       gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW 
(webdav_browser->priv->create_edit_description_textview)), "", -1);
+}
+
+static void
+webdav_browser_create_clicked_cb (GtkWidget *button,
+                                 EWebDAVBrowser *webdav_browser)
+{
+       GCallback callback;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       if (button != webdav_browser->priv->create_collection_button &&
+           webdav_browser_any_parent_is_book_or_calendar (webdav_browser)) {
+               const gchar *msg;
+
+               if (button == webdav_browser->priv->create_book_button)
+                       msg = _("It is not allowed to create book under another book or calendar");
+               else if (button == webdav_browser->priv->create_calendar_button)
+                       msg = _("It is not allowed to create calendar under another book or calendar");
+
+               gtk_widget_hide (webdav_browser->priv->create_edit_hint_popover);
+
+               gtk_label_set_text (GTK_LABEL (webdav_browser->priv->create_edit_hint_label), msg);
+               gtk_popover_set_relative_to (GTK_POPOVER (webdav_browser->priv->create_edit_hint_popover), 
button);
+
+               gtk_widget_show (webdav_browser->priv->create_edit_hint_popover);
+
+               return;
+       }
+
+       webdav_browser_prepare_popover (webdav_browser,
+               button == webdav_browser->priv->create_book_button,
+               button == webdav_browser->priv->create_calendar_button);
+
+       gtk_popover_set_relative_to (GTK_POPOVER (webdav_browser->priv->create_edit_popover), button);
+
+       g_signal_handlers_disconnect_by_data (webdav_browser->priv->create_edit_save_button, webdav_browser);
+
+       if (button == webdav_browser->priv->create_book_button)
+               callback = G_CALLBACK (webdav_browser_create_book_save_clicked_cb);
+       else if (button == webdav_browser->priv->create_calendar_button)
+               callback = G_CALLBACK (webdav_browser_create_calendar_save_clicked_cb);
+       else
+               callback = G_CALLBACK (webdav_browser_create_collection_save_clicked_cb);
+
+       g_signal_connect (webdav_browser->priv->create_edit_save_button, "clicked", callback, webdav_browser);
+
+       gtk_widget_show (webdav_browser->priv->create_edit_popover);
+
+       gtk_widget_grab_focus (webdav_browser->priv->create_edit_name_entry);
+}
+
+static void
+webdav_browser_edit_clicked_cb (GtkWidget *button,
+                               EWebDAVBrowser *webdav_browser)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GtkTreeSelection *selection;
+       gchar *href;
+       gchar *display_name = NULL, *description = NULL;
+       guint editing_flags = E_EDITING_FLAG_NONE, supports = E_WEBDAV_RESOURCE_SUPPORTS_NONE;
+       GdkRGBA *rgba = NULL;
+       gboolean color_is_set = FALSE;
+       GCallback callback;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (GTK_IS_POPOVER (webdav_browser->priv->create_edit_popover));
+
+       href = webdav_browser_dup_selected_href (webdav_browser);
+       g_return_if_fail (href != NULL);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (webdav_browser->priv->tree_view));
+       g_return_if_fail (gtk_tree_selection_get_selected (selection, &model, &iter));
+
+       gtk_tree_model_get (model, &iter,
+               COLUMN_STRING_DISPLAY_NAME, &display_name,
+               COLUMN_STRING_DESCRIPTION, &description,
+               COLUMN_RGBA_COLOR, &rgba,
+               COLUMN_BOOL_COLOR_VISIBLE, &color_is_set,
+               COLUMN_UINT_EDITING_FLAGS, &editing_flags,
+               COLUMN_UINT_SUPPORTS, &supports,
+               -1);
+
+       webdav_browser_prepare_popover (webdav_browser,
+               (editing_flags & E_EDITING_FLAG_IS_BOOK) != 0,
+               (editing_flags & E_EDITING_FLAG_IS_CALENDAR) != 0);
+
+       if ((editing_flags & E_EDITING_FLAG_IS_CALENDAR) != 0) {
+               if (color_is_set && rgba)
+                       gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER 
(webdav_browser->priv->create_edit_color_chooser), rgba);
+
+               gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_event_check),
+                       (supports & E_WEBDAV_RESOURCE_SUPPORTS_EVENTS) != 0);
+               gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_memo_check),
+                       (supports & E_WEBDAV_RESOURCE_SUPPORTS_MEMOS) != 0);
+               gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON 
(webdav_browser->priv->create_edit_task_check),
+                       (supports & E_WEBDAV_RESOURCE_SUPPORTS_TASKS) != 0);
+
+               gtk_widget_set_sensitive (webdav_browser->priv->create_edit_support_label, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->create_edit_event_check, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->create_edit_memo_check, FALSE);
+               gtk_widget_set_sensitive (webdav_browser->priv->create_edit_task_check, FALSE);
+       }
+
+       gtk_entry_set_text (GTK_ENTRY (webdav_browser->priv->create_edit_name_entry), display_name);
+
+       if (description) {
+               gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW 
(webdav_browser->priv->create_edit_description_textview)),
+                       description, -1);
+       }
+
+       gtk_popover_set_relative_to (GTK_POPOVER (webdav_browser->priv->create_edit_popover), button);
+
+       g_signal_handlers_disconnect_by_data (webdav_browser->priv->create_edit_save_button, webdav_browser);
+
+       if ((editing_flags & E_EDITING_FLAG_IS_BOOK) != 0)
+               callback = G_CALLBACK (webdav_browser_edit_book_save_clicked_cb);
+       else if ((editing_flags & E_EDITING_FLAG_IS_CALENDAR) != 0)
+               callback = G_CALLBACK (webdav_browser_edit_calendar_save_clicked_cb);
+       else
+               callback = G_CALLBACK (webdav_browser_edit_collection_save_clicked_cb);
+
+       g_signal_connect (webdav_browser->priv->create_edit_save_button, "clicked", callback, webdav_browser);
+
+       gtk_widget_show (webdav_browser->priv->create_edit_popover);
+
+       gtk_widget_grab_focus (webdav_browser->priv->create_edit_name_entry);
+
+       g_free (href);
+       g_free (description);
+       g_free (display_name);
+
+       if (rgba)
+               gdk_rgba_free (rgba);
+}
+
+typedef struct _DeleteData {
+       GWeakRef *webdav_browser_weakref;
+       gchar *href;
+} DeleteData;
+
+static void
+delete_data_free (gpointer ptr)
+{
+       DeleteData *dd = ptr;
+
+       if (dd) {
+               e_weak_ref_free (dd->webdav_browser_weakref);
+               g_free (dd->href);
+               g_free (dd);
+       }
+}
+
+static void
+webdav_browser_delete_done (EWebDAVBrowser *webdav_browser,
+                           gpointer user_data)
+{
+       GtkTreeRowReference *reference;
+       const gchar *href = user_data;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (href != NULL);
+
+       reference = g_hash_table_lookup (webdav_browser->priv->href_to_reference, href);
+       if (reference) {
+               GtkTreeModel *model;
+               GtkTreePath *path;
+               GtkTreeIter iter;
+
+               model = gtk_tree_row_reference_get_model (reference);
+               path = gtk_tree_row_reference_get_path (reference);
+
+               if (gtk_tree_model_get_iter (model, &iter, path))
+                       gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+
+               gtk_tree_path_free (path);
+       }
+}
+
+static void
+webdav_browser_delete_thread (EAlertSinkThreadJobData *job_data,
+                             gpointer user_data,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       EWebDAVBrowser *webdav_browser;
+       EWebDAVSession *session;
+       DeleteData *dd = user_data;
+
+       g_return_if_fail (dd != NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       webdav_browser = g_weak_ref_get (dd->webdav_browser_weakref);
+       if (!webdav_browser)
+               return;
+
+       session = webdav_browser_ref_session (webdav_browser);
+       if (!session) {
+               g_object_unref (webdav_browser);
+               return;
+       }
+
+       if (e_webdav_session_delete_sync (session, dd->href, E_WEBDAV_DEPTH_THIS_AND_CHILDREN, NULL, 
cancellable, error)) {
+               webdav_browser_schedule_ui_update (webdav_browser,
+                       webdav_browser_delete_done, g_strdup (dd->href), g_free);
+       } else {
+               webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+       }
+
+       g_object_unref (webdav_browser);
+       g_object_unref (session);
+}
+
+static void
+webdav_browser_delete_clicked_cb (GtkWidget *button,
+                                 EWebDAVBrowser *webdav_browser)
+{
+       GtkWidget *parent;
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GtkTreeSelection *selection;
+       gchar *href;
+       gchar *display_name = NULL;
+       const gchar *question_tag, *description, *error_message;
+       guint editing_flags = E_EDITING_FLAG_NONE;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       href = webdav_browser_dup_selected_href (webdav_browser);
+       g_return_if_fail (href != NULL);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (webdav_browser->priv->tree_view));
+       g_return_if_fail (gtk_tree_selection_get_selected (selection, &model, &iter));
+
+       gtk_tree_model_get (model, &iter,
+               COLUMN_STRING_DISPLAY_NAME, &display_name,
+               COLUMN_UINT_EDITING_FLAGS, &editing_flags,
+               -1);
+
+       if ((editing_flags & E_EDITING_FLAG_IS_BOOK) != 0) {
+               question_tag = "addressbook:ask-delete-remote-addressbook";
+               description = _("Deleting book…");
+               error_message = _("Failed to delete book");
+       } else if ((editing_flags & E_EDITING_FLAG_IS_CALENDAR) != 0) {
+               question_tag = "calendar:prompt-delete-remote-calendar";
+               description = _("Deleting calendar…");
+               error_message = _("Failed to delete calendar");
+       } else {
+               question_tag = "system:prompt-delete-remote-collection";
+               description = _("Deleting collection…");
+               error_message = _("Failed to delete collection");
+       }
+
+       parent = gtk_widget_get_toplevel (button);
+       if (!GTK_IS_WINDOW (parent))
+               parent = NULL;
+
+       if (e_alert_run_dialog_for_args (parent ? GTK_WINDOW (parent) : NULL, question_tag, display_name, 
NULL) == GTK_RESPONSE_YES) {
+               EActivity *activity;
+               DeleteData *dd;
+
+               dd = g_new0 (DeleteData, 1);
+               dd->webdav_browser_weakref = e_weak_ref_new (webdav_browser);
+               dd->href = g_strdup (href);
+
+               e_webdav_browser_abort (webdav_browser);
+
+               g_clear_object (&webdav_browser->priv->cancellable);
+
+               webdav_browser_change_busy_state (webdav_browser, TRUE);
+
+               activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (webdav_browser),
+                       description, "system:generic-error", error_message,
+                       webdav_browser_delete_thread, dd,
+                       delete_data_free);
+
+               if (activity) {
+                       webdav_browser->priv->cancellable = e_activity_get_cancellable (activity);
+
+                       if (webdav_browser->priv->cancellable)
+                               g_object_ref (webdav_browser->priv->cancellable);
+
+                       e_activity_bar_set_activity (webdav_browser->priv->activity_bar, activity);
+
+                       g_object_unref (activity);
+               } else {
+                       webdav_browser_change_busy_state (webdav_browser, FALSE);
+                       webdav_browser_schedule_ui_update (webdav_browser, NULL, NULL, NULL);
+               }
+       }
+
+       g_free (href);
+       g_free (display_name);
+}
+
+static void
+webdav_browser_refresh (EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       gtk_widget_set_sensitive (webdav_browser->priv->refresh_button, webdav_browser->priv->session != 
NULL);
+
+       gtk_tree_store_clear (GTK_TREE_STORE (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (
+               gtk_tree_view_get_model (GTK_TREE_VIEW (webdav_browser->priv->tree_view))))));
+
+       g_hash_table_remove_all (webdav_browser->priv->href_to_reference);
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+       g_slist_free_full (webdav_browser->priv->resources, resource_data_free);
+       webdav_browser->priv->resources = NULL;
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+       if (webdav_browser->priv->session) {
+               ESource *source;
+               ESourceWebdav *webdav_extension;
+               SoupURI *suri;
+
+               source = e_soup_session_get_source (E_SOUP_SESSION (webdav_browser->priv->session));
+               g_return_if_fail (E_IS_SOURCE (source));
+               g_return_if_fail (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND));
+
+               webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+               suri = e_source_webdav_dup_soup_uri (webdav_extension);
+
+               g_return_if_fail (suri != NULL);
+
+               gtk_label_set_text (webdav_browser->priv->url_label, soup_uri_get_host (suri));
+
+               soup_uri_free (suri);
+
+               webdav_browser_search_user_home (webdav_browser);
+       } else {
+               gtk_label_set_text (webdav_browser->priv->url_label, "");
+       }
+}
+
+static gint
+webdav_browser_compare_iters_cb (GtkTreeModel *model,
+                                GtkTreeIter *aa,
+                                GtkTreeIter *bb,
+                                gpointer user_data)
+{
+       gchar *aa_display_name = NULL, *bb_display_name = NULL;
+       gint res;
+
+       if (!aa || !bb)
+               return aa == bb ? 0 : bb ? -1 : 1;
+
+       gtk_tree_model_get (model, aa, COLUMN_STRING_DISPLAY_NAME, &aa_display_name, -1);
+       gtk_tree_model_get (model, bb, COLUMN_STRING_DISPLAY_NAME, &bb_display_name, -1);
+
+       if (!aa_display_name || !bb_display_name)
+               res = g_strcmp0 (aa_display_name, bb_display_name);
+       else
+               res = g_utf8_collate (aa_display_name, bb_display_name);
+
+       g_free (aa_display_name);
+       g_free (bb_display_name);
+
+       return res;
+}
+
+static GtkWidget *
+webdav_browser_tree_view_new (EWebDAVBrowser *webdav_browser)
+{
+       GtkTreeStore *tree_store;
+       GtkTreeView *tree_view;
+       GtkTreeViewColumn *column;
+       GtkTreeModel *sort_model;
+       GtkCellRenderer *cell_renderer;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), NULL);
+
+       tree_store = gtk_tree_store_new (N_COLUMNS,
+               G_TYPE_STRING,  /* COLUMN_STRING_DISPLAY_NAME */
+               G_TYPE_STRING,  /* COLUMN_STRING_TYPE */
+               G_TYPE_STRING,  /* COLUMN_STRING_HREF */
+               G_TYPE_STRING,  /* COLUMN_STRING_DESCRIPTION */
+               G_TYPE_STRING,  /* COLUMN_STRING_ICON_NAME */
+               G_TYPE_BOOLEAN, /* COLUMN_BOOL_ICON_VISIBLE */
+               GDK_TYPE_RGBA,  /* COLUMN_RGBA_COLOR */
+               G_TYPE_BOOLEAN, /* COLUMN_BOOL_COLOR_VISIBLE */
+               G_TYPE_BOOLEAN, /* COLUMN_BOOL_CHILDREN_LOADED */
+               G_TYPE_UINT,    /* COLUMN_UINT_EDITING_FLAGS */
+               G_TYPE_UINT     /* COLUMN_UINT_SUPPORTS */
+       );
+
+       sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (tree_store));
+       gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (sort_model),
+               webdav_browser_compare_iters_cb, NULL, NULL);
+
+       tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (sort_model));
+
+       g_object_unref (sort_model);
+       g_object_unref (tree_store);
+
+       gtk_tree_view_set_reorderable (tree_view, FALSE);
+       gtk_tree_view_set_tooltip_column (tree_view, COLUMN_STRING_DESCRIPTION);
+
+       /* Column: Name */
+
+       column = gtk_tree_view_column_new ();
+       gtk_tree_view_column_set_expand (column, TRUE);
+       gtk_tree_view_column_set_resizable (column, TRUE);
+       gtk_tree_view_column_set_title (column, _("Name"));
+
+       cell_renderer = gtk_cell_renderer_pixbuf_new ();
+       g_object_set (cell_renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
+       gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
+
+       gtk_tree_view_column_add_attribute (column, cell_renderer, "icon-name", COLUMN_STRING_ICON_NAME);
+       gtk_tree_view_column_add_attribute (column, cell_renderer, "visible", COLUMN_BOOL_ICON_VISIBLE);
+
+       cell_renderer = e_cell_renderer_color_new ();
+       gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
+
+       gtk_tree_view_column_add_attribute (column, cell_renderer, "rgba", COLUMN_RGBA_COLOR);
+       gtk_tree_view_column_add_attribute (column, cell_renderer, "visible", COLUMN_BOOL_COLOR_VISIBLE);
+
+       cell_renderer = gtk_cell_renderer_text_new ();
+       g_object_set (cell_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+       gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
+
+       gtk_tree_view_column_add_attribute (column, cell_renderer, "text", COLUMN_STRING_DISPLAY_NAME);
+
+       gtk_tree_view_append_column (tree_view, column);
+       gtk_tree_view_set_expander_column (tree_view, column);
+
+       /* Column: Type */
+
+       column = gtk_tree_view_column_new ();
+       gtk_tree_view_column_set_expand (column, FALSE);
+       gtk_tree_view_column_set_resizable (column, TRUE);
+       gtk_tree_view_column_set_title (column, _("Type"));
+
+       cell_renderer = gtk_cell_renderer_text_new ();
+       g_object_set (cell_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+       gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+
+       gtk_tree_view_column_add_attribute (column, cell_renderer, "text", COLUMN_STRING_TYPE);
+
+       gtk_tree_view_append_column (tree_view, column);
+
+       return GTK_WIDGET (tree_view);
+}
+
+static void
+webdav_browser_create_popover (EWebDAVBrowser *webdav_browser)
+{
+       GtkWidget *widget, *label;
+       GtkGrid *grid;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (webdav_browser->priv->create_edit_popover == NULL);
+
+       widget = gtk_grid_new ();
+       grid = GTK_GRID (widget);
+       gtk_grid_set_column_spacing (grid, 6);
+       gtk_grid_set_row_spacing (grid, 6);
+
+       widget = gtk_label_new_with_mnemonic (_("_Name:"));
+       gtk_widget_set_halign (widget, GTK_ALIGN_END);
+       gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+       label = widget;
+
+       widget = gtk_entry_new ();
+       gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+       gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+       webdav_browser->priv->create_edit_name_entry = widget;
+
+       widget = gtk_label_new_with_mnemonic (_("_Color:"));
+       gtk_widget_set_halign (widget, GTK_ALIGN_END);
+       gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+       webdav_browser->priv->create_edit_color_label = widget;
+       label = widget;
+
+       widget = gtk_color_button_new ();
+       gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+       gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+       webdav_browser->priv->create_edit_color_chooser = widget;
+
+       widget = gtk_label_new (_("For Components:"));
+       gtk_widget_set_halign (widget, GTK_ALIGN_END);
+       gtk_widget_set_valign (widget, GTK_ALIGN_START);
+       gtk_grid_attach (grid, widget, 0, 2, 1, 1);
+       webdav_browser->priv->create_edit_support_label = widget;
+
+       widget = gtk_check_button_new_with_mnemonic (_("_Events"));
+       gtk_grid_attach (grid, widget, 1, 2, 1, 1);
+       webdav_browser->priv->create_edit_event_check = widget;
+
+       widget = gtk_check_button_new_with_mnemonic (_("_Memos"));
+       gtk_grid_attach (grid, widget, 1, 3, 1, 1);
+       webdav_browser->priv->create_edit_memo_check = widget;
+
+       widget = gtk_check_button_new_with_mnemonic (_("_Tasks"));
+       gtk_grid_attach (grid, widget, 1, 4, 1, 1);
+       webdav_browser->priv->create_edit_task_check = widget;
+
+       widget = gtk_label_new_with_mnemonic (_("_Description:"));
+       gtk_widget_set_halign (widget, GTK_ALIGN_END);
+       gtk_widget_set_valign (widget, GTK_ALIGN_START);
+       gtk_grid_attach (grid, widget, 0, 5, 1, 1);
+       webdav_browser->priv->create_edit_description_label = widget;
+       label = widget;
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, 
GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+       gtk_grid_attach (grid, widget, 1, 5, 1, 1);
+       webdav_browser->priv->create_edit_description_scrolled_window = widget;
+
+       widget = gtk_text_view_new ();
+       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (widget), GTK_WRAP_WORD);
+       gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+       e_spell_text_view_attach (GTK_TEXT_VIEW (widget));
+       gtk_container_add (GTK_CONTAINER (webdav_browser->priv->create_edit_description_scrolled_window), 
widget);
+       webdav_browser->priv->create_edit_description_textview = widget;
+
+       widget = gtk_button_new_with_mnemonic (_("_Save"));
+       gtk_widget_set_halign (widget, GTK_ALIGN_END);
+       gtk_grid_attach (grid, widget, 0, 6, 2, 1);
+       webdav_browser->priv->create_edit_save_button = widget;
+
+       gtk_widget_show_all (GTK_WIDGET (grid));
+
+       widget = gtk_popover_new (GTK_WIDGET (webdav_browser));
+       gtk_popover_set_position (GTK_POPOVER (widget), GTK_POS_BOTTOM);
+       gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (grid));
+       gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
+       webdav_browser->priv->create_edit_popover = widget;
+
+       label = gtk_label_new ("");
+       gtk_widget_show (label);
+       webdav_browser->priv->create_edit_hint_label = label;
+
+       widget = gtk_popover_new (webdav_browser->priv->create_edit_popover);
+       gtk_popover_set_position (GTK_POPOVER (widget), GTK_POS_BOTTOM);
+       gtk_popover_set_modal (GTK_POPOVER (widget), FALSE);
+       gtk_container_add (GTK_CONTAINER (widget), label);
+       gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
+       webdav_browser->priv->create_edit_hint_popover = widget;
+}
+
+static void
+webdav_browser_submit_alert (EAlertSink *alert_sink,
+                            EAlert *alert)
+{
+       EWebDAVBrowser *webdav_browser;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (alert_sink));
+
+       webdav_browser = E_WEBDAV_BROWSER (alert_sink);
+
+       e_alert_bar_submit_alert (webdav_browser->priv->alert_bar, alert);
+}
+
+static void
+webdav_browser_set_credentials_prompter (EWebDAVBrowser *webdav_browser,
+                                        ECredentialsPrompter *credentials_prompter)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       g_return_if_fail (E_IS_CREDENTIALS_PROMPTER (credentials_prompter));
+       g_return_if_fail (webdav_browser->priv->credentials_prompter == NULL);
+
+       webdav_browser->priv->credentials_prompter = g_object_ref (credentials_prompter);
+}
+
+static void
+webdav_browser_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CREDENTIALS_PROMPTER:
+                       webdav_browser_set_credentials_prompter (
+                               E_WEBDAV_BROWSER (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_SOURCE:
+                       e_webdav_browser_set_source (
+                               E_WEBDAV_BROWSER (object),
+                               g_value_get_object (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+webdav_browser_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_CREDENTIALS_PROMPTER:
+                       g_value_set_object (
+                               value,
+                               e_webdav_browser_get_credentials_prompter (
+                               E_WEBDAV_BROWSER (object)));
+                       return;
+
+               case PROP_SOURCE:
+                       g_value_take_object (
+                               value,
+                               e_webdav_browser_ref_source (
+                               E_WEBDAV_BROWSER (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+webdav_browser_dispose (GObject *object)
+{
+       EWebDAVBrowser *webdav_browser = E_WEBDAV_BROWSER (object);
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+
+       if (webdav_browser->priv->update_ui_id) {
+               g_source_remove (webdav_browser->priv->update_ui_id);
+               webdav_browser->priv->update_ui_id = 0;
+       }
+
+       if (webdav_browser->priv->cancellable) {
+               g_cancellable_cancel (webdav_browser->priv->cancellable);
+               g_clear_object (&webdav_browser->priv->cancellable);
+       }
+
+       g_clear_object (&webdav_browser->priv->session);
+       g_clear_object (&webdav_browser->priv->credentials_prompter);
+
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_webdav_browser_parent_class)->dispose (object);
+}
+
+static void
+webdav_browser_finalize (GObject *object)
+{
+       EWebDAVBrowser *webdav_browser = E_WEBDAV_BROWSER (object);
+
+       g_slist_free_full (webdav_browser->priv->resources, resource_data_free);
+       g_hash_table_destroy (webdav_browser->priv->href_to_reference);
+
+       g_mutex_clear (&webdav_browser->priv->property_lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_webdav_browser_parent_class)->finalize (object);
+}
+
+static void
+webdav_browser_constructed (GObject *object)
+{
+       EWebDAVBrowser *webdav_browser = E_WEBDAV_BROWSER (object);
+       GtkTreeSelection *selection;
+       GtkWidget *container;
+       GtkWidget *widget;
+       GtkGrid *grid;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_webdav_browser_parent_class)->constructed (object);
+
+       grid = GTK_GRID (webdav_browser);
+       gtk_grid_set_column_spacing (grid, 6);
+       gtk_grid_set_row_spacing (grid, 6);
+
+       widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+       gtk_grid_attach (grid, widget, 0, 0, 2, 1);
+
+       container = widget;
+
+       widget = gtk_label_new (_("WebDAV server:"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+
+       widget = gtk_label_new ("");
+       webdav_browser->priv->url_label = GTK_LABEL (widget);
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget), GTK_POLICY_AUTOMATIC, 
GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+       gtk_widget_set_hexpand (widget, TRUE);
+       gtk_widget_set_vexpand (widget, TRUE);
+       gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+       container = widget;
+
+       widget = webdav_browser_tree_view_new (webdav_browser);
+       gtk_container_add (GTK_CONTAINER (container), widget);
+       webdav_browser->priv->tree_view = widget;
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+       g_signal_connect (selection, "changed",
+               G_CALLBACK (webdav_browser_selection_changed_cb), webdav_browser);
+
+       g_signal_connect (widget, "row-expanded",
+               G_CALLBACK (webdav_browser_row_expanded_cb), webdav_browser);
+
+       widget = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
+       gtk_button_box_set_layout (GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_START);
+       gtk_box_set_spacing (GTK_BOX (widget), 6);
+       gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+
+       container = widget;
+
+       widget = gtk_button_new_with_mnemonic (_("Create _Book"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->create_book_button = widget;
+
+       g_signal_connect (webdav_browser->priv->create_book_button, "clicked",
+               G_CALLBACK (webdav_browser_create_clicked_cb), webdav_browser);
+
+       widget = gtk_button_new_with_mnemonic (_("Create _Calendar"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->create_calendar_button = widget;
+
+       g_signal_connect (webdav_browser->priv->create_calendar_button, "clicked",
+               G_CALLBACK (webdav_browser_create_clicked_cb), webdav_browser);
+
+       widget = gtk_button_new_with_mnemonic (_("Create Collectio_n"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->create_collection_button = widget;
+
+       g_signal_connect (webdav_browser->priv->create_collection_button, "clicked",
+               G_CALLBACK (webdav_browser_create_clicked_cb), webdav_browser);
+
+       widget = gtk_button_new_with_mnemonic (_("_Edit"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->edit_button = widget;
+
+       g_signal_connect (webdav_browser->priv->edit_button, "clicked",
+               G_CALLBACK (webdav_browser_edit_clicked_cb), webdav_browser);
+
+       widget = gtk_button_new_with_mnemonic (_("_Delete"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->delete_button = widget;
+
+       g_signal_connect (webdav_browser->priv->delete_button, "clicked",
+               G_CALLBACK (webdav_browser_delete_clicked_cb), webdav_browser);
+
+       /* widget = gtk_button_new_with_mnemonic (_("_Permissions"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->permissions_button = widget; */
+
+       widget = gtk_button_new_with_mnemonic (_("_Refresh"));
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+       webdav_browser->priv->refresh_button = widget;
+
+       g_signal_connect_swapped (webdav_browser->priv->refresh_button, "clicked",
+               G_CALLBACK (webdav_browser_refresh), webdav_browser);
+
+       gtk_widget_show_all (GTK_WIDGET (grid));
+
+       /* Add the bars after show_all call, because they manage
+          their visibility on their own */
+       widget = e_alert_bar_new ();
+       gtk_widget_set_margin_bottom (widget, 6);
+       gtk_grid_attach (grid, widget, 0, 2, 2, 1);
+       webdav_browser->priv->alert_bar = E_ALERT_BAR (widget);
+
+       widget = e_activity_bar_new ();
+       gtk_widget_set_margin_bottom (widget, 6);
+       gtk_grid_attach (grid, widget, 0, 3, 2, 1);
+       webdav_browser->priv->activity_bar = E_ACTIVITY_BAR (widget);
+
+       webdav_browser_create_popover (webdav_browser);
+}
+
+static void
+e_webdav_browser_class_init (EWebDAVBrowserClass *klass)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (klass, sizeof (EWebDAVBrowserPrivate));
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->set_property = webdav_browser_set_property;
+       object_class->get_property = webdav_browser_get_property;
+       object_class->dispose = webdav_browser_dispose;
+       object_class->finalize = webdav_browser_finalize;
+       object_class->constructed = webdav_browser_constructed;
+
+       /**
+        * EWebDAVBrowser:credentials-prompter:
+        *
+        * The #ECredentialsPrompter used to ask for credentials when needed.
+        *
+        * Since: 3.26
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_CREDENTIALS_PROMPTER,
+               g_param_spec_object (
+                       "credentials-prompter",
+                       "Credentials Prompter",
+                       "an ECredentialsPrompter",
+                       E_TYPE_CREDENTIALS_PROMPTER,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       /**
+        * EWebDAVBrowser:source:
+        *
+        * The #ESource currently used for the GUI. It can be %NULL.
+        *
+        * Since: 3.26
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_SOURCE,
+               g_param_spec_object (
+                       "source",
+                       "Source",
+                       "an ESource",
+                       E_TYPE_SOURCE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+}
+
+static void
+webdav_browser_alert_sink_init (EAlertSinkInterface *iface)
+{
+       iface->submit_alert = webdav_browser_submit_alert;
+}
+
+static void
+e_webdav_browser_init (EWebDAVBrowser *webdav_browser)
+{
+       webdav_browser->priv = G_TYPE_INSTANCE_GET_PRIVATE (webdav_browser, E_TYPE_WEBDAV_BROWSER, 
EWebDAVBrowserPrivate);
+
+       g_mutex_init (&webdav_browser->priv->property_lock);
+
+       webdav_browser->priv->href_to_reference = g_hash_table_new_full (camel_strcase_hash, 
camel_strcase_equal,
+               g_free, (GDestroyNotify) gtk_tree_row_reference_free);
+}
+
+/**
+ * e_webdav_browser_new:
+ * @credentials_prompter: an #ECredentialsPrompter
+ *
+ * Creates a new #EWebDAVBrowser instance.
+ *
+ * Returns: (transfer full): an #EWebDAVBrowser as a #GtkWidget
+ *
+ * Since: 3.26
+ **/
+GtkWidget *
+e_webdav_browser_new (ECredentialsPrompter *credentials_prompter)
+{
+       return g_object_new (E_TYPE_WEBDAV_BROWSER,
+               "credentials-prompter", credentials_prompter,
+               NULL);
+}
+
+/**
+ * e_webdav_browser_get_credentials_prompter:
+ * @webdav_browser: an #EWebDAVBrowser
+ *
+ * Returns: (transfer none): an #ECredentialsPrompter used to call
+ *    of e_webdav_browser_new()
+ *
+ * Since: 3.26
+ **/
+ECredentialsPrompter *
+e_webdav_browser_get_credentials_prompter (EWebDAVBrowser *webdav_browser)
+{
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), NULL);
+
+       return webdav_browser->priv->credentials_prompter;
+}
+
+/**
+ * e_webdav_browser_set_source:
+ * @webdav_browser: an #EWebDAVBrowser
+ * @source: (nullable): an #ESource
+ *
+ * Sets the @source to be the one used for the @webdav_browser.
+ * It can be %NULL, to have none set.
+ *
+ * Since: 3.26
+ **/
+void
+e_webdav_browser_set_source (EWebDAVBrowser *webdav_browser,
+                            ESource *source)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+       if (source)
+               g_return_if_fail (E_IS_SOURCE (source));
+
+       e_alert_bar_clear (webdav_browser->priv->alert_bar);
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+
+       if (!source && !webdav_browser->priv->session) {
+               g_mutex_unlock (&webdav_browser->priv->property_lock);
+               return;
+       }
+
+       g_clear_object (&webdav_browser->priv->session);
+
+       if (source) {
+               webdav_browser->priv->session = e_webdav_session_new (source);
+
+               if (webdav_browser->priv->session)
+                       e_soup_session_setup_logging (E_SOUP_SESSION (webdav_browser->priv->session), 
g_getenv ("WEBDAV_DEBUG"));
+       }
+
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+       webdav_browser_refresh (webdav_browser);
+
+       g_object_notify (G_OBJECT (webdav_browser), "source");
+}
+
+/**
+ * e_webdav_browser_ref_source:
+ * @webdav_browser: an #EWebDAVBrowser
+ *
+ * Returns: (transfer full) (nullable): an #ESource, currently used by @webdav_browser;
+ *    if not %NULL, then free with g_object_unref(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+ESource *
+e_webdav_browser_ref_source (EWebDAVBrowser *webdav_browser)
+{
+       ESource *source = NULL;
+
+       g_return_val_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser), NULL);
+
+       g_mutex_lock (&webdav_browser->priv->property_lock);
+
+       if (webdav_browser->priv->session) {
+               source = e_soup_session_get_source (E_SOUP_SESSION (webdav_browser->priv->session));
+               if (source)
+                       g_object_ref (source);
+       }
+
+       g_mutex_unlock (&webdav_browser->priv->property_lock);
+
+       return source;
+}
+
+/**
+ * e_webdav_browser_abort:
+ * @webdav_browser: an #EWebDAVBrowser
+ *
+ * Aborts any ongoing operation. It does nothing, if no
+ * operation is running.
+ *
+ * Since: 3.26
+ **/
+void
+e_webdav_browser_abort (EWebDAVBrowser *webdav_browser)
+{
+       g_return_if_fail (E_IS_WEBDAV_BROWSER (webdav_browser));
+
+       if (webdav_browser->priv->cancellable)
+               g_cancellable_cancel (webdav_browser->priv->cancellable);
+}
diff --git a/src/e-util/e-webdav-browser.h b/src/e-util/e-webdav-browser.h
new file mode 100644
index 0000000..b043e5f
--- /dev/null
+++ b/src/e-util/e-webdav-browser.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_WEBDAV_BROWSER_H
+#define E_WEBDAV_BROWSER_H
+
+#include <gtk/gtk.h>
+#include <libedataserverui/libedataserverui.h>
+
+/* Standard GObject macros */
+#define E_TYPE_WEBDAV_BROWSER \
+       (e_webdav_browser_get_type ())
+#define E_WEBDAV_BROWSER(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_WEBDAV_BROWSER, EWebDAVBrowser))
+#define E_WEBDAV_BROWSER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_WEBDAV_BROWSER, EWebDAVBrowserClass))
+#define E_IS_WEBDAV_BROWSER(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_WEBDAV_BROWSER))
+#define E_IS_WEBDAV_BROWSER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_WEBDAV_BROWSER))
+#define E_WEBDAV_BROWSER_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_WEBDAV_BROWSER, EWebDAVBrowserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EWebDAVBrowser EWebDAVBrowser;
+typedef struct _EWebDAVBrowserClass EWebDAVBrowserClass;
+typedef struct _EWebDAVBrowserPrivate EWebDAVBrowserPrivate;
+
+/**
+ * EWebDAVBrowser:
+ *
+ * Contains only private data that should be read and manipulated using
+ * the functions below.
+ *
+ * Since: 3.26
+ **/
+struct _EWebDAVBrowser {
+       GtkGrid parent;
+       EWebDAVBrowserPrivate *priv;
+};
+
+struct _EWebDAVBrowserClass {
+       GtkGridClass parent_class;
+};
+
+GType          e_webdav_browser_get_type               (void) G_GNUC_CONST;
+GtkWidget *    e_webdav_browser_new                    (ECredentialsPrompter *credentials_prompter);
+ECredentialsPrompter *
+               e_webdav_browser_get_credentials_prompter
+                                                       (EWebDAVBrowser *webdav_browser);
+void           e_webdav_browser_set_source             (EWebDAVBrowser *webdav_browser,
+                                                        ESource *source);
+ESource *      e_webdav_browser_ref_source             (EWebDAVBrowser *webdav_browser);
+void           e_webdav_browser_abort                  (EWebDAVBrowser *webdav_browser);
+void           e_webdav_browser_refresh                (EWebDAVBrowser *webdav_browser);
+
+G_END_DECLS
+
+#endif /* E_WEBDAV_BROWSER_H */
diff --git a/src/modules/accounts-window/CMakeLists.txt b/src/modules/accounts-window/CMakeLists.txt
index 864076b..1c51b47 100644
--- a/src/modules/accounts-window/CMakeLists.txt
+++ b/src/modules/accounts-window/CMakeLists.txt
@@ -6,6 +6,8 @@ set(sources
        accounts-window.c
        e-accounts-window-editors.c
        e-accounts-window-editors.h
+       e-webdav-browser-page.c
+       e-webdav-browser-page.h
 )
 set(extra_defines)
 set(extra_cflags)
diff --git a/src/modules/accounts-window/accounts-window.c b/src/modules/accounts-window/accounts-window.c
index 6a8e07a..400fbc2 100644
--- a/src/modules/accounts-window/accounts-window.c
+++ b/src/modules/accounts-window/accounts-window.c
@@ -21,6 +21,7 @@
 #include <glib-object.h>
 
 #include "e-accounts-window-editors.h"
+#include "e-webdav-browser-page.h"
 
 /* Module Entry Points */
 void e_module_load (GTypeModule *type_module);
@@ -30,6 +31,7 @@ G_MODULE_EXPORT void
 e_module_load (GTypeModule *type_module)
 {
        e_accounts_window_editors_type_register (type_module);
+       e_webdav_browser_page_type_register (type_module);
 }
 
 G_MODULE_EXPORT void
diff --git a/src/modules/accounts-window/e-webdav-browser-page.c 
b/src/modules/accounts-window/e-webdav-browser-page.c
new file mode 100644
index 0000000..ade9008
--- /dev/null
+++ b/src/modules/accounts-window/e-webdav-browser-page.c
@@ -0,0 +1,236 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "evolution-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "e-util/e-util.h"
+#include "shell/e-shell.h"
+
+#include "e-webdav-browser-page.h"
+
+/* Standard GObject macros */
+#define E_TYPE_WEBDAV_BROWSER_PAGE \
+       (e_webdav_browser_page_get_type ())
+#define E_WEBDAV_BROWSER_PAGE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_WEBDAV_BROWSER_PAGE, EWebDAVBrowserPage))
+#define E_WEBDAV_BROWSER_PAGE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_WEBDAV_BROWSER_PAGE, EWebDAVBrowserPageClass))
+#define E_IS_WEBDAV_BROWSER_PAGE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_WEBDAV_BROWSER_PAGE))
+#define E_IS_WEBDAV_BROWSER_PAGE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_WEBDAV_BROWSER_PAGE))
+#define E_WEBDAV_BROWSER_PAGE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_WEBDAV_BROWSER_PAGE, EWebDAVBrowserPageClass))
+
+typedef struct _EWebDAVBrowserPage EWebDAVBrowserPage;
+typedef struct _EWebDAVBrowserPageClass EWebDAVBrowserPageClass;
+
+struct _EWebDAVBrowserPage {
+       EExtension parent;
+
+       GtkWidget *browse_button;
+       EWebDAVBrowser *webdav_browser;
+       gint page_index;
+};
+
+struct _EWebDAVBrowserPageClass {
+       EExtensionClass parent_class;
+};
+
+GType e_webdav_browser_page_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_DYNAMIC_TYPE (EWebDAVBrowserPage, e_webdav_browser_page, E_TYPE_EXTENSION)
+
+static void
+webdav_browser_page_selection_changed_cb (EAccountsWindow *accounts_window,
+                                         ESource *source,
+                                         gpointer user_data)
+{
+       EWebDAVBrowserPage *page = user_data;
+       gboolean has_path = FALSE;
+
+       g_return_if_fail (E_IS_ACCOUNTS_WINDOW (accounts_window));
+       g_return_if_fail (E_IS_WEBDAV_BROWSER_PAGE (page));
+
+       if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
+               gchar *path;
+
+               path = e_source_webdav_dup_resource_path (e_source_get_extension (source, 
E_SOURCE_EXTENSION_WEBDAV_BACKEND));
+
+               has_path = path && *path;
+
+               g_free (path);
+       }
+
+       gtk_widget_set_sensitive (page->browse_button, has_path);
+}
+
+static void
+webdav_browser_page_browse_button_clicked_cb (GtkButton *button,
+                                             gpointer user_data)
+{
+       EWebDAVBrowserPage *page = user_data;
+       EAccountsWindow *accounts_window;
+       ESource *source;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER_PAGE (page));
+
+       accounts_window = E_ACCOUNTS_WINDOW (e_extension_get_extensible (E_EXTENSION (page)));
+       g_return_if_fail (E_IS_ACCOUNTS_WINDOW (accounts_window));
+
+       source = e_accounts_window_ref_selected_source (accounts_window);
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       e_accounts_window_activate_page (accounts_window, page->page_index);
+       e_webdav_browser_set_source (page->webdav_browser, source);
+
+       g_object_unref (source);
+}
+
+static void
+webdav_browser_back_button_clicked_cb (GtkButton *button,
+                                      gpointer user_data)
+{
+       EWebDAVBrowserPage *page = user_data;
+       EAccountsWindow *accounts_window;
+
+       g_return_if_fail (E_IS_WEBDAV_BROWSER_PAGE (page));
+
+       accounts_window = E_ACCOUNTS_WINDOW (e_extension_get_extensible (E_EXTENSION (page)));
+       g_return_if_fail (E_IS_ACCOUNTS_WINDOW (accounts_window));
+
+       e_webdav_browser_abort (page->webdav_browser);
+       e_webdav_browser_set_source (page->webdav_browser, NULL);
+
+       e_accounts_window_activate_page (accounts_window, -1);
+}
+
+static void
+webdav_browser_page_constructed (GObject *object)
+{
+       EAccountsWindow *accounts_window;
+       ECredentialsPrompter *credentials_prompter;
+       EWebDAVBrowserPage *page;
+       EShell *shell;
+       GtkButtonBox *button_box;
+       GtkWidget *widget;
+       GtkWidget *vbox;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_webdav_browser_page_parent_class)->constructed (object);
+
+       page = E_WEBDAV_BROWSER_PAGE (object);
+       accounts_window = E_ACCOUNTS_WINDOW (e_extension_get_extensible (E_EXTENSION (page)));
+
+       g_signal_connect (accounts_window, "selection-changed",
+               G_CALLBACK (webdav_browser_page_selection_changed_cb), object);
+
+       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+       gtk_widget_show (vbox);
+
+       shell = e_shell_get_default ();
+       /* Can be NULL in test-accounts-window */
+       if (shell) {
+               credentials_prompter = e_shell_get_credentials_prompter (shell);
+               g_object_ref (credentials_prompter);
+       } else {
+               credentials_prompter = e_credentials_prompter_new (e_accounts_window_get_registry 
(accounts_window));
+       }
+
+       widget = e_webdav_browser_new (credentials_prompter);
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               NULL);
+       gtk_widget_show (widget);
+       gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0);
+       page->webdav_browser = E_WEBDAV_BROWSER (widget);
+
+       g_object_unref (credentials_prompter);
+
+       widget = e_dialog_button_new_with_icon ("go-previous", _("_Back"));
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_END,
+               "vexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               NULL);
+       gtk_widget_show (widget);
+       gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked",
+               G_CALLBACK (webdav_browser_back_button_clicked_cb), page);
+
+       page->page_index = e_accounts_window_add_page (accounts_window, vbox);
+
+       button_box = e_accounts_window_get_button_box (accounts_window);
+
+       widget = gtk_label_new ("");
+       gtk_widget_show (widget);
+       gtk_box_pack_start (GTK_BOX (button_box), widget, FALSE, FALSE, 0);
+
+       widget = gtk_button_new_with_mnemonic (_("_Browse"));
+       gtk_widget_set_sensitive (widget, FALSE);
+       gtk_widget_set_tooltip_text (widget, _("Browse a WebDAV (CalDAV or CardDAV) server and create, edit 
or delete address books, calendars, memo lists or task lists there"));
+       gtk_widget_show (widget);
+       gtk_box_pack_start (GTK_BOX (button_box), widget, FALSE, FALSE, 0);
+       page->browse_button = widget;
+
+       g_signal_connect (widget, "clicked",
+               G_CALLBACK (webdav_browser_page_browse_button_clicked_cb), page);
+}
+
+static void
+e_webdav_browser_page_class_init (EWebDAVBrowserPageClass *class)
+{
+       GObjectClass *object_class;
+       EExtensionClass *extension_class;
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->constructed = webdav_browser_page_constructed;
+
+       extension_class = E_EXTENSION_CLASS (class);
+       extension_class->extensible_type = E_TYPE_ACCOUNTS_WINDOW;
+}
+
+static void
+e_webdav_browser_page_class_finalize (EWebDAVBrowserPageClass *class)
+{
+}
+
+static void
+e_webdav_browser_page_init (EWebDAVBrowserPage *extension)
+{
+}
+
+void
+e_webdav_browser_page_type_register (GTypeModule *type_module)
+{
+       /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+        *     function, so we have to wrap it with a public function in
+        *     order to register types from a separate compilation unit. */
+       e_webdav_browser_page_register_type (type_module);
+}
diff --git a/src/modules/accounts-window/e-webdav-browser-page.h 
b/src/modules/accounts-window/e-webdav-browser-page.h
new file mode 100644
index 0000000..75239e0
--- /dev/null
+++ b/src/modules/accounts-window/e-webdav-browser-page.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef E_WEBDAV_BROWSER_PAGE_H
+#define E_WEBDAV_BROWSER_PAGE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_webdav_browser_page_type_register (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_WEBDAV_BROWSER_PAGE_H */


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