1 /*
2 * e-google-chooser-button.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 #include "evolution-config.h"
19
20 #include <glib/gi18n-lib.h>
21
22 #include <libedataserverui/libedataserverui.h>
23
24 #include "module-cal-config-google.h"
25 #include "e-google-chooser-button.h"
26
27 #define E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE(obj) \
28 (G_TYPE_INSTANCE_GET_PRIVATE \
29 ((obj), E_TYPE_GOOGLE_CHOOSER_BUTTON, EGoogleChooserButtonPrivate))
30
31 struct _EGoogleChooserButtonPrivate {
32 ESource *source;
33 ESourceConfig *config;
34 GtkWidget *label;
35 };
36
37 enum {
38 PROP_0,
39 PROP_SOURCE,
40 PROP_CONFIG
41 };
42
G_DEFINE_DYNAMIC_TYPE(EGoogleChooserButton,e_google_chooser_button,GTK_TYPE_BUTTON)43 G_DEFINE_DYNAMIC_TYPE (
44 EGoogleChooserButton,
45 e_google_chooser_button,
46 GTK_TYPE_BUTTON)
47
48 static void
49 google_chooser_button_set_source (EGoogleChooserButton *button,
50 ESource *source)
51 {
52 g_return_if_fail (E_IS_SOURCE (source));
53 g_return_if_fail (button->priv->source == NULL);
54
55 button->priv->source = g_object_ref (source);
56 }
57
58 static void
google_chooser_button_set_config(EGoogleChooserButton * button,ESourceConfig * config)59 google_chooser_button_set_config (EGoogleChooserButton *button,
60 ESourceConfig *config)
61 {
62 g_return_if_fail (E_IS_SOURCE_CONFIG (config));
63 g_return_if_fail (button->priv->config == NULL);
64
65 button->priv->config = g_object_ref (config);
66 }
67
68 static void
google_chooser_button_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)69 google_chooser_button_set_property (GObject *object,
70 guint property_id,
71 const GValue *value,
72 GParamSpec *pspec)
73 {
74 switch (property_id) {
75 case PROP_SOURCE:
76 google_chooser_button_set_source (
77 E_GOOGLE_CHOOSER_BUTTON (object),
78 g_value_get_object (value));
79 return;
80
81 case PROP_CONFIG:
82 google_chooser_button_set_config (
83 E_GOOGLE_CHOOSER_BUTTON (object),
84 g_value_get_object (value));
85 return;
86 }
87
88 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
89 }
90
91 static void
google_chooser_button_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)92 google_chooser_button_get_property (GObject *object,
93 guint property_id,
94 GValue *value,
95 GParamSpec *pspec)
96 {
97 switch (property_id) {
98 case PROP_SOURCE:
99 g_value_set_object (
100 value,
101 e_google_chooser_button_get_source (
102 E_GOOGLE_CHOOSER_BUTTON (object)));
103 return;
104
105 case PROP_CONFIG:
106 g_value_set_object (
107 value,
108 e_google_chooser_button_get_config (
109 E_GOOGLE_CHOOSER_BUTTON (object)));
110 return;
111 }
112
113 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
114 }
115
116 static void
google_chooser_button_dispose(GObject * object)117 google_chooser_button_dispose (GObject *object)
118 {
119 EGoogleChooserButtonPrivate *priv;
120
121 priv = E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE (object);
122
123 g_clear_object (&priv->source);
124 g_clear_object (&priv->config);
125 g_clear_object (&priv->label);
126
127 /* Chain up to parent's dispose() method. */
128 G_OBJECT_CLASS (e_google_chooser_button_parent_class)->dispose (object);
129 }
130
131 static void
google_chooser_button_constructed(GObject * object)132 google_chooser_button_constructed (GObject *object)
133 {
134 EGoogleChooserButton *button;
135 ESourceWebdav *webdav_extension;
136 GBindingFlags binding_flags;
137 GtkWidget *widget;
138 const gchar *display_name;
139
140 button = E_GOOGLE_CHOOSER_BUTTON (object);
141
142 /* Chain up to parent's constructed() method. */
143 G_OBJECT_CLASS (e_google_chooser_button_parent_class)->constructed (object);
144
145 widget = gtk_label_new (_("Default User Calendar"));
146 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
147 gtk_container_add (GTK_CONTAINER (button), widget);
148 button->priv->label = g_object_ref (widget);
149 gtk_widget_show (widget);
150
151 webdav_extension = e_source_get_extension (
152 button->priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
153 display_name = e_source_webdav_get_display_name (webdav_extension);
154
155 binding_flags = G_BINDING_DEFAULT;
156 if (display_name != NULL && *display_name != '\0')
157 binding_flags |= G_BINDING_SYNC_CREATE;
158
159 e_binding_bind_property (
160 webdav_extension, "display-name",
161 button->priv->label, "label",
162 binding_flags);
163 }
164
165 static GtkWindow *
google_config_get_dialog_parent_cb(ECredentialsPrompter * prompter,GtkWindow * dialog)166 google_config_get_dialog_parent_cb (ECredentialsPrompter *prompter,
167 GtkWindow *dialog)
168 {
169 return dialog;
170 }
171
172 static void
google_chooser_button_clicked(GtkButton * button)173 google_chooser_button_clicked (GtkButton *button)
174 {
175 EGoogleChooserButtonPrivate *priv;
176 gpointer parent;
177 ESourceRegistry *registry;
178 ECredentialsPrompter *prompter;
179 ESourceWebdav *webdav_extension;
180 ESourceAuthentication *authentication_extension;
181 SoupURI *uri;
182 gchar *base_url;
183 GtkDialog *dialog;
184 gulong handler_id;
185 guint supports_filter = 0;
186 gboolean can_google_auth;
187 const gchar *title = NULL;
188
189 priv = E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE (button);
190
191 parent = gtk_widget_get_toplevel (GTK_WIDGET (button));
192 parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
193
194 registry = e_source_config_get_registry (priv->config);
195
196 authentication_extension = e_source_get_extension (priv->source, E_SOURCE_EXTENSION_AUTHENTICATION);
197 webdav_extension = e_source_get_extension (priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
198
199 uri = e_source_webdav_dup_soup_uri (webdav_extension);
200 can_google_auth = e_module_cal_config_google_is_supported (NULL, registry) &&
201 g_strcmp0 (e_source_authentication_get_method (authentication_extension), "OAuth2") != 0;
202
203 e_google_chooser_button_construct_default_uri (uri, e_source_authentication_get_user (authentication_extension));
204
205 if (can_google_auth) {
206 /* Prefer 'Google', aka internal OAuth2, authentication method, if available */
207 e_source_authentication_set_method (authentication_extension, "Google");
208
209 /* See https://developers.google.com/google-apps/calendar/caldav/v2/guide */
210 soup_uri_set_host (uri, "apidata.googleusercontent.com");
211 soup_uri_set_path (uri, "/caldav/v2/");
212 } else {
213 soup_uri_set_host (uri, "www.google.com");
214 /* To find also calendar email, not only calendars */
215 soup_uri_set_path (uri, "/calendar/dav/");
216 }
217
218 /* Google's CalDAV interface requires a secure connection. */
219 soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
220
221 e_source_webdav_set_soup_uri (webdav_extension, uri);
222
223 switch (e_cal_source_config_get_source_type (E_CAL_SOURCE_CONFIG (priv->config))) {
224 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
225 supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_EVENTS;
226 title = _("Choose a Calendar");
227 break;
228 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
229 supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_MEMOS;
230 title = _("Choose a Memo List");
231 break;
232 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
233 supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_TASKS;
234 title = _("Choose a Task List");
235 break;
236 default:
237 g_return_if_reached ();
238 }
239
240 prompter = e_credentials_prompter_new (registry);
241 e_credentials_prompter_set_auto_prompt (prompter, FALSE);
242
243 base_url = soup_uri_to_string (uri, FALSE);
244
245 dialog = e_webdav_discover_dialog_new (parent, title, prompter, priv->source, base_url, supports_filter);
246
247 if (parent != NULL)
248 e_binding_bind_property (
249 parent, "icon-name",
250 dialog, "icon-name",
251 G_BINDING_SYNC_CREATE);
252
253 handler_id = g_signal_connect (prompter, "get-dialog-parent",
254 G_CALLBACK (google_config_get_dialog_parent_cb), dialog);
255
256 e_webdav_discover_dialog_refresh (dialog);
257
258 if (gtk_dialog_run (dialog) == GTK_RESPONSE_ACCEPT) {
259 gchar *href = NULL, *display_name = NULL, *color = NULL, *email;
260 guint supports = 0, order = 0;
261 GtkWidget *content;
262
263 content = e_webdav_discover_dialog_get_content (dialog);
264
265 if (e_webdav_discover_content_get_selected (content, 0, &href, &supports, &display_name, &color, &order)) {
266 soup_uri_free (uri);
267 uri = soup_uri_new (href);
268
269 if (uri) {
270 ESourceSelectable *selectable_extension;
271 const gchar *extension_name;
272
273 switch (e_cal_source_config_get_source_type (E_CAL_SOURCE_CONFIG (priv->config))) {
274 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
275 extension_name = E_SOURCE_EXTENSION_CALENDAR;
276 break;
277 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
278 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
279 break;
280 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
281 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
282 break;
283 default:
284 g_return_if_reached ();
285 }
286
287 selectable_extension = e_source_get_extension (priv->source, extension_name);
288
289 e_source_set_display_name (priv->source, display_name);
290
291 e_source_webdav_set_display_name (webdav_extension, display_name);
292 e_source_webdav_set_soup_uri (webdav_extension, uri);
293 e_source_webdav_set_order (webdav_extension, order);
294
295 if (color && *color)
296 e_source_selectable_set_color (selectable_extension, color);
297
298 e_source_selectable_set_order (selectable_extension, order);
299 }
300
301 g_free (href);
302 g_free (display_name);
303 g_free (color);
304
305 href = NULL;
306 display_name = NULL;
307 color = NULL;
308 }
309
310 email = e_webdav_discover_content_get_user_address (content);
311 if (email && *email)
312 e_source_webdav_set_email_address (webdav_extension, email);
313 g_free (email);
314 }
315
316 g_signal_handler_disconnect (prompter, handler_id);
317
318 gtk_widget_destroy (GTK_WIDGET (dialog));
319
320 g_object_unref (prompter);
321 if (uri)
322 soup_uri_free (uri);
323 g_free (base_url);
324 }
325
326 static void
e_google_chooser_button_class_init(EGoogleChooserButtonClass * class)327 e_google_chooser_button_class_init (EGoogleChooserButtonClass *class)
328 {
329 GObjectClass *object_class;
330 GtkButtonClass *button_class;
331
332 g_type_class_add_private (class, sizeof (EGoogleChooserButtonPrivate));
333
334 object_class = G_OBJECT_CLASS (class);
335 object_class->set_property = google_chooser_button_set_property;
336 object_class->get_property = google_chooser_button_get_property;
337 object_class->dispose = google_chooser_button_dispose;
338 object_class->constructed = google_chooser_button_constructed;
339
340 button_class = GTK_BUTTON_CLASS (class);
341 button_class->clicked = google_chooser_button_clicked;
342
343 g_object_class_install_property (
344 object_class,
345 PROP_SOURCE,
346 g_param_spec_object (
347 "source",
348 NULL,
349 NULL,
350 E_TYPE_SOURCE,
351 G_PARAM_READWRITE |
352 G_PARAM_CONSTRUCT_ONLY));
353
354 g_object_class_install_property (
355 object_class,
356 PROP_CONFIG,
357 g_param_spec_object (
358 "config",
359 NULL,
360 NULL,
361 E_TYPE_SOURCE_CONFIG,
362 G_PARAM_READWRITE |
363 G_PARAM_CONSTRUCT_ONLY));
364 }
365
366 static void
e_google_chooser_button_class_finalize(EGoogleChooserButtonClass * class)367 e_google_chooser_button_class_finalize (EGoogleChooserButtonClass *class)
368 {
369 }
370
371 static void
e_google_chooser_button_init(EGoogleChooserButton * button)372 e_google_chooser_button_init (EGoogleChooserButton *button)
373 {
374 button->priv = E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE (button);
375 }
376
377 void
e_google_chooser_button_type_register(GTypeModule * type_module)378 e_google_chooser_button_type_register (GTypeModule *type_module)
379 {
380 /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
381 * function, so we have to wrap it with a public function in
382 * order to register types from a separate compilation unit. */
383 e_google_chooser_button_register_type (type_module);
384 }
385
386 GtkWidget *
e_google_chooser_button_new(ESource * source,ESourceConfig * config)387 e_google_chooser_button_new (ESource *source,
388 ESourceConfig *config)
389 {
390 g_return_val_if_fail (E_IS_SOURCE (source), NULL);
391
392 return g_object_new (
393 E_TYPE_GOOGLE_CHOOSER_BUTTON,
394 "source", source,
395 "config", config, NULL);
396 }
397
398 ESource *
e_google_chooser_button_get_source(EGoogleChooserButton * button)399 e_google_chooser_button_get_source (EGoogleChooserButton *button)
400 {
401 g_return_val_if_fail (E_IS_GOOGLE_CHOOSER_BUTTON (button), NULL);
402
403 return button->priv->source;
404 }
405
406 ESourceConfig *
e_google_chooser_button_get_config(EGoogleChooserButton * button)407 e_google_chooser_button_get_config (EGoogleChooserButton *button)
408 {
409 g_return_val_if_fail (E_IS_GOOGLE_CHOOSER_BUTTON (button), NULL);
410
411 return button->priv->config;
412 }
413
414 static gchar *
google_chooser_decode_user(const gchar * user)415 google_chooser_decode_user (const gchar *user)
416 {
417 gchar *decoded_user;
418
419 if (user == NULL || *user == '\0')
420 return NULL;
421
422 /* Decode any encoded 'at' symbols ('%40' -> '@'). */
423 if (strstr (user, "%40") != NULL) {
424 gchar **segments;
425
426 segments = g_strsplit (user, "%40", 0);
427 decoded_user = g_strjoinv ("@", segments);
428 g_strfreev (segments);
429
430 /* If no domain is given, append "@gmail.com". */
431 } else if (strstr (user, "@") == NULL) {
432 decoded_user = g_strconcat (user, "@gmail.com", NULL);
433
434 /* Otherwise the user name should be fine as is. */
435 } else {
436 decoded_user = g_strdup (user);
437 }
438
439 return decoded_user;
440 }
441
442 void
e_google_chooser_button_construct_default_uri(SoupURI * soup_uri,const gchar * username)443 e_google_chooser_button_construct_default_uri (SoupURI *soup_uri,
444 const gchar *username)
445 {
446 gchar *decoded_user, *path;
447
448 decoded_user = google_chooser_decode_user (username);
449 if (!decoded_user)
450 return;
451
452 if (g_strcmp0 (soup_uri_get_host (soup_uri), "apidata.googleusercontent.com") == 0)
453 path = g_strdup_printf ("/caldav/v2/%s/events", decoded_user);
454 else
455 path = g_strdup_printf ("/calendar/dav/%s/events", decoded_user);
456
457 soup_uri_set_user (soup_uri, decoded_user);
458 soup_uri_set_path (soup_uri, path);
459
460 g_free (decoded_user);
461 g_free (path);
462 }
463