aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/caldav
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2009-12-08 04:19:50 +0800
committerMilan Crha <mcrha@redhat.com>2009-12-08 04:19:50 +0800
commita7cede983ca1580a16df40fd07cebea2d69cefa6 (patch)
treecd66cd60ae1610d5e035b5f3f79c588d94faf73c /plugins/caldav
parent31204c9cc21ec32c8006124cf18763161daa99d2 (diff)
downloadgsoc2013-evolution-a7cede983ca1580a16df40fd07cebea2d69cefa6.tar.gz
gsoc2013-evolution-a7cede983ca1580a16df40fd07cebea2d69cefa6.tar.zst
gsoc2013-evolution-a7cede983ca1580a16df40fd07cebea2d69cefa6.zip
Bug #359755 - Support for CalDAV collections
Diffstat (limited to 'plugins/caldav')
-rw-r--r--plugins/caldav/Makefile.am25
-rw-r--r--plugins/caldav/caldav-browse-server.c1341
-rw-r--r--plugins/caldav/caldav-browse-server.h32
-rw-r--r--plugins/caldav/caldav-source.c47
4 files changed, 1436 insertions, 9 deletions
diff --git a/plugins/caldav/Makefile.am b/plugins/caldav/Makefile.am
index 51e12acbe1..18c2a91648 100644
--- a/plugins/caldav/Makefile.am
+++ b/plugins/caldav/Makefile.am
@@ -3,18 +3,25 @@
plugin_DATA = org-gnome-evolution-caldav.eplug
plugin_LTLIBRARIES = liborg-gnome-evolution-caldav.la
-liborg_gnome_evolution_caldav_la_CPPFLAGS = \
- $(AM_CPPFLAGS) \
- -I . \
- -I$(top_srcdir) \
- -DCALDAV_UIDIR=\""$(uidir)"\" \
- $(EVOLUTION_CALENDAR_CFLAGS)
-
-liborg_gnome_evolution_caldav_la_SOURCES = caldav-source.c
+liborg_gnome_evolution_caldav_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I . \
+ -I$(top_srcdir) \
+ -DCALDAV_UIDIR=\""$(uidir)"\" \
+ $(EVOLUTION_CALENDAR_CFLAGS) \
+ $(LIBSOUP_CFLAGS)
+
+liborg_gnome_evolution_caldav_la_SOURCES = \
+ caldav-source.c \
+ caldav-browse-server.h \
+ caldav-browse-server.c
liborg_gnome_evolution_caldav_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/widgets/misc/libemiscwidgets.la \
$(EVOLUTION_CALENDAR_LIBS) \
- $(EPLUGIN_LIBS)
+ $(EPLUGIN_LIBS) \
+ $(LIBSOUP_LIBS)
liborg_gnome_evolution_caldav_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
diff --git a/plugins/caldav/caldav-browse-server.c b/plugins/caldav/caldav-browse-server.c
new file mode 100644
index 0000000000..8598cdb36c
--- /dev/null
+++ b/plugins/caldav/caldav-browse-server.c
@@ -0,0 +1,1341 @@
+/*
+ * caldav-browse-server.c
+ *
+ * This program 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; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <libsoup/soup.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include <libecal/e-cal.h>
+#include <libedataserver/e-proxy.h>
+#include <libedataserverui/e-cell-renderer-color.h>
+#include <libedataserverui/e-passwords.h>
+
+#include <e-util/e-dialog-utils.h>
+#include <widgets/misc/e-spinner.h>
+
+#include "caldav-browse-server.h"
+
+#define XC (xmlChar *)
+
+enum {
+ CALDAV_THREAD_SHOULD_SLEEP,
+ CALDAV_THREAD_SHOULD_WORK,
+ CALDAV_THREAD_SHOULD_DIE
+};
+
+enum {
+ COL_BOOL_IS_LOADED,
+ COL_STRING_HREF,
+ COL_BOOL_IS_CALENDAR,
+ COL_STRING_SUPPORTS,
+ COL_STRING_DISPLAYNAME,
+ COL_GDK_COLOR,
+ COL_BOOL_HAS_COLOR,
+ COL_BOOL_SENSITIVE
+};
+
+typedef void (*process_message_cb) (GObject *dialog, guint status_code, const gchar *msg_body, gpointer user_data);
+
+static void send_xml_message (xmlDocPtr doc, const gchar *msg_type, const gchar *url, GObject *dialog, process_message_cb cb, gpointer cb_user_data, const gchar *info);
+
+static gchar *
+xpath_get_string (xmlXPathContextPtr xpctx, const gchar *path_format, ...)
+{
+ gchar *res = NULL, *path, *tmp;
+ va_list args;
+ xmlXPathObjectPtr obj;
+
+ g_return_val_if_fail (xpctx != NULL, NULL);
+ g_return_val_if_fail (path_format != NULL, NULL);
+
+ va_start (args, path_format);
+ tmp = g_strdup_vprintf (path_format, args);
+ va_end (args);
+
+ if (1 || strchr (tmp, '@') == NULL) {
+ path = g_strconcat ("string(", tmp, ")", NULL);
+ g_free (tmp);
+ } else {
+ path = tmp;
+ }
+
+ obj = xmlXPathEvalExpression (XC path, xpctx);
+ g_free (path);
+
+ if (obj == NULL)
+ return NULL;
+
+ if (obj->type == XPATH_STRING)
+ res = g_strdup ((gchar *) obj->stringval);
+
+ xmlXPathFreeObject (obj);
+
+ return res;
+}
+
+static gboolean
+xpath_exists (xmlXPathContextPtr xpctx, xmlXPathObjectPtr *resobj, const gchar *path_format, ...)
+{
+ gchar *path;
+ va_list args;
+ xmlXPathObjectPtr obj;
+
+ g_return_val_if_fail (xpctx != NULL, FALSE);
+ g_return_val_if_fail (path_format != NULL, FALSE);
+
+ va_start (args, path_format);
+ path = g_strdup_vprintf (path_format, args);
+ va_end (args);
+
+ obj = xmlXPathEvalExpression (XC path, xpctx);
+ g_free (path);
+
+ if (obj && (obj->type != XPATH_NODESET || xmlXPathNodeSetGetLength (obj->nodesetval) == 0)) {
+ xmlXPathFreeObject (obj);
+ obj = NULL;
+ }
+
+ if (resobj)
+ *resobj = obj;
+ else if (obj != NULL)
+ xmlXPathFreeObject (obj);
+
+ return obj != NULL;
+}
+
+static gchar *
+change_url_path (const gchar *base_url, const gchar *new_path)
+{
+ SoupURI *suri;
+ gchar *url;
+
+ g_return_val_if_fail (base_url != NULL, NULL);
+ g_return_val_if_fail (new_path != NULL, NULL);
+
+ suri = soup_uri_new (base_url);
+ if (!suri)
+ return NULL;
+
+ soup_uri_set_path (suri, new_path);
+
+ url = soup_uri_to_string (suri, FALSE);
+
+ soup_uri_free (suri);
+
+ return url;
+}
+
+static void
+report_error (GObject *dialog, gboolean is_fatal, const gchar *msg)
+{
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+ g_return_if_fail (msg != NULL);
+
+ if (is_fatal) {
+ GtkWidget *content_area, *w;
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ w = g_object_get_data (dialog, "caldav-info-label");
+ gtk_widget_hide (w);
+
+ w = g_object_get_data (dialog, "caldav-tree-sw");
+ gtk_widget_hide (w);
+
+ w = gtk_label_new (msg);
+ gtk_widget_show (w);
+ gtk_box_pack_start (GTK_BOX (content_area), w, TRUE, TRUE, 10);
+
+ w = g_object_get_data (dialog, "caldav-new-url-entry");
+ if (w)
+ gtk_entry_set_text (GTK_ENTRY (w), "");
+ } else {
+ GtkLabel *label = g_object_get_data (dialog, "caldav-info-label");
+
+ if (label)
+ gtk_label_set_text (label, msg);
+ }
+}
+
+static gboolean
+check_soup_status (GObject *dialog, guint status_code, const gchar *msg_body, gboolean is_fatal)
+{
+ gchar *msg;
+
+ if (status_code == 207)
+ return TRUE;
+
+ if (status_code == 401 || status_code == 403) {
+ msg = g_strdup (_("Authentication failed. Server requires correct login."));
+ } else if (status_code == 404) {
+ msg = g_strdup (_("Given URL cannot be found."));
+ } else {
+ const gchar *phrase = soup_status_get_phrase (status_code);
+
+ msg = g_strdup_printf (_("Server returned unexpected data.\n%d - %s"), status_code, phrase ? phrase : _("Unknown error"));
+ }
+
+ report_error (dialog, is_fatal, msg);
+
+ g_free (msg);
+
+ return FALSE;
+}
+
+struct test_exists_data {
+ const gchar *href;
+ gboolean exists;
+};
+
+static gboolean
+test_href_exists_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
+{
+ struct test_exists_data *ted = user_data;
+ gchar *href = NULL;
+
+ g_return_val_if_fail (model != NULL, TRUE);
+ g_return_val_if_fail (iter != NULL, TRUE);
+ g_return_val_if_fail (ted != NULL, TRUE);
+ g_return_val_if_fail (ted->href != NULL, TRUE);
+
+ gtk_tree_model_get (model, iter, COL_STRING_HREF, &href, -1);
+
+ ted->exists = href && g_ascii_strcasecmp (href, ted->href) == 0;
+
+ g_free (href);
+
+ return ted->exists;
+}
+
+static void
+add_collection_node_to_tree (GtkTreeStore *store, GtkTreeIter *parent_iter, const gchar *href)
+{
+ SoupURI *suri;
+ const gchar *path;
+ GtkTreeIter iter, loading_iter;
+ struct test_exists_data ted;
+ gchar *displayname, **tmp;
+
+ g_return_if_fail (store != NULL);
+ g_return_if_fail (GTK_IS_TREE_STORE (store));
+ g_return_if_fail (href != NULL);
+
+ suri = soup_uri_new (href);
+ if (suri && suri->path && (*suri->path != '/' || strlen (suri->path) > 1))
+ href = suri->path;
+
+ ted.href = href;
+ ted.exists = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (store), test_href_exists_cb, &ted);
+
+ if (ted.exists) {
+ if (suri)
+ soup_uri_free (suri);
+ return;
+ }
+
+ path = href;
+ tmp = g_strsplit (path, "/", -1);
+
+ /* parent_iter is not set for the root folder node, where whole path is shown */
+ if (tmp && parent_iter) {
+ /* pick the last non-empty path part */
+ gint idx = 0;
+
+ while (tmp [idx]) {
+ idx++;
+ }
+
+ idx--;
+
+ while (idx >= 0 && !tmp [idx][0]) {
+ idx--;
+ }
+
+ if (idx >= 0)
+ path = tmp [idx];
+ }
+
+ displayname = soup_uri_decode (path);
+
+ gtk_tree_store_append (store, &iter, parent_iter);
+ gtk_tree_store_set (store, &iter,
+ COL_BOOL_IS_LOADED, FALSE,
+ COL_BOOL_IS_CALENDAR, FALSE,
+ COL_STRING_HREF, href,
+ COL_STRING_DISPLAYNAME, displayname ? displayname : path,
+ COL_BOOL_SENSITIVE, TRUE,
+ -1);
+
+ g_free (displayname);
+ g_strfreev (tmp);
+ if (suri)
+ soup_uri_free (suri);
+
+ /* not localized "Loading...", because will be removed on expand immediately */
+ gtk_tree_store_append (store, &loading_iter, &iter);
+ gtk_tree_store_set (store, &loading_iter,
+ COL_BOOL_IS_LOADED, FALSE,
+ COL_BOOL_IS_CALENDAR, FALSE,
+ COL_STRING_DISPLAYNAME, "Loading...",
+ COL_BOOL_SENSITIVE, FALSE,
+ -1);
+}
+
+/* called with "caldav-thread-mutex" unlocked; 'user_data' is parent tree iter, NULL for "User's calendars" */
+static void
+traverse_users_calendars_cb (GObject *dialog, guint status_code, const gchar *msg_body, gpointer user_data)
+{
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpctx;
+ xmlXPathObjectPtr xpathObj;
+ GtkTreeIter *parent_iter = user_data, par_iter;
+
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+ g_return_if_fail (msg_body != NULL);
+
+ if (!check_soup_status (dialog, status_code, msg_body, TRUE))
+ return;
+
+ doc = xmlReadMemory (msg_body, strlen (msg_body), "response.xml", NULL, 0);
+ if (!doc) {
+ report_error (dialog, TRUE, _("Failed to parse server response."));
+ return;
+ }
+
+ xpctx = xmlXPathNewContext (doc);
+ xmlXPathRegisterNs (xpctx, XC "D", XC "DAV:");
+ xmlXPathRegisterNs (xpctx, XC "C", XC "urn:ietf:params:xml:ns:caldav");
+ xmlXPathRegisterNs (xpctx, XC "CS", XC "http://calendarserver.org/ns/");
+ xmlXPathRegisterNs (xpctx, XC "IC", XC "http://apple.com/ns/ical/");
+
+ xpathObj = xmlXPathEvalExpression (XC "/D:multistatus/D:response", xpctx);
+ if (xpathObj && xpathObj->type == XPATH_NODESET) {
+ GtkWidget *tree = g_object_get_data (G_OBJECT (dialog), "caldav-tree");
+ GtkTreeStore *store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (tree)));
+ GtkTreeIter iter;
+ gint i, n;
+
+ n = xmlXPathNodeSetGetLength (xpathObj->nodesetval);
+ for (i = 0; i < n; i++) {
+ xmlXPathObjectPtr suppObj;
+ GString *supports;
+ gchar *href, *displayname, *color_str;
+ GdkColor color;
+ gchar *str;
+ guint status;
+ gboolean sensitive;
+
+ #define response(_x) "/D:multistatus/D:response[%d]/" _x
+ #define prop(_x) response ("D:propstat/D:prop/" _x)
+
+ str = xpath_get_string (xpctx, response ("D:propstat/D:status"), i + 1);
+ if (!str || !soup_headers_parse_status_line (str, NULL, &status, NULL) || status != 200) {
+ g_free (str);
+ continue;
+ }
+
+ g_free (str);
+
+ if (!xpath_exists (xpctx, NULL, prop ("D:resourcetype/C:calendar"), i + 1)) {
+ /* not a calendar node */
+
+ if (user_data != NULL && xpath_exists (xpctx, NULL, prop ("D:resourcetype/D:collection"), i + 1)) {
+ /* can be browseable, add node for loading */
+ href = xpath_get_string (xpctx, response ("D:href"), i + 1);
+ if (href && *href)
+ add_collection_node_to_tree (store, parent_iter, href);
+
+ g_free (href);
+ }
+ continue;
+ }
+
+ href = xpath_get_string (xpctx, response ("D:href"), i + 1);
+ if (!href || !*href) {
+ /* href should be there always */
+ g_free (href);
+ continue;
+ }
+
+ displayname = xpath_get_string (xpctx, prop ("D:displayname"), i + 1);
+ color_str = xpath_get_string (xpctx, prop ("IC:calendar-color"), i + 1);
+ if (color_str && !gdk_color_parse (color_str, &color)) {
+ g_free (color_str);
+ color_str = NULL;
+ }
+
+ sensitive = FALSE;
+ supports = NULL;
+ suppObj = NULL;
+ if (xpath_exists (xpctx, &suppObj, prop ("C:supported-calendar-component-set/C:comp"), i + 1)) {
+ if (suppObj->type == XPATH_NODESET) {
+ const gchar *source_type = g_object_get_data (G_OBJECT (dialog), "caldav-source-type");
+ gint j, szj = xmlXPathNodeSetGetLength (suppObj->nodesetval);
+
+ for (j = 0; j < szj; j++) {
+ gchar *comp = xpath_get_string (xpctx, prop ("C:supported-calendar-component-set/C:comp[%d]/@name"), i + 1, j + 1);
+
+ if (!comp)
+ continue;
+
+ if (!g_str_equal (comp, "VEVENT") && !g_str_equal (comp, "VTODO") && !g_str_equal (comp, "VJOURNAL")) {
+ g_free (comp);
+ continue;
+ }
+
+ /* this calendar source supports our type, thus can be selected */
+ sensitive = sensitive || (source_type && comp && g_str_equal (source_type, comp));
+
+ if (!supports)
+ supports = g_string_new ("");
+ else
+ g_string_append (supports, " ");
+
+ if (g_str_equal (comp, "VEVENT"))
+ g_string_append (supports, _("Events"));
+ else if (g_str_equal (comp, "VTODO"))
+ g_string_append (supports, _("Tasks"));
+ else if (g_str_equal (comp, "VJOURNAL"))
+ g_string_append (supports, _("Memos"));
+
+ g_free (comp);
+ }
+ }
+
+ xmlXPathFreeObject (suppObj);
+ }
+
+ if (tree) {
+ g_return_if_fail (store != NULL);
+
+ if (!parent_iter) {
+ /* filling "User's calendars" node */
+ gtk_tree_store_append (store, &par_iter, NULL);
+ gtk_tree_store_set (store, &par_iter,
+ COL_BOOL_IS_LOADED, TRUE,
+ COL_BOOL_IS_CALENDAR, FALSE,
+ COL_STRING_DISPLAYNAME, _("User's calendars"),
+ COL_BOOL_SENSITIVE, TRUE,
+ -1);
+
+ parent_iter = &par_iter;
+ }
+
+ gtk_tree_store_append (store, &iter, parent_iter);
+ gtk_tree_store_set (store, &iter,
+ COL_BOOL_IS_LOADED, TRUE,
+ COL_BOOL_IS_CALENDAR, TRUE,
+ COL_STRING_HREF, href,
+ COL_STRING_SUPPORTS, supports ? supports->str : "",
+ COL_STRING_DISPLAYNAME, displayname && *displayname ? displayname : href,
+ COL_GDK_COLOR, color_str ? &color : NULL,
+ COL_BOOL_HAS_COLOR, color_str != NULL,
+ COL_BOOL_SENSITIVE, sensitive,
+ -1);
+ }
+
+ g_free (href);
+ g_free (displayname);
+ g_free (color_str);
+ if (supports)
+ g_string_free (supports, TRUE);
+ }
+
+ if (parent_iter) {
+ /* expand loaded node */
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), parent_iter);
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree), path);
+ gtk_tree_path_free (path);
+ }
+
+ if (user_data == NULL) {
+ /* it was checking for user's calendars, thus add node for browsing from the base url */
+ add_collection_node_to_tree (store, NULL, g_object_get_data (dialog, "caldav-base-url"));
+ }
+ }
+
+ if (xpathObj)
+ xmlXPathFreeObject (xpathObj);
+ xmlXPathFreeContext (xpctx);
+ xmlFreeDoc (doc);
+}
+
+static void
+fetch_folder_content (GObject *dialog, const gchar *relative_path, const GtkTreeIter *parent_iter, const gchar *op_info)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root, node;
+ xmlNsPtr nsdav, nsc, nscs, nsical;
+ gchar *url;
+
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+ g_return_if_fail (relative_path != NULL);
+
+ doc = xmlNewDoc (XC "1.0");
+ root = xmlNewDocNode (doc, NULL, XC "propfind", NULL);
+
+ nsdav = xmlNewNs (root, XC "DAV:", XC "D");
+ nsc = xmlNewNs (root, XC "urn:ietf:params:xml:ns:caldav", XC "C");
+ nscs = xmlNewNs (root, XC "http://calendarserver.org/ns/", XC "CS");
+ nsical = xmlNewNs (root, XC "http://apple.com/ns/ical/", XC "IC");
+
+ xmlSetNs (root, nsdav);
+ xmlDocSetRootElement (doc, root);
+
+ node = xmlNewTextChild (root, nsdav, XC "prop", NULL);
+ xmlNewTextChild (node, nsdav, XC "displayname", NULL);
+ xmlNewTextChild (node, nsdav, XC "resourcetype", NULL);
+ xmlNewTextChild (node, nsc, XC "calendar-description", NULL);
+ xmlNewTextChild (node, nsc, XC "supported-calendar-component-set", NULL);
+ xmlNewTextChild (node, nscs, XC "getctag", NULL);
+ xmlNewTextChild (node, nsical, XC "calendar-color", NULL);
+
+ url = change_url_path (g_object_get_data (dialog, "caldav-base-url"), relative_path);
+ if (url) {
+ GtkTreeIter *par_iter = NULL;
+
+ if (parent_iter) {
+ gchar *key;
+
+ par_iter = g_new0 (GtkTreeIter, 1);
+ *par_iter = *parent_iter;
+
+ /* will be freed on dialog destroy */
+ key = g_strdup_printf ("caldav-to-free-%p", par_iter);
+ g_object_set_data_full (dialog, key, par_iter, g_free);
+ g_free (key);
+ }
+
+ send_xml_message (doc, "PROPFIND", url, G_OBJECT (dialog), traverse_users_calendars_cb, par_iter, op_info);
+ } else {
+ report_error (dialog, TRUE, _("Failed to get server URL."));
+ }
+
+ xmlFreeDoc (doc);
+
+ g_free (url);
+}
+
+/* called with "caldav-thread-mutex" unlocked; user_data is not NULL when called second time on principal */
+static void
+find_users_calendar_cb (GObject *dialog, guint status_code, const gchar *msg_body, gpointer user_data)
+{
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpctx;
+ gchar *calendar_home_set, *url;
+
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+ g_return_if_fail (msg_body != NULL);
+
+ if (!check_soup_status (dialog, status_code, msg_body, TRUE))
+ return;
+
+ doc = xmlReadMemory (msg_body, strlen (msg_body), "response.xml", NULL, 0);
+ if (!doc) {
+ report_error (dialog, TRUE, _("Failed to parse server response."));
+ return;
+ }
+
+ xpctx = xmlXPathNewContext (doc);
+ xmlXPathRegisterNs (xpctx, XC "D", XC "DAV:");
+ xmlXPathRegisterNs (xpctx, XC "C", XC "urn:ietf:params:xml:ns:caldav");
+
+ calendar_home_set = xpath_get_string (xpctx, "/D:multistatus/D:response/D:propstat/D:prop/C:calendar-home-set/D:href");
+ if (user_data == NULL && (!calendar_home_set || !*calendar_home_set)) {
+ g_free (calendar_home_set);
+
+ calendar_home_set = xpath_get_string (xpctx, "/D:multistatus/D:response/D:propstat/D:prop/D:current-user-principal/D:href");
+ if (!calendar_home_set || !*calendar_home_set) {
+ g_free (calendar_home_set);
+ calendar_home_set = xpath_get_string (xpctx, "/D:multistatus/D:response/D:propstat/D:prop/D:principal-URL/D:href");
+ }
+
+ xmlXPathFreeContext (xpctx);
+ xmlFreeDoc (doc);
+
+ if (calendar_home_set && *calendar_home_set) {
+ xmlNodePtr root, node;
+ xmlNsPtr nsdav, nsc;
+
+ /* ask on principal user's calendar home address */
+ doc = xmlNewDoc (XC "1.0");
+ root = xmlNewDocNode (doc, NULL, XC "propfind", NULL);
+ nsc = xmlNewNs (root, XC "urn:ietf:params:xml:ns:caldav", XC "C");
+ nsdav = xmlNewNs (root, XC "DAV:", XC "D");
+ xmlSetNs (root, nsdav);
+ xmlDocSetRootElement (doc, root);
+
+ node = xmlNewTextChild (root, nsdav, XC "prop", NULL);
+ xmlNewTextChild (node, nsdav, XC "current-user-principal", NULL);
+ xmlNewTextChild (node, nsc, XC "calendar-home-set", NULL);
+
+ url = change_url_path (g_object_get_data (dialog, "caldav-base-url"), calendar_home_set);
+ if (url) {
+ send_xml_message (doc, "PROPFIND", url, dialog, find_users_calendar_cb, GINT_TO_POINTER (1), _("Searching for user's calendars..."));
+ } else {
+ report_error (dialog, TRUE, _("Failed to get server URL."));
+ }
+
+ xmlFreeDoc (doc);
+
+ g_free (url);
+ g_free (calendar_home_set);
+
+ return;
+ }
+ } else {
+ xmlXPathFreeContext (xpctx);
+ xmlFreeDoc (doc);
+ }
+
+ if (!calendar_home_set || !*calendar_home_set) {
+ report_error (dialog, FALSE, _("Could not find any user calendar."));
+ } else {
+ fetch_folder_content (dialog, calendar_home_set, NULL, _("Searching for user's calendars..."));
+ }
+
+ g_free (calendar_home_set);
+}
+
+static void
+redirect_handler (SoupMessage *msg, gpointer user_data)
+{
+ if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
+ SoupSession *soup_session = user_data;
+ SoupURI *new_uri;
+ const gchar *new_loc;
+
+ new_loc = soup_message_headers_get (msg->response_headers, "Location");
+ if (!new_loc)
+ return;
+
+ new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
+ if (!new_uri) {
+ soup_message_set_status_full (msg,
+ SOUP_STATUS_MALFORMED,
+ "Invalid Redirect URL");
+ return;
+ }
+
+ soup_message_set_uri (msg, new_uri);
+ soup_session_requeue_message (soup_session, msg);
+
+ soup_uri_free (new_uri);
+ }
+}
+
+static void
+send_and_handle_redirection (SoupSession *soup_session, SoupMessage *msg)
+{
+ soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
+ soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
+ soup_session_send_message (soup_session, msg);
+}
+
+static gpointer
+caldav_browse_server_thread (gpointer data)
+{
+ GObject *dialog = data;
+ GCond *cond;
+ GMutex *mutex;
+ SoupSession *session;
+ gint task;
+
+ g_return_val_if_fail (dialog != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
+
+ cond = g_object_get_data (dialog, "caldav-thread-cond");
+ mutex = g_object_get_data (dialog, "caldav-thread-mutex");
+ session = g_object_get_data (dialog, "caldav-session");
+
+ g_return_val_if_fail (cond != NULL, NULL);
+ g_return_val_if_fail (mutex != NULL, NULL);
+ g_return_val_if_fail (session != NULL, NULL);
+
+ g_mutex_lock (mutex);
+
+ while (task = GPOINTER_TO_INT (g_object_get_data (dialog, "caldav-thread-task")), task != CALDAV_THREAD_SHOULD_DIE) {
+ if (task == CALDAV_THREAD_SHOULD_SLEEP) {
+ g_cond_wait (cond, mutex);
+ } else if (task == CALDAV_THREAD_SHOULD_WORK) {
+ SoupMessage *message;
+
+ g_object_set_data (dialog, "caldav-thread-task", GINT_TO_POINTER (CALDAV_THREAD_SHOULD_SLEEP));
+
+ message = g_object_get_data (dialog, "caldav-thread-message");
+ if (!message) {
+ g_warning ("%s: No message to send", G_STRFUNC);
+ continue;
+ }
+
+ g_object_set_data (dialog, "caldav-thread-message-sent", NULL);
+
+ g_object_ref (message);
+
+ g_mutex_unlock (mutex);
+ send_and_handle_redirection (session, message);
+ g_mutex_lock (mutex);
+
+ g_object_set_data (dialog, "caldav-thread-message-sent", message);
+
+ g_object_unref (message);
+ }
+ }
+
+ soup_session_abort (session);
+ g_object_set_data (dialog, "caldav-thread-poll", GINT_TO_POINTER (0));
+
+ g_object_set_data (dialog, "caldav-thread-cond", NULL);
+ g_object_set_data (dialog, "caldav-thread-mutex", NULL);
+ g_object_set_data (dialog, "caldav-session", NULL);
+
+ g_mutex_unlock (mutex);
+
+ g_cond_free (cond);
+ g_mutex_free (mutex);
+ g_object_unref (session);
+
+ return NULL;
+}
+
+static void
+soup_authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer data)
+{
+ GObject *dialog = data;
+ const gchar *username, *password;
+
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+
+ username = g_object_get_data (dialog, "caldav-username");
+ password = g_object_get_data (dialog, "caldav-password");
+
+ if (!username || !*username || (retrying && (!password || !*password)))
+ return;
+
+ if (!password || !*password || retrying) {
+ gchar *pass, *prompt, *add = NULL;
+
+ if (retrying && msg && msg->reason_phrase) {
+ add = g_strdup_printf (_("Previous attempt failed: %s"), msg->reason_phrase);
+ } else if (retrying && msg && msg->status_code) {
+ add = g_strdup_printf (_("Previous attempt failed with code %d"), msg->status_code);
+ }
+
+ prompt = g_strdup_printf (_("Enter password for user <b>%s</b> on server <b>%s</b>"), username, soup_auth_get_host (auth));
+ if (add) {
+ gchar *tmp;
+
+ tmp = g_strconcat (prompt, "\n", add, NULL);
+
+ g_free (prompt);
+ prompt = tmp;
+ }
+
+ pass = e_passwords_ask_password (_("Enter password"),
+ "Calendar", "caldav-search-server", prompt,
+ E_PASSWORDS_REMEMBER_NEVER | E_PASSWORDS_DISABLE_REMEMBER | E_PASSWORDS_SECRET,
+ NULL, GTK_WINDOW (dialog));
+
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-password", pass, g_free);
+
+ password = pass;
+
+ g_free (prompt);
+ g_free (add);
+ }
+
+ if (!retrying || password)
+ soup_auth_authenticate (auth, username, password);
+}
+
+/* the dialog is about to die, so cancel any pending operations to close the thread too */
+static void
+dialog_response_cb (GObject *dialog, gint response_id, gpointer user_data)
+{
+ GCond *cond;
+ GMutex *mutex;
+
+ g_return_if_fail (dialog == user_data);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+
+ cond = g_object_get_data (dialog, "caldav-thread-cond");
+ mutex = g_object_get_data (dialog, "caldav-thread-mutex");
+
+ g_return_if_fail (mutex != NULL);
+
+ g_mutex_lock (mutex);
+ g_object_set_data (dialog, "caldav-thread-task", GINT_TO_POINTER (CALDAV_THREAD_SHOULD_DIE));
+
+ if (cond)
+ g_cond_signal (cond);
+ g_mutex_unlock (mutex);
+}
+
+static gboolean
+check_message (GtkWindow *dialog, SoupMessage *message, const gchar *url)
+{
+ g_return_val_if_fail (dialog != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_DIALOG (dialog), FALSE);
+
+ if (!message)
+ e_notice (GTK_WINDOW (dialog), GTK_MESSAGE_ERROR, _("Cannot create soup message for URL '%s'"), url ? url : "[null]");
+
+ return message != NULL;
+}
+
+static void
+indicate_busy (GObject *dialog, gboolean is_busy)
+{
+ GtkWidget *spinner = g_object_get_data (dialog, "caldav-spinner");
+
+ gtk_widget_set_sensitive (g_object_get_data (dialog, "caldav-tree"), !is_busy);
+
+ if (is_busy) {
+ gtk_widget_show (spinner);
+ } else {
+ gtk_widget_hide (spinner);
+ }
+}
+
+struct poll_data {
+ GObject *dialog;
+ SoupMessage *message;
+ process_message_cb cb;
+ gpointer cb_user_data;
+};
+
+static gboolean
+poll_for_message_sent_cb (gpointer data)
+{
+ struct poll_data *pd = data;
+ GMutex *mutex;
+ SoupMessage *sent_message;
+ gboolean again = TRUE;
+ guint status_code = -1;
+ gchar *msg_body = NULL;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ mutex = g_object_get_data (pd->dialog, "caldav-thread-mutex");
+ /* thread most likely finished already */
+ if (!mutex)
+ return FALSE;
+
+ g_mutex_lock (mutex);
+ sent_message = g_object_get_data (pd->dialog, "caldav-thread-message-sent");
+ again = sent_message == NULL;
+
+ if (sent_message == pd->message) {
+ GtkLabel *label = g_object_get_data (pd->dialog, "caldav-info-label");
+
+ if (label)
+ gtk_label_set_text (label, "");
+
+ g_object_ref (pd->message);
+ g_object_set_data (pd->dialog, "caldav-thread-message-sent", NULL);
+ g_object_set_data (pd->dialog, "caldav-thread-message", NULL);
+
+ if (pd->cb) {
+ status_code = pd->message->status_code;
+ msg_body = g_strndup (pd->message->response_body->data, pd->message->response_body->length);
+ }
+
+ g_object_unref (pd->message);
+ }
+
+ if (!again) {
+ indicate_busy (pd->dialog, FALSE);
+ g_object_set_data (pd->dialog, "caldav-thread-poll", GINT_TO_POINTER (0));
+ }
+
+ g_mutex_unlock (mutex);
+
+ if (!again && pd->cb && msg_body) {
+ (*pd->cb) (pd->dialog, status_code, msg_body, pd->cb_user_data);
+ }
+
+ g_free (msg_body);
+
+ return again;
+}
+
+static void
+send_xml_message (xmlDocPtr doc, const gchar *msg_type, const gchar *url, GObject *dialog, process_message_cb cb, gpointer cb_user_data, const gchar *info)
+{
+ GCond *cond;
+ GMutex *mutex;
+ SoupSession *session;
+ SoupMessage *message;
+ xmlOutputBufferPtr buf;
+ guint poll_id;
+ struct poll_data *pd;
+
+ g_return_if_fail (doc != NULL);
+ g_return_if_fail (msg_type != NULL);
+ g_return_if_fail (url != NULL);
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+
+ cond = g_object_get_data (dialog, "caldav-thread-cond");
+ mutex = g_object_get_data (dialog, "caldav-thread-mutex");
+ session = g_object_get_data (dialog, "caldav-session");
+
+ g_return_if_fail (cond != NULL);
+ g_return_if_fail (mutex != NULL);
+ g_return_if_fail (session != NULL);
+
+ message = soup_message_new (msg_type, url);
+ if (!check_message (GTK_WINDOW (dialog), message, url))
+ return;
+
+ buf = xmlAllocOutputBuffer (NULL);
+ xmlNodeDumpOutput (buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
+ xmlOutputBufferFlush (buf);
+
+ soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
+ soup_message_headers_append (message->request_headers, "Depth", "1");
+ soup_message_set_request (message, "application/xml", SOUP_MEMORY_COPY, (const gchar *) buf->buffer->content, buf->buffer->use);
+
+ /* Clean up the memory */
+ xmlOutputBufferClose (buf);
+
+ g_mutex_lock (mutex);
+
+ soup_session_abort (session);
+
+ g_object_set_data (dialog, "caldav-thread-task", GINT_TO_POINTER (CALDAV_THREAD_SHOULD_WORK));
+ g_object_set_data (dialog, "caldav-thread-message-sent", NULL);
+ g_object_set_data_full (dialog, "caldav-thread-message", message, g_object_unref);
+
+ g_cond_signal (cond);
+
+ pd = g_new0 (struct poll_data, 1);
+ pd->dialog = dialog;
+ pd->message = message;
+ pd->cb = cb;
+ pd->cb_user_data = cb_user_data;
+
+ indicate_busy (dialog, TRUE);
+
+ if (info) {
+ GtkLabel *label = g_object_get_data (dialog, "caldav-info-label");
+
+ if (label)
+ gtk_label_set_text (label, info);
+ }
+
+ /* polling for caldav-thread-message-sent because want to update UI, which is only possible from main thread */
+ poll_id = g_timeout_add_full (G_PRIORITY_DEFAULT, 250, poll_for_message_sent_cb, pd, g_free);
+
+ g_object_set_data_full (dialog, "caldav-thread-poll", GINT_TO_POINTER (poll_id), (GDestroyNotify) g_source_remove);
+
+ g_mutex_unlock (mutex);
+}
+
+static void
+url_entry_changed (GtkEntry *entry, GObject *dialog)
+{
+ const gchar *url;
+
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+
+ url = gtk_entry_get_text (entry);
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, url && *url);
+}
+
+static void
+tree_selection_changed_cb (GtkTreeSelection *selection, GtkEntry *url_entry)
+{
+ gboolean ok = FALSE;
+ GtkTreeModel *model = NULL;
+ GtkTreeIter iter;
+
+ g_return_if_fail (selection != NULL);
+ g_return_if_fail (url_entry != NULL);
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gchar *href = NULL;
+
+ gtk_tree_model_get (model, &iter,
+ COL_BOOL_IS_CALENDAR, &ok,
+ COL_STRING_HREF, &href,
+ -1);
+
+ ok = ok && href && *href;
+
+ if (ok)
+ gtk_entry_set_text (url_entry, href);
+
+ g_free (href);
+ }
+
+ if (!ok)
+ gtk_entry_set_text (url_entry, "");
+}
+
+static void
+tree_row_expanded_cb (GtkTreeView *tree, GtkTreeIter *iter, GtkTreePath *path, GObject *dialog)
+{
+ GtkTreeModel *model;
+ gboolean is_loaded = TRUE;
+ gchar *href = NULL;
+
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+ g_return_if_fail (iter != NULL);
+
+ model = gtk_tree_view_get_model (tree);
+ gtk_tree_model_get (model, iter,
+ COL_BOOL_IS_LOADED, &is_loaded,
+ COL_STRING_HREF, &href,
+ -1);
+
+ if (!is_loaded) {
+ /* unset unloaded flag */
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter, COL_BOOL_IS_LOADED, TRUE, -1);
+
+ /* remove the "Loading..." node */
+ while (gtk_tree_model_iter_has_child (model, iter)) {
+ GtkTreeIter child;
+
+ if (!gtk_tree_model_iter_nth_child (model, &child, iter, 0) ||
+ !gtk_tree_store_remove (GTK_TREE_STORE (model), &child))
+ break;
+ }
+
+ /* fetch content */
+ fetch_folder_content (dialog, href, iter, _("Searching folder content..."));
+ }
+
+ g_free (href);
+}
+
+static void
+init_dialog (GtkDialog *dialog, GtkWidget **new_url_entry, const gchar *url, const gchar *username, gint source_type)
+{
+ GtkBox *content_area;
+ GtkWidget *label, *info_box, *spinner, *info_label;
+ GtkWidget *tree, *scrolled_window;
+ GtkTreeStore *store;
+ GtkTreeSelection *selection;
+ SoupSession *session;
+ EProxy *proxy;
+ SoupURI *proxy_uri = NULL;
+ GThread *thread;
+ GError *error = NULL;
+ GMutex *thread_mutex;
+ GCond *thread_cond;
+ const gchar *source_type_str;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ g_return_if_fail (dialog != NULL);
+ g_return_if_fail (GTK_IS_DIALOG (dialog));
+ g_return_if_fail (new_url_entry != NULL);
+ g_return_if_fail (url != NULL);
+
+ content_area = GTK_BOX (gtk_dialog_get_content_area (dialog));
+ g_return_if_fail (content_area != NULL);
+
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 300, 240);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+
+ *new_url_entry = gtk_entry_new ();
+ gtk_box_pack_start (content_area, *new_url_entry, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (*new_url_entry), "changed", G_CALLBACK (url_entry_changed), dialog);
+
+ g_object_set_data (G_OBJECT (dialog), "caldav-new-url-entry", *new_url_entry);
+
+ label = gtk_label_new (_("List of available calendars:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (content_area, label, FALSE, FALSE, 0);
+
+ store = gtk_tree_store_new (8,
+ G_TYPE_BOOLEAN, /* COL_BOOL_IS_LOADED */
+ G_TYPE_STRING, /* COL_STRING_HREF */
+ G_TYPE_BOOLEAN, /* COL_BOOL_IS_CALENDAR */
+ G_TYPE_STRING, /* COL_STRING_SUPPORTS */
+ G_TYPE_STRING, /* COL_STRING_DISPLAYNAME */
+ GDK_TYPE_COLOR, /* COL_GDK_COLOR */
+ G_TYPE_BOOLEAN, /* COL_BOOL_HAS_COLOR */
+ G_TYPE_BOOLEAN); /* COL_BOOL_SENSITIVE */
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+ tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), tree);
+ gtk_box_pack_start (content_area, scrolled_window, TRUE, TRUE, 0);
+
+ g_object_set_data (G_OBJECT (dialog), "caldav-tree", tree);
+ g_object_set_data (G_OBJECT (dialog), "caldav-tree-sw", scrolled_window);
+
+ renderer = e_cell_renderer_color_new ();
+ column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, _("Name"), renderer, "color", COL_GDK_COLOR, "visible", COL_BOOL_HAS_COLOR, "sensitive", COL_BOOL_SENSITIVE, NULL) - 1);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (
+ GTK_CELL_LAYOUT (column), renderer, TRUE);
+ gtk_cell_layout_set_attributes (
+ GTK_CELL_LAYOUT (column), renderer,
+ "text", COL_STRING_DISPLAYNAME,
+ "sensitive", COL_BOOL_SENSITIVE,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, _("Supports"), renderer, "text", COL_STRING_SUPPORTS, "sensitive", COL_BOOL_SENSITIVE, NULL);
+
+ /*renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, _("href"), renderer, "text", COL_STRING_HREF, "sensitive", COL_BOOL_SENSITIVE, NULL);*/
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+ g_signal_connect (selection, "changed", G_CALLBACK (tree_selection_changed_cb), *new_url_entry);
+
+ g_signal_connect (tree, "row-expanded", G_CALLBACK (tree_row_expanded_cb), dialog);
+
+ info_box = gtk_hbox_new (FALSE, 2);
+
+ spinner = e_spinner_new_spinning_small_shown ();
+ gtk_box_pack_start (GTK_BOX (info_box), spinner, FALSE, FALSE, 0);
+ g_object_set_data (G_OBJECT (dialog), "caldav-spinner", spinner);
+
+ info_label = gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (info_label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (info_box), info_label, FALSE, FALSE, 0);
+ g_object_set_data (G_OBJECT (dialog), "caldav-info-label", info_label);
+
+ gtk_box_pack_start (content_area, info_box, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (GTK_WIDGET (content_area));
+ gtk_widget_hide (*new_url_entry);
+ gtk_widget_hide (spinner);
+
+ session = soup_session_sync_new ();
+ if (g_getenv ("CALDAV_DEBUG") != NULL) {
+ SoupLogger *logger;
+
+ logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, 100 * 1024 * 1024);
+ soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+ g_object_unref (logger);
+ }
+
+ proxy = e_proxy_new ();
+ e_proxy_setup_proxy (proxy);
+
+ /* use proxy if necessary */
+ if (e_proxy_require_proxy_for_uri (proxy, url)) {
+ proxy_uri = e_proxy_peek_uri_for (proxy, url);
+ }
+
+ g_object_set (session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+ g_object_unref (proxy);
+
+ g_signal_connect (session, "authenticate", G_CALLBACK (soup_authenticate), dialog);
+
+ switch (source_type) {
+ default:
+ case E_CAL_SOURCE_TYPE_EVENT:
+ source_type_str = "VEVENT";
+ break;
+ case E_CAL_SOURCE_TYPE_TODO:
+ source_type_str = "VTODO";
+ break;
+ case E_CAL_SOURCE_TYPE_JOURNAL:
+ source_type_str = "VJOURNAL";
+ break;
+ }
+
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-source-type", g_strdup (source_type_str), g_free);
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-base-url", g_strdup (url), g_free);
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-username", g_strdup (username), g_free);
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-session", session, NULL); /* it is freed at the end of thread life */
+
+ thread_mutex = g_mutex_new ();
+ thread_cond = g_cond_new ();
+
+ g_object_set_data (G_OBJECT (dialog), "caldav-thread-task", GINT_TO_POINTER (CALDAV_THREAD_SHOULD_SLEEP));
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-thread-mutex", thread_mutex, NULL); /* it is freed at the end of thread life */
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-thread-cond", thread_cond, NULL); /* it is freed at the end of thread life */
+
+ /* create thread at the end, to have all properties on the dialog set */
+ thread = g_thread_create (caldav_browse_server_thread, dialog, TRUE, &error);
+
+ if (error || !thread) {
+ e_notice (GTK_WINDOW (dialog), GTK_MESSAGE_ERROR, _("Failed to create thread: %s"), error ? error->message : _("Unknown error"));
+ if (error)
+ g_error_free (error);
+ } else {
+ xmlDocPtr doc;
+ xmlNodePtr root, node;
+ xmlNsPtr nsdav, nsc;
+
+ g_object_set_data_full (G_OBJECT (dialog), "caldav-thread", thread, (GDestroyNotify) g_thread_join);
+
+ doc = xmlNewDoc (XC "1.0");
+ root = xmlNewDocNode (doc, NULL, XC "propfind", NULL);
+ nsc = xmlNewNs (root, XC "urn:ietf:params:xml:ns:caldav", XC "C");
+ nsdav = xmlNewNs (root, XC "DAV:", XC "D");
+ xmlSetNs (root, nsdav);
+ xmlDocSetRootElement (doc, root);
+
+ node = xmlNewTextChild (root, nsdav, XC "prop", NULL);
+ xmlNewTextChild (node, nsdav, XC "current-user-principal", NULL);
+ xmlNewTextChild (node, nsdav, XC "principal-URL", NULL);
+ xmlNewTextChild (node, nsc, XC "calendar-home-set", NULL);
+
+ send_xml_message (doc, "PROPFIND", url, G_OBJECT (dialog), find_users_calendar_cb, NULL, _("Searching for user's calendars..."));
+
+ xmlFreeDoc (doc);
+ }
+
+ g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), dialog);
+
+ url_entry_changed (GTK_ENTRY (*new_url_entry), G_OBJECT (dialog));
+}
+
+static gchar *
+prepare_url (const gchar *server_url, gboolean use_ssl)
+{
+ gchar *url;
+ gint len;
+
+ g_return_val_if_fail (server_url != NULL, NULL);
+ g_return_val_if_fail (*server_url, NULL);
+
+ if (g_str_has_prefix (server_url, "caldav://")) {
+ url = g_strconcat (use_ssl ? "https://" : "http://", server_url + 9, NULL);
+ } else {
+ url = g_strdup (server_url);
+ }
+
+ if (url) {
+ SoupURI *suri = soup_uri_new (url);
+
+ /* properly encode uri */
+ if (suri && suri->path) {
+ gchar *tmp = soup_uri_encode (suri->path, NULL);
+ gchar *path = soup_uri_normalize (tmp, "/");
+
+ soup_uri_set_path (suri, path);
+
+ g_free (tmp);
+ g_free (path);
+ g_free (url);
+
+ url = soup_uri_to_string (suri, FALSE);
+ } else {
+ g_free (url);
+ soup_uri_free (suri);
+ return NULL;
+ }
+
+ soup_uri_free (suri);
+ }
+
+ /* remove trailing slashes... */
+ len = strlen (url);
+ while (len--) {
+ if (url[len] == '/') {
+ url[len] = '\0';
+ } else {
+ break;
+ }
+ }
+
+ /* ...and append exactly one slash */
+ if (url && *url) {
+ gchar *tmp = url;
+
+ url = g_strconcat (url, "/", NULL);
+
+ g_free (tmp);
+ } else {
+ g_free (url);
+ url = NULL;
+ }
+
+ return url;
+}
+
+gchar *
+caldav_browse_server (GtkWindow *parent, const gchar *server_url, const gchar *username, gboolean use_ssl, gint source_type)
+{
+ GtkWidget *dialog, *new_url_entry;
+ gchar *url, *new_url = NULL;
+
+ g_return_val_if_fail (server_url != NULL, NULL);
+
+ url = prepare_url (server_url, use_ssl);
+
+ if (!url || !*url) {
+ e_notice (parent, GTK_MESSAGE_ERROR, _("Server URL '%s' is not a valid URL"), server_url);
+ g_free (url);
+ return NULL;
+ }
+
+ dialog = gtk_dialog_new_with_buttons (
+ _("Browse for a CalDAV calendar"),
+ parent,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+
+ new_url_entry = NULL;
+ init_dialog (GTK_DIALOG (dialog), &new_url_entry, url, username, source_type);
+
+ if (new_url_entry && gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+ const gchar *nu = gtk_entry_get_text (GTK_ENTRY (new_url_entry));
+
+ if (nu && *nu)
+ new_url = change_url_path (server_url, nu);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ g_free (url);
+
+ return new_url;
+}
diff --git a/plugins/caldav/caldav-browse-server.h b/plugins/caldav/caldav-browse-server.h
new file mode 100644
index 0000000000..5c7cde6291
--- /dev/null
+++ b/plugins/caldav/caldav-browse-server.h
@@ -0,0 +1,32 @@
+/*
+ * caldav-browse-server.h
+ *
+ * This program 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; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef CALDAV_BROWSE_SERVER_H
+#define CALDAV_BROWSE_SERVER_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+/* Opens a window with a list of available calendars for a given server;
+ Returns server URL of a calendar user chose, or NULL to let it be as is. */
+gchar *caldav_browse_server (GtkWindow *parent, const gchar *server_url, const gchar *username, gboolean use_ssl, gint source_type);
+
+#endif /* CALDAV_BROWSE_SERVER_H */
diff --git a/plugins/caldav/caldav-source.c b/plugins/caldav/caldav-source.c
index f5d8f59d12..4821cbf2f4 100644
--- a/plugins/caldav/caldav-source.c
+++ b/plugins/caldav/caldav-source.c
@@ -38,6 +38,8 @@
#include <string.h>
+#include "caldav-browse-server.h"
+
#define d(x)
/*****************************************************************************/
@@ -303,6 +305,39 @@ combobox_changed (GtkComboBox *combobox, ESource *source)
g_free (refresh_str);
}
+static void
+browse_cal_clicked_cb (GtkButton *button, gpointer user_data)
+{
+ GtkEntry *url, *username;
+ GtkToggleButton *ssl;
+ gchar *new_url;
+
+ g_return_if_fail (button != NULL);
+
+ url = g_object_get_data (G_OBJECT (button), "caldav-url");
+ username = g_object_get_data (G_OBJECT (button), "caldav-username");
+ ssl = g_object_get_data (G_OBJECT (button), "caldav-ssl");
+
+ g_return_if_fail (url != NULL);
+ g_return_if_fail (GTK_IS_ENTRY (url));
+ g_return_if_fail (username != NULL);
+ g_return_if_fail (GTK_IS_ENTRY (username));
+ g_return_if_fail (ssl != NULL);
+ g_return_if_fail (GTK_IS_TOGGLE_BUTTON (ssl));
+
+ new_url = caldav_browse_server (
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))),
+ gtk_entry_get_text (url),
+ gtk_entry_get_text (username),
+ gtk_toggle_button_get_active (ssl),
+ GPOINTER_TO_INT (user_data));
+
+ if (new_url) {
+ gtk_entry_set_text (url, new_url);
+ g_free (new_url);
+ }
+}
+
GtkWidget *
oge_caldav (EPlugin *epl,
EConfigHookItemFactoryData *data)
@@ -318,6 +353,7 @@ oge_caldav (EPlugin *epl,
GtkWidget *widget;
GtkWidget *luser;
GtkWidget *user;
+ GtkWidget *browse_cal;
GtkWidget *label, *hbox, *spin, *combobox;
gchar *uri;
gchar *username;
@@ -429,6 +465,17 @@ oge_caldav (EPlugin *epl,
g_free (uri);
g_free (username);
+ browse_cal = gtk_button_new_with_mnemonic (_("Brows_e server for a calendar"));
+ gtk_widget_show (browse_cal);
+ gtk_table_attach (GTK_TABLE (parent), browse_cal, 1, 2, row, row + 1, GTK_FILL, 0, 0, 0);
+
+ g_object_set_data (G_OBJECT (browse_cal), "caldav-url", location);
+ g_object_set_data (G_OBJECT (browse_cal), "caldav-username", user);
+ g_object_set_data (G_OBJECT (browse_cal), "caldav-ssl", cssl);
+ g_signal_connect (G_OBJECT (browse_cal), "clicked", G_CALLBACK (browse_cal_clicked_cb), GINT_TO_POINTER (t->source_type));
+
+ row++;
+
/* add refresh option */
label = gtk_label_new_with_mnemonic (_("Re_fresh:"));
gtk_widget_show (label);