aboutsummaryrefslogtreecommitdiffstats
path: root/modules/cal-config-google/e-google-chooser.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/cal-config-google/e-google-chooser.c')
-rw-r--r--modules/cal-config-google/e-google-chooser.c623
1 files changed, 623 insertions, 0 deletions
diff --git a/modules/cal-config-google/e-google-chooser.c b/modules/cal-config-google/e-google-chooser.c
new file mode 100644
index 0000000000..d5a4064b0f
--- /dev/null
+++ b/modules/cal-config-google/e-google-chooser.c
@@ -0,0 +1,623 @@
+/*
+ * e-google-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-google-chooser.h"
+
+#include <config.h>
+#include <string.h>
+#include <gdata/gdata.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-source-authentication.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_GOOGLE_CHOOSER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_GOOGLE_CHOOSER, EGoogleChooserPrivate))
+
+#define CALDAV_EVENTS_PATH_FORMAT "/calendar/dav/%s/events"
+
+typedef struct _Context Context;
+
+struct _EGoogleChooserPrivate {
+ ESource *source;
+};
+
+struct _Context {
+ GCancellable *cancellable;
+ GDataCalendarService *service;
+ GDataClientLoginAuthorizer *authorizer;
+ ESource *source;
+};
+
+enum {
+ PROP_0,
+ PROP_SOURCE
+};
+
+enum {
+ COLUMN_COLOR,
+ COLUMN_PATH,
+ COLUMN_TITLE,
+ COLUMN_WRITABLE,
+ NUM_COLUMNS
+};
+
+G_DEFINE_DYNAMIC_TYPE (
+ EGoogleChooser,
+ e_google_chooser,
+ GTK_TYPE_TREE_VIEW)
+
+static void
+context_free (Context *context)
+{
+ if (context->cancellable != NULL)
+ g_object_unref (context->cancellable);
+
+ if (context->service != NULL)
+ g_object_unref (context->service);
+
+ if (context->authorizer != NULL)
+ g_object_unref (context->authorizer);
+
+ if (context->source != NULL)
+ g_object_unref (context->source);
+
+ g_slice_free (Context, context);
+}
+
+static gchar *
+google_chooser_extract_caldav_events_path (const gchar *uri)
+{
+ SoupURI *soup_uri;
+ gchar *resource_name;
+ gchar *path;
+ gchar *cp;
+
+ soup_uri = soup_uri_new (uri);
+ g_return_val_if_fail (soup_uri != NULL, NULL);
+
+ /* Isolate the resource name in the "feeds" URI. */
+
+ cp = strstr (soup_uri->path, "/feeds/");
+ g_return_val_if_fail (cp != NULL, NULL);
+
+ /* strlen("/feeds/) == 7 */
+ resource_name = g_strdup (cp + 7);
+ cp = strchr (resource_name, '/');
+ if (cp != NULL)
+ *cp = '\0';
+
+ /* Decode any encoded 'at' symbols ('%40' -> '@'). */
+ if (strstr (resource_name, "%40") != NULL) {
+ gchar **segments;
+
+ segments = g_strsplit (resource_name, "%40", 0);
+ g_free (resource_name);
+ resource_name = g_strjoinv ("@", segments);
+ g_strfreev (segments);
+ }
+
+ /* Use the decoded resource name in the CalDAV events path. */
+ path = g_strdup_printf (CALDAV_EVENTS_PATH_FORMAT, resource_name);
+
+ g_free (resource_name);
+
+ soup_uri_free (soup_uri);
+
+ return path;
+}
+
+static void
+google_chooser_set_source (EGoogleChooser *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
+google_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SOURCE:
+ google_chooser_set_source (
+ E_GOOGLE_CHOOSER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+google_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SOURCE:
+ g_value_set_object (
+ value, e_google_chooser_get_source (
+ E_GOOGLE_CHOOSER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+google_chooser_dispose (GObject *object)
+{
+ EGoogleChooserPrivate *priv;
+
+ priv = E_GOOGLE_CHOOSER_GET_PRIVATE (object);
+
+ if (priv->source != NULL) {
+ g_object_unref (priv->source);
+ priv->source = NULL;
+ }
+
+ /* Chain up parent's dispose() method. */
+ G_OBJECT_CLASS (e_google_chooser_parent_class)->dispose (object);
+}
+
+static void
+google_chooser_constructed (GObject *object)
+{
+ GtkTreeView *tree_view;
+ GtkListStore *list_store;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ tree_view = GTK_TREE_VIEW (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_google_chooser_parent_class)->constructed (object);
+
+ list_store = gtk_list_store_new (
+ NUM_COLUMNS,
+ GDK_TYPE_COLOR, /* COLUMN_COLOR */
+ G_TYPE_STRING, /* COLUMN_PATH */
+ G_TYPE_STRING, /* COLUMN_TITLE */
+ G_TYPE_BOOLEAN); /* COLUMN_WRITABLE */
+
+ 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_add_attribute (
+ column, renderer, "color", COLUMN_COLOR);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "text", COLUMN_TITLE);
+}
+
+static void
+e_google_chooser_class_init (EGoogleChooserClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EGoogleChooserPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = google_chooser_set_property;
+ object_class->get_property = google_chooser_get_property;
+ object_class->dispose = google_chooser_dispose;
+ object_class->constructed = google_chooser_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE,
+ g_param_spec_object (
+ "source",
+ "Source",
+ "Google data source",
+ E_TYPE_SOURCE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_google_chooser_class_finalize (EGoogleChooserClass *class)
+{
+}
+
+static void
+e_google_chooser_init (EGoogleChooser *chooser)
+{
+ chooser->priv = E_GOOGLE_CHOOSER_GET_PRIVATE (chooser);
+}
+
+void
+e_google_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_google_chooser_register_type (type_module);
+}
+
+GtkWidget *
+e_google_chooser_new (ESource *source)
+{
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+ return g_object_new (E_TYPE_GOOGLE_CHOOSER, "source", source, NULL);
+}
+
+ESource *
+e_google_chooser_get_source (EGoogleChooser *chooser)
+{
+ g_return_val_if_fail (E_IS_GOOGLE_CHOOSER (chooser), NULL);
+
+ return chooser->priv->source;
+}
+
+gchar *
+e_google_chooser_get_decoded_user (EGoogleChooser *chooser)
+{
+ ESource *source;
+ ESourceAuthentication *authentication_extension;
+ const gchar *user;
+ gchar *decoded_user;
+
+ g_return_val_if_fail (E_IS_GOOGLE_CHOOSER (chooser), NULL);
+
+ source = e_google_chooser_get_source (chooser);
+
+ authentication_extension = e_source_get_extension (
+ source, E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ user = e_source_authentication_get_user (authentication_extension);
+ if (user == NULL || *user == '\0')
+ return NULL;
+
+ /* Decode any encoded 'at' symbols ('%40' -> '@'). */
+ if (strstr (user, "%40") != NULL) {
+ gchar **segments;
+
+ segments = g_strsplit (user, "%40", 0);
+ decoded_user = g_strjoinv ("@", segments);
+ g_strfreev (segments);
+
+ /* If no domain is given, append "@gmail.com". */
+ } else if (strstr (user, "@") == NULL) {
+ decoded_user = g_strconcat (user, "@gmail.com", NULL);
+
+ /* Otherwise the user name should be fine as is. */
+ } else {
+ decoded_user = g_strdup (user);
+ }
+
+ return decoded_user;
+}
+
+static void
+google_chooser_query_cb (GDataService *service,
+ GAsyncResult *result,
+ GSimpleAsyncResult *simple)
+{
+ GObject *object;
+ GDataFeed *feed;
+ GList *list, *link;
+ GtkTreeView *tree_view;
+ GtkListStore *list_store;
+ GtkTreeModel *tree_model;
+ GError *error = NULL;
+
+ feed = gdata_service_query_finish (service, result, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (feed == NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ g_return_if_fail (GDATA_IS_FEED (feed));
+
+ list = gdata_feed_get_entries (feed);
+
+ /* This returns a new reference, for reasons passing understanding. */
+ object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
+
+ tree_view = GTK_TREE_VIEW (object);
+ tree_model = gtk_tree_view_get_model (tree_view);
+ list_store = GTK_LIST_STORE (tree_model);
+
+ gtk_list_store_clear (list_store);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GDataCalendarCalendar *calendar;
+ GDataEntry *entry;
+ GDataLink *alt;
+ GDataColor color;
+ GdkColor gdkcolor;
+ GtkTreeIter iter;
+ const gchar *uri;
+ const gchar *title;
+ const gchar *access;
+ gboolean writable;
+ gchar *path;
+
+ entry = GDATA_ENTRY (link->data);
+ calendar = GDATA_CALENDAR_CALENDAR (entry);
+
+ /* Skip hidden entries. */
+ if (gdata_calendar_calendar_is_hidden (calendar))
+ continue;
+
+ /* Look up the alternate link, skip if there is none. */
+ alt = gdata_entry_look_up_link (entry, GDATA_LINK_ALTERNATE);
+ if (alt == NULL)
+ continue;
+
+ uri = gdata_link_get_uri (alt);
+ title = gdata_entry_get_title (entry);
+ gdata_calendar_calendar_get_color (calendar, &color);
+ access = gdata_calendar_calendar_get_access_level (calendar);
+
+ if (uri == NULL || *uri == '\0')
+ continue;
+
+ if (title == NULL || *title == '\0')
+ continue;
+
+ path = google_chooser_extract_caldav_events_path (uri);
+
+ gdkcolor.pixel = 0;
+ gdkcolor.red = color.red * 256;
+ gdkcolor.green = color.green * 256;
+ gdkcolor.blue = color.blue * 256;
+
+ if (access == NULL)
+ writable = TRUE;
+ else if (g_ascii_strcasecmp (access, "owner") == 0)
+ writable = TRUE;
+ else if (g_ascii_strcasecmp (access, "contributor") == 0)
+ writable = TRUE;
+ else
+ writable = FALSE;
+
+ gtk_list_store_append (list_store, &iter);
+
+ gtk_list_store_set (
+ list_store, &iter,
+ COLUMN_COLOR, &gdkcolor,
+ COLUMN_PATH, path,
+ COLUMN_TITLE, title,
+ COLUMN_WRITABLE, writable,
+ -1);
+
+ g_free (path);
+ }
+
+ g_object_unref (object);
+ g_object_unref (feed);
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+google_chooser_authenticate_cb (GDataClientLoginAuthorizer *authorizer,
+ GAsyncResult *result,
+ GSimpleAsyncResult *simple)
+{
+ Context *context;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ gdata_client_login_authorizer_authenticate_finish (
+ authorizer, result, &error);
+
+ if (error != NULL) {
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ /* We're authenticated, now query for all calendars. */
+
+ gdata_calendar_service_query_all_calendars_async (
+ context->service, NULL, context->cancellable,
+ NULL, NULL, NULL, (GAsyncReadyCallback)
+ google_chooser_query_cb, simple);
+}
+
+void
+e_google_chooser_populate (EGoogleChooser *chooser,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GDataClientLoginAuthorizer *authorizer;
+ GDataCalendarService *service;
+ GSimpleAsyncResult *simple;
+ Context *context;
+ ESource *source;
+ gpointer parent;
+ gchar *password;
+ gchar *prompt;
+ gchar *user;
+
+ g_return_if_fail (E_IS_GOOGLE_CHOOSER (chooser));
+
+ source = e_google_chooser_get_source (chooser);
+
+ authorizer = gdata_client_login_authorizer_new (
+ PACKAGE_NAME, GDATA_TYPE_CALENDAR_SERVICE);
+
+ service = gdata_calendar_service_new (GDATA_AUTHORIZER (authorizer));
+
+ context = g_slice_new0 (Context);
+ context->service = service; /* takes ownership */
+ context->source = g_object_ref (source);
+
+ if (G_IS_CANCELLABLE (cancellable))
+ context->cancellable = g_object_ref (cancellable);
+ else
+ context->cancellable = g_cancellable_new ();
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (chooser), callback,
+ user_data, e_google_chooser_populate);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) context_free);
+
+ /* Prompt for a password. */
+
+ user = e_google_chooser_get_decoded_user (chooser);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ prompt = g_strdup_printf (
+ _("Enter Google password for user '%s'."), user);
+
+ /* XXX The 'key' (3rd) argument doesn't matter since we're
+ * passing E_PASSWORDS_REMEMBER_NEVER, it just needs to
+ * be non-NULL. This API is degenerating rapidly. */
+ password = e_passwords_ask_password (
+ "", NULL, "bogus key", prompt,
+ E_PASSWORDS_REMEMBER_NEVER |
+ E_PASSWORDS_DISABLE_REMEMBER |
+ E_PASSWORDS_SECRET, NULL, parent);
+
+ g_free (prompt);
+
+ if (password == NULL) {
+ g_cancellable_cancel (context->cancellable);
+ g_simple_async_result_set_error (
+ simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ "%s", _("User declined to provide a password"));
+ g_simple_async_result_complete (simple);
+ g_object_unref (authorizer);
+ g_object_unref (simple);
+ g_free (user);
+ return;
+ }
+
+ /* Try authenticating. */
+
+ gdata_client_login_authorizer_authenticate_async (
+ authorizer, user, password,
+ context->cancellable, (GAsyncReadyCallback)
+ google_chooser_authenticate_cb, simple);
+
+ g_free (password);
+ g_free (user);
+
+ g_object_unref (authorizer);
+}
+
+gboolean
+e_google_chooser_populate_finish (EGoogleChooser *chooser,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (chooser),
+ e_google_chooser_populate), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Assume success unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+gboolean
+e_google_chooser_apply_selected (EGoogleChooser *chooser)
+{
+ ESourceSelectable *selectable_extension;
+ ESourceWebdav *webdav_extension;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ ESource *source;
+ GdkColor *color;
+ SoupURI *soup_uri;
+ gchar *color_spec;
+ gchar *title;
+ gchar *path;
+
+ g_return_val_if_fail (E_IS_GOOGLE_CHOOSER (chooser), FALSE);
+
+ source = e_google_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_COLOR, &color,
+ COLUMN_PATH, &path,
+ COLUMN_TITLE, &title,
+ -1);
+
+ selectable_extension = e_source_get_extension (
+ source, E_SOURCE_EXTENSION_CALENDAR);
+
+ webdav_extension = e_source_get_extension (
+ source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+
+ e_source_set_display_name (source, title);
+
+ e_source_webdav_set_display_name (webdav_extension, title);
+
+ /* XXX Might be easier to expose get/set_path functions? */
+ soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
+ soup_uri_set_path (soup_uri, path);
+ e_source_webdav_set_soup_uri (webdav_extension, soup_uri);
+ soup_uri_free (soup_uri);
+
+ 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 (title);
+ g_free (path);
+
+ return TRUE;
+}