diff options
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | modules/Makefile.am | 1 | ||||
-rw-r--r-- | modules/cal-config-caldav/Makefile.am | 30 | ||||
-rw-r--r-- | modules/cal-config-caldav/e-caldav-chooser-dialog.c | 477 | ||||
-rw-r--r-- | modules/cal-config-caldav/e-caldav-chooser-dialog.h | 68 | ||||
-rw-r--r-- | modules/cal-config-caldav/e-caldav-chooser.c | 1643 | ||||
-rw-r--r-- | modules/cal-config-caldav/e-caldav-chooser.h | 81 | ||||
-rw-r--r-- | modules/cal-config-caldav/evolution-cal-config-caldav.c | 381 | ||||
-rw-r--r-- | plugins/caldav/Makefile.am | 34 | ||||
-rw-r--r-- | plugins/caldav/caldav-browse-server.c | 1657 | ||||
-rw-r--r-- | plugins/caldav/caldav-browse-server.h | 38 | ||||
-rw-r--r-- | plugins/caldav/caldav-source.c | 297 | ||||
-rw-r--r-- | plugins/caldav/org-gnome-evolution-caldav.eplug.xml | 27 |
13 files changed, 2683 insertions, 2055 deletions
diff --git a/configure.ac b/configure.ac index dacd541d91..ed52d6a399 100644 --- a/configure.ac +++ b/configure.ac @@ -1298,7 +1298,7 @@ AC_ARG_ENABLE([plugins], [enable_plugins="$enableval"],[enable_plugins=all]) dnl Add any new plugins here -plugins_base_always="calendar-http itip-formatter default-source mark-all-read publish-calendar caldav imap-features google-account-setup" +plugins_base_always="calendar-http itip-formatter default-source mark-all-read publish-calendar imap-features google-account-setup" plugins_base="$plugins_base_always" dist_plugins_base="$plugins_base_always calendar-weather" @@ -1637,6 +1637,7 @@ modules/book-config-google/Makefile modules/book-config-ldap/Makefile modules/book-config-local/Makefile modules/book-config-webdav/Makefile +modules/cal-config-caldav/Makefile modules/cal-config-local/Makefile modules/composer-autosave/Makefile modules/mailto-handler/Makefile @@ -1654,7 +1655,6 @@ plugins/Makefile plugins/attachment-reminder/Makefile plugins/audio-inline/Makefile plugins/bbdb/Makefile -plugins/caldav/Makefile plugins/calendar-http/Makefile plugins/calendar-weather/Makefile plugins/dbx-import/Makefile diff --git a/modules/Makefile.am b/modules/Makefile.am index 043c53d1ce..71b7d07cf8 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -24,6 +24,7 @@ SUBDIRS = \ $(CONFIG_LDAP_DIR) \ book-config-local \ book-config-webdav \ + cal-config-caldav \ cal-config-local \ composer-autosave \ mailto-handler \ diff --git a/modules/cal-config-caldav/Makefile.am b/modules/cal-config-caldav/Makefile.am new file mode 100644 index 0000000000..8c3d03125a --- /dev/null +++ b/modules/cal-config-caldav/Makefile.am @@ -0,0 +1,30 @@ +module_LTLIBRARIES = module-cal-config-caldav.la + +module_cal_config_caldav_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DG_LOG_DOMAIN=\"evolution-cal-config-caldav\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) \ + $(LIBSOUP_CFLAGS) + +module_cal_config_caldav_la_SOURCES = \ + evolution-cal-config-caldav.c \ + e-caldav-chooser.c \ + e-caldav-chooser.h \ + e-caldav-chooser-dialog.c \ + e-caldav-chooser-dialog.h + +module_cal_config_caldav_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ + $(top_builddir)/calendar/gui/libevolution-calendar.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + $(LIBSOUP_LIBS) + +module_cal_config_caldav_la_LDFLAGS = \ + -module -avoid-version $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/cal-config-caldav/e-caldav-chooser-dialog.c b/modules/cal-config-caldav/e-caldav-chooser-dialog.c new file mode 100644 index 0000000000..29848a017a --- /dev/null +++ b/modules/cal-config-caldav/e-caldav-chooser-dialog.c @@ -0,0 +1,477 @@ +/* + * e-caldav-chooser-dialog.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 <webcal://www.gnu.org/licenses/> + * + */ + +#include "e-caldav-chooser-dialog.h" + +#include <config.h> +#include <glib/gi18n-lib.h> + +#define E_CALDAV_CHOOSER_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CALDAV_CHOOSER_DIALOG, ECaldavChooserDialogPrivate)) + +struct _ECaldavChooserDialogPrivate { + ECaldavChooser *chooser; + GCancellable *cancellable; + + GtkWidget *info_bar; /* not referenced */ + GtkWidget *info_bar_label; /* not referenced */ +}; + +enum { + PROP_0, + PROP_CHOOSER +}; + +/* Forward Declarations */ +static void caldav_chooser_dialog_populated_cb + (GObject *source_object, + GAsyncResult *result, + gpointer user_data); + +G_DEFINE_DYNAMIC_TYPE ( + ECaldavChooserDialog, + e_caldav_chooser_dialog, + GTK_TYPE_DIALOG) + +static void +caldav_chooser_dialog_done (ECaldavChooserDialog *dialog, + const GError *error) +{ + GdkWindow *window; + + /* Reset the mouse cursor to normal. */ + window = gtk_widget_get_window (GTK_WIDGET (dialog)); + gdk_window_set_cursor (window, NULL); + + if (error != NULL) { + GtkLabel *label; + + label = GTK_LABEL (dialog->priv->info_bar_label); + gtk_label_set_text (label, error->message); + gtk_widget_show (dialog->priv->info_bar); + } +} + +static void +caldav_chooser_dialog_authenticate_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ESourceRegistry *registry; + ECaldavChooserDialog *dialog; + ECaldavChooser *chooser; + GError *error = NULL; + + registry = E_SOURCE_REGISTRY (source_object); + dialog = E_CALDAV_CHOOSER_DIALOG (user_data); + + chooser = e_caldav_chooser_dialog_get_chooser (dialog); + + e_source_registry_authenticate_finish (registry, result, &error); + + /* Ignore cancellations, and leave the mouse cursor alone + * since the GdkWindow may have already been destroyed. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + /* do nothing */ + + /* Successful authentication, so try populating again. */ + } else if (error == NULL) { + e_caldav_chooser_populate ( + chooser, dialog->priv->cancellable, + caldav_chooser_dialog_populated_cb, + g_object_ref (dialog)); + + /* Still not working? Give up and display an error message. */ + } else { + caldav_chooser_dialog_done (dialog, error); + } + + g_clear_error (&error); + g_object_unref (dialog); +} + +static void +caldav_chooser_dialog_populated_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ECaldavChooserDialog *dialog; + ECaldavChooser *chooser; + GError *error = NULL; + + chooser = E_CALDAV_CHOOSER (source_object); + dialog = E_CALDAV_CHOOSER_DIALOG (user_data); + + e_caldav_chooser_populate_finish (chooser, result, &error); + + /* Ignore cancellations, and leave the mouse cursor alone + * since the GdkWindow may have already been destroyed. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + /* do nothing */ + + /* We will likely get this error on the first try, since WebDAV + * servers generally require authentication. It means we waste a + * round-trip to the server, but we don't want to risk prompting + * for authentication unnecessarily. */ + } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { + ESourceRegistry *registry; + ESource *source; + + registry = e_caldav_chooser_get_registry (chooser); + source = e_caldav_chooser_get_source (chooser); + + e_source_registry_authenticate ( + registry, source, + E_SOURCE_AUTHENTICATOR (chooser), + dialog->priv->cancellable, + caldav_chooser_dialog_authenticate_cb, + g_object_ref (dialog)); + + /* We were either successful or got an unexpected error. */ + } else { + caldav_chooser_dialog_done (dialog, error); + } + + g_clear_error (&error); + g_object_unref (dialog); +} + +static void +caldav_chooser_dialog_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GtkDialog *dialog) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_APPLY); +} + +static void +caldav_chooser_dialog_selection_changed_cb (GtkTreeSelection *selection, + GtkDialog *dialog) +{ + gboolean sensitive; + + sensitive = (gtk_tree_selection_count_selected_rows (selection) > 0); + + gtk_dialog_set_response_sensitive ( + dialog, GTK_RESPONSE_APPLY, sensitive); +} + +static void +caldav_chooser_dialog_set_chooser (ECaldavChooserDialog *dialog, + ECaldavChooser *chooser) +{ + g_return_if_fail (E_IS_CALDAV_CHOOSER (chooser)); + g_return_if_fail (dialog->priv->chooser == NULL); + + dialog->priv->chooser = g_object_ref_sink (chooser); +} + +static void +caldav_chooser_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CHOOSER: + caldav_chooser_dialog_set_chooser ( + E_CALDAV_CHOOSER_DIALOG (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +caldav_chooser_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CHOOSER: + g_value_set_object ( + value, + e_caldav_chooser_dialog_get_chooser ( + E_CALDAV_CHOOSER_DIALOG (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +caldav_chooser_dialog_dispose (GObject *object) +{ + ECaldavChooserDialogPrivate *priv; + + priv = E_CALDAV_CHOOSER_DIALOG_GET_PRIVATE (object); + + if (priv->chooser != NULL) { + g_signal_handlers_disconnect_by_func ( + priv->chooser, caldav_chooser_dialog_row_activated_cb, + object); + g_object_unref (priv->chooser); + priv->chooser = NULL; + } + + if (priv->cancellable != NULL) { + g_cancellable_cancel (priv->cancellable); + g_object_unref (priv->cancellable); + priv->cancellable = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_caldav_chooser_dialog_parent_class)->dispose (object); +} + +static void +caldav_chooser_dialog_constructed (GObject *object) +{ + ECaldavChooserDialog *dialog; + GtkTreeSelection *selection; + GtkWidget *container; + GtkWidget *widget; + GtkWidget *vbox; + const gchar *title; + + dialog = E_CALDAV_CHOOSER_DIALOG (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_caldav_chooser_dialog_parent_class)-> + constructed (object); + + switch (e_caldav_chooser_get_source_type (dialog->priv->chooser)) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + title = _("Choose a Calendar"); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + title = _("Choose a Memo List"); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + title = _("Choose a Task List"); + break; + default: + g_warn_if_reached (); + title = ""; + } + + gtk_dialog_add_button ( + GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + + gtk_dialog_add_button ( + GTK_DIALOG (dialog), + GTK_STOCK_APPLY, GTK_RESPONSE_APPLY); + + gtk_dialog_set_default_response ( + GTK_DIALOG (dialog), GTK_RESPONSE_APPLY); + gtk_dialog_set_response_sensitive ( + GTK_DIALOG (dialog), GTK_RESPONSE_APPLY, FALSE); + + gtk_window_set_title (GTK_WINDOW (dialog), title); + gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 400); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + + container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + widget = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (widget), 5); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = vbox = 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_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + widget = GTK_WIDGET (dialog->priv->chooser); + gtk_container_add (GTK_CONTAINER (container), widget); + gtk_widget_show (widget); + + g_signal_connect ( + widget, "row-activated", + G_CALLBACK (caldav_chooser_dialog_row_activated_cb), dialog); + + /* Build the info bar, but hide it initially. */ + + container = vbox; + + widget = gtk_info_bar_new (); + gtk_info_bar_set_message_type ( + GTK_INFO_BAR (widget), GTK_MESSAGE_WARNING); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + dialog->priv->info_bar = widget; /* do not reference */ + gtk_widget_hide (widget); + + container = gtk_info_bar_get_content_area (GTK_INFO_BAR (widget)); + + widget = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_image_new_from_stock ( + GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); + + widget = gtk_label_new (""); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + dialog->priv->info_bar_label = widget; /* do not reference */ + gtk_widget_show (widget); + + /* Listen for tree view selection changes. */ + + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (dialog->priv->chooser)); + + g_signal_connect ( + selection, "changed", + G_CALLBACK (caldav_chooser_dialog_selection_changed_cb), + dialog); +} + +static void +caldav_chooser_dialog_realize (GtkWidget *widget) +{ + ECaldavChooserDialogPrivate *priv; + GdkCursor *cursor; + GdkWindow *window; + GdkDisplay *display; + + priv = E_CALDAV_CHOOSER_DIALOG_GET_PRIVATE (widget); + + /* Chain up to parent's realize() method. */ + GTK_WIDGET_CLASS (e_caldav_chooser_dialog_parent_class)-> + realize (widget); + + g_return_if_fail (priv->cancellable == NULL); + priv->cancellable = g_cancellable_new (); + + /* Show a busy mouse cursor while populating. */ + window = gtk_widget_get_window (widget); + display = gtk_widget_get_display (widget); + cursor = gdk_cursor_new_for_display (display, GDK_WATCH); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + + e_caldav_chooser_populate ( + priv->chooser, priv->cancellable, + caldav_chooser_dialog_populated_cb, + g_object_ref (widget)); +} + +static void +caldav_chooser_dialog_response (GtkDialog *dialog, + gint response_id) +{ + ECaldavChooserDialogPrivate *priv; + + priv = E_CALDAV_CHOOSER_DIALOG_GET_PRIVATE (dialog); + + if (response_id == GTK_RESPONSE_APPLY) + e_caldav_chooser_apply_selected (priv->chooser); +} + +static void +e_caldav_chooser_dialog_class_init (ECaldavChooserDialogClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkDialogClass *dialog_class; + + g_type_class_add_private (class, sizeof (ECaldavChooserDialogPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = caldav_chooser_dialog_set_property; + object_class->get_property = caldav_chooser_dialog_get_property; + object_class->dispose = caldav_chooser_dialog_dispose; + object_class->constructed = caldav_chooser_dialog_constructed; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->realize = caldav_chooser_dialog_realize; + + dialog_class = GTK_DIALOG_CLASS (class); + dialog_class->response = caldav_chooser_dialog_response; + + g_object_class_install_property ( + object_class, + PROP_CHOOSER, + g_param_spec_object ( + "chooser", + NULL, + NULL, + E_TYPE_CALDAV_CHOOSER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +e_caldav_chooser_dialog_class_finalize (ECaldavChooserDialogClass *class) +{ +} + +static void +e_caldav_chooser_dialog_init (ECaldavChooserDialog *dialog) +{ + dialog->priv = E_CALDAV_CHOOSER_DIALOG_GET_PRIVATE (dialog); +} + +void +e_caldav_chooser_dialog_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_caldav_chooser_dialog_register_type (type_module); +} + +GtkWidget * +e_caldav_chooser_dialog_new (ECaldavChooser *chooser, + GtkWindow *parent) +{ + g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL); + + return g_object_new ( + E_TYPE_CALDAV_CHOOSER_DIALOG, + "chooser", chooser, "transient-for", parent, NULL); +} + +ECaldavChooser * +e_caldav_chooser_dialog_get_chooser (ECaldavChooserDialog *dialog) +{ + g_return_val_if_fail (E_IS_CALDAV_CHOOSER_DIALOG (dialog), NULL); + + return dialog->priv->chooser; +} + diff --git a/modules/cal-config-caldav/e-caldav-chooser-dialog.h b/modules/cal-config-caldav/e-caldav-chooser-dialog.h new file mode 100644 index 0000000000..6c5500f8ee --- /dev/null +++ b/modules/cal-config-caldav/e-caldav-chooser-dialog.h @@ -0,0 +1,68 @@ +/* + * e-caldav-chooser-dialog.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 <webcal://www.gnu.org/licenses/> + * + */ + +#ifndef E_CALDAV_CHOOSER_DIALOG_H +#define E_CALDAV_CHOOSER_DIALOG_H + +#include "e-caldav-chooser.h" + +/* Standard GObject macros */ +#define E_TYPE_CALDAV_CHOOSER_DIALOG \ + (e_caldav_chooser_dialog_get_type ()) +#define E_CALDAV_CHOOSER_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CALDAV_CHOOSER_DIALOG, ECaldavChooserDialog)) +#define E_CALDAV_CHOOSER_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CALDAV_CHOOSER_DIALOG, ECaldavChooserDialogClass)) +#define E_IS_CALDAV_CHOOSER_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CALDAV_CHOOSER_DIALOG)) +#define E_IS_CALDAV_CHOOSER_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CALDAV_CHOOSER_DIALOG)) +#define E_CALDAV_CHOOSER_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CALDAV_CHOOSER_DIALOG, ECaldavChooserDialogClass)) + +G_BEGIN_DECLS + +typedef struct _ECaldavChooserDialog ECaldavChooserDialog; +typedef struct _ECaldavChooserDialogClass ECaldavChooserDialogClass; +typedef struct _ECaldavChooserDialogPrivate ECaldavChooserDialogPrivate; + +struct _ECaldavChooserDialog { + GtkDialog parent; + ECaldavChooserDialogPrivate *priv; +}; + +struct _ECaldavChooserDialogClass { + GtkDialogClass parent_class; +}; + +GType e_caldav_chooser_dialog_get_type (void); +void e_caldav_chooser_dialog_type_register + (GTypeModule *type_module); +GtkWidget * e_caldav_chooser_dialog_new (ECaldavChooser *chooser, + GtkWindow *parent); +ECaldavChooser *e_caldav_chooser_dialog_get_chooser + (ECaldavChooserDialog *dialog); + +G_END_DECLS + +#endif /* E_CALDAV_CHOOSER_DIALOG_H */ diff --git a/modules/cal-config-caldav/e-caldav-chooser.c b/modules/cal-config-caldav/e-caldav-chooser.c new file mode 100644 index 0000000000..841007bc85 --- /dev/null +++ b/modules/cal-config-caldav/e-caldav-chooser.c @@ -0,0 +1,1643 @@ +/* + * e-caldav-chooser.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 <webcal://www.gnu.org/licenses/> + * + */ + +#include "e-caldav-chooser.h" + +#include <config.h> +#include <glib/gi18n-lib.h> + +#include <libsoup/soup.h> +#include <libsoup/soup-gnome.h> + +#include <libxml/tree.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +#include <libedataserver/e-source-authentication.h> +#include <libedataserver/e-source-authenticator.h> +#include <libedataserver/e-source-calendar.h> +#include <libedataserver/e-source-webdav.h> +#include <libedataserverui/e-cell-renderer-color.h> +#include <libedataserverui/e-passwords.h> + +#define E_CALDAV_CHOOSER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CALDAV_CHOOSER, ECaldavChooserPrivate)) + +#define XC(string) ((xmlChar *) string) + +/* Standard Namespaces */ +#define NS_WEBDAV "DAV:" +#define NS_CALDAV "urn:ietf:params:xml:ns:caldav" + +/* Application-Specific Namespaces */ +#define NS_CALSRV "http://calendarserver.org/ns/" +#define NS_ICAL "http://apple.com/ns/ical/" + +typedef struct _Context Context; + +struct _ECaldavChooserPrivate { + ESourceRegistry *registry; + ESource *source; + ECalClientSourceType source_type; + SoupSession *session; + GList *user_address_set; + gchar *password; +}; + +struct _Context { + SoupSession *session; + + GCancellable *cancellable; + gulong cancelled_handler_id; + + GList *user_address_set; +}; + +enum { + PROP_0, + PROP_REGISTRY, + PROP_SOURCE, + PROP_SOURCE_TYPE +}; + +/* Mainly for readability. */ +enum { + DEPTH_0, + DEPTH_1 +}; + +typedef enum { + SUPPORTS_VEVENT = 1 << 0, + SUPPORTS_VTODO = 1 << 1, + SUPPORTS_VJOURNAL = 1 << 2, + SUPPORTS_ALL = 0x7 +} SupportedComponentSet; + +enum { + COLUMN_DISPLAY_NAME, /* G_TYPE_STRING */ + COLUMN_PATH_ENCODED, /* G_TYPE_STRING */ + COLUMN_PATH_DECODED, /* G_TYPE_STRING */ + COLUMN_COLOR, /* GDK_TYPE_COLOR */ + COLUMN_HAS_COLOR, /* G_TYPE_BOOLEAN */ + NUM_COLUMNS +}; + +/* Forward Declarations */ +static void e_caldav_chooser_authenticator_init + (ESourceAuthenticatorInterface *interface); +static void caldav_chooser_get_collection_details + (SoupSession *session, + SoupMessage *message, + const gchar *path, + GSimpleAsyncResult *simple); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED ( + ECaldavChooser, + e_caldav_chooser, + GTK_TYPE_TREE_VIEW, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC ( + E_TYPE_SOURCE_AUTHENTICATOR, + e_caldav_chooser_authenticator_init)) + +static void +context_cancel_message (GCancellable *cancellable, + Context *context) +{ + soup_session_abort (context->session); +} + +static Context * +context_new (ECaldavChooser *chooser, + GCancellable *cancellable) +{ + Context *context; + + context = g_slice_new0 (Context); + context->session = g_object_ref (chooser->priv->session); + + if (cancellable != NULL) { + context->cancellable = g_object_ref (cancellable); + context->cancelled_handler_id = g_cancellable_connect ( + context->cancellable, + G_CALLBACK (context_cancel_message), + context, (GDestroyNotify) NULL); + } + + return context; +} + +static void +context_free (Context *context) +{ + if (context->session != NULL) + g_object_unref (context->session); + + if (context->cancellable != NULL) { + g_cancellable_disconnect ( + context->cancellable, + context->cancelled_handler_id); + g_object_unref (context->cancellable); + } + + g_list_free_full ( + context->user_address_set, + (GDestroyNotify) g_free); + + g_slice_free (Context, context); +} + +static void +caldav_chooser_redirect (SoupMessage *message, + SoupSession *session) +{ + SoupURI *soup_uri; + const gchar *location; + + if (!SOUP_STATUS_IS_REDIRECTION (message->status_code)) + return; + + location = soup_message_headers_get ( + message->response_headers, "Location"); + + if (location == NULL) + return; + + soup_uri = soup_uri_new_with_base ( + soup_message_get_uri (message), location); + + if (soup_uri == NULL) { + soup_message_set_status_full ( + message, SOUP_STATUS_MALFORMED, + "Invalid Redirect URL"); + return; + } + + soup_message_set_uri (message, soup_uri); + soup_session_requeue_message (session, message); + + soup_uri_free (soup_uri); +} + +static G_GNUC_NULL_TERMINATED SoupMessage * +caldav_chooser_new_propfind (SoupSession *session, + SoupURI *soup_uri, + gint depth, + ...) +{ + GHashTable *namespaces; + SoupMessage *message; + xmlDocPtr doc; + xmlNodePtr root; + xmlNodePtr node; + xmlNsPtr ns; + xmlOutputBufferPtr output; + gpointer key; + va_list va; + + /* Construct the XML content. */ + + doc = xmlNewDoc (XC ("1.0")); + node = xmlNewDocNode (doc, NULL, XC ("propfind"), NULL); + + /* Build a hash table of namespace URIs to xmlNs structs. */ + namespaces = g_hash_table_new (NULL, NULL); + + ns = xmlNewNs (node, XC (NS_CALDAV), XC ("C")); + g_hash_table_insert (namespaces, (gpointer) NS_CALDAV, ns); + + ns = xmlNewNs (node, XC (NS_CALSRV), XC ("CS")); + g_hash_table_insert (namespaces, (gpointer) NS_CALSRV, ns); + + ns = xmlNewNs (node, XC (NS_ICAL), XC ("IC")); + g_hash_table_insert (namespaces, (gpointer) NS_ICAL, ns); + + /* Add WebDAV last since we use it below. */ + ns = xmlNewNs (node, XC (NS_WEBDAV), XC ("D")); + g_hash_table_insert (namespaces, (gpointer) NS_WEBDAV, ns); + + xmlSetNs (node, ns); + xmlDocSetRootElement (doc, node); + + node = xmlNewTextChild (node, ns, XC ("prop"), NULL); + + va_start (va, depth); + while ((key = va_arg (va, gpointer)) != NULL) { + xmlChar *name; + + ns = g_hash_table_lookup (namespaces, key); + name = va_arg (va, xmlChar *); + + if (ns != NULL && name != NULL) + xmlNewTextChild (node, ns, name, NULL); + else + g_warn_if_reached (); + } + va_end (va); + + g_hash_table_destroy (namespaces); + + /* Construct the SoupMessage. */ + + message = soup_message_new_from_uri (SOUP_METHOD_PROPFIND, soup_uri); + + soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); + + soup_message_headers_append ( + message->request_headers, + "User-Agent", "Evolution/" VERSION); + + soup_message_headers_append ( + message->request_headers, + "Depth", (depth == 0) ? "0" : "1"); + + output = xmlAllocOutputBuffer (NULL); + + root = xmlDocGetRootElement (doc); + xmlNodeDumpOutput (output, doc, root, 0, 1, NULL); + xmlOutputBufferFlush (output); + + soup_message_set_request ( + message, "application/xml", SOUP_MEMORY_COPY, + (gchar *) output->buffer->content, output->buffer->use); + + xmlOutputBufferClose (output); + + soup_message_add_header_handler ( + message, "got-body", "Location", + G_CALLBACK (caldav_chooser_redirect), session); + + return message; +} + +static void +caldav_chooser_authenticate_cb (SoupSession *session, + SoupMessage *message, + SoupAuth *auth, + gboolean retrying, + ECaldavChooser *chooser) +{ + ESource *source; + ESourceAuthentication *extension; + const gchar *extension_name; + const gchar *username; + const gchar *password; + + source = e_caldav_chooser_get_source (chooser); + extension_name = E_SOURCE_EXTENSION_AUTHENTICATION; + extension = e_source_get_extension (source, extension_name); + + username = e_source_authentication_get_user (extension); + password = chooser->priv->password; + + /* If our password was rejected, let the operation fail. */ + if (retrying) + return; + + /* If we don't have a username, let the operation fail. */ + if (username == NULL || *username == '\0') + return; + + /* If we don't have a password, let the operation fail. */ + if (password == NULL || *password == '\0') + return; + + soup_auth_authenticate (auth, username, password); +} + +static void +caldav_chooser_configure_session (ECaldavChooser *chooser, + SoupSession *session) +{ + ESource *source; + ESourceWebdav *extension; + const gchar *extension_name; + + source = e_caldav_chooser_get_source (chooser); + extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND; + extension = e_source_get_extension (source, extension_name); + + g_object_bind_property ( + extension, "ignore-invalid-cert", + session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN); + + 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); + } + + /* This adds proxy support. */ + soup_session_add_feature_by_type ( + session, SOUP_TYPE_GNOME_FEATURES_2_26); + + g_signal_connect ( + session, "authenticate", + G_CALLBACK (caldav_chooser_authenticate_cb), chooser); +} + +static gboolean +caldav_chooser_check_successful (SoupMessage *message, + GError **error) +{ + GIOErrorEnum error_code; + + /* Loosely copied from the GVFS DAV backend. */ + + if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) + return TRUE; + + switch (message->status_code) { + case SOUP_STATUS_CANCELLED: + error_code = G_IO_ERROR_CANCELLED; + break; + case SOUP_STATUS_NOT_FOUND: + error_code = G_IO_ERROR_NOT_FOUND; + break; + case SOUP_STATUS_UNAUTHORIZED: + case SOUP_STATUS_PAYMENT_REQUIRED: + case SOUP_STATUS_FORBIDDEN: + error_code = G_IO_ERROR_PERMISSION_DENIED; + break; + case SOUP_STATUS_REQUEST_TIMEOUT: + error_code = G_IO_ERROR_TIMED_OUT; + break; + case SOUP_STATUS_CANT_RESOLVE: + error_code = G_IO_ERROR_HOST_NOT_FOUND; + break; + case SOUP_STATUS_NOT_IMPLEMENTED: + error_code = G_IO_ERROR_NOT_SUPPORTED; + break; + case SOUP_STATUS_INSUFFICIENT_STORAGE: + error_code = G_IO_ERROR_NO_SPACE; + break; + default: + error_code = G_IO_ERROR_FAILED; + break; + } + + g_set_error ( + error, G_IO_ERROR, error_code, + _("HTTP Error: %s"), message->reason_phrase); + + return FALSE; +} + +static xmlDocPtr +caldav_chooser_parse_xml (SoupMessage *message, + const gchar *expected_name, + GError **error) +{ + xmlDocPtr doc; + xmlNodePtr root; + + if (!caldav_chooser_check_successful (message, error)) + return NULL; + + doc = xmlReadMemory ( + message->response_body->data, + message->response_body->length, + "response.xml", NULL, + XML_PARSE_NONET | + XML_PARSE_NOWARNING | + XML_PARSE_NOBLANKS | + XML_PARSE_NSCLEAN | + XML_PARSE_NOCDATA | + XML_PARSE_COMPACT); + + if (doc == NULL) { + g_set_error_literal ( + error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Could not parse response")); + return NULL; + } + + root = xmlDocGetRootElement (doc); + + if (root == NULL || root->children == NULL) { + g_set_error_literal ( + error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Empty response")); + xmlFreeDoc (doc); + return NULL; + } + + if (g_strcmp0 ((gchar *) root->name, expected_name) != 0) { + g_set_error_literal ( + error, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unexpected reply from server")); + xmlFreeDoc (doc); + return NULL; + } + + return doc; +} + +static xmlXPathObjectPtr +caldav_chooser_get_xpath (xmlXPathContextPtr xp_ctx, + const gchar *path_format, + ...) +{ + xmlXPathObjectPtr xp_obj; + va_list va; + gchar *path; + + va_start (va, path_format); + path = g_strdup_vprintf (path_format, va); + va_end (va); + + xp_obj = xmlXPathEvalExpression (XC (path), xp_ctx); + + g_free (path); + + if (xp_obj == NULL) + return NULL; + + if (xp_obj->type != XPATH_NODESET) { + xmlXPathFreeObject (xp_obj); + return NULL; + } + + if (xmlXPathNodeSetGetLength (xp_obj->nodesetval) == 0) { + xmlXPathFreeObject (xp_obj); + return NULL; + } + + return xp_obj; +} + +static gchar * +caldav_chooser_get_xpath_string (xmlXPathContextPtr xp_ctx, + const gchar *path_format, + ...) +{ + xmlXPathObjectPtr xp_obj; + va_list va; + gchar *path; + gchar *expression; + gchar *string = NULL; + + va_start (va, path_format); + path = g_strdup_vprintf (path_format, va); + va_end (va); + + expression = g_strdup_printf ("string(%s)", path); + xp_obj = xmlXPathEvalExpression (XC (expression), xp_ctx); + g_free (expression); + + g_free (path); + + if (xp_obj == NULL) + return NULL; + + if (xp_obj->type == XPATH_STRING) + string = g_strdup ((gchar *) xp_obj->stringval); + + /* If the string is empty, return NULL. */ + if (string != NULL && *string == '\0') { + g_free (string); + string = NULL; + } + + xmlXPathFreeObject (xp_obj); + + return string; +} + +static void +caldav_chooser_process_user_address_set (xmlXPathContextPtr xp_ctx, + Context *context) +{ + xmlXPathObjectPtr xp_obj; + gint ii, length; + + /* XXX Is response[1] safe to assume? */ + xp_obj = caldav_chooser_get_xpath ( + xp_ctx, + "/D:multistatus" + "/D:response" + "/D:propstat" + "/D:prop" + "/C:calendar-user-address-set"); + + if (xp_obj == NULL) + return; + + length = xmlXPathNodeSetGetLength (xp_obj->nodesetval); + + for (ii = 0; ii < length; ii++) { + GList *duplicate; + const gchar *address; + gchar *href; + + href = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response" + "/D:propstat" + "/D:prop" + "/C:calendar-user-address-set" + "/D:href[%d]", ii + 1); + + if (href == NULL) + continue; + + if (!g_str_has_prefix (href, "mailto:")) { + g_free (href); + continue; + } + + /* strlen("mailto:") == 7 */ + address = href + 7; + + /* Avoid duplicates. */ + duplicate = g_list_find_custom ( + context->user_address_set, + address, (GCompareFunc) strdup); + + if (duplicate != NULL) { + g_free (href); + continue; + } + + context->user_address_set = g_list_append ( + context->user_address_set, g_strdup (address)); + + g_free (href); + } + + xmlXPathFreeObject (xp_obj); +} + +static SupportedComponentSet +caldav_chooser_get_supported_component_set (xmlXPathContextPtr xp_ctx, + gint index) +{ + xmlXPathObjectPtr xp_obj; + SupportedComponentSet set = 0; + gint ii, length; + + xp_obj = caldav_chooser_get_xpath ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:propstat" + "/D:prop" + "/C:supported-calendar-component-set" + "/C:comp", index); + + /* If the property is not present, assume all component + * types are supported. (RFC 4791, Section 5.2.3) */ + if (xp_obj == NULL) + return SUPPORTS_ALL; + + length = xmlXPathNodeSetGetLength (xp_obj->nodesetval); + + for (ii = 0; ii < length; ii++) { + gchar *name; + + name = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:propstat" + "/D:prop" + "/C:supported-calendar-component-set" + "/C:comp[%d]" + "/@name", index, ii + 1); + + if (name == NULL) + continue; + + if (g_ascii_strcasecmp (name, "VEVENT")) + set |= SUPPORTS_VEVENT; + else if (g_ascii_strcasecmp (name, "VTODO")) + set |= SUPPORTS_VTODO; + else if (g_ascii_strcasecmp (name, "VJOURNAL")) + set |= SUPPORTS_VJOURNAL; + + g_free (name); + } + + xmlXPathFreeObject (xp_obj); + + return set; +} + +static void +caldav_chooser_process_response (SoupSession *session, + SoupMessage *message, + GSimpleAsyncResult *simple, + xmlXPathContextPtr xp_ctx, + gint index) +{ + GObject *object; + xmlXPathObjectPtr xp_obj; + SupportedComponentSet comp_set; + ECaldavChooser *chooser; + GtkTreeModel *tree_model; + GtkTreeIter iter; + GdkColor color; + gchar *color_spec; + gchar *display_name; + gchar *href_decoded; + gchar *href_encoded; + gchar *status_line; + guint status; + gboolean has_color; + gboolean success; + + /* This returns a new reference, for reasons passing understanding. */ + object = g_async_result_get_source_object (G_ASYNC_RESULT (simple)); + + chooser = E_CALDAV_CHOOSER (object); + tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (object)); + + g_object_unref (object); + + status_line = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:propstat" + "/D:status", + index); + + if (status_line == NULL) + return; + + success = soup_headers_parse_status_line ( + status_line, NULL, &status, NULL); + + g_free (status_line); + + if (!success || status != SOUP_STATUS_OK) + return; + + href_encoded = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:href", + index); + + if (href_encoded == NULL) + return; + + href_decoded = soup_uri_decode (href_encoded); + + /* Get the display name or fall back to the href. */ + + display_name = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:propstat" + "/D:prop" + "/D:displayname", + index); + + if (display_name == NULL) { + gchar *href_copy, *cp; + + href_copy = g_strdup (href_decoded); + + /* Use the last non-empty path segment. */ + while ((cp = strrchr (href_copy, '/')) != NULL) { + if (*(cp + 1) == '\0') + *cp = '\0'; + else { + display_name = g_strdup (cp + 1); + break; + } + } + + g_free (href_copy); + } + + /* Make sure the resource is a calendar. */ + + xp_obj = caldav_chooser_get_xpath ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:propstat" + "/D:prop" + "/D:resourcetype" + "/C:calendar", + index); + + if (xp_obj == NULL) + goto exit; + + xmlXPathFreeObject (xp_obj); + + /* Get the color specification string. */ + + color_spec = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response[%d]" + "/D:propstat" + "/D:prop" + "/IC:calendar-color", + index); + + if (color_spec != NULL) + has_color = gdk_color_parse (color_spec, &color); + else + has_color = FALSE; + + g_free (color_spec); + + /* Which calendar component types are supported? */ + + comp_set = caldav_chooser_get_supported_component_set (xp_ctx, index); + + switch (e_caldav_chooser_get_source_type (chooser)) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + if ((comp_set & SUPPORTS_VEVENT) == 0) + goto exit; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + if ((comp_set & SUPPORTS_VJOURNAL) == 0) + goto exit; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + if ((comp_set & SUPPORTS_VTODO) == 0) + goto exit; + break; + default: + goto exit; + } + + /* Append a new tree model row. */ + + gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter); + + gtk_list_store_set ( + GTK_LIST_STORE (tree_model), &iter, + COLUMN_DISPLAY_NAME, display_name, + COLUMN_PATH_ENCODED, href_encoded, + COLUMN_PATH_DECODED, href_decoded, + COLUMN_COLOR, has_color ? &color : NULL, + COLUMN_HAS_COLOR, has_color, + -1); + +exit: + g_free (display_name); + g_free (href_decoded); + g_free (href_encoded); +} + +static void +caldav_chooser_collection_details_cb (SoupSession *session, + SoupMessage *message, + GSimpleAsyncResult *simple) +{ + xmlDocPtr doc; + xmlXPathContextPtr xp_ctx; + xmlXPathObjectPtr xp_obj; + GError *error = NULL; + + doc = caldav_chooser_parse_xml (message, "multistatus", &error); + + if (error != NULL) { + g_warn_if_fail (doc == NULL); + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + goto exit; + } + + xp_ctx = xmlXPathNewContext (doc); + xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV)); + xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV)); + xmlXPathRegisterNs (xp_ctx, XC ("CS"), XC (NS_CALSRV)); + xmlXPathRegisterNs (xp_ctx, XC ("IC"), XC (NS_ICAL)); + + xp_obj = caldav_chooser_get_xpath ( + xp_ctx, + "/D:multistatus" + "/D:response"); + + if (xp_obj != NULL) { + gint length, ii; + + length = xmlXPathNodeSetGetLength (xp_obj->nodesetval); + + for (ii = 0; ii < length; ii++) + caldav_chooser_process_response ( + session, message, simple, xp_ctx, ii + 1); + + xmlXPathFreeObject (xp_obj); + } + + xmlXPathFreeContext (xp_ctx); + xmlFreeDoc (doc); + +exit: + /* If we were cancelled then we're in a GCancellable::cancelled + * signal handler right now and GCancellable has its mutex locked, + * which means calling g_cancellable_disconnect() now will deadlock + * when it too tries to acquire the mutex. So defer the GAsyncResult + * completion to an idle callback to avoid this deadlock. */ + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static void +caldav_chooser_get_collection_details (SoupSession *session, + SoupMessage *message, + const gchar *path, + GSimpleAsyncResult *simple) +{ + SoupURI *soup_uri; + + soup_uri = soup_uri_copy (soup_message_get_uri (message)); + soup_uri_set_path (soup_uri, path); + + message = caldav_chooser_new_propfind ( + session, soup_uri, DEPTH_1, + NS_WEBDAV, XC ("displayname"), + NS_WEBDAV, XC ("resourcetype"), + NS_CALDAV, XC ("calendar-description"), + NS_CALDAV, XC ("supported-calendar-component-set"), + NS_CALDAV, XC ("calendar-user-address-set"), + NS_CALSRV, XC ("getctag"), + NS_ICAL, XC ("calendar-color"), + NULL); + + /* This takes ownership of the message. */ + soup_session_queue_message ( + session, message, (SoupSessionCallback) + caldav_chooser_collection_details_cb, simple); + + soup_uri_free (soup_uri); +} + +static void +caldav_chooser_calendar_home_set_cb (SoupSession *session, + SoupMessage *message, + GSimpleAsyncResult *simple) +{ + Context *context; + SoupURI *soup_uri; + xmlDocPtr doc; + xmlXPathContextPtr xp_ctx; + xmlXPathObjectPtr xp_obj; + gchar *calendar_home_set; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + doc = caldav_chooser_parse_xml (message, "multistatus", &error); + + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + g_error_free (error); + return; + } + + g_return_if_fail (doc != NULL); + + xp_ctx = xmlXPathNewContext (doc); + xmlXPathRegisterNs (xp_ctx, XC ("D"), XC (NS_WEBDAV)); + xmlXPathRegisterNs (xp_ctx, XC ("C"), XC (NS_CALDAV)); + + /* Record any "C:calendar-user-address-set" properties. */ + caldav_chooser_process_user_address_set (xp_ctx, context); + + /* Try to find the calendar home URL using the + * following properties in order of preference: + * + * "C:calendar-home-set" + * "D:current-user-principal" + * "D:principal-URL" + * + * If the second or third URL preference is used, rerun + * the PROPFIND method on that URL at Depth=1 in hopes + * of getting a proper "C:calendar-home-set" property. + */ + + /* FIXME There can be multiple "D:href" elements for a + * "C:calendar-home-set". We're only processing + * the first one. Need to iterate over them. */ + + calendar_home_set = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response" + "/D:propstat" + "/D:prop" + "/C:calendar-home-set" + "/D:href"); + + if (calendar_home_set != NULL) + goto get_collection_details; + + g_free (calendar_home_set); + + calendar_home_set = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response" + "/D:propstat" + "/D:prop" + "/D:current-user-principal" + "/D:href"); + + if (calendar_home_set != NULL) + goto retry_propfind; + + g_free (calendar_home_set); + + calendar_home_set = caldav_chooser_get_xpath_string ( + xp_ctx, + "/D:multistatus" + "/D:response" + "/D:propstat" + "/D:prop" + "/D:principal-URL" + "/D:href"); + + if (calendar_home_set != NULL) + goto retry_propfind; + + g_free (calendar_home_set); + calendar_home_set = NULL; + + /* None of the aforementioned properties are present. If the + * user-supplied CalDAV URL is a calendar resource, use that. */ + + xp_obj = caldav_chooser_get_xpath ( + xp_ctx, + "/D:multistatus" + "/D:response" + "/D:propstat" + "/D:prop" + "/D:resourcetype" + "/C:calendar"); + + if (xp_obj != NULL) { + soup_uri = soup_message_get_uri (message); + + if (soup_uri->path != NULL && *soup_uri->path != '\0') { + gchar *slash; + + soup_uri = soup_uri_copy (soup_uri); + + slash = strrchr (soup_uri->path, '/'); + while (slash != NULL && slash != soup_uri->path) { + + if (slash[1] != '\0') { + slash[1] = '\0'; + calendar_home_set = + g_strdup (soup_uri->path); + break; + } + + slash[0] = '\0'; + slash = strrchr (soup_uri->path, '/'); + } + + soup_uri_free (soup_uri); + } + + xmlXPathFreeObject (xp_obj); + } + + if (calendar_home_set == NULL || *calendar_home_set == '\0') { + g_free (calendar_home_set); + g_simple_async_result_set_error ( + simple, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Could not locate user's calendars")); + g_simple_async_result_complete (simple); + g_object_unref (simple); + return; + } + +get_collection_details: + + xmlXPathFreeContext (xp_ctx); + xmlFreeDoc (doc); + + caldav_chooser_get_collection_details ( + session, message, calendar_home_set, simple); + + g_free (calendar_home_set); + + return; + +retry_propfind: + + xmlXPathFreeContext (xp_ctx); + xmlFreeDoc (doc); + + soup_uri = soup_uri_copy (soup_message_get_uri (message)); + soup_uri_set_path (soup_uri, calendar_home_set); + + /* Note that we omit "D:resourcetype", "D:current-user-principal" + * and "D:principal-URL" in order to short-circuit the recursion. */ + message = caldav_chooser_new_propfind ( + session, soup_uri, DEPTH_1, + NS_CALDAV, XC ("calendar-home-set"), + NS_CALDAV, XC ("calendar-user-address-set"), + NULL); + + /* This takes ownership of the message. */ + soup_session_queue_message ( + session, message, (SoupSessionCallback) + caldav_chooser_calendar_home_set_cb, simple); + + soup_uri_free (soup_uri); + + g_free (calendar_home_set); +} + +static void +caldav_chooser_set_registry (ECaldavChooser *chooser, + ESourceRegistry *registry) +{ + g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); + g_return_if_fail (chooser->priv->registry == NULL); + + chooser->priv->registry = g_object_ref (registry); +} + +static void +caldav_chooser_set_source (ECaldavChooser *chooser, + ESource *source) +{ + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (chooser->priv->source == NULL); + + chooser->priv->source = g_object_ref (source); +} + +static void +caldav_chooser_set_source_type (ECaldavChooser *chooser, + ECalClientSourceType source_type) +{ + chooser->priv->source_type = source_type; +} + +static void +caldav_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_REGISTRY: + caldav_chooser_set_registry ( + E_CALDAV_CHOOSER (object), + g_value_get_object (value)); + return; + + case PROP_SOURCE: + caldav_chooser_set_source ( + E_CALDAV_CHOOSER (object), + g_value_get_object (value)); + return; + + case PROP_SOURCE_TYPE: + caldav_chooser_set_source_type ( + E_CALDAV_CHOOSER (object), + g_value_get_enum (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +caldav_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_REGISTRY: + g_value_set_object ( + value, e_caldav_chooser_get_registry ( + E_CALDAV_CHOOSER (object))); + return; + + case PROP_SOURCE: + g_value_set_object ( + value, e_caldav_chooser_get_source ( + E_CALDAV_CHOOSER (object))); + return; + + case PROP_SOURCE_TYPE: + g_value_set_enum ( + value, e_caldav_chooser_get_source_type ( + E_CALDAV_CHOOSER (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +caldav_chooser_dispose (GObject *object) +{ + ECaldavChooserPrivate *priv; + + priv = E_CALDAV_CHOOSER_GET_PRIVATE (object); + + if (priv->registry != NULL) { + g_object_unref (priv->registry); + priv->registry = NULL; + } + + if (priv->source != NULL) { + g_object_unref (priv->source); + priv->source = NULL; + } + + if (priv->session != NULL) { + g_object_unref (priv->session); + priv->session = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_caldav_chooser_parent_class)->dispose (object); +} + +static void +caldav_chooser_finalize (GObject *object) +{ + ECaldavChooserPrivate *priv; + + priv = E_CALDAV_CHOOSER_GET_PRIVATE (object); + + g_list_free_full ( + priv->user_address_set, + (GDestroyNotify) g_free); + + g_free (priv->password); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_caldav_chooser_parent_class)->finalize (object); +} + +static void +caldav_chooser_constructed (GObject *object) +{ + ECaldavChooser *chooser; + GtkTreeView *tree_view; + GtkListStore *list_store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + SoupSession *session; + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_caldav_chooser_parent_class)->constructed (object); + + chooser = E_CALDAV_CHOOSER (object); + session = soup_session_async_new (); + caldav_chooser_configure_session (chooser, session); + chooser->priv->session = session; + + tree_view = GTK_TREE_VIEW (object); + + list_store = gtk_list_store_new ( + NUM_COLUMNS, + G_TYPE_STRING, /* COLUMN_DISPLAY_NAME */ + G_TYPE_STRING, /* COLUMN_PATH_ENCODED */ + G_TYPE_STRING, /* COLUMN_PATH_DECODED */ + GDK_TYPE_COLOR, /* COLUMN_COLOR */ + G_TYPE_BOOLEAN); /* COLUMN_HAS_COLOR */ + + gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store)); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_expand (column, TRUE); + gtk_tree_view_column_set_title (column, _("Name")); + gtk_tree_view_insert_column (tree_view, column, -1); + + renderer = e_cell_renderer_color_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_attributes ( + column, renderer, + "color", COLUMN_COLOR, + "visible", COLUMN_HAS_COLOR, + NULL); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes ( + column, renderer, + "text", COLUMN_DISPLAY_NAME, + NULL); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_expand (column, FALSE); + gtk_tree_view_column_set_title (column, _("Path")); + gtk_tree_view_insert_column (tree_view, column, -1); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes ( + column, renderer, + "text", COLUMN_PATH_DECODED, + NULL); +} + +/* Helper for caldav_chooser_try_password_sync() */ +static void +caldav_chooser_try_password_cancelled_cb (GCancellable *cancellable, + SoupSession *session) +{ + soup_session_abort (session); +} + +static ESourceAuthenticationResult +caldav_chooser_try_password_sync (ESourceAuthenticator *auth, + const GString *password, + GCancellable *cancellable, + GError **error) +{ + ECaldavChooser *chooser; + ESourceAuthenticationResult result; + SoupMessage *message; + SoupSession *session; + SoupURI *soup_uri; + ESource *source; + ESourceWebdav *extension; + const gchar *extension_name; + gulong cancel_id = 0; + GError *local_error = NULL; + + chooser = E_CALDAV_CHOOSER (auth); + + /* Cache the password for later use in our + * SoupSession::authenticate signal handler. */ + g_free (chooser->priv->password); + chooser->priv->password = g_strdup (password->str); + + /* Create our own SoupSession so we + * can try the password synchronously. */ + session = soup_session_sync_new (); + caldav_chooser_configure_session (chooser, session); + + source = e_caldav_chooser_get_source (chooser); + extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND; + extension = e_source_get_extension (source, extension_name); + + soup_uri = e_source_webdav_dup_soup_uri (extension); + g_return_val_if_fail (soup_uri != NULL, E_SOURCE_AUTHENTICATION_ERROR); + + /* Try some simple PROPFIND query. We don't care about the query + * result, only whether the CalDAV server will accept our password. */ + message = caldav_chooser_new_propfind ( + session, soup_uri, DEPTH_0, + NS_WEBDAV, XC ("resourcetype"), + NULL); + + if (G_IS_CANCELLABLE (cancellable)) + cancel_id = g_cancellable_connect ( + cancellable, + G_CALLBACK (caldav_chooser_try_password_cancelled_cb), + g_object_ref (session), + (GDestroyNotify) g_object_unref); + + soup_session_send_message (session, message); + + if (cancel_id > 0) + g_cancellable_disconnect (cancellable, cancel_id); + + if (caldav_chooser_check_successful (message, &local_error)) { + result = E_SOURCE_AUTHENTICATION_ACCEPTED; + + } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { + result = E_SOURCE_AUTHENTICATION_REJECTED; + g_clear_error (&local_error); + + } else { + result = E_SOURCE_AUTHENTICATION_ERROR; + } + + if (local_error != NULL) + g_propagate_error (error, local_error); + + g_object_unref (message); + g_object_unref (session); + + soup_uri_free (soup_uri); + + return result; +} + +static void +e_caldav_chooser_class_init (ECaldavChooserClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (ECaldavChooserPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = caldav_chooser_set_property; + object_class->get_property = caldav_chooser_get_property; + object_class->dispose = caldav_chooser_dispose; + object_class->finalize = caldav_chooser_finalize; + object_class->constructed = caldav_chooser_constructed; + + g_object_class_install_property ( + object_class, + PROP_REGISTRY, + g_param_spec_object ( + "registry", + "Registry", + "Data source registry", + E_TYPE_SOURCE_REGISTRY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property ( + object_class, + PROP_SOURCE, + g_param_spec_object ( + "source", + "Source", + "CalDAV data source", + E_TYPE_SOURCE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property ( + object_class, + PROP_SOURCE_TYPE, + g_param_spec_enum ( + "source-type", + "Source Type", + "The iCalendar object type", + E_TYPE_CAL_CLIENT_SOURCE_TYPE, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +e_caldav_chooser_class_finalize (ECaldavChooserClass *class) +{ +} + +static void +e_caldav_chooser_authenticator_init (ESourceAuthenticatorInterface *interface) +{ + interface->try_password_sync = caldav_chooser_try_password_sync; +} + +static void +e_caldav_chooser_init (ECaldavChooser *chooser) +{ + chooser->priv = E_CALDAV_CHOOSER_GET_PRIVATE (chooser); +} + +void +e_caldav_chooser_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_caldav_chooser_register_type (type_module); +} + +GtkWidget * +e_caldav_chooser_new (ESourceRegistry *registry, + ESource *source, + ECalClientSourceType source_type) +{ + g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); + g_return_val_if_fail (E_IS_SOURCE (source), NULL); + + return g_object_new ( + E_TYPE_CALDAV_CHOOSER, + "registry", registry, "source", source, + "source-type", source_type, NULL); +} + +ESourceRegistry * +e_caldav_chooser_get_registry (ECaldavChooser *chooser) +{ + g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), NULL); + + return chooser->priv->registry; +} + +ESource * +e_caldav_chooser_get_source (ECaldavChooser *chooser) +{ + g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), NULL); + + return chooser->priv->source; +} + +ECalClientSourceType +e_caldav_chooser_get_source_type (ECaldavChooser *chooser) +{ + g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), 0); + + return chooser->priv->source_type; +} + +void +e_caldav_chooser_populate (ECaldavChooser *chooser, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Context *context; + ESource *source; + SoupURI *soup_uri; + SoupMessage *message; + ESourceWebdav *extension; + GtkTreeModel *tree_model; + GSimpleAsyncResult *simple; + const gchar *extension_name; + + g_return_if_fail (E_IS_CALDAV_CHOOSER (chooser)); + + tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser)); + gtk_list_store_clear (GTK_LIST_STORE (tree_model)); + soup_session_abort (chooser->priv->session); + + source = e_caldav_chooser_get_source (chooser); + extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND; + extension = e_source_get_extension (source, extension_name); + + soup_uri = e_source_webdav_dup_soup_uri (extension); + g_return_if_fail (soup_uri != NULL); + + context = context_new (chooser, cancellable); + + simple = g_simple_async_result_new ( + G_OBJECT (chooser), callback, + user_data, e_caldav_chooser_populate); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) context_free); + + message = caldav_chooser_new_propfind ( + context->session, soup_uri, DEPTH_0, + NS_WEBDAV, XC ("resourcetype"), + NS_CALDAV, XC ("calendar-home-set"), + NS_CALDAV, XC ("calendar-user-address-set"), + NS_WEBDAV, XC ("current-user-principal"), + NS_WEBDAV, XC ("principal-URL"), + NULL); + + /* This takes ownership of the message. */ + soup_session_queue_message ( + context->session, message, (SoupSessionCallback) + caldav_chooser_calendar_home_set_cb, simple); + + soup_uri_free (soup_uri); +} + +gboolean +e_caldav_chooser_populate_finish (ECaldavChooser *chooser, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + Context *context; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (chooser), + e_caldav_chooser_populate), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + context = g_simple_async_result_get_op_res_gpointer (simple); + + if (g_simple_async_result_propagate_error (simple, error)) + return FALSE; + + /* Transfer user addresses to the private struct. */ + + g_list_free_full ( + chooser->priv->user_address_set, + (GDestroyNotify) g_free); + + chooser->priv->user_address_set = context->user_address_set; + context->user_address_set = NULL; + + return TRUE; +} + +gboolean +e_caldav_chooser_apply_selected (ECaldavChooser *chooser) +{ + ESourceWebdav *webdav_extension; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + ESource *source; + GdkColor *color; + gboolean has_color; + gchar *display_name; + gchar *path_encoded; + + g_return_val_if_fail (E_IS_CALDAV_CHOOSER (chooser), FALSE); + + source = e_caldav_chooser_get_source (chooser); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser)); + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return FALSE; + + gtk_tree_model_get ( + model, &iter, + COLUMN_DISPLAY_NAME, &display_name, + COLUMN_PATH_ENCODED, &path_encoded, + COLUMN_HAS_COLOR, &has_color, + COLUMN_COLOR, &color, + -1); + + /* Sanity check. */ + g_warn_if_fail ( + (has_color && color != NULL) || + (!has_color && color == NULL)); + + webdav_extension = e_source_get_extension ( + source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); + + e_source_set_display_name (source, display_name); + + e_source_webdav_set_display_name (webdav_extension, display_name); + e_source_webdav_set_resource_path (webdav_extension, path_encoded); + + /* XXX For now just pick the first user address in the list. + * Might be better to compare the list against our own mail + * accounts and give preference to matches (especially if an + * address matches the default mail account), but I'm not sure + * if multiple user addresses are common enough to justify the + * extra effort. */ + if (chooser->priv->user_address_set != NULL) + e_source_webdav_set_email_address ( + webdav_extension, + chooser->priv->user_address_set->data); + + if (has_color) { + ESourceSelectable *selectable_extension; + const gchar *extension_name; + gchar *color_spec; + + switch (e_caldav_chooser_get_source_type (chooser)) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + extension_name = E_SOURCE_EXTENSION_CALENDAR; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + extension_name = E_SOURCE_EXTENSION_MEMO_LIST; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + extension_name = E_SOURCE_EXTENSION_TASK_LIST; + break; + default: + g_return_val_if_reached (TRUE); + } + + selectable_extension = + e_source_get_extension (source, extension_name); + + color_spec = gdk_color_to_string (color); + e_source_selectable_set_color ( + selectable_extension, color_spec); + g_free (color_spec); + + gdk_color_free (color); + } + + g_free (display_name); + g_free (path_encoded); + + return TRUE; +} + diff --git a/modules/cal-config-caldav/e-caldav-chooser.h b/modules/cal-config-caldav/e-caldav-chooser.h new file mode 100644 index 0000000000..b436161fdb --- /dev/null +++ b/modules/cal-config-caldav/e-caldav-chooser.h @@ -0,0 +1,81 @@ +/* + * e-caldav-chooser.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 <webcal://www.gnu.org/licenses/> + * + */ + +#ifndef E_CALDAV_CHOOSER_H +#define E_CALDAV_CHOOSER_H + +#include <gtk/gtk.h> +#include <libecal/e-cal-client.h> +#include <libedataserver/e-source-registry.h> + +/* Standard GObject macros */ +#define E_TYPE_CALDAV_CHOOSER \ + (e_caldav_chooser_get_type ()) +#define E_CALDAV_CHOOSER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CALDAV_CHOOSER, ECaldavChooser)) +#define E_CALDAV_CHOOSER_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CALDAV_CHOOSER, ECaldavChooserClass)) +#define E_IS_CALDAV_CHOOSER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CALDAV_CHOOSER)) +#define E_IS_CALDAV_CHOOSER_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CALDAV_CHOOSER)) +#define E_CALDAV_CHOOSER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CALDAV_CHOOSER, ECaldavChooserClass)) + +G_BEGIN_DECLS + +typedef struct _ECaldavChooser ECaldavChooser; +typedef struct _ECaldavChooserClass ECaldavChooserClass; +typedef struct _ECaldavChooserPrivate ECaldavChooserPrivate; + +struct _ECaldavChooser { + GtkTreeView parent; + ECaldavChooserPrivate *priv; +}; + +struct _ECaldavChooserClass { + GtkTreeViewClass parent_class; +}; + +GType e_caldav_chooser_get_type (void); +void e_caldav_chooser_type_register (GTypeModule *type_module); +GtkWidget * e_caldav_chooser_new (ESourceRegistry *registry, + ESource *source, + ECalClientSourceType source_type); +ESourceRegistry * + e_caldav_chooser_get_registry (ECaldavChooser *chooser); +ESource * e_caldav_chooser_get_source (ECaldavChooser *chooser); +ECalClientSourceType + e_caldav_chooser_get_source_type + (ECaldavChooser *chooser); +void e_caldav_chooser_populate (ECaldavChooser *chooser, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean e_caldav_chooser_populate_finish + (ECaldavChooser *chooser, + GAsyncResult *result, + GError **error); +gboolean e_caldav_chooser_apply_selected (ECaldavChooser *chooser); + +#endif /* E_CALDAV_CHOOSER_H */ diff --git a/modules/cal-config-caldav/evolution-cal-config-caldav.c b/modules/cal-config-caldav/evolution-cal-config-caldav.c new file mode 100644 index 0000000000..0d8d6dd6cf --- /dev/null +++ b/modules/cal-config-caldav/evolution-cal-config-caldav.c @@ -0,0 +1,381 @@ +/* + * evolution-cal-config-caldav.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/> + * + */ + +#include <config.h> +#include <glib/gi18n-lib.h> + +#include <libebackend/e-extension.h> +#include <libedataserver/e-source-authentication.h> +#include <libedataserver/e-source-security.h> +#include <libedataserver/e-source-webdav.h> + +#include <misc/e-interval-chooser.h> +#include <misc/e-source-config-backend.h> +#include <calendar/gui/e-cal-source-config.h> + +#include "e-caldav-chooser.h" +#include "e-caldav-chooser-dialog.h" + +#define HTTP_PORT 80 +#define HTTPS_PORT 443 + +typedef ESourceConfigBackend ECalConfigCalDAV; +typedef ESourceConfigBackendClass ECalConfigCalDAVClass; + +typedef struct _Context Context; + +struct _Context { + ESourceConfigBackend *backend; /* not referenced */ + ESource *scratch_source; /* not referenced */ + + GtkWidget *server_entry; + GtkWidget *path_entry; + GtkWidget *email_entry; + GtkWidget *find_button; + GtkWidget *auto_schedule_toggle; + + GSocketConnectable *connectable; +}; + +/* Module Entry Points */ +void e_module_load (GTypeModule *type_module); +void e_module_unload (GTypeModule *type_module); + +/* Forward Declarations */ +GType e_cal_config_caldav_get_type (void); + +G_DEFINE_DYNAMIC_TYPE ( + ECalConfigCalDAV, + e_cal_config_caldav, + E_TYPE_SOURCE_CONFIG_BACKEND) + +static Context * +cal_config_caldav_context_new (ESourceConfigBackend *backend, + ESource *scratch_source) +{ + Context *context; + + context = g_slice_new0 (Context); + context->backend = backend; + context->scratch_source = scratch_source; + + return context; +} + +static void +cal_config_caldav_context_free (Context *context) +{ + g_object_unref (context->server_entry); + g_object_unref (context->path_entry); + g_object_unref (context->email_entry); + g_object_unref (context->find_button); + g_object_unref (context->auto_schedule_toggle); + + if (context->connectable != NULL) + g_object_unref (context->connectable); + + g_slice_free (Context, context); +} + +static gchar * +cal_config_caldav_get_server (ESource *scratch_source) +{ + ESourceAuthentication *authentication_extension; + ESourceSecurity *security_extension; + const gchar *host; + gboolean secure; + guint16 default_port; + guint16 port; + + authentication_extension = e_source_get_extension ( + scratch_source, E_SOURCE_EXTENSION_AUTHENTICATION); + host = e_source_authentication_get_host (authentication_extension); + port = e_source_authentication_get_port (authentication_extension); + + security_extension = e_source_get_extension ( + scratch_source, E_SOURCE_EXTENSION_SECURITY); + secure = e_source_security_get_secure (security_extension); + default_port = secure ? HTTPS_PORT: HTTP_PORT; + + if (port == 0) + port = default_port; + + if (host == NULL || *host == '\0') + return NULL; + + if (port == default_port) + return g_strdup (host); + + return g_strdup_printf ("%s:%u", host, port); +} + +static void +cal_config_caldav_server_changed_cb (GtkEntry *entry, + Context *context) +{ + ESourceAuthentication *authentication_extension; + ESourceSecurity *security_extension; + const gchar *host_and_port; + const gchar *host; + gboolean secure; + guint16 default_port; + guint16 port; + + if (context->connectable != NULL) { + g_object_unref (context->connectable); + context->connectable = NULL; + } + + authentication_extension = e_source_get_extension ( + context->scratch_source, E_SOURCE_EXTENSION_AUTHENTICATION); + + security_extension = e_source_get_extension ( + context->scratch_source, E_SOURCE_EXTENSION_SECURITY); + + host_and_port = gtk_entry_get_text (entry); + secure = e_source_security_get_secure (security_extension); + default_port = secure ? HTTPS_PORT : HTTP_PORT; + + if (host_and_port != NULL && *host_and_port != '\0') + context->connectable = g_network_address_parse ( + host_and_port, default_port, NULL); + + if (context->connectable != NULL) { + GNetworkAddress *address; + + address = G_NETWORK_ADDRESS (context->connectable); + host = g_network_address_get_hostname (address); + port = g_network_address_get_port (address); + } else { + host = NULL; + port = 0; + } + + e_source_authentication_set_host (authentication_extension, host); + e_source_authentication_set_port (authentication_extension, port); +} + +static void +cal_config_caldav_run_dialog (GtkButton *button, + Context *context) +{ + ESourceConfig *config; + ESourceRegistry *registry; + ECalClientSourceType source_type; + GtkWidget *dialog; + GtkWidget *widget; + gpointer parent; + + config = e_source_config_backend_get_config (context->backend); + registry = e_source_config_get_registry (config); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (config)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + source_type = e_cal_source_config_get_source_type ( + E_CAL_SOURCE_CONFIG (config)); + + widget = e_caldav_chooser_new ( + registry, context->scratch_source, source_type); + + dialog = e_caldav_chooser_dialog_new ( + E_CALDAV_CHOOSER (widget), parent); + + if (parent != NULL) + g_object_bind_property ( + parent, "icon-name", + dialog, "icon-name", + G_BINDING_SYNC_CREATE); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); +} + +static void +cal_config_caldav_insert_widgets (ESourceConfigBackend *backend, + ESource *scratch_source) +{ + ESourceConfig *config; + ESourceExtension *extension; + ECalClientSourceType source_type; + GtkWidget *widget; + Context *context; + gchar *text; + const gchar *extension_name; + const gchar *label; + const gchar *uid; + + context = cal_config_caldav_context_new (backend, scratch_source); + uid = e_source_get_uid (scratch_source); + config = e_source_config_backend_get_config (backend); + + g_object_set_data_full ( + G_OBJECT (backend), uid, context, + (GDestroyNotify) cal_config_caldav_context_free); + + source_type = e_cal_source_config_get_source_type ( + E_CAL_SOURCE_CONFIG (config)); + + e_cal_source_config_add_offline_toggle ( + E_CAL_SOURCE_CONFIG (config), scratch_source); + + widget = gtk_entry_new (); + e_source_config_insert_widget ( + config, scratch_source, _("Server:"), widget); + context->server_entry = g_object_ref (widget); + gtk_widget_show (widget); + + /* Connect the signal before initializing the entry text. */ + g_signal_connect ( + widget, "changed", + G_CALLBACK (cal_config_caldav_server_changed_cb), context); + + text = cal_config_caldav_get_server (scratch_source); + if (text != NULL) { + gtk_entry_set_text (GTK_ENTRY (context->server_entry), text); + g_free (text); + } + + e_source_config_add_secure_connection_for_webdav ( + config, scratch_source); + + e_source_config_add_user_entry (config, scratch_source); + + switch (source_type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + label = _("Find Calendars"); + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + label = _("Find Memo Lists"); + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + label = _("Find Task Lists"); + break; + default: + g_return_if_reached (); + } + + widget = gtk_button_new_with_label (label); + e_source_config_insert_widget ( + config, scratch_source, NULL, widget); + context->find_button = g_object_ref (widget); + gtk_widget_show (widget); + + g_signal_connect ( + widget, "clicked", + G_CALLBACK (cal_config_caldav_run_dialog), context); + + widget = gtk_entry_new (); + e_source_config_insert_widget ( + config, scratch_source, _("Path:"), widget); + context->path_entry = g_object_ref (widget); + gtk_widget_show (widget); + + widget = gtk_entry_new (); + e_source_config_insert_widget ( + config, scratch_source, _("Email:"), widget); + context->email_entry = g_object_ref (widget); + gtk_widget_show (widget); + + widget = gtk_check_button_new_with_label ( + _("Server handles meeting invitations")); + e_source_config_insert_widget ( + config, scratch_source, NULL, widget); + context->auto_schedule_toggle = g_object_ref (widget); + gtk_widget_show (widget); + + e_source_config_add_refresh_interval (config, scratch_source); + + extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND; + extension = e_source_get_extension (scratch_source, extension_name); + + g_object_bind_property ( + extension, "calendar-auto-schedule", + context->auto_schedule_toggle, "active", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + extension, "email-address", + context->email_entry, "text", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + extension, "resource-path", + context->path_entry, "text", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); +} + +static gboolean +cal_config_caldav_check_complete (ESourceConfigBackend *backend, + ESource *scratch_source) +{ + Context *context; + const gchar *uid; + gboolean complete; + + uid = e_source_get_uid (scratch_source); + context = g_object_get_data (G_OBJECT (backend), uid); + g_return_val_if_fail (context != NULL, FALSE); + + complete = (context->connectable != NULL); + + gtk_widget_set_sensitive (context->find_button, complete); + + return complete; +} + +static void +e_cal_config_caldav_class_init (ESourceConfigBackendClass *class) +{ + EExtensionClass *extension_class; + + extension_class = E_EXTENSION_CLASS (class); + extension_class->extensible_type = E_TYPE_CAL_SOURCE_CONFIG; + + class->parent_uid = "caldav-stub"; + class->backend_name = "caldav"; + class->insert_widgets = cal_config_caldav_insert_widgets; + class->check_complete = cal_config_caldav_check_complete; +} + +static void +e_cal_config_caldav_class_finalize (ESourceConfigBackendClass *class) +{ +} + +static void +e_cal_config_caldav_init (ESourceConfigBackend *backend) +{ +} + +G_MODULE_EXPORT void +e_module_load (GTypeModule *type_module) +{ + e_caldav_chooser_type_register (type_module); + e_caldav_chooser_dialog_type_register (type_module); + e_cal_config_caldav_register_type (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ +} diff --git a/plugins/caldav/Makefile.am b/plugins/caldav/Makefile.am deleted file mode 100644 index 437f574850..0000000000 --- a/plugins/caldav/Makefile.am +++ /dev/null @@ -1,34 +0,0 @@ -@EVO_PLUGIN_RULE@ - -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_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_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)/shell/libeshell.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ - $(top_builddir)/libemail-utils/libemail-utils.la \ - $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) - -liborg_gnome_evolution_caldav_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED) - -EXTRA_DIST = \ - org-gnome-evolution-caldav.eplug.xml - -CLEANFILES = org-gnome-evolution-caldav.eplug - --include $(top_srcdir)/git.mk diff --git a/plugins/caldav/caldav-browse-server.c b/plugins/caldav/caldav-browse-server.c deleted file mode 100644 index 2597e3cc49..0000000000 --- a/plugins/caldav/caldav-browse-server.c +++ /dev/null @@ -1,1657 +0,0 @@ -/* - * 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 <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-client.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 <libemail-utils/e-account-utils.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, const gchar *msg_path, guint status_code, const gchar *reason_phrase, const gchar *msg_body, gpointer user_data); - -static void send_xml_message (xmlDocPtr doc, gboolean depth_1, 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 = g_object_get_data (dialog, "caldav-usermail-hbox"); - gtk_widget_hide (w); - - w = g_object_get_data (dialog, "caldav-new-autoschedule-check"); - 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 *reason_phrase, - 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, reason_phrase ? reason_phrase : (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, - const gchar *msg_path, - guint status_code, - const gchar *reason_phrase, - 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)); - - if (!check_soup_status (dialog, status_code, reason_phrase, msg_body, TRUE)) - return; - - g_return_if_fail (msg_body != NULL); - - 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 path or the msg_path*/ - if (msg_path && *msg_path) { - add_collection_node_to_tree (store, NULL, msg_path); - } else { - SoupURI *suri; - - suri = soup_uri_new (g_object_get_data (dialog, "caldav-base-url")); - - add_collection_node_to_tree (store, NULL, (suri && suri->path && *suri->path) ? suri->path : "/"); - - if (suri) - soup_uri_free (suri); - } - } - } - - 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, nsc, XC "calendar-user-address-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, TRUE, 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); -} - -static gboolean -mail_account_configured (const gchar *email) -{ - gboolean found = FALSE; - EAccountList *accounts; - EIterator *iterator; - - g_return_val_if_fail (email != NULL, FALSE); - g_return_val_if_fail (*email, FALSE); - - accounts = e_get_account_list (); - g_return_val_if_fail (accounts != NULL, FALSE); - - for (iterator = e_list_get_iterator (E_LIST (accounts)); - !found && e_iterator_is_valid (iterator); - e_iterator_next (iterator)) { - EAccount *acc = (EAccount *) e_iterator_get (iterator); - const gchar *address; - - if (!acc) - continue; - - address = e_account_get_string (acc, E_ACCOUNT_ID_ADDRESS); - if (!address || !*address) - continue; - - found = g_strcmp0 (address, email) == 0; - } - - g_object_unref (iterator); - - return found; -} - -static void -add_usermail (GtkComboBoxText *usermail_combo, - const gchar *email, - gboolean is_first) -{ - GtkTreeModel *model; - GtkTreeIter iter; - gboolean found = FALSE; - - g_return_if_fail (usermail_combo != NULL); - g_return_if_fail (email != NULL); - - if (!*email) - return; - - model = gtk_combo_box_get_model (GTK_COMBO_BOX (usermail_combo)); - g_return_if_fail (model != NULL); - - if (gtk_tree_model_get_iter_first (model, &iter)) { - do { - gchar *value = NULL; - - gtk_tree_model_get (model, &iter, 0, &value, -1); - - found = value && g_ascii_strcasecmp (value, email) == 0; - if (found && (is_first || mail_account_configured (email))) - gtk_combo_box_set_active_iter (GTK_COMBO_BOX (usermail_combo), &iter); - - g_free (value); - } while (!found && gtk_tree_model_iter_next (model, &iter)); - } - - if (!found) { - gtk_combo_box_text_append_text (usermail_combo, email); - if (gtk_tree_model_iter_n_children (model, NULL) == 1 || is_first || mail_account_configured (email)) - gtk_combo_box_set_active (GTK_COMBO_BOX (usermail_combo), gtk_tree_model_iter_n_children (model, NULL) - 1); - } -} - -/* 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, - const gchar *msg_path, - guint status_code, - const gchar *reason_phrase, - const gchar *msg_body, - gpointer user_data) -{ - xmlDocPtr doc; - xmlXPathContextPtr xpctx; - xmlXPathObjectPtr suppObj; - gchar *calendar_home_set, *url; - gboolean base_url_is_calendar = FALSE; - - g_return_if_fail (dialog != NULL); - g_return_if_fail (GTK_IS_DIALOG (dialog)); - - if (!check_soup_status (dialog, status_code, reason_phrase, msg_body, TRUE)) - return; - - g_return_if_fail (msg_body != NULL); - - 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"); - - if (user_data == NULL) - base_url_is_calendar = xpath_exists (xpctx, NULL, "/D:multistatus/D:response/D:propstat/D:prop/D:resourcetype/C:calendar"); - - if (xpath_exists (xpctx, &suppObj, "/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set")) { - if (suppObj->type == XPATH_NODESET) { - GtkComboBoxText *usermail_combo = GTK_COMBO_BOX_TEXT (g_object_get_data (dialog, "caldav-new-usermail-combo")); - gboolean is_first = TRUE; - gint jj, szjj = xmlXPathNodeSetGetLength (suppObj->nodesetval); - - for (jj = 0; jj < szjj; jj++) { - gchar *href = xpath_get_string (xpctx, "/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href[%d]", jj + 1); - - if (!href || !g_str_has_prefix (href, "mailto:")) { - g_free (href); - continue; - } - - add_usermail (usermail_combo, href + 7, is_first); - is_first = FALSE; - - g_free (href); - } - } - - xmlXPathFreeObject (suppObj); - } - - 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); - xmlNewTextChild (node, nsc, XC "calendar-user-address-set", NULL); - - url = change_url_path (g_object_get_data (dialog, "caldav-base-url"), calendar_home_set); - if (url) { - send_xml_message (doc, TRUE, 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 (base_url_is_calendar && (!calendar_home_set || !*calendar_home_set)) { - SoupURI *suri = soup_uri_new (g_object_get_data (dialog, "caldav-base-url")); - if (suri) { - if (suri->path && *suri->path) { - gchar *slash; - - while (slash = strrchr (suri->path, '/'), slash && slash != suri->path) { - if (slash[1] != 0) { - slash[1] = 0; - g_free (calendar_home_set); - calendar_home_set = g_strdup (suri->path); - break; - } - - *slash = 0; - } - } - soup_uri_free (suri); - } - } - - 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 (!retrying && !password) - password = e_passwords_get_password (NULL, g_object_get_data (dialog, "caldav-auth-key")); - - if (!password || !*password || retrying) { - gchar *pass, *prompt, *add = NULL; - gchar *bold_user, *bold_host; - - 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); - } - - bold_user = g_strconcat ("<b>", username, "</b>", NULL); - bold_host = g_strconcat ("<b>", soup_auth_get_host (auth), "</b>", NULL); - prompt = g_strdup_printf (_("Enter password for user %s on server %s"), bold_user, bold_host); - g_free (bold_user); - g_free (bold_host); - if (add) { - gchar *tmp; - - tmp = g_strconcat (prompt, "\n", add, NULL); - - g_free (prompt); - prompt = tmp; - } - - pass = e_passwords_ask_password (_("Enter password"), - NULL, g_object_get_data (dialog, "caldav-auth-key"), 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_path = NULL; - gchar *msg_body = NULL; - gchar *reason_phrase = 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) { - const SoupURI *suri = soup_message_get_uri (pd->message); - const gchar *header; - - status_code = pd->message->status_code; - reason_phrase = g_strdup (pd->message->reason_phrase); - msg_body = g_strndup (pd->message->response_body->data, pd->message->response_body->length); - - if (suri && suri->path) - msg_path = g_strdup (suri->path); - - header = soup_message_headers_get (pd->message->response_headers, "DAV"); - if (header) { - gboolean autoschedule = soup_header_contains (header, "calendar-auto-schedule"); - - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (g_object_get_data (pd->dialog, "caldav-new-autoschedule-check")), autoschedule); - } - } - - 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) { - (*pd->cb) (pd->dialog, msg_path, status_code, reason_phrase, msg_body, pd->cb_user_data); - } - - g_free (msg_body); - g_free (msg_path); - g_free (reason_phrase); - - return again; -} - -static void -send_xml_message (xmlDocPtr doc, - gboolean depth_1, - 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 (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 ("PROPFIND", 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", depth_1 ? "1" : "0"); - 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, - GtkWidget **new_usermail_combo, - GtkWidget **new_autoschedule_check, - const gchar *url, - const gchar *username, - const gchar *usermail, - gboolean autoschedule, - gint source_type, - gboolean ignore_invalid_cert) -{ - GtkBox *content_area; - GtkWidget *label, *info_box, *spinner, *info_label, *hbox; - GtkWidget *tree, *scrolled_window; - GtkTreeStore *store; - GtkTreeSelection *selection; - SoupSession *session; - EProxy *proxy; - SoupURI *soup_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 (new_usermail_combo != NULL); - g_return_if_fail (new_autoschedule_check != 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 ( - *new_url_entry, "changed", - G_CALLBACK (url_entry_changed), dialog); - - *new_usermail_combo = gtk_combo_box_text_new (); - if (usermail && *usermail) { - gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (*new_usermail_combo), usermail); - gtk_combo_box_set_active (GTK_COMBO_BOX (*new_usermail_combo), 0); - } - - *new_autoschedule_check = gtk_check_button_new_with_mnemonic (_("Server _handles meeting invitations")); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (*new_autoschedule_check), autoschedule); - - g_object_set_data (G_OBJECT (dialog), "caldav-new-url-entry", *new_url_entry); - g_object_set_data (G_OBJECT (dialog), "caldav-new-usermail-combo", *new_usermail_combo); - g_object_set_data (G_OBJECT (dialog), "caldav-new-autoschedule-check", *new_autoschedule_check); - - 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_widget_set_size_request (scrolled_window, 320, 240); - 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 = gtk_spinner_new (); - gtk_spinner_start (GTK_SPINNER (spinner)); - 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); - - hbox = gtk_hbox_new (FALSE, 2); - gtk_box_pack_start (content_area, hbox, FALSE, FALSE, 0); - - label = gtk_label_new_with_mnemonic (_("User e_mail:")); - gtk_label_set_mnemonic_widget (GTK_LABEL (label), *new_usermail_combo); - - gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2); - gtk_box_pack_start (GTK_BOX (hbox), *new_usermail_combo, FALSE, FALSE, 2); - - g_object_set_data (G_OBJECT (dialog), "caldav-usermail-hbox", hbox); - - gtk_box_pack_start (content_area, *new_autoschedule_check, 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_with_options ( - SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, !ignore_invalid_cert, - NULL); - - 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)) { - soup_uri = e_proxy_peek_uri_for (proxy, url); - } - - g_object_set (session, SOUP_SESSION_PROXY_URI, soup_uri, NULL); - g_object_unref (proxy); - - g_signal_connect ( - session, "authenticate", - G_CALLBACK (soup_authenticate), dialog); - - switch (source_type) { - default: - case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: - source_type_str = "VEVENT"; - break; - case E_CAL_CLIENT_SOURCE_TYPE_TASKS: - source_type_str = "VTODO"; - break; - case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: - source_type_str = "VJOURNAL"; - break; - } - - soup_uri = soup_uri_new (url); - g_return_if_fail (soup_uri != NULL); - - soup_uri_set_scheme (soup_uri, "caldav"); - soup_uri_set_user (soup_uri, username); - - g_object_set_data_full (G_OBJECT (dialog), "caldav-auth-key", soup_uri_to_string (soup_uri, FALSE), g_free); - 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 */ - - soup_uri_free (soup_uri); - - 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, nsdav, XC "resourcetype", NULL); - xmlNewTextChild (node, nsc, XC "calendar-home-set", NULL); - xmlNewTextChild (node, nsc, XC "calendar-user-address-set", NULL); - - send_xml_message (doc, FALSE, 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, - gboolean ignore_invalid_cert, - gchar **new_usermail, - gboolean *new_autoschedule, - gint source_type) -{ - GtkWidget *dialog, *new_url_entry, *new_usermail_combo, *new_autoschedule_check; - gchar *url, *new_url = NULL; - SoupURI *soup_uri = NULL; - - g_return_val_if_fail (server_url != NULL, NULL); - - url = prepare_url (server_url, use_ssl); - if (url && *url) - soup_uri = soup_uri_new (url); - - if (!url || !*url || !soup_uri) { - e_notice (parent, GTK_MESSAGE_ERROR, _("Server URL '%s' is not a valid URL"), server_url); - if (soup_uri) - soup_uri_free (soup_uri); - g_free (url); - return NULL; - } - - soup_uri_free (soup_uri); - - dialog = gtk_dialog_new_with_buttons ( - _("Browse for a CalDAV calendar"), - parent, - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, - NULL); - - new_url_entry = NULL; - new_usermail_combo = NULL; - new_autoschedule_check = NULL; - init_dialog (GTK_DIALOG (dialog), - &new_url_entry, - &new_usermail_combo, - &new_autoschedule_check, - url, - username, - new_usermail ? *new_usermail : NULL, - new_autoschedule ? *new_autoschedule : FALSE, - source_type, - ignore_invalid_cert); - - if (new_url_entry && gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { - const gchar *txt; - - txt = gtk_entry_get_text (GTK_ENTRY (new_url_entry)); - if (txt && *txt) - new_url = change_url_path (server_url, txt); - - txt = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (new_usermail_combo)); - if (txt && *txt && new_usermail) { - g_free (*new_usermail); - *new_usermail = g_strdup (txt); - } - - if (new_autoschedule) - *new_autoschedule = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (new_autoschedule_check)); - } - - 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 deleted file mode 100644 index 8f37603838..0000000000 --- a/plugins/caldav/caldav-browse-server.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 <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, - gboolean ignore_invalid_cert, - gchar **new_usermail, - gboolean *new_autoschedule, - gint source_type); - -#endif /* CALDAV_BROWSE_SERVER_H */ diff --git a/plugins/caldav/caldav-source.c b/plugins/caldav/caldav-source.c deleted file mode 100644 index 130d0fa834..0000000000 --- a/plugins/caldav/caldav-source.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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/> - * - * - * Authors: - * Christian Kellner <gicmo@gnome.org> - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <glib/gi18n-lib.h> - -#include <gtk/gtk.h> - -#include <e-util/e-config.h> -#include <e-util/e-plugin.h> -#include <e-util/e-plugin-util.h> -#include <shell/e-shell.h> -#include <calendar/gui/e-cal-config.h> -#include <libedataserver/e-account-list.h> -#include <libecal/e-cal-client.h> - -#include <string.h> - -#include "caldav-browse-server.h" - -#define d(x) - -/*****************************************************************************/ -/* prototypes */ -gint e_plugin_lib_enable (EPlugin *ep, - gint enable); - -GtkWidget * oge_caldav (EPlugin *epl, - EConfigHookItemFactoryData *data); - -/*****************************************************************************/ -/* plugin intialization */ - -static void -ensure_caldav_source_group (const gchar *backend_name) -{ - EShellBackend *backend; - ESourceList *source_list = NULL; - - backend = e_shell_get_backend_by_name (e_shell_get_default (), backend_name); - g_return_if_fail (backend != NULL); - - g_object_get (G_OBJECT (backend), "source-list", &source_list, NULL); - g_return_if_fail (source_list != NULL); - - e_source_list_ensure_group (source_list, _("CalDAV"), "caldav://", FALSE); - g_object_unref (source_list); -} - -gint -e_plugin_lib_enable (EPlugin *ep, - gint enable) -{ - - if (enable) { - d(g_print ("CalDAV Eplugin starting up ...\n")); - ensure_caldav_source_group ("calendar"); - ensure_caldav_source_group ("tasks"); - ensure_caldav_source_group ("memos"); - } - - return 0; -} - -/*****************************************************************************/ - -static void -location_changed_cb (GtkEntry *editable, - ESource *source) -{ - SoupURI *suri; - gchar *ruri; - const gchar *uri, *username; - - uri = gtk_entry_get_text (editable); - - suri = soup_uri_new (uri); - if (!suri) - return; - - username = e_source_get_property (source, "username"); - if (username && !*username) - username = NULL; - - soup_uri_set_user (suri, username); - - ruri = e_plugin_util_uri_no_proto (suri); - e_source_set_relative_uri (source, ruri); - g_free (ruri); - soup_uri_free (suri); -} - -static void -user_changed_cb (GtkEntry *editable, - ESource *source) -{ - SoupURI *suri; - gchar *uri, *ruri; - const gchar *user; - - uri = e_source_get_uri (source); - user = gtk_entry_get_text (editable); - - if (uri == NULL) - return; - - suri = soup_uri_new (uri); - g_free (uri); - - if (suri == NULL) - return; - - soup_uri_set_user (suri, NULL); - - if (user != NULL && *user) { - soup_uri_set_user (suri, user); - e_source_set_property (source, "auth", "1"); - } else { - e_source_set_property (source, "auth", NULL); - } - - e_source_set_property (source, "username", user); - ruri = e_plugin_util_uri_no_proto (suri); - e_source_set_relative_uri (source, ruri); - g_free (ruri); - soup_uri_free (suri); -} - -static void -browse_cal_clicked_cb (GtkButton *button, - gpointer user_data) -{ - GtkEntry *url, *username, *usermail; - GtkToggleButton *ssl, *ignore_cert, *autoschedule; - gchar *new_url, *new_usermail; - gboolean new_autoschedule; - - g_return_if_fail (button != NULL); - - url = g_object_get_data (G_OBJECT (button), "caldav-url"); - ssl = g_object_get_data (G_OBJECT (button), "caldav-ssl"); - ignore_cert = g_object_get_data (G_OBJECT (button), "caldav-ignore-cert"); - username = g_object_get_data (G_OBJECT (button), "caldav-username"); - usermail = g_object_get_data (G_OBJECT (button), "caldav-usermail"); - autoschedule = g_object_get_data (G_OBJECT (button), "caldav-autoschedule"); - - g_return_if_fail (url != NULL); - g_return_if_fail (GTK_IS_ENTRY (url)); - g_return_if_fail (ssl != NULL); - g_return_if_fail (GTK_IS_TOGGLE_BUTTON (ssl)); - g_return_if_fail (ignore_cert != NULL); - g_return_if_fail (GTK_IS_TOGGLE_BUTTON (ignore_cert)); - g_return_if_fail (username != NULL); - g_return_if_fail (GTK_IS_ENTRY (username)); - g_return_if_fail (usermail != NULL); - g_return_if_fail (GTK_IS_ENTRY (usermail)); - g_return_if_fail (autoschedule != NULL); - g_return_if_fail (GTK_IS_TOGGLE_BUTTON (autoschedule)); - - new_usermail = g_strdup (gtk_entry_get_text (usermail)), - new_autoschedule = gtk_toggle_button_get_active (autoschedule); - - 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), - gtk_toggle_button_get_active (ignore_cert), - &new_usermail, - &new_autoschedule, - GPOINTER_TO_INT (user_data)); - - if (new_url) { - gtk_entry_set_text (url, new_url); - g_free (new_url); - - if (new_usermail) - gtk_entry_set_text (usermail, new_usermail); - - gtk_toggle_button_set_active (autoschedule, new_autoschedule); - } - - g_free (new_usermail); -} - -GtkWidget * -oge_caldav (EPlugin *epl, - EConfigHookItemFactoryData *data) -{ - ECalConfigTargetSource *t = (ECalConfigTargetSource *) data->target; - ESource *source; - SoupURI *suri; - GtkWidget *parent, *location, *ssl, *ignore_cert, *user, *mail, *autoschedule, *browse_cal; - gchar *uri, *username; - guint n_rows; - - source = t->source; - - if (!e_plugin_util_is_group_proto (e_source_peek_group (source), "caldav")) { - return NULL; - } - - /* Extract the username from the uri so we can prefill the - * dialog right, remove the username from the url then */ - uri = e_source_get_uri (source); - suri = soup_uri_new (uri); - g_free (uri); - - if (suri) { - soup_uri_set_user (suri, NULL); - soup_uri_set_password (suri, NULL); - uri = soup_uri_to_string (suri, FALSE); - soup_uri_free (suri); - } else { - uri = g_strdup (""); - } - - username = e_source_get_duped_property (source, "username"); - - /* Build up the UI */ - parent = data->parent; - - location = e_plugin_util_add_entry (parent, _("_URL:"), NULL, NULL); - gtk_entry_set_text (GTK_ENTRY (location), uri); - - g_signal_connect ( - location, "changed", - G_CALLBACK (location_changed_cb), source); - - ssl = e_plugin_util_add_check (parent, _("Use _secure connection"), source, "ssl", "1", "0"); - ignore_cert = e_plugin_util_add_check (parent, _("_Ignore invalid SSL certificate"), source, "ignore-invalid-cert", "1", NULL); - - g_object_bind_property ( - ssl, "active", - ignore_cert, "sensitive", - G_BINDING_SYNC_CREATE); - - user = e_plugin_util_add_entry (parent, _("User_name:"), NULL, NULL); - gtk_entry_set_text (GTK_ENTRY (user), username ? username : ""); - - g_signal_connect ( - user, "changed", - G_CALLBACK (user_changed_cb), source); - - g_free (uri); - g_free (username); - - mail = e_plugin_util_add_entry (parent, _("User e_mail:"), source, "usermail"); - autoschedule = e_plugin_util_add_check ( - parent, _("Server _handles meeting invitations"), - source, "autoschedule", "1", "0"); - - browse_cal = gtk_button_new_with_mnemonic (_("Brows_e server for a calendar")); - gtk_widget_show (browse_cal); - g_object_get (parent, "n-rows", &n_rows, NULL); - gtk_table_attach ( - GTK_TABLE (parent), browse_cal, 1, 2, - n_rows, n_rows + 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-ssl", ssl); - g_object_set_data (G_OBJECT (browse_cal), "caldav-ignore-cert", ignore_cert); - g_object_set_data (G_OBJECT (browse_cal), "caldav-username", user); - g_object_set_data (G_OBJECT (browse_cal), "caldav-usermail", mail); - g_object_set_data (G_OBJECT (browse_cal), "caldav-autoschedule", autoschedule); - - g_signal_connect ( - browse_cal, "clicked", - G_CALLBACK (browse_cal_clicked_cb), - GINT_TO_POINTER (t->source_type)); - - e_plugin_util_add_refresh (parent, _("Re_fresh:"), source, "refresh"); - - return location; -} diff --git a/plugins/caldav/org-gnome-evolution-caldav.eplug.xml b/plugins/caldav/org-gnome-evolution-caldav.eplug.xml deleted file mode 100644 index 42efe6390e..0000000000 --- a/plugins/caldav/org-gnome-evolution-caldav.eplug.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0"?> -<e-plugin-list> - <e-plugin - type="shlib" - id="org.gnome.evolution.caldav" - location="@PLUGINDIR@/liborg-gnome-evolution-caldav@SOEXT@" - load-on-startup="true" - domain="@GETTEXT_PACKAGE@" - localedir="@LOCALEDIR@" - _name="CalDAV Support" - system_plugin="true"> - <author name="Christian Kellner" email="gicmo@gnome.org"/> - <_description>Add CalDAV support to Evolution.</_description> - - <hook class="org.gnome.evolution.calendar.config:1.0"> - <group target="source" - id="org.gnome.evolution.calendar.calendarProperties"> - - <item type="item_table" - path="00.general/00.source/99.caldav" - factory="oge_caldav"/> - - </group> - </hook> - </e-plugin> - -</e-plugin-list> |