evolution-3.6.4/modules/cal-config-google/e-google-chooser.c

No issues found

  1 /*
  2  * e-google-chooser.c
  3  *
  4  * This program is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 of the License, or (at your option) version 3.
  8  *
  9  * This program is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with the program; if not, see <webcal://www.gnu.org/licenses/>
 16  *
 17  */
 18 
 19 #include "e-google-chooser.h"
 20 
 21 #include <config.h>
 22 #include <string.h>
 23 #include <gdata/gdata.h>
 24 #include <glib/gi18n-lib.h>
 25 
 26 #include <libedataserverui/libedataserverui.h>
 27 
 28 #define E_GOOGLE_CHOOSER_GET_PRIVATE(obj) \
 29 	(G_TYPE_INSTANCE_GET_PRIVATE \
 30 	((obj), E_TYPE_GOOGLE_CHOOSER, EGoogleChooserPrivate))
 31 
 32 #define CALDAV_EVENTS_PATH_FORMAT "/calendar/dav/%s/events"
 33 
 34 typedef struct _Context Context;
 35 
 36 struct _EGoogleChooserPrivate {
 37 	ESource *source;
 38 };
 39 
 40 struct _Context {
 41 	GCancellable *cancellable;
 42 	GDataCalendarService *service;
 43 	GDataClientLoginAuthorizer *authorizer;
 44 	ESource *source;
 45 };
 46 
 47 enum {
 48 	PROP_0,
 49 	PROP_SOURCE
 50 };
 51 
 52 enum {
 53 	COLUMN_COLOR,
 54 	COLUMN_PATH,
 55 	COLUMN_TITLE,
 56 	COLUMN_WRITABLE,
 57 	NUM_COLUMNS
 58 };
 59 
 60 G_DEFINE_DYNAMIC_TYPE (
 61 	EGoogleChooser,
 62 	e_google_chooser,
 63 	GTK_TYPE_TREE_VIEW)
 64 
 65 static void
 66 context_free (Context *context)
 67 {
 68 	if (context->cancellable != NULL)
 69 		g_object_unref (context->cancellable);
 70 
 71 	if (context->service != NULL)
 72 		g_object_unref (context->service);
 73 
 74 	if (context->authorizer != NULL)
 75 		g_object_unref (context->authorizer);
 76 
 77 	if (context->source != NULL)
 78 		g_object_unref (context->source);
 79 
 80 	g_slice_free (Context, context);
 81 }
 82 
 83 static gchar *
 84 google_chooser_extract_caldav_events_path (const gchar *uri)
 85 {
 86 	SoupURI *soup_uri;
 87 	gchar *resource_name;
 88 	gchar *path;
 89 	gchar *cp;
 90 
 91 	soup_uri = soup_uri_new (uri);
 92 	g_return_val_if_fail (soup_uri != NULL, NULL);
 93 
 94 	/* Isolate the resource name in the "feeds" URI. */
 95 
 96 	cp = strstr (soup_uri->path, "/feeds/");
 97 	g_return_val_if_fail (cp != NULL, NULL);
 98 
 99 	/* strlen("/feeds/) == 7 */
100 	resource_name = g_strdup (cp + 7);
101 	cp = strchr (resource_name, '/');
102 	if (cp != NULL)
103 		*cp = '\0';
104 
105 	/* Decode any encoded 'at' symbols ('%40' -> '@'). */
106 	if (strstr (resource_name, "%40") != NULL) {
107 		gchar **segments;
108 
109 		segments = g_strsplit (resource_name, "%40", 0);
110 		g_free (resource_name);
111 		resource_name = g_strjoinv ("@", segments);
112 		g_strfreev (segments);
113 	}
114 
115 	/* Use the decoded resource name in the CalDAV events path. */
116 	path = g_strdup_printf (CALDAV_EVENTS_PATH_FORMAT, resource_name);
117 
118 	g_free (resource_name);
119 
120 	soup_uri_free (soup_uri);
121 
122 	return path;
123 }
124 
125 static void
126 google_chooser_set_source (EGoogleChooser *chooser,
127                            ESource *source)
128 {
129 	g_return_if_fail (E_IS_SOURCE (source));
130 	g_return_if_fail (chooser->priv->source == NULL);
131 
132 	chooser->priv->source = g_object_ref (source);
133 }
134 
135 static void
136 google_chooser_set_property (GObject *object,
137                              guint property_id,
138                              const GValue *value,
139                              GParamSpec *pspec)
140 {
141 	switch (property_id) {
142 		case PROP_SOURCE:
143 			google_chooser_set_source (
144 				E_GOOGLE_CHOOSER (object),
145 				g_value_get_object (value));
146 			return;
147 	}
148 
149 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
150 }
151 
152 static void
153 google_chooser_get_property (GObject *object,
154                              guint property_id,
155                              GValue *value,
156                              GParamSpec *pspec)
157 {
158 	switch (property_id) {
159 		case PROP_SOURCE:
160 			g_value_set_object (
161 				value, e_google_chooser_get_source (
162 				E_GOOGLE_CHOOSER (object)));
163 			return;
164 	}
165 
166 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
167 }
168 
169 static void
170 google_chooser_dispose (GObject *object)
171 {
172 	EGoogleChooserPrivate *priv;
173 
174 	priv = E_GOOGLE_CHOOSER_GET_PRIVATE (object);
175 
176 	if (priv->source != NULL) {
177 		g_object_unref (priv->source);
178 		priv->source = NULL;
179 	}
180 
181 	/* Chain up parent's dispose() method. */
182 	G_OBJECT_CLASS (e_google_chooser_parent_class)->dispose (object);
183 }
184 
185 static void
186 google_chooser_constructed (GObject *object)
187 {
188 	GtkTreeView *tree_view;
189 	GtkListStore *list_store;
190 	GtkCellRenderer *renderer;
191 	GtkTreeViewColumn *column;
192 
193 	tree_view = GTK_TREE_VIEW (object);
194 
195 	/* Chain up to parent's constructed() method. */
196 	G_OBJECT_CLASS (e_google_chooser_parent_class)->constructed (object);
197 
198 	list_store = gtk_list_store_new (
199 		NUM_COLUMNS,
200 		GDK_TYPE_COLOR,		/* COLUMN_COLOR */
201 		G_TYPE_STRING,		/* COLUMN_PATH */
202 		G_TYPE_STRING,		/* COLUMN_TITLE */
203 		G_TYPE_BOOLEAN);	/* COLUMN_WRITABLE */
204 
205 	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
206 
207 	column = gtk_tree_view_column_new ();
208 	gtk_tree_view_column_set_expand (column, TRUE);
209 	gtk_tree_view_column_set_title (column, _("Name"));
210 	gtk_tree_view_insert_column (tree_view, column, -1);
211 
212 	renderer = e_cell_renderer_color_new ();
213 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
214 	gtk_tree_view_column_add_attribute (
215 		column, renderer, "color", COLUMN_COLOR);
216 
217 	renderer = gtk_cell_renderer_text_new ();
218 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
219 	gtk_tree_view_column_add_attribute (
220 		column, renderer, "text", COLUMN_TITLE);
221 }
222 
223 static void
224 e_google_chooser_class_init (EGoogleChooserClass *class)
225 {
226 	GObjectClass *object_class;
227 
228 	g_type_class_add_private (class, sizeof (EGoogleChooserPrivate));
229 
230 	object_class = G_OBJECT_CLASS (class);
231 	object_class->set_property = google_chooser_set_property;
232 	object_class->get_property = google_chooser_get_property;
233 	object_class->dispose = google_chooser_dispose;
234 	object_class->constructed = google_chooser_constructed;
235 
236 	g_object_class_install_property (
237 		object_class,
238 		PROP_SOURCE,
239 		g_param_spec_object (
240 			"source",
241 			"Source",
242 			"Google data source",
243 			E_TYPE_SOURCE,
244 			G_PARAM_READWRITE |
245 			G_PARAM_CONSTRUCT_ONLY));
246 }
247 
248 static void
249 e_google_chooser_class_finalize (EGoogleChooserClass *class)
250 {
251 }
252 
253 static void
254 e_google_chooser_init (EGoogleChooser *chooser)
255 {
256 	chooser->priv = E_GOOGLE_CHOOSER_GET_PRIVATE (chooser);
257 }
258 
259 void
260 e_google_chooser_type_register (GTypeModule *type_module)
261 {
262 	/* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
263 	 *     function, so we have to wrap it with a public function in
264 	 *     order to register types from a separate compilation unit. */
265 	e_google_chooser_register_type (type_module);
266 }
267 
268 GtkWidget *
269 e_google_chooser_new (ESource *source)
270 {
271 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
272 
273 	return g_object_new (E_TYPE_GOOGLE_CHOOSER, "source", source, NULL);
274 }
275 
276 ESource *
277 e_google_chooser_get_source (EGoogleChooser *chooser)
278 {
279 	g_return_val_if_fail (E_IS_GOOGLE_CHOOSER (chooser), NULL);
280 
281 	return chooser->priv->source;
282 }
283 
284 gchar *
285 e_google_chooser_get_decoded_user (EGoogleChooser *chooser)
286 {
287 	ESource *source;
288 	ESourceAuthentication *authentication_extension;
289 	const gchar *user;
290 	gchar *decoded_user;
291 
292 	g_return_val_if_fail (E_IS_GOOGLE_CHOOSER (chooser), NULL);
293 
294 	source = e_google_chooser_get_source (chooser);
295 
296 	authentication_extension = e_source_get_extension (
297 		source, E_SOURCE_EXTENSION_AUTHENTICATION);
298 
299 	user = e_source_authentication_get_user (authentication_extension);
300 	if (user == NULL || *user == '\0')
301 		return NULL;
302 
303 	/* Decode any encoded 'at' symbols ('%40' -> '@'). */
304 	if (strstr (user, "%40") != NULL) {
305 		gchar **segments;
306 
307 		segments = g_strsplit (user, "%40", 0);
308 		decoded_user = g_strjoinv ("@", segments);
309 		g_strfreev (segments);
310 
311 	/* If no domain is given, append "@gmail.com". */
312 	} else if (strstr (user, "@") == NULL) {
313 		decoded_user = g_strconcat (user, "@gmail.com", NULL);
314 
315 	/* Otherwise the user name should be fine as is. */
316 	} else {
317 		decoded_user = g_strdup (user);
318 	}
319 
320 	return decoded_user;
321 }
322 
323 static void
324 google_chooser_query_cb (GDataService *service,
325                          GAsyncResult *result,
326                          GSimpleAsyncResult *simple)
327 {
328 	GObject *object;
329 	GDataFeed *feed;
330 	GList *list, *link;
331 	GtkTreeView *tree_view;
332 	GtkListStore *list_store;
333 	GtkTreeModel *tree_model;
334 	GError *error = NULL;
335 
336 	feed = gdata_service_query_finish (service, result, &error);
337 
338 	if (error != NULL) {
339 		g_warn_if_fail (feed == NULL);
340 		g_simple_async_result_set_from_error (simple, error);
341 		g_simple_async_result_complete (simple);
342 		g_object_unref (simple);
343 		return;
344 	}
345 
346 	g_return_if_fail (GDATA_IS_FEED (feed));
347 
348 	list = gdata_feed_get_entries (feed);
349 
350 	/* This returns a new reference, for reasons passing understanding. */
351 	object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
352 
353 	tree_view = GTK_TREE_VIEW (object);
354 	tree_model = gtk_tree_view_get_model (tree_view);
355 	list_store = GTK_LIST_STORE (tree_model);
356 
357 	gtk_list_store_clear (list_store);
358 
359 	for (link = list; link != NULL; link = g_list_next (link)) {
360 		GDataCalendarCalendar *calendar;
361 		GDataEntry *entry;
362 		GDataLink *alt;
363 		GDataColor color;
364 		GdkColor gdkcolor;
365 		GtkTreeIter iter;
366 		const gchar *uri;
367 		const gchar *title;
368 		const gchar *access;
369 		gboolean writable;
370 		gchar *path;
371 
372 		entry = GDATA_ENTRY (link->data);
373 		calendar = GDATA_CALENDAR_CALENDAR (entry);
374 
375 		/* Skip hidden entries. */
376 		if (gdata_calendar_calendar_is_hidden (calendar))
377 			continue;
378 
379 		/* Look up the alternate link, skip if there is none. */
380 		alt = gdata_entry_look_up_link (entry, GDATA_LINK_ALTERNATE);
381 		if (alt == NULL)
382 			continue;
383 
384 		uri = gdata_link_get_uri (alt);
385 		title = gdata_entry_get_title (entry);
386 		gdata_calendar_calendar_get_color (calendar, &color);
387 		access = gdata_calendar_calendar_get_access_level (calendar);
388 
389 		if (uri == NULL || *uri == '\0')
390 			continue;
391 
392 		if (title == NULL || *title == '\0')
393 			continue;
394 
395 		path = google_chooser_extract_caldav_events_path (uri);
396 
397 		gdkcolor.pixel = 0;
398 		gdkcolor.red = color.red * 256;
399 		gdkcolor.green = color.green * 256;
400 		gdkcolor.blue = color.blue * 256;
401 
402 		if (access == NULL)
403 			writable = TRUE;
404 		else if (g_ascii_strcasecmp (access, "owner") == 0)
405 			writable = TRUE;
406 		else if (g_ascii_strcasecmp (access, "contributor") == 0)
407 			writable = TRUE;
408 		else
409 			writable = FALSE;
410 
411 		gtk_list_store_append (list_store, &iter);
412 
413 		gtk_list_store_set (
414 			list_store, &iter,
415 			COLUMN_COLOR, &gdkcolor,
416 			COLUMN_PATH, path,
417 			COLUMN_TITLE, title,
418 			COLUMN_WRITABLE, writable,
419 			-1);
420 
421 		g_free (path);
422 	}
423 
424 	g_object_unref (object);
425 	g_object_unref (feed);
426 
427 	g_simple_async_result_complete (simple);
428 	g_object_unref (simple);
429 }
430 
431 static void
432 google_chooser_authenticate_cb (GDataClientLoginAuthorizer *authorizer,
433                                 GAsyncResult *result,
434                                 GSimpleAsyncResult *simple)
435 {
436 	Context *context;
437 	GError *error = NULL;
438 
439 	context = g_simple_async_result_get_op_res_gpointer (simple);
440 
441 	gdata_client_login_authorizer_authenticate_finish (
442 		authorizer, result, &error);
443 
444 	if (error != NULL) {
445 		g_simple_async_result_set_from_error (simple, error);
446 		g_simple_async_result_complete (simple);
447 		g_object_unref (simple);
448 		return;
449 	}
450 
451 	/* We're authenticated, now query for all calendars. */
452 
453 	gdata_calendar_service_query_all_calendars_async (
454 		context->service, NULL, context->cancellable,
455 		NULL, NULL, NULL, (GAsyncReadyCallback)
456 		google_chooser_query_cb, simple);
457 }
458 
459 void
460 e_google_chooser_populate (EGoogleChooser *chooser,
461                            GCancellable *cancellable,
462                            GAsyncReadyCallback callback,
463                            gpointer user_data)
464 {
465 	GDataClientLoginAuthorizer *authorizer;
466 	GDataCalendarService *service;
467 	GSimpleAsyncResult *simple;
468 	Context *context;
469 	ESource *source;
470 	gpointer parent;
471 	gchar *password;
472 	gchar *prompt;
473 	gchar *user;
474 
475 	g_return_if_fail (E_IS_GOOGLE_CHOOSER (chooser));
476 
477 	source = e_google_chooser_get_source (chooser);
478 
479 	authorizer = gdata_client_login_authorizer_new (
480 		PACKAGE_NAME, GDATA_TYPE_CALENDAR_SERVICE);
481 
482 	service = gdata_calendar_service_new (GDATA_AUTHORIZER (authorizer));
483 
484 	context = g_slice_new0 (Context);
485 	context->service = service;  /* takes ownership */
486 	context->source = g_object_ref (source);
487 
488 	if (G_IS_CANCELLABLE (cancellable))
489 		context->cancellable = g_object_ref (cancellable);
490 	else
491 		context->cancellable = g_cancellable_new ();
492 
493 	simple = g_simple_async_result_new (
494 		G_OBJECT (chooser), callback,
495 		user_data, e_google_chooser_populate);
496 
497 	g_simple_async_result_set_op_res_gpointer (
498 		simple, context, (GDestroyNotify) context_free);
499 
500 	/* Prompt for a password. */
501 
502 	user = e_google_chooser_get_decoded_user (chooser);
503 
504 	parent = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
505 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
506 
507 	prompt = g_strdup_printf (
508 		_("Enter Google password for user '%s'."), user);
509 
510 	/* XXX The 'key' (3rd) argument doesn't matter since we're
511 	 *     passing E_PASSWORDS_REMEMBER_NEVER, it just needs to
512 	 *     be non-NULL.  This API is degenerating rapidly. */
513 	password = e_passwords_ask_password (
514 		"", NULL, "bogus key", prompt,
515 		E_PASSWORDS_REMEMBER_NEVER |
516 		E_PASSWORDS_DISABLE_REMEMBER |
517 		E_PASSWORDS_SECRET, NULL, parent);
518 
519 	g_free (prompt);
520 
521 	if (password == NULL) {
522 		g_cancellable_cancel (context->cancellable);
523 		g_simple_async_result_set_error (
524 			simple, G_IO_ERROR, G_IO_ERROR_CANCELLED,
525 			"%s", _("User declined to provide a password"));
526 		g_simple_async_result_complete (simple);
527 		g_object_unref (authorizer);
528 		g_object_unref (simple);
529 		g_free (user);
530 		return;
531 	}
532 
533 	/* Try authenticating. */
534 
535 	gdata_client_login_authorizer_authenticate_async (
536 		authorizer, user, password,
537 		context->cancellable, (GAsyncReadyCallback)
538 		google_chooser_authenticate_cb, simple);
539 
540 	g_free (password);
541 	g_free (user);
542 
543 	g_object_unref (authorizer);
544 }
545 
546 gboolean
547 e_google_chooser_populate_finish (EGoogleChooser *chooser,
548                                   GAsyncResult *result,
549                                   GError **error)
550 {
551 	GSimpleAsyncResult *simple;
552 
553 	g_return_val_if_fail (
554 		g_simple_async_result_is_valid (
555 		result, G_OBJECT (chooser),
556 		e_google_chooser_populate), FALSE);
557 
558 	simple = G_SIMPLE_ASYNC_RESULT (result);
559 
560 	/* Assume success unless a GError is set. */
561 	return !g_simple_async_result_propagate_error (simple, error);
562 }
563 
564 gboolean
565 e_google_chooser_apply_selected (EGoogleChooser *chooser)
566 {
567 	ESourceSelectable *selectable_extension;
568 	ESourceWebdav *webdav_extension;
569 	GtkTreeSelection *selection;
570 	GtkTreeModel *model;
571 	GtkTreeIter iter;
572 	ESource *source;
573 	GdkColor *color;
574 	SoupURI *soup_uri;
575 	gchar *color_spec;
576 	gchar *title;
577 	gchar *path;
578 
579 	g_return_val_if_fail (E_IS_GOOGLE_CHOOSER (chooser), FALSE);
580 
581 	source = e_google_chooser_get_source (chooser);
582 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser));
583 
584 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
585 		return FALSE;
586 
587 	gtk_tree_model_get (
588 		model, &iter,
589 		COLUMN_COLOR, &color,
590 		COLUMN_PATH, &path,
591 		COLUMN_TITLE, &title,
592 		-1);
593 
594 	selectable_extension = e_source_get_extension (
595 		source, E_SOURCE_EXTENSION_CALENDAR);
596 
597 	webdav_extension = e_source_get_extension (
598 		source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
599 
600 	e_source_set_display_name (source, title);
601 
602 	e_source_webdav_set_display_name (webdav_extension, title);
603 
604 	/* XXX Might be easier to expose get/set_path functions? */
605 	soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
606 	soup_uri_set_path (soup_uri, path);
607 	e_source_webdav_set_soup_uri (webdav_extension, soup_uri);
608 	soup_uri_free (soup_uri);
609 
610 	color_spec = gdk_color_to_string (color);
611 	e_source_selectable_set_color (selectable_extension, color_spec);
612 	g_free (color_spec);
613 
614 	gdk_color_free (color);
615 	g_free (title);
616 	g_free (path);
617 
618 	return TRUE;
619 }