diff --git a/po/POTFILES.in b/po/POTFILES.in index 9ffb348f29..546f5876ef 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -406,6 +406,7 @@ src/modules/backup-restore/org-gnome-backup-restore.error.xml src/modules/bogofilter/evolution-bogofilter.c src/modules/bogofilter/org.gnome.Evolution-bogofilter.metainfo.xml.in src/modules/book-config-carddav/evolution-book-config-carddav.c +src/modules/book-config-google/e-google-book-chooser-button.c src/modules/book-config-google/evolution-book-config-google.c src/modules/book-config-ldap/evolution-book-config-ldap.c src/modules/cal-config-caldav/evolution-cal-config-caldav.c diff --git a/src/e-util/e-source-config.c b/src/e-util/e-source-config.c index 52d5af2c74..cecf258188 100644 --- a/src/e-util/e-source-config.c +++ b/src/e-util/e-source-config.c @@ -505,9 +505,10 @@ source_config_init_for_editing_source (ESourceConfig *config) backend_name = e_source_backend_get_backend_name (extension); g_return_if_fail (backend_name != NULL); - /* Special-case Google calendars to use 'google' editor, instead - of the 'caldav' editor, even they use 'caldav' calendar backend. */ - if (g_ascii_strcasecmp (backend_name, "caldav") == 0 && + /* Special-case Google books and calendars to use 'google' editor, instead + of the 'carddav'/'caldav' editor, even they use 'carddav'/'caldav' backend. */ + if ((g_ascii_strcasecmp (backend_name, "caldav") == 0 || + g_ascii_strcasecmp (backend_name, "carddav") == 0) && g_strcmp0 (e_source_get_parent (original_source), "google-stub") == 0) backend_name = "google"; diff --git a/src/modules/book-config-google/CMakeLists.txt b/src/modules/book-config-google/CMakeLists.txt index d455caf038..d7b878668b 100644 --- a/src/modules/book-config-google/CMakeLists.txt +++ b/src/modules/book-config-google/CMakeLists.txt @@ -1,5 +1,7 @@ set(extra_deps) set(sources + e-google-book-chooser-button.c + e-google-book-chooser-button.h evolution-book-config-google.c ) set(extra_defines) diff --git a/src/modules/book-config-google/e-google-book-chooser-button.c b/src/modules/book-config-google/e-google-book-chooser-button.c new file mode 100644 index 0000000000..796353a1b3 --- /dev/null +++ b/src/modules/book-config-google/e-google-book-chooser-button.c @@ -0,0 +1,402 @@ +/* + * 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. + * + * 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 General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + */ + +#include "evolution-config.h" + +#include + +#include + +#include "e-google-book-chooser-button.h" + +struct _EGoogleBookChooserButtonPrivate { + ESource *source; + ESourceConfig *config; + GtkWidget *label; +}; + +enum { + PROP_0, + PROP_SOURCE, + PROP_CONFIG +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (EGoogleBookChooserButton, e_google_book_chooser_button, GTK_TYPE_BUTTON, 0, + G_ADD_PRIVATE_DYNAMIC (EGoogleBookChooserButton)) + +static void +google_book_chooser_button_set_source (EGoogleBookChooserButton *button, + ESource *source) +{ + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (button->priv->source == NULL); + + button->priv->source = g_object_ref (source); +} + +static void +google_book_chooser_button_set_config (EGoogleBookChooserButton *button, + ESourceConfig *config) +{ + g_return_if_fail (E_IS_SOURCE_CONFIG (config)); + g_return_if_fail (button->priv->config == NULL); + + button->priv->config = g_object_ref (config); +} + +static void +google_book_chooser_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_SOURCE: + google_book_chooser_button_set_source ( + E_GOOGLE_BOOK_CHOOSER_BUTTON (object), + g_value_get_object (value)); + return; + + case PROP_CONFIG: + google_book_chooser_button_set_config ( + E_GOOGLE_BOOK_CHOOSER_BUTTON (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +google_book_chooser_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_SOURCE: + g_value_set_object ( + value, + e_google_book_chooser_button_get_source ( + E_GOOGLE_BOOK_CHOOSER_BUTTON (object))); + return; + + case PROP_CONFIG: + g_value_set_object ( + value, + e_google_book_chooser_button_get_config ( + E_GOOGLE_BOOK_CHOOSER_BUTTON (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +google_book_chooser_button_dispose (GObject *object) +{ + EGoogleBookChooserButton *button = E_GOOGLE_BOOK_CHOOSER_BUTTON (object); + + g_clear_object (&button->priv->source); + g_clear_object (&button->priv->config); + g_clear_object (&button->priv->label); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_google_book_chooser_button_parent_class)->dispose (object); +} + +static void +google_book_chooser_button_constructed (GObject *object) +{ + EGoogleBookChooserButton *button; + ESourceWebdav *webdav_extension; + GBindingFlags binding_flags; + GtkWidget *widget; + const gchar *display_name; + + button = E_GOOGLE_BOOK_CHOOSER_BUTTON (object); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_google_book_chooser_button_parent_class)->constructed (object); + + widget = gtk_label_new (_("Default User Address Book")); + gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5); + gtk_container_add (GTK_CONTAINER (button), widget); + button->priv->label = g_object_ref (widget); + gtk_widget_show (widget); + + webdav_extension = e_source_get_extension ( + button->priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); + display_name = e_source_webdav_get_display_name (webdav_extension); + + binding_flags = G_BINDING_DEFAULT; + if (display_name != NULL && *display_name != '\0') + binding_flags |= G_BINDING_SYNC_CREATE; + + e_binding_bind_property ( + webdav_extension, "display-name", + button->priv->label, "label", + binding_flags); +} + +static GtkWindow * +google_config_get_dialog_parent_cb (ECredentialsPrompter *prompter, + GtkWindow *dialog) +{ + return dialog; +} + +static void +google_book_chooser_button_clicked (GtkButton *btn) +{ + EGoogleBookChooserButton *button; + gpointer parent; + ESourceRegistry *registry; + ECredentialsPrompter *prompter; + ESourceWebdav *webdav_extension; + ESourceAuthentication *authentication_extension; + SoupURI *uri; + gchar *base_url; + GtkDialog *dialog; + gulong handler_id; + guint supports_filter = 0; + const gchar *title = NULL; + + button = E_GOOGLE_BOOK_CHOOSER_BUTTON (btn); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (button)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + registry = e_source_config_get_registry (button->priv->config); + + authentication_extension = e_source_get_extension (button->priv->source, E_SOURCE_EXTENSION_AUTHENTICATION); + webdav_extension = e_source_get_extension (button->priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); + + uri = e_source_webdav_dup_soup_uri (webdav_extension); + + e_google_book_chooser_button_construct_default_uri (uri, e_source_authentication_get_user (authentication_extension)); + + /* Prefer 'Google', aka internal OAuth2, authentication method, if available */ + e_source_authentication_set_method (authentication_extension, "Google"); + + /* See https://developers.google.com/people/carddav */ + soup_uri_set_host (uri, "www.googleapis.com"); + soup_uri_set_path (uri, "/.well-known/carddav"); + + /* Google's CardDAV interface requires a secure connection. */ + soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS); + + e_source_webdav_set_soup_uri (webdav_extension, uri); + + prompter = e_credentials_prompter_new (registry); + e_credentials_prompter_set_auto_prompt (prompter, FALSE); + + supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_CONTACTS; + title = _("Choose an Address Book"); + base_url = soup_uri_to_string (uri, FALSE); + + dialog = e_webdav_discover_dialog_new (parent, title, prompter, button->priv->source, base_url, supports_filter); + + if (parent != NULL) + e_binding_bind_property ( + parent, "icon-name", + dialog, "icon-name", + G_BINDING_SYNC_CREATE); + + handler_id = g_signal_connect (prompter, "get-dialog-parent", + G_CALLBACK (google_config_get_dialog_parent_cb), dialog); + + e_webdav_discover_dialog_refresh (dialog); + + if (gtk_dialog_run (dialog) == GTK_RESPONSE_ACCEPT) { + gchar *href = NULL, *display_name = NULL, *color = NULL, *email; + guint supports = 0, order = 0; + GtkWidget *content; + + content = e_webdav_discover_dialog_get_content (dialog); + + if (e_webdav_discover_content_get_selected (content, 0, &href, &supports, &display_name, &color, &order)) { + soup_uri_free (uri); + uri = soup_uri_new (href); + + if (uri) { + ESourceAddressBook *addressbook_extension; + + addressbook_extension = e_source_get_extension (button->priv->source, E_SOURCE_EXTENSION_ADDRESS_BOOK); + + e_source_set_display_name (button->priv->source, display_name); + + e_source_webdav_set_display_name (webdav_extension, display_name); + e_source_webdav_set_soup_uri (webdav_extension, uri); + e_source_webdav_set_order (webdav_extension, order); + + e_source_address_book_set_order (addressbook_extension, order); + } + + g_clear_pointer (&href, g_free); + g_clear_pointer (&display_name, g_free); + g_clear_pointer (&color, g_free); + } + + email = e_webdav_discover_content_get_user_address (content); + if (email && *email) + e_source_webdav_set_email_address (webdav_extension, email); + g_free (email); + } + + g_signal_handler_disconnect (prompter, handler_id); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + g_object_unref (prompter); + if (uri) + soup_uri_free (uri); + g_free (base_url); +} + +static void +e_google_book_chooser_button_class_init (EGoogleBookChooserButtonClass *class) +{ + GObjectClass *object_class; + GtkButtonClass *button_class; + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = google_book_chooser_button_set_property; + object_class->get_property = google_book_chooser_button_get_property; + object_class->dispose = google_book_chooser_button_dispose; + object_class->constructed = google_book_chooser_button_constructed; + + button_class = GTK_BUTTON_CLASS (class); + button_class->clicked = google_book_chooser_button_clicked; + + g_object_class_install_property ( + object_class, + PROP_SOURCE, + g_param_spec_object ( + "source", + NULL, + NULL, + E_TYPE_SOURCE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property ( + object_class, + PROP_CONFIG, + g_param_spec_object ( + "config", + NULL, + NULL, + E_TYPE_SOURCE_CONFIG, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +e_google_book_chooser_button_class_finalize (EGoogleBookChooserButtonClass *class) +{ +} + +static void +e_google_book_chooser_button_init (EGoogleBookChooserButton *button) +{ + button->priv = e_google_book_chooser_button_get_instance_private (button); +} + +void +e_google_book_chooser_button_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_book_chooser_button_register_type (type_module); +} + +GtkWidget * +e_google_book_chooser_button_new (ESource *source, + ESourceConfig *config) +{ + g_return_val_if_fail (E_IS_SOURCE (source), NULL); + + return g_object_new ( + E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON, + "source", source, + "config", config, NULL); +} + +ESource * +e_google_book_chooser_button_get_source (EGoogleBookChooserButton *button) +{ + g_return_val_if_fail (E_IS_GOOGLE_BOOK_CHOOSER_BUTTON (button), NULL); + + return button->priv->source; +} + +ESourceConfig * +e_google_book_chooser_button_get_config (EGoogleBookChooserButton *button) +{ + g_return_val_if_fail (E_IS_GOOGLE_BOOK_CHOOSER_BUTTON (button), NULL); + + return button->priv->config; +} + +static gchar * +google_book_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; +} + +void +e_google_book_chooser_button_construct_default_uri (SoupURI *soup_uri, + const gchar *username) +{ + gchar *decoded_user, *path; + + decoded_user = google_book_chooser_decode_user (username); + if (!decoded_user) + return; + + path = g_strdup_printf ("/carddav/v1/principals/%s/lists/default/", decoded_user); + + soup_uri_set_scheme (soup_uri, SOUP_URI_SCHEME_HTTPS); + soup_uri_set_user (soup_uri, decoded_user); + soup_uri_set_host (soup_uri, "www.googleapis.com"); + soup_uri_set_path (soup_uri, path); + + g_free (decoded_user); + g_free (path); +} diff --git a/src/modules/book-config-google/e-google-book-chooser-button.h b/src/modules/book-config-google/e-google-book-chooser-button.h new file mode 100644 index 0000000000..2483d637b2 --- /dev/null +++ b/src/modules/book-config-google/e-google-book-chooser-button.h @@ -0,0 +1,70 @@ +/* + * 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. + * + * 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 General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + */ + +#ifndef E_GOOGLE_BOOK_CHOOSER_BUTTON_H +#define E_GOOGLE_BOOK_CHOOSER_BUTTON_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON \ + (e_google_book_chooser_button_get_type ()) +#define E_GOOGLE_BOOK_CHOOSER_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON, EGoogleBookChooserButton)) +#define E_GOOGLE_BOOK_CHOOSER_BUTTON_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON, EGoogleBookChooserButtonClass)) +#define E_IS_GOOGLE_BOOK_CHOOSER_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON)) +#define E_IS_GOOGLE_BOOK_CHOOSER_BUTTON_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON)) +#define E_GOOGLE_BOOK_CHOOSER_BUTTON_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_GOOGLE_BOOK_CHOOSER_BUTTON, EGoogleBookChooserButtonClass)) + +G_BEGIN_DECLS + +typedef struct _EGoogleBookChooserButton EGoogleBookChooserButton; +typedef struct _EGoogleBookChooserButtonClass EGoogleBookChooserButtonClass; +typedef struct _EGoogleBookChooserButtonPrivate EGoogleBookChooserButtonPrivate; + +struct _EGoogleBookChooserButton { + GtkButton parent; + EGoogleBookChooserButtonPrivate *priv; +}; + +struct _EGoogleBookChooserButtonClass { + GtkButtonClass parent_class; +}; + +GType e_google_book_chooser_button_get_type (void); +void e_google_book_chooser_button_type_register + (GTypeModule *type_module); +GtkWidget * e_google_book_chooser_button_new(ESource *source, + ESourceConfig *config); +ESource * e_google_book_chooser_button_get_source + (EGoogleBookChooserButton *button); +ESourceConfig * e_google_book_chooser_button_get_config + (EGoogleBookChooserButton *button); +void e_google_book_chooser_button_construct_default_uri + (SoupURI *soup_uri, + const gchar *username); + +G_END_DECLS + +#endif /* E_GOOGLE_BOOK_CHOOSER_BUTTON_H */ diff --git a/src/modules/book-config-google/evolution-book-config-google.c b/src/modules/book-config-google/evolution-book-config-google.c index 1d00ea40ff..850c502ab3 100644 --- a/src/modules/book-config-google/evolution-book-config-google.c +++ b/src/modules/book-config-google/evolution-book-config-google.c @@ -23,6 +23,8 @@ #include +#include "e-google-book-chooser-button.h" + typedef ESourceConfigBackend EBookConfigGoogle; typedef ESourceConfigBackendClass EBookConfigGoogleClass; @@ -55,6 +57,7 @@ book_config_google_insert_widgets (ESourceConfigBackend *backend, ESource *scratch_source) { ESourceConfig *config; + GtkWidget *widget; Context *context; const gchar *uid; @@ -68,6 +71,10 @@ book_config_google_insert_widgets (ESourceConfigBackend *backend, context->user_entry = e_source_config_add_user_entry (config, scratch_source); + widget = e_google_book_chooser_button_new (scratch_source, config); + e_source_config_insert_widget (config, scratch_source, _("Address Book:"), widget); + gtk_widget_show (widget); + e_source_config_add_refresh_interval (config, scratch_source); } @@ -101,23 +108,32 @@ book_config_google_commit_changes (ESourceConfigBackend *backend, { ESource *collection_source; ESourceConfig *config; + ESourceBackend *addressbook_extension; + ESourceWebdav *webdav_extension; ESourceAuthentication *extension; + SoupURI *soup_uri; const gchar *extension_name; const gchar *user; config = e_source_config_backend_get_config (backend); collection_source = e_source_config_get_collection_source (config); + addressbook_extension = e_source_get_extension (scratch_source, E_SOURCE_EXTENSION_ADDRESS_BOOK); + webdav_extension = e_source_get_extension (scratch_source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); + extension_name = E_SOURCE_EXTENSION_AUTHENTICATION; extension = e_source_get_extension (scratch_source, extension_name); - if (!collection_source || ( - !e_source_has_extension (collection_source, E_SOURCE_EXTENSION_GOA) && - !e_source_has_extension (collection_source, E_SOURCE_EXTENSION_UOA))) { - e_source_authentication_set_host (extension, "www.google.com"); + if (!collection_source || + !e_source_authentication_get_is_external (extension)) { + e_source_authentication_set_host (extension, "www.googleapis.com"); e_source_authentication_set_method (extension, "Google"); } + /* The backend name is "carddav" even though the ESource is + * a child of the built-in "Google" source. */ + e_source_backend_set_backend_name (addressbook_extension, "carddav"); + user = e_source_authentication_get_user (extension); g_return_if_fail (user != NULL); @@ -129,6 +145,19 @@ book_config_google_commit_changes (ESourceConfigBackend *backend, e_source_authentication_set_user (extension, full_user); g_free (full_user); } + + soup_uri = e_source_webdav_dup_soup_uri (webdav_extension); + + if (!soup_uri->path || !*soup_uri->path || g_strcmp0 (soup_uri->path, "/") == 0) { + e_google_book_chooser_button_construct_default_uri (soup_uri, e_source_authentication_get_user (extension)); + } + + /* Google's CalDAV interface requires a secure connection. */ + soup_uri_set_scheme (soup_uri, SOUP_URI_SCHEME_HTTPS); + + e_source_webdav_set_soup_uri (webdav_extension, soup_uri); + + soup_uri_free (soup_uri); } static void @@ -159,6 +188,7 @@ e_book_config_google_init (ESourceConfigBackend *backend) G_MODULE_EXPORT void e_module_load (GTypeModule *type_module) { + e_google_book_chooser_button_type_register (type_module); e_book_config_google_register_type (type_module); }