1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * GData Client
4  * Copyright (C) Philip Withnall 2009, 2014, 2015 <philip@tecnocode.co.uk>
5  *
6  * GData Client is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * GData Client is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /**
21  * SECTION:gdata-calendar-service
22  * @short_description: GData Calendar service object
23  * @stability: Stable
24  * @include: gdata/services/calendar/gdata-calendar-service.h
25  *
26  * #GDataCalendarService is a subclass of #GDataService for communicating with the GData API of Google Calendar. It supports querying
27  * for, inserting, editing and deleting events from calendars, as well as operations on the calendars themselves.
28  *
29  * For more details of Google Calendar's GData API, see the
30  * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/">
31  * online documentation</ulink>.
32  *
33  * Each calendar accessible through the service has an access control list (ACL) which defines the level of access to the calendar to each user, and
34  * which users the calendar is shared with. For more information about ACLs for calendars, see the
35  * <ulink type="http" url="https://developers.google.com/google-apps/calendar/v3/reference/acl">online documentation on
36  * sharing calendars</ulink>.
37  *
38  * <example>
39  * 	<title>Retrieving the Access Control List for a Calendar</title>
40  * 	<programlisting>
41  *	GDataCalendarService *service;
42  *	GDataCalendarCalendar *calendar;
43  *	GDataFeed *acl_feed;
44  *	GDataAccessRule *rule, *new_rule;
45  *	GDataLink *acl_link;
46  *	GList *i;
47  *	GError *error = NULL;
48  *
49  *	/<!-- -->* Create a service and retrieve a calendar to work on *<!-- -->/
50  *	service = create_calendar_service ();
51  *	calendar = get_calendar (service);
52  *
53  *	/<!-- -->* Query the service for the ACL for the given calendar *<!-- -->/
54  *	acl_feed = gdata_access_handler_get_rules (GDATA_ACCESS_HANDLER (calendar), GDATA_SERVICE (service), NULL, NULL, NULL, &error);
55  *
56  *	if (error != NULL) {
57  *		g_error ("Error getting ACL feed for calendar: %s", error->message);
58  *		g_error_free (error);
59  *		g_object_unref (calendar);
60  *		g_object_unref (service);
61  *		return;
62  *	}
63  *
64  *	/<!-- -->* Iterate through the ACL *<!-- -->/
65  *	for (i = gdata_feed_get_entries (acl_feed); i != NULL; i = i->next) {
66  *		const gchar *scope_value;
67  *
68  *		rule = GDATA_ACCESS_RULE (i->data);
69  *
70  *		/<!-- -->* Do something with the access rule here. As an example, we update the rule applying to test@gmail.com and delete all
71  *		 * the other rules. We then insert another rule for example@gmail.com below. *<!-- -->/
72  *		gdata_access_rule_get_scope (rule, NULL, &scope_value);
73  *		if (scope_value != NULL && strcmp (scope_value, "test@gmail.com") == 0) {
74  *			GDataAccessRule *updated_rule;
75  *
76  *			/<!-- -->* Update the rule to make test@gmail.com an editor (full read/write access to the calendar, but they can't change
77  *			 * the ACL). *<!-- -->/
78  *			gdata_access_rule_set_role (rule, GDATA_CALENDAR_ACCESS_ROLE_EDITOR);
79  *			updated_rule = GDATA_ACCESS_RULE (gdata_service_update_entry (GDATA_SERVICE (service), GDATA_ENTRY (rule), NULL, &error));
80  *
81  *			if (error != NULL) {
82  *				g_error ("Error updating access rule for %s: %s", scope_value, error->message);
83  *				g_error_free (error);
84  *				g_object_unref (acl_feed);
85  *				g_object_unref (calendar);
86  *				g_object_unref (service);
87  *				return;
88  *			}
89  *
90  *			g_object_unref (updated_rule);
91  *		} else {
92  *			/<!-- -->* Delete any rule which doesn't apply to test@gmail.com *<!-- -->/
93  *			gdata_service_delete_entry (GDATA_SERVICE (service), GDATA_ENTRY (rule), NULL, &error);
94  *
95  *			if (error != NULL) {
96  *				g_error ("Error deleting access rule for %s: %s", scope_value, error->message);
97  *				g_error_free (error);
98  *				g_object_unref (acl_feed);
99  *				g_object_unref (calendar);
100  *				g_object_unref (service);
101  *				return;
102  *			}
103  *		}
104  *	}
105  *
106  *	g_object_unref (acl_feed);
107  *
108  *	/<!-- -->* Create and insert a new access rule for example@gmail.com which allows them to view free/busy information for events in the
109  *	 * calendar, but doesn't allow them to view the full event details. *<!-- -->/
110  *	rule = gdata_access_rule_new (NULL);
111  *	gdata_access_rule_set_role (rule, GDATA_CALENDAR_ACCESS_ROLE_FREE_BUSY);
112  *	gdata_access_rule_set_scope (rule, GDATA_ACCESS_SCOPE_USER, "example@gmail.com");
113  *
114  *	acl_link = gdata_entry_look_up_link (GDATA_ENTRY (calendar), GDATA_LINK_ACCESS_CONTROL_LIST);
115  *	new_rule = GDATA_ACCESS_RULE (gdata_service_insert_entry (GDATA_SERVICE (service), gdata_link_get_uri (acl_link), GDATA_ENTRY (rule),
116  *	                                                          NULL, &error));
117  *
118  *	g_object_unref (rule);
119  *	g_object_unref (calendar);
120  *	g_object_unref (service);
121  *
122  *	if (error != NULL) {
123  *		g_error ("Error inserting access rule: %s", error->message);
124  *		g_error_free (error);
125  *		return;
126  *	}
127  *
128  *	g_object_unref (acl_link);
129  * 	</programlisting>
130  * </example>
131  *
132  * Before version 0.17.2, the Calendar service could be manipulated using
133  * batch operations. That is no longer supported, and any batch operations
134  * created on the calendar will fail.
135  */
136 
137 #include <config.h>
138 #include <glib.h>
139 #include <glib/gi18n-lib.h>
140 #include <libsoup/soup.h>
141 #include <string.h>
142 
143 #include "gdata-calendar-service.h"
144 #include "gdata-batchable.h"
145 #include "gdata-service.h"
146 #include "gdata-private.h"
147 #include "gdata-query.h"
148 #include "gdata-calendar-feed.h"
149 
150 /* Standards reference here:
151  * https://developers.google.com/google-apps/calendar/v3/reference/ */
152 
153 static void
154 parse_error_response (GDataService *self,
155                       GDataOperationType operation_type,
156                       guint status,
157                       const gchar *reason_phrase,
158                       const gchar *response_body,
159                       gint length,
160                       GError **error);
161 static GList *get_authorization_domains (void);
162 
163 _GDATA_DEFINE_AUTHORIZATION_DOMAIN (calendar, "cl", "https://www.google.com/calendar/feeds/")
G_DEFINE_TYPE_WITH_CODE(GDataCalendarService,gdata_calendar_service,GDATA_TYPE_SERVICE,G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE,NULL))164 G_DEFINE_TYPE_WITH_CODE (GDataCalendarService, gdata_calendar_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
165 
166 static void
167 gdata_calendar_service_class_init (GDataCalendarServiceClass *klass)
168 {
169 	GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
170 	service_class->feed_type = GDATA_TYPE_CALENDAR_FEED;
171 	service_class->parse_error_response = parse_error_response;
172 	service_class->get_authorization_domains = get_authorization_domains;
173 }
174 
175 static void
gdata_calendar_service_init(GDataCalendarService * self)176 gdata_calendar_service_init (GDataCalendarService *self)
177 {
178 	/* Nothing to see here */
179 }
180 
181 /* The error format used by the Google Calendar API doesn’t seem to be
182  * documented anywhere, which is a little frustrating. Here’s an example of it:
183  *     {
184  *      "error": {
185  *       "errors": [
186  *        {
187  *         "domain": "global",
188  *         "reason": "parseError",
189  *         "message": "Parse Error",
190  *        }
191  *       ],
192  *       "code": 400,
193  *       "message": "Parse Error"
194  *      }
195  *     }
196  * or:
197  *     {
198  *      "error": {
199  *       "errors": [
200  *        {
201  *         "domain": "global",
202  *         "reason": "required",
203  *         "message": "Missing end time."
204  *        }
205  *       ],
206  *       "code": 400,
207  *       "message": "Missing end time."
208  *      }
209  *     }
210  */
211 static void
parse_error_response(GDataService * self,GDataOperationType operation_type,guint status,const gchar * reason_phrase,const gchar * response_body,gint length,GError ** error)212 parse_error_response (GDataService *self,
213                       GDataOperationType operation_type,
214                       guint status,
215                       const gchar *reason_phrase,
216                       const gchar *response_body,
217                       gint length,
218                       GError **error)
219 {
220 	JsonParser *parser = NULL;  /* owned */
221 	JsonReader *reader = NULL;  /* owned */
222 	gint i;
223 	GError *child_error = NULL;
224 
225 	if (response_body == NULL) {
226 		goto parent;
227 	}
228 
229 	if (length == -1) {
230 		length = strlen (response_body);
231 	}
232 
233 	parser = json_parser_new ();
234 	if (!json_parser_load_from_data (parser, response_body, length,
235 	                                 &child_error)) {
236 		goto parent;
237 	}
238 
239 	reader = json_reader_new (json_parser_get_root (parser));
240 
241 	/* Check that the outermost node is an object. */
242 	if (!json_reader_is_object (reader)) {
243 		goto parent;
244 	}
245 
246 	/* Grab the ‘error’ member, then its ‘errors’ member. */
247 	if (!json_reader_read_member (reader, "error") ||
248 	    !json_reader_is_object (reader) ||
249 	    !json_reader_read_member (reader, "errors") ||
250 	    !json_reader_is_array (reader)) {
251 		goto parent;
252 	}
253 
254 	/* Parse each of the errors. Return the first one, and print out any
255 	 * others. */
256 	for (i = 0; i < json_reader_count_elements (reader); i++) {
257 		const gchar *domain, *reason, *message, *extended_help;
258 		const gchar *location_type, *location;
259 
260 		/* Parse the error. */
261 		if (!json_reader_read_element (reader, i) ||
262 		    !json_reader_is_object (reader)) {
263 			goto parent;
264 		}
265 
266 		json_reader_read_member (reader, "domain");
267 		domain = json_reader_get_string_value (reader);
268 		json_reader_end_member (reader);
269 
270 		json_reader_read_member (reader, "reason");
271 		reason = json_reader_get_string_value (reader);
272 		json_reader_end_member (reader);
273 
274 		json_reader_read_member (reader, "message");
275 		message = json_reader_get_string_value (reader);
276 		json_reader_end_member (reader);
277 
278 		json_reader_read_member (reader, "extendedHelp");
279 		extended_help = json_reader_get_string_value (reader);
280 		json_reader_end_member (reader);
281 
282 		json_reader_read_member (reader, "locationType");
283 		location_type = json_reader_get_string_value (reader);
284 		json_reader_end_member (reader);
285 
286 		json_reader_read_member (reader, "location");
287 		location = json_reader_get_string_value (reader);
288 		json_reader_end_member (reader);
289 
290 		/* End the error element. */
291 		json_reader_end_element (reader);
292 
293 		/* Create an error message, but only for the first error */
294 		if (error == NULL || *error == NULL) {
295 			if (g_strcmp0 (domain, "usageLimits") == 0 &&
296 			    g_strcmp0 (reason,
297 			               "dailyLimitExceededUnreg") == 0) {
298 				/* Daily Limit for Unauthenticated Use
299 				 * Exceeded. */
300 				g_set_error (error, GDATA_SERVICE_ERROR,
301 				             GDATA_SERVICE_ERROR_API_QUOTA_EXCEEDED,
302 				             _("You have made too many API "
303 				               "calls recently. Please wait a "
304 				               "few minutes and try again."));
305 			} else if (g_strcmp0 (domain, "global") == 0 &&
306 			           g_strcmp0 (reason, "notFound") == 0) {
307 				/* Calendar not found. */
308 				g_set_error (error, GDATA_SERVICE_ERROR,
309 				             GDATA_SERVICE_ERROR_NOT_FOUND,
310 				             /* Translators: the parameter is an
311 				              * error message returned by the
312 				              * server. */
313 				             _("The requested resource was not found: %s"),
314 				             message);
315 			} else if ((g_strcmp0 (domain, "global") == 0 &&
316 			            g_strcmp0 (reason, "required") == 0) ||
317 			           (g_strcmp0 (domain, "global") == 0 &&
318 			            g_strcmp0 (reason, "conditionNotMet") == 0)) {
319 				/* Client-side protocol error. */
320 				g_set_error (error, GDATA_SERVICE_ERROR,
321 				             GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
322 				             /* Translators: the parameter is an
323 				              * error message returned by the
324 				              * server. */
325 				             _("Invalid request URI or header, "
326 				               "or unsupported nonstandard "
327 				               "parameter: %s"), message);
328 			} else if (g_strcmp0 (domain, "global") == 0 &&
329 			           (g_strcmp0 (reason, "authError") == 0 ||
330 			            g_strcmp0 (reason, "required") == 0)) {
331 				/* Authentication problem */
332 				g_set_error (error, GDATA_SERVICE_ERROR,
333 				             GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
334 				             _("You must be authenticated to "
335 				               "do this."));
336 			} else if (g_strcmp0 (domain, "global") == 0 &&
337 			           g_strcmp0 (reason, "forbidden") == 0) {
338 				g_set_error (error, GDATA_SERVICE_ERROR,
339 				             GDATA_SERVICE_ERROR_FORBIDDEN,
340 				             _("Access was denied by the user "
341 				               "or server."));
342 			} else {
343 				/* Unknown or validation (protocol) error. Fall
344 				 * back to working off the HTTP status code. */
345 				g_warning ("Unknown error code ‘%s’ in domain "
346 				           "‘%s’ received with location type "
347 				           "‘%s’, location ‘%s’, extended help "
348 				           "‘%s’ and message ‘%s’.",
349 				           reason, domain, location_type,
350 				           location, extended_help, message);
351 
352 				goto parent;
353 			}
354 		} else {
355 			/* For all errors after the first, log the error in the
356 			 * terminal. */
357 			g_debug ("Error message received in response: domain "
358 			         "‘%s’, reason ‘%s’, extended help ‘%s’, "
359 			         "message ‘%s’, location type ‘%s’, location "
360 			         "‘%s’.",
361 			         domain, reason, extended_help, message,
362 			         location_type, location);
363 		}
364 	}
365 
366 	/* End the ‘errors’ and ‘error’ members. */
367 	json_reader_end_element (reader);
368 	json_reader_end_element (reader);
369 
370 	g_clear_object (&reader);
371 	g_clear_object (&parser);
372 
373 	/* Ensure we’ve actually set an error message. */
374 	g_assert (error == NULL || *error != NULL);
375 
376 	return;
377 
378 parent:
379 	g_clear_object (&reader);
380 	g_clear_object (&parser);
381 
382 	/* Chain up to the parent class */
383 	GDATA_SERVICE_CLASS (gdata_calendar_service_parent_class)->parse_error_response (self, operation_type, status, reason_phrase,
384 	                                                                                 response_body, length, error);
385 }
386 
387 static GList *
get_authorization_domains(void)388 get_authorization_domains (void)
389 {
390 	return g_list_prepend (NULL, get_calendar_authorization_domain ());
391 }
392 
393 /**
394  * gdata_calendar_service_new:
395  * @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
396  *
397  * Creates a new #GDataCalendarService using the given #GDataAuthorizer. If @authorizer is %NULL, all requests are made as an unauthenticated user.
398  *
399  * Return value: a new #GDataCalendarService, or %NULL; unref with g_object_unref()
400  *
401  * Since: 0.9.0
402  */
403 GDataCalendarService *
gdata_calendar_service_new(GDataAuthorizer * authorizer)404 gdata_calendar_service_new (GDataAuthorizer *authorizer)
405 {
406 	g_return_val_if_fail (authorizer == NULL || GDATA_IS_AUTHORIZER (authorizer), NULL);
407 
408 	return g_object_new (GDATA_TYPE_CALENDAR_SERVICE,
409 	                     "authorizer", authorizer,
410 	                     NULL);
411 }
412 
413 /**
414  * gdata_calendar_service_get_primary_authorization_domain:
415  *
416  * The primary #GDataAuthorizationDomain for interacting with Google Calendar. This will not normally need to be used, as it's used internally
417  * by the #GDataCalendarService methods. However, if using the plain #GDataService methods to implement custom queries or requests which libgdata
418  * does not support natively, then this domain may be needed to authorize the requests.
419  *
420  * The domain never changes, and is interned so that pointer comparison can be used to differentiate it from other authorization domains.
421  *
422  * Return value: (transfer none): the service's authorization domain
423  *
424  * Since: 0.9.0
425  */
426 GDataAuthorizationDomain *
gdata_calendar_service_get_primary_authorization_domain(void)427 gdata_calendar_service_get_primary_authorization_domain (void)
428 {
429 	return get_calendar_authorization_domain ();
430 }
431 
432 /**
433  * gdata_calendar_service_query_all_calendars:
434  * @self: a #GDataCalendarService
435  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
436  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
437  * @progress_callback: (allow-none) (scope call) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
438  * @progress_user_data: (closure): data to pass to the @progress_callback function
439  * @error: a #GError, or %NULL
440  *
441  * Queries the service to return a list of all calendars from the authenticated account which match the given
442  * @query. It will return all calendars the user has read access to, including primary, secondary and imported
443  * calendars.
444  *
445  * For more details, see gdata_service_query().
446  *
447  * Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref()
448  */
449 GDataFeed *
gdata_calendar_service_query_all_calendars(GDataCalendarService * self,GDataQuery * query,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GError ** error)450 gdata_calendar_service_query_all_calendars (GDataCalendarService *self, GDataQuery *query, GCancellable *cancellable,
451                                             GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
452 {
453 	GDataFeed *feed;
454 	gchar *request_uri;
455 
456 	g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL);
457 	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
458 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
459 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
460 
461 	/* Ensure we're authenticated first */
462 	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
463 	                                               get_calendar_authorization_domain ()) == FALSE) {
464 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
465 		                     _("You must be authenticated to query all calendars."));
466 		return NULL;
467 	}
468 
469 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList", NULL);
470 	feed = gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
471 	                            cancellable, progress_callback, progress_user_data, error);
472 	g_free (request_uri);
473 
474 	return feed;
475 }
476 
477 /**
478  * gdata_calendar_service_query_all_calendars_async:
479  * @self: a #GDataCalendarService
480  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
481  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
482  * @progress_callback: (allow-none) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
483  * @progress_user_data: (closure): data to pass to the @progress_callback function
484  * @destroy_progress_user_data: (allow-none): the function to call when @progress_callback will not be called any more, or %NULL. This function will be
485  * called with @progress_user_data as a parameter and can be used to free any memory allocated for it.
486  * @callback: a #GAsyncReadyCallback to call when authentication is finished
487  * @user_data: (closure): data to pass to the @callback function
488  *
489  * Queries the service to return a list of all calendars from the authenticated account which match the given
490  * @query. @self and @query are all reffed when this function is called, so can safely be unreffed after
491  * this function returns.
492  *
493  * For more details, see gdata_calendar_service_query_all_calendars(), which is the synchronous version of
494  * this function, and gdata_service_query_async(), which is the base asynchronous query function.
495  *
496  * Since: 0.9.1
497  */
498 void
gdata_calendar_service_query_all_calendars_async(GDataCalendarService * self,GDataQuery * query,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GDestroyNotify destroy_progress_user_data,GAsyncReadyCallback callback,gpointer user_data)499 gdata_calendar_service_query_all_calendars_async (GDataCalendarService *self, GDataQuery *query, GCancellable *cancellable,
500                                                   GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
501                                                   GDestroyNotify destroy_progress_user_data,
502                                                   GAsyncReadyCallback callback, gpointer user_data)
503 {
504 	gchar *request_uri;
505 
506 	g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self));
507 	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
508 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
509 	g_return_if_fail (callback != NULL);
510 
511 	/* Ensure we're authenticated first */
512 	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
513 	                                               get_calendar_authorization_domain ()) == FALSE) {
514 		g_autoptr(GTask) task = NULL;
515 
516 		task = g_task_new (self, cancellable, callback, user_data);
517 		g_task_set_source_tag (task, gdata_service_query_async);
518 		g_task_return_new_error (task, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, "%s",
519 		                         _("You must be authenticated to query all calendars."));
520 
521 		return;
522 	}
523 
524 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList", NULL);
525 	gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
526 	                           cancellable, progress_callback, progress_user_data, destroy_progress_user_data, callback, user_data);
527 	g_free (request_uri);
528 }
529 
530 /**
531  * gdata_calendar_service_query_own_calendars:
532  * @self: a #GDataCalendarService
533  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
534  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
535  * @progress_callback: (allow-none) (scope call) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
536  * @progress_user_data: (closure): data to pass to the @progress_callback function
537  * @error: a #GError, or %NULL
538  *
539  * Queries the service to return a list of calendars from the authenticated account which match the given
540  * @query, and the authenticated user owns. (i.e. They have full read/write access to the calendar, as well
541  * as the ability to set permissions on the calendar.)
542  *
543  * For more details, see gdata_service_query().
544  *
545  * Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref()
546  */
547 GDataFeed *
gdata_calendar_service_query_own_calendars(GDataCalendarService * self,GDataQuery * query,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GError ** error)548 gdata_calendar_service_query_own_calendars (GDataCalendarService *self, GDataQuery *query, GCancellable *cancellable,
549                                             GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
550 {
551 	GDataFeed *feed;
552 	gchar *request_uri;
553 
554 	g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL);
555 	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
556 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
557 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
558 
559 	/* Ensure we're authenticated first */
560 	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
561 	                                               get_calendar_authorization_domain ()) == FALSE) {
562 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
563 		                     _("You must be authenticated to query your own calendars."));
564 		return NULL;
565 	}
566 
567 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList?minAccessRole=owner", NULL);
568 	feed = gdata_service_query (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
569 	                            cancellable, progress_callback, progress_user_data, error);
570 	g_free (request_uri);
571 
572 	return feed;
573 }
574 
575 /**
576  * gdata_calendar_service_query_own_calendars_async:
577  * @self: a #GDataCalendarService
578  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
579  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
580  * @progress_callback: (allow-none) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
581  * @progress_user_data: (closure): data to pass to the @progress_callback function
582  * @destroy_progress_user_data: (allow-none): the function to call when @progress_callback will not be called any more, or %NULL. This function will be
583  * called with @progress_user_data as a parameter and can be used to free any memory allocated for it.
584  * @callback: a #GAsyncReadyCallback to call when authentication is finished
585  * @user_data: (closure): data to pass to the @callback function
586  *
587  * Queries the service to return a list of calendars from the authenticated account which match the given
588  * @query, and the authenticated user owns. @self and @query are all reffed when this function is called,
589  * so can safely be unreffed after this function returns.
590  *
591  * For more details, see gdata_calendar_service_query_own_calendars(), which is the synchronous version of
592  * this function, and gdata_service_query_async(), which is the base asynchronous query function.
593  *
594  * Since: 0.9.1
595  */
596 void
gdata_calendar_service_query_own_calendars_async(GDataCalendarService * self,GDataQuery * query,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GDestroyNotify destroy_progress_user_data,GAsyncReadyCallback callback,gpointer user_data)597 gdata_calendar_service_query_own_calendars_async (GDataCalendarService *self, GDataQuery *query, GCancellable *cancellable,
598                                                   GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
599                                                   GDestroyNotify destroy_progress_user_data,
600                                                   GAsyncReadyCallback callback, gpointer user_data)
601 {
602 	gchar *request_uri;
603 
604 	g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self));
605 	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
606 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
607 	g_return_if_fail (callback != NULL);
608 
609 	/* Ensure we're authenticated first */
610 	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
611 	                                               get_calendar_authorization_domain ()) == FALSE) {
612 		g_autoptr(GTask) task = NULL;
613 
614 		task = g_task_new (self, cancellable, callback, user_data);
615 		g_task_set_source_tag (task, gdata_service_query_async);
616 		g_task_return_new_error (task, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, "%s",
617 		                         _("You must be authenticated to query your own calendars."));
618 
619 		return;
620 	}
621 
622 	request_uri = g_strconcat (_gdata_service_get_scheme (), "://www.googleapis.com/calendar/v3/users/me/calendarList?minAccessRole=owner", NULL);
623 	gdata_service_query_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), request_uri, query, GDATA_TYPE_CALENDAR_CALENDAR,
624 	                           cancellable, progress_callback, progress_user_data, destroy_progress_user_data, callback, user_data);
625 	g_free (request_uri);
626 }
627 
628 static gchar *
build_events_uri(GDataCalendarCalendar * calendar)629 build_events_uri (GDataCalendarCalendar *calendar)
630 {
631 	GString *uri;
632 	const gchar *calendar_id;
633 
634 	calendar_id = (calendar != NULL) ? gdata_entry_get_id (GDATA_ENTRY (calendar)) : "default";
635 
636 	uri = g_string_new (_gdata_service_get_scheme ());
637 	g_string_append (uri, "://www.googleapis.com/calendar/v3/calendars/");
638 	g_string_append_uri_escaped (uri, calendar_id, NULL, FALSE);
639 	g_string_append (uri, "/events");
640 
641 	return g_string_free (uri, FALSE);
642 }
643 
644 /**
645  * gdata_calendar_service_query_events:
646  * @self: a #GDataCalendarService
647  * @calendar: a #GDataCalendarCalendar
648  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
649  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
650  * @progress_callback: (allow-none) (scope call) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
651  * @progress_user_data: (closure): data to pass to the @progress_callback function
652  * @error: a #GError, or %NULL
653  *
654  * Queries the service to return a list of events in the given @calendar, which match @query.
655  *
656  * For more details, see gdata_service_query().
657  *
658  * Return value: (transfer full): a #GDataFeed of query results; unref with g_object_unref()
659  */
660 GDataFeed *
gdata_calendar_service_query_events(GDataCalendarService * self,GDataCalendarCalendar * calendar,GDataQuery * query,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GError ** error)661 gdata_calendar_service_query_events (GDataCalendarService *self, GDataCalendarCalendar *calendar, GDataQuery *query, GCancellable *cancellable,
662                                      GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
663 {
664 	gchar *request_uri;
665 	GDataFeed *feed;
666 
667 	g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL);
668 	g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (calendar), NULL);
669 	g_return_val_if_fail (query == NULL || GDATA_IS_QUERY (query), NULL);
670 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
671 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
672 
673 	/* Ensure we're authenticated first */
674 	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
675 	                                               get_calendar_authorization_domain ()) == FALSE) {
676 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
677 		                     _("You must be authenticated to query your own calendars."));
678 		return NULL;
679 	}
680 
681 	/* Execute the query. */
682 	request_uri = build_events_uri (calendar);
683 	feed = gdata_service_query (GDATA_SERVICE (self),
684 	                            get_calendar_authorization_domain (),
685 	                            request_uri, query,
686 	                            GDATA_TYPE_CALENDAR_EVENT, cancellable,
687 	                            progress_callback, progress_user_data,
688 	                            error);
689 	g_free (request_uri);
690 
691 	return feed;
692 }
693 
694 /**
695  * gdata_calendar_service_query_events_async:
696  * @self: a #GDataCalendarService
697  * @calendar: a #GDataCalendarCalendar
698  * @query: (allow-none): a #GDataQuery with the query parameters, or %NULL
699  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
700  * @progress_callback: (allow-none) (closure progress_user_data): a #GDataQueryProgressCallback to call when an entry is loaded, or %NULL
701  * @progress_user_data: (closure): data to pass to the @progress_callback function
702  * @destroy_progress_user_data: (allow-none): the function to call when @progress_callback will not be called any more, or %NULL. This function will be
703  * called with @progress_user_data as a parameter and can be used to free any memory allocated for it.
704  * @callback: a #GAsyncReadyCallback to call when the query is finished
705  * @user_data: (closure): data to pass to the @callback function
706  *
707  * Queries the service to return a list of events in the given @calendar, which match @query. @self, @calendar and @query are all reffed when this
708  * function is called, so can safely be unreffed after this function returns.
709  *
710  * Get the results of the query using gdata_service_query_finish() in the @callback.
711  *
712  * For more details, see gdata_calendar_service_query_events(), which is the synchronous version of this function, and gdata_service_query_async(),
713  * which is the base asynchronous query function.
714  *
715  * Since: 0.9.1
716  */
717 void
gdata_calendar_service_query_events_async(GDataCalendarService * self,GDataCalendarCalendar * calendar,GDataQuery * query,GCancellable * cancellable,GDataQueryProgressCallback progress_callback,gpointer progress_user_data,GDestroyNotify destroy_progress_user_data,GAsyncReadyCallback callback,gpointer user_data)718 gdata_calendar_service_query_events_async (GDataCalendarService *self, GDataCalendarCalendar *calendar, GDataQuery *query, GCancellable *cancellable,
719                                            GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
720                                            GDestroyNotify destroy_progress_user_data,
721                                            GAsyncReadyCallback callback, gpointer user_data)
722 {
723 	gchar *request_uri;
724 
725 	g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self));
726 	g_return_if_fail (GDATA_IS_CALENDAR_CALENDAR (calendar));
727 	g_return_if_fail (query == NULL || GDATA_IS_QUERY (query));
728 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
729 	g_return_if_fail (callback != NULL);
730 
731 	/* Ensure we're authenticated first */
732 	if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
733 	                                               get_calendar_authorization_domain ()) == FALSE) {
734 		g_autoptr(GTask) task = NULL;
735 
736 		task = g_task_new (self, cancellable, callback, user_data);
737 		g_task_set_source_tag (task, gdata_service_query_async);
738 		g_task_return_new_error (task, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, "%s",
739 		                         _("You must be authenticated to query your own calendars."));
740 
741 		return;
742 	}
743 
744 	/* Execute the query. */
745 	request_uri = build_events_uri (calendar);
746 	gdata_service_query_async (GDATA_SERVICE (self),
747 	                           get_calendar_authorization_domain (),
748 	                           request_uri, query,
749 	                           GDATA_TYPE_CALENDAR_EVENT, cancellable,
750 	                           progress_callback, progress_user_data,
751 	                           destroy_progress_user_data, callback,
752 	                           user_data);
753 	g_free (request_uri);
754 }
755 
756 /**
757  * gdata_calendar_service_insert_event:
758  * @self: a #GDataCalendarService
759  * @event: the #GDataCalendarEvent to insert
760  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
761  * @error: a #GError, or %NULL
762  *
763  * Inserts @event by uploading it to the online calendar service.
764  *
765  * For more details, see gdata_service_insert_entry().
766  *
767  * Return value: (transfer full): an updated #GDataCalendarEvent, or %NULL; unref with g_object_unref()
768  *
769  * Since: 0.2.0
770  * Deprecated: 0.17.2: Use gdata_calendar_service_insert_calendar_event()
771  *   instead to be able to specify the calendar to add the event to; otherwise
772  *   the default calendar will be used.
773  */
774 GDataCalendarEvent *
gdata_calendar_service_insert_event(GDataCalendarService * self,GDataCalendarEvent * event,GCancellable * cancellable,GError ** error)775 gdata_calendar_service_insert_event (GDataCalendarService *self, GDataCalendarEvent *event, GCancellable *cancellable, GError **error)
776 {
777 	gchar *uri;
778 	GDataEntry *entry;
779 
780 	g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL);
781 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (event), NULL);
782 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
783 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
784 
785 	uri = build_events_uri (NULL);
786 	entry = gdata_service_insert_entry (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, GDATA_ENTRY (event), cancellable, error);
787 	g_free (uri);
788 
789 	return GDATA_CALENDAR_EVENT (entry);
790 }
791 
792 /**
793  * gdata_calendar_service_insert_calendar_event:
794  * @self: a #GDataCalendarService
795  * @calendar: the #GDataCalendarCalendar to insert the event into
796  * @event: the #GDataCalendarEvent to insert
797  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
798  * @error: a #GError, or %NULL
799  *
800  * Inserts @event by uploading it to the online calendar service, adding it to
801  * the specified @calendar.
802  *
803  * For more details, see gdata_service_insert_entry().
804  *
805  * Return value: (transfer full): an updated #GDataCalendarEvent, or %NULL;
806  * unref with g_object_unref()
807  *
808  * Since: 0.17.2
809  */
810 GDataCalendarEvent *
gdata_calendar_service_insert_calendar_event(GDataCalendarService * self,GDataCalendarCalendar * calendar,GDataCalendarEvent * event,GCancellable * cancellable,GError ** error)811 gdata_calendar_service_insert_calendar_event (GDataCalendarService *self,
812                                               GDataCalendarCalendar *calendar,
813                                               GDataCalendarEvent *event,
814                                               GCancellable *cancellable,
815                                               GError **error)
816 {
817 	gchar *uri;
818 	GDataEntry *entry;
819 
820 	g_return_val_if_fail (GDATA_IS_CALENDAR_SERVICE (self), NULL);
821 	g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (calendar), NULL);
822 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (event), NULL);
823 	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
824 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
825 
826 	uri = build_events_uri (calendar);
827 	entry = gdata_service_insert_entry (GDATA_SERVICE (self),
828 	                                    get_calendar_authorization_domain (),
829 	                                    uri, GDATA_ENTRY (event),
830 	                                    cancellable, error);
831 	g_free (uri);
832 
833 	return GDATA_CALENDAR_EVENT (entry);
834 }
835 
836 /**
837  * gdata_calendar_service_insert_event_async:
838  * @self: a #GDataCalendarService
839  * @event: the #GDataCalendarEvent to insert
840  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
841  * @callback: a #GAsyncReadyCallback to call when insertion is finished
842  * @user_data: (closure): data to pass to the @callback function
843  *
844  * Inserts @event by uploading it to the online calendar service. @self and @event are both reffed when this function is called, so can safely be
845  * unreffed after this function returns.
846  *
847  * @callback should call gdata_service_insert_entry_finish() to obtain a #GDataCalendarEvent representing the inserted event and to check for possible
848  * errors.
849  *
850  * For more details, see gdata_calendar_service_insert_event(), which is the synchronous version of this function, and
851  * gdata_service_insert_entry_async(), which is the base asynchronous insertion function.
852  *
853  * Since: 0.8.0
854  * Deprecated: 0.17.2: Use
855  *   gdata_calendar_service_insert_calendar_event_async() instead to be able to
856  *   specify the calendar to add the event to; otherwise the default calendar
857  *   will be used.
858  */
859 void
gdata_calendar_service_insert_event_async(GDataCalendarService * self,GDataCalendarEvent * event,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)860 gdata_calendar_service_insert_event_async (GDataCalendarService *self, GDataCalendarEvent *event, GCancellable *cancellable,
861                                            GAsyncReadyCallback callback, gpointer user_data)
862 {
863 	gchar *uri;
864 
865 	g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self));
866 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (event));
867 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
868 
869 	uri = build_events_uri (NULL);
870 	gdata_service_insert_entry_async (GDATA_SERVICE (self), get_calendar_authorization_domain (), uri, GDATA_ENTRY (event), cancellable,
871 	                                  callback, user_data);
872 	g_free (uri);
873 }
874 
875 /**
876  * gdata_calendar_service_insert_calendar_event_async:
877  * @self: a #GDataCalendarService
878  * @calendar: the #GDataCalendarCalendar to insert the event into
879  * @event: the #GDataCalendarEvent to insert
880  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
881  * @callback: a #GAsyncReadyCallback to call when insertion is finished
882  * @user_data: (closure): data to pass to the @callback function
883  *
884  * Inserts @event by uploading it to the online calendar service, adding it to
885  * the specified @calendar. @self and @event are both reffed when this function
886  * is called, so can safely be unreffed after this function returns.
887  *
888  * @callback should call gdata_service_insert_entry_finish() to obtain a
889  * #GDataCalendarEvent representing the inserted event and to check for possible
890  * errors.
891  *
892  * For more details, see gdata_calendar_service_insert_event(), which is the
893  * synchronous version of this function, and gdata_service_insert_entry_async(),
894  * which is the base asynchronous insertion function.
895  *
896  * Since: 0.17.2
897  */
898 void
gdata_calendar_service_insert_calendar_event_async(GDataCalendarService * self,GDataCalendarCalendar * calendar,GDataCalendarEvent * event,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)899 gdata_calendar_service_insert_calendar_event_async (GDataCalendarService *self,
900                                                     GDataCalendarCalendar *calendar,
901                                                     GDataCalendarEvent *event,
902                                                     GCancellable *cancellable,
903                                                     GAsyncReadyCallback callback,
904                                                     gpointer user_data)
905 {
906 	gchar *uri;
907 
908 	g_return_if_fail (GDATA_IS_CALENDAR_SERVICE (self));
909 	g_return_if_fail (GDATA_IS_CALENDAR_EVENT (event));
910 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
911 
912 	uri = build_events_uri (calendar);
913 	gdata_service_insert_entry_async (GDATA_SERVICE (self),
914 	                                  get_calendar_authorization_domain (),
915 	                                  uri, GDATA_ENTRY (event), cancellable,
916 	                                  callback, user_data);
917 	g_free (uri);
918 }
919