diff options
Diffstat (limited to 'modules/cal-config-google/e-google-chooser.c')
-rw-r--r-- | modules/cal-config-google/e-google-chooser.c | 623 |
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; +} |