aboutsummaryrefslogblamecommitdiffstats
path: root/modules/cal-config-google/e-google-chooser.c
blob: ed3365ce441dadd6793422dd065a9710a7e1f90a (plain) (tree)
























                                                                               
                          

































































































                                                                          



























                                                                      




































































































































































                                                                             








                                                                           
                                                 




























































































































































































                                                                              
                                                                  


                                                                   
                                        








































































































                                                                          


                                                          
                                                              














                                                                         
/*
 * 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 <e-util/e-util.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 gchar *
google_chooser_decode_user (const gchar *user)
{
    gchar *decoded_user;

    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_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;

    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);
    return google_chooser_decode_user (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' (2nd) 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 (
        "", "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;
}

void
e_google_chooser_construct_default_uri (SoupURI *soup_uri,
                                        const gchar *username)
{
    gchar *decoded_user, *path;

    decoded_user = google_chooser_decode_user (username);
    if (!decoded_user)
        return;

    path = g_strdup_printf (CALDAV_EVENTS_PATH_FORMAT, decoded_user);

    soup_uri_set_user (soup_uri, decoded_user);
    soup_uri_set_path (soup_uri, path);

    g_free (decoded_user);
    g_free (path);
}